실무에서 Go를 익히면서 Go언어 자체에 대한 궁금증과, 기본적인 지식에 대한 갈망을 해결하고자
유명한! Tucker의 Go 언어 프로그래밍 을 읽고 조금씩 정리했던 내용이다.
궁금하신 분들을 위해 빠르게 책 후기부터 공유해보자면,
< 책 후기 >
- 깔끔하다.
- 코드 보기가 편하다.
- 기초 문법을 공부하기 좋다.
- 실습도 있어서 입문자가 보기 좋을 것 같다.
// 조건문 검사 전에 초기문을 넣을 수 있음. 초기문은 검사에 사용할 변수를 초기화할 때 주로 사용.
if filename, success := UploadFile(); success {
// 초기문에서 선언한 변수의 범위는 if문 안으로 한정됨.
fmt.Println("Upload Success", filename)
} else {
fmt.Println("Failed to upload")
}
if day == 1 {
fmt.Println("첫재 날입니다.")
} else if day ==2 {
fmt.Println("둘재 날입니다.")
} else {
fmt.Println("프로젝트를 진행하세요.")
}
switch day {
case 1:
fmt.Println("첫째 날입니다.")
case 2:
fmt.Println("둘째 날입니다.")
default:
fmt.Println("프로젝트를 진행하세요.")
}
switch true {
case temp < 10, temp > 30: // -> temp < 10 || temp > 30
fmt.println("바깥 활동하기 좋은 날씨가 아닙니다.")
case temp >=10 && temp < 20:
fmt.println("약간 추우니 가벼운 겉옷을 준비하세요.")
default:
fmt.println("따뜻합니다.")
→ 그러면 좀더 넓은 개념의 조건을 앞에 두어 검사를 최소한 하도록 하는 것이 좋을까?
func getMyAge() int {
return 22
}
// 결과값 비교
func main() {
switch age := getMyAge(); age { // getMyAge()가 실행되어서 age 초기화
case 10:
fmt.Println("Teenage")
case 33:
fmt.Println("Pair 3")
default:
fmt.Println("My age is", age)
}
fmt.Println("age is", age) // Error - age 변수는 사라짐.
}
func main() {
switch age := getMyAge(); true {
case age < 10:
fmt.Println("Chile")
case age < 20:
fmt.Println("Teenager")
case age <30:
fmt.Println("20s")
default:
fmt.Println("My age is", age)
}
}
2022.01.17
초기화: 닫는 중괄호가 마지막 요소와 같은 줄에 있지 않은 경우 마지막 항목 뒤에 쉼표를 찍어줘야 한다
a ;= [2][5]int{
{1, 2, 3, 4, 5},
{5, 6, 7, 8, 9} } // 쉼표 없음
b ;= [2][5]int{
{1, 2, 3, 4, 5},
{5, 6, 7, 8, 9}, // 쉼표 있음
}
type 타입명 struct {
필드명 타입
...
필드명 타입
}
2022.02.01
type Student struct {
Age int // 1️⃣ 대문자로 시작하는 필드는 외부로 공개
No int
Score float64
}
// 함수내 s 인수
func PrintStudent(s student) {
fmt.Print("나이:%d 번호:%d 점수:%.2f/n", s.Age, s.No, s.Score)
}
func main() {
var student = Student{15, 23, 88.2}
// 2️⃣ student 구조체 모든 필드가 student2로 복사된다
student2 := student
// 3️⃣ 함수 호출 시에도 구조체가 복사된다
PrintStudent(student2)
1️⃣ 필드명이 대문자로 시작하는 경우 패키지 외부로 공개되는 필드
2️⃣ student의 모든 필드값이 student2fh 복사된다. Age, No, Score의 모든 필드값!
3️⃣ PrintStudent() 함수는 Student 타입을 인수로 받기 때문에 → student2의 모든 필드값이 PrintStudent()
함수 내 s 인수로 복사
2023.02.06
필드 배치 순서에 따른 구조체 크기 변화
type User struct {
Age int32
Score float64
}
func main() {
user := User{23, 77.2}
fmt.Println(unsafe.Sizeof(user)) // 해당 변수에 메모리 공간 크기 반환
}
// 16
int의 크기는 8바이트, int32는 4바이트
Age는 4바이트, Score는 8바이트
💡 왜 User의 크기는 12바이트가 아니라, 16바이트 일까?User 구조체의 변수 user의 시작 주소가 240번지이면 → Age의 시작주소도 240번지
Age는 4바이트 공간을 차지, 바로 붙여서 Score를 할당하면 Score 주소는 244
그러나, 244는 8의 배수가 아니므로 성능 손해 → Scroe 시작주소는 248
→ 4바이트 띄어져 할당하므로 4바이트 손해
💡 메모리 낭비를 줄이기 위해서는 8바이트 보다 작은 필드는 몰아서 배치하는 것이 효율 적이다!결합도(Coupling): 모듈간 상호 의존 관계를 형성해서 서로 강하게 연결되어 있는 정도
응집도(Cohesion): 모듈의 완성도, 모듈 내부의 모든 기능이 단일 목적에 충실하게 모여있는 정도
메모리 주소를 값으로 갖는 타입
*: 메모리 주소를 가리키는 포인터 변수( 변수 type 선언 시 사용)
&: 메모리 주소값을 가리키는 기호 (값!!!)
var a int
var p *int
p = &a // a의 메모리 주소를 포인터 변수 p에 대입
*p = 20 // p를 이용하여 변수 a 값을 변경시킴
var p *int
if p != nil {
// p가 nil이 아니다 -> p가 유효한 메모리 주소를 가리킨다
}
type Data struct {
value int
data [200]int
}
func ChangeData(arg *Data) { // 매개변수로 Data 포인터를 받음 -> Data의 주소값을 받음(data형식)
arg.value = 999
arg.data = 999 // arg 데이터를 변경
}
func main() {
var data Data
ChangeData(&data) // 인수로 data의 주소를 넘긴다(값)
}
value = 999
data[100] = 999
→ data의 주소를 인수로 전달
→ 1608바이트의 구조체 전부가 복사되는게 아니라 메모리 주소인 8바이트만 복사됨
<기존 방식>
<구조체를 생성해 초기화 하는 방식>
// Data타입 구조체 변수 data 선언
var data Data
var p *Data = &data
// data 변수의 주소값 반환
// *Data 타입 구조체 변수 p 선언
var p *Data = &Data{}
// Data 구조체를 만들어 주소 반환
→ 포인터 변수 p만 가지고도 구조체의 필드값에 접근하고 변경할 수 있다
// CheckFollowing 회원이 팔로우한 유저인지 확인
func (u *userRepo) CheckFollowing(ctx context.Context, userId string, targetId string) (*entity.Follow, error) {
var follow entity.Follow
err := u.db.WithContext(ctx).Where("user_id = ? and target_id = ?", userId, targetId).
First(&follow).Error
if err != nil {
return nil, err
}
return &follow, err
}
변수를 새로 정의함 → 메모리 공간 소비
// CheckFollowing 회원이 팔로우한 유저인지 확인
func (u *userRepo) CheckFollowing(ctx context.Context, userId string, targetId string) (*entity.Follow, error) {
var follow *entity.Follow
err := u.db.WithContext(ctx).Where("user_id = ? and target_id = ?", userId, targetId).
First(&follow).Error
if err != nil {
return nil, err
}
return follow, err
}
기존 할당된 entity.Follow 변수를 가리키도록 함 → 메모리 세이브
메모리에 존재하는 데이터의 실체
var p1 *Data = &Data{}
var p2 *Data = p1
var p3 *Data = p1
→ 포인터 변수 3개가 모두 한개의 Data 인스턴스를 가리킨다
→ 포인터를 이용하여 인스턴스에 접근할 수 있다!
→ 구조체 포인터를 함수 매개변수로 받는 다는 것 → 구조체 인스턴스로 입력을 받겠다
2023.02.07
// 1. 문자열의 길이를 알 수 있는 방법
func main() {
str := "Hello 월드"
runes := []rune(str) // string 타입 -> rune타입 : 각 글자들로 이뤄진 배열로 변환
fmt.Pringf("len(runes) = %d\n", len(runes))
// len(unes) = 8
// 문자열의 구조
type StringHeader struct {
Data uinptr
Len int
}
2023.02.14
func main() {
str1 := "안녕하세요"
str2 := str1
// "안녕하세요"
// "안녕하세요"
str1 문자열을 하나 복사해서 str2가 가리키게 하는 것이 아니라!
구조체 변수는 복사될 때 구조체 크기만큼 메모리가 복사되는데, 즉 Data포인터 값과 Len값이 복사된다.
→ 모두 같은 메모리 데이터를 가리키게 된다
→ 문자열 데이터가 복사되지 않으므로 메모리 성능 문제는 없다.
func main() {
var str string = "Hello"
str[2] = 'a' // -> Error!!
var slice []byte = []byte(str)
slice[2] ='a'
fmt.Println(str) // Hello
fmt.Printf("%s\n", slice) // Healo
→ 슬라이스 타입으로 변환할 때 문자열을 복사해서 새로운 공간을 만들어 슬라이스가 가리키도록 한다
→ 그래야 불변의 원칙을 지킬 수 있기 때문
→ 메모리 낭비를 줄이기 위해 strings.Builder
를 사용하면 메모리를 새로 생성하지 않고 기존 메모리 공간에 빈자리가 있으면 더하게 하면 된다.
import (
_ "github.com/mattn/go-sqlite3"
Go 패키지 ⊂ Go 모듈
Go build를 하려면 반드시 Go 모듈 루트 폴더에 go.mod 파일이 있어야 한다.
Go mod tidy
: Go 모듈에 필요한 패키지를 찾아서 다운해주고 필요한 패키지 정보를 go.mod파일과 go.sum 파일에 적어준다.
2023.02.21
배열: 정한 크기에서 더 이상 늘어나지 않는다.
슬라이스: 동적 배열 → 자동으로 배열 크기를 증가시키는 자료구조
var slice1 = []int{1,2,3}
var slice2 = []int{1, 5:2. 10:3} // [ 1 0 0 0 0 2 0 0 0 0 3 ]
//배열선언
var array = [...]int{1,2,3}
대괄호 안에 길이를 넣지 않는다.
var slice = make([]int, 3)
slice 변수는 3개짜리 int 슬라이스 값을 갖는다.
각 슬라이스 요솟값은 int 타입의 기본값인 0
Go 언어에서 모든 값의 대입은 복사로 일어난다. → 함수에서 인수로 전달될 때, 다른 변수에 대입할 때
포인터는 포인터의 값인 메모리 주소가 복사되고, 구조체가 복사될 때는 구조체의 모든 필드 복사
배열은 배열의 모든값이 복사된다.
func changeArray(array2 [5]int) {
array2[2] = 200
}
func changeSlice(slice2 []int) {
slice2[2] = 200
}
func main() {
array := [5]int{1, 2, 3, 4, 5}
slice := []int{1, 2, 3, 4, 5}
changeArray(array) // [1 2 3 4 5]
changeSlice(slice) // [1 2 200 4 5]
→ slice 의 3번째 값은 변했지만 array의 3번째 값은 변하지 않았다.
array 타입은 [5]int로 크기는 40바이트(8x5 = 40)
slice 타입은 []int → 내부는 포인터, len, cap 세개의 필드를 갖는 구조체 이다.
포인터, len, cap 각각 8바이트로 총 24바이트
slice1 := []int{1, 2, 3}
slice2 := append(slice1, 4, 5}
slice[1] = 100
// slice1 = {1, 100, 3}
// slice2 = {1, 2, 3, 4, 5, ⭕️}
append()함수가 호출되면 먼저 빈공간이 충분한지 확인한다.
만약 빈공간이 충분하지 않다면, 일반적으로 기존 배열의 2배 크기를 마련한다 → 새로운 배열을 만든다
배열의 일부를 집어내는 기능으로 결과물도 슬라이스이다.
array[시작인덱스:끝인덱스] → 시작인덱스부터 끝인덱스-1까지의 배열 일부를 슬라이싱한다.
array := [5]int{1, 2, 3, 4, 5}
slice := array[1:2]
array[1] = 100 // slice=100 -> slice의 포인터가 array의 2인덱스를 가리키기 때문이다.
slice1 := []int{1, 2, 3, 4, 5}
slice2 := make([]int, len(slice1)) // slice1과 같은 길이의 슬라이스 생성
// 방법1
for i, v := range slice1 {
slice2[i] = v
}
slice[1] = 100
// slice1 -> [1, 100, 3, 4, 5]
// slice2 -> [1, 2, 3, 4, 5]
// 방법2
slice2 := append([]int{}, slice1...)
//방법3
new := copy(slice2, slice1) // slice1을 slice2에 복사
func copy(dst, src []Type) int
많은 도움이 되었습니다, 감사합니다.