자바스크립트를 활성화 해주세요

Tucker의 Go 언어 프로그래밍 13장 요약

 ·  ☕ 3 min read

Tucker의 Go 언어 프로그래밍스터디 요약 노트입니다.

13장. 구조체

구조체의 Go 언어에서의 특징만 확인해보자.

  1. class가 없이, struct만 존재한다.
  2. 상속의 is-a 관계가 아닌 has-a 관계로만 구조체 관계를 정의한다.
  3. 구조체 속의 구조체 사용에서, 내부 구조체 필드 이름을 제외하면 구조체 확장과 비슷하게 사용할 수 있다.
  4. 메서드의 선언, 정의는 구조체 밖에서 이루어진다.

이 중 메서드 관련 내용은 추후 책에서 다루므로 여기에선 간단하게 무슨 의미인지 언급만 하고 지나가도록 하겠다.

구조체의 선언

책에서는 무조건적으로 struct 타입 선언 시, 타입 명을 같이 선언해야 하는 것 처럼 표현되어있지만, 타입 이름이 없는 익명 구조체 타입을 선언할 수 있다. (물론 활용도가 이름 있는 타입에 비해 떨어지기 때문에 없다고 생각해도 무방할 것으로 보인다.)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import "fmt"

func main() {
	var contact1 struct {
		Name	string
		Id	int
		Cell	string
	}

	fmt.Println(contact1)
	
	contact1.Id = 1
	contact1.Name = "John Doe"
	contact1.Cell = "1 234-567-890"

	// Copy anonymous struct into the other variable
	contact2 := contact1

	contact2.Name = "Jane Doe"
	contact2.Id = 2

	fmt.Println(contact1)
	fmt.Println(contact2)
}
$ ./go_anonymous_struct
{ 0 }
{John Doe 1 1 234-567-890}
{Jane Doe 2 1 234-567-890}

구조체를 포함하는 구조체

먼저 다른 구조체를 일반 타입처럼 포함하는 방식으로 실험해보자. 추가로 내부 구조체를 변수로 선언한 뒤, 바로 복사가 가능한지, 구조체 내부 필드 중 일부만 초기화하는 것도 확인해보자.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

import (
	"fmt"
	"time"
)

type User struct {
	Name	string
	Id	int
	Age	int
}

type VipUser struct {
	UserInfo	User
	Level		int
	Since		time.Time
}

func main() {
	var john User = User{"John Doe", 1, 30}
	var johnVip VipUser = VipUser{UserInfo: john, Level: 1}
	janeVip := VipUser{User{"Jane Doe", 2, 32}, 2, time.Now()}

	fmt.Println(johnVip)
	fmt.Println(janeVip)

	fmt.Printf("VIP user %s became VIP since %v", janeVip.UserInfo.Name, janeVip.Since)
}
$ ./go_struct_in_struct
{{John Doe 1 30} 1 0001-01-01 00:00:00 +0000 UTC}
{{Jane Doe 2 32} 2 2021-05-31 13:06:28.78326 +0900 KST m=+0.000120543}
VIP user Jane Doe became VIP since 2021-05-31 13:06:28.78326 +0900 KST m=+0.000120543

이제 포함된 필드(Embedded field) 방식으로 다시 실험해보자.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

import (
	"fmt"
	"time"
)

type User struct {
	Name	string
	Id	int
	Age	int
}

type VipUser struct {
	User
	Level	int
	Since	time.Time
}

func main() {
	var john User = User{"John Doe", 1, 30}
	var johnVip VipUser = VipUser{User: john, Level: 1}
	janeVip := VipUser{User{"Jane Doe", 2, 32}, 2, time.Now()}

	fmt.Println(johnVip)
	fmt.Println(janeVip)

	fmt.Printf("VIP user %s became VIP since %v", janeVip.Name, janeVip.Since)
}
$ ./go_embedded_struct
{{John Doe 1 30} 1 0001-01-01 00:00:00 +0000 UTC}
{{Jane Doe 2 32} 2 2021-05-31 13:13:14.075062 +0900 KST m=+0.000131959}
VIP user Jane Doe became VIP since 2021-05-31 13:13:14.075062 +0900 KST m=+0.000131959

구조체 패딩

go에서도 구조체 내 멤버 접근 속도를 빠르게 하기 위해 메모리 패딩을 수행한다. 확실한 비교를 위해 책에 나온 예제에서 주소 값까지 찍어보자.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package main

import (
	"fmt"
	"unsafe"
)

type Unoptimized struct {
	A	int8
	B	int
	C 	int8
	D 	int
	E 	int8
}

type Optimized struct {
	A 	int8
	C 	int8
	E 	int8
	B 	int
	D 	int
}

func main() {
	var1 := Unoptimized{}
	var2 := Optimized{}

	fmt.Printf("sizeof Unoptimized: %d, Optimized: %d\n",
		unsafe.Sizeof(var1), unsafe.Sizeof(var2))

	fmt.Printf("\nUnoptimized\n\tA: %p\n\tB: %p\n\tC: %p\n\tD: %p\n\tE: %p\n",
		&var1.A, &var1.B, &var1.C, &var1.D, &var1.E)
	fmt.Printf("\nOptimized\n\tA: %p\n\tB: %p\n\tC: %p\n\tD: %p\n\tE: %p\n",
		&var2.A, &var2.B, &var2.C, &var2.D, &var2.E)
}
$ ./go_struct_padding
sizeof Unoptimized: 40, Optimized: 24

Unoptimized
        A: 0x140000b2030
        B: 0x140000b2038
        C: 0x140000b2040
        D: 0x140000b2048
        E: 0x140000b2050

Optimized
        A: 0x140000ba000
        B: 0x140000ba008
        C: 0x140000ba001
        D: 0x140000ba010
        E: 0x140000ba002

구조체의 메서드

비슷하게 struct만 사용할 수 있는 C언어와 비교해보자.

// struct 선언
struct MyStr {
    uint capacity;
    uint length;
    char *storage;

    // method 목록 선언 (함수 포인터)
    int (*GetLength)(struct MyStr *ms);
    void (*Append)(struct MyStr *ms, struct MyStr *follow);
};

// method 구현 (함수 정의)
int _getLength(struct MyStr *ms) {
    // Implemenation...
};
void _append(struct MyStr *ms, struct MyStr *follow) {
    // Implemenation...
};

// struct 변수 초기화
struct MyStr str1 = {0, 0, NULL};
str1.GetLength = _getLength;    // 함수 포인터 매핑
str1.Append = _append;          // 함수 포인터 매핑

// struct method 사용
int length = str1.GetLength(&str1);
str1.Append(&str1, &str2);
// struct 선언
type MyStr struct {
    capacity    uint
    length      uint
    storage     *byte;
}

// method 선언, 정의
func (ms *MyStr) Length() int {
    // Implementation...
}
func (ms *MyStr) Append(MyStr *follow) {
    // Implementation...
}

// struct 변수 초기화
var str1 MyStr = MyStr {0, 0, nil}

// struct method 사용
length := str1.GetLength();
str1.Append(&str2);

위 예시에서 보듯 method 목록 선언이 구조체 밖에서 이루어진다. 더 자세한 부분은 추후 메서드를 다루는 장에서 자세히 보도록 하겠다.

저자 강의


JaeSang Yoo
글쓴이
JaeSang Yoo
The Programmer

목차