Menu

Golang 구조체: 정의, 초기화, 비교, 구조체 태그

구조체는 이름 있는 필드를 하나의 타입으로 묶습니다. 정의와 초기화, 제로 값, 구조체 포인터, 익명 구조체, 비교, 구조체 태그, NewX 생성자 관례를 알아봅니다.

이 페이지에는 실행 가능한 에디터가 있습니다 - 편집하고 실행하면 결과를 바로 볼 수 있습니다.

구조체 정의하고 만들기

구조체 타입은 이름 있는 필드의 목록입니다. type Name struct { ... }로 선언하고, 복합 리터럴로 값을 만듭니다.

출력:

Ana 31
{Name:Ana Email:ana@example.com Age:32 Admin:false}
{Name: Email: Age:0 Admin:false}
main.User{Name:"Bob", Email:"", Age:0, Admin:false}

%+v는 필드 이름을, %#v는 Go 문법을 출력합니다. 디버깅하면서 구조체를 살펴보는 가장 빠른 방법입니다.

같은 타입의 필드는 한 줄에 쓸 수 있습니다: X, Y float64. 대문자로 시작하는 필드 이름은 공개되고(다른 패키지와 encoding/json에서 보임), 소문자로 시작하는 이름은 그렇지 않습니다.

리터럴 형태

User{Name: "Ana", Age: 31}        // named fields: order-free, omitted fields are zero
User{"Ana", "a@x.com", 31, false} // positional: every field, in order
&User{Name: "Ana"}                // pointer to a new struct
new(User)                         // pointer to a zeroed struct

필드 이름을 쓰는 형태를 권합니다. 위치 기반 리터럴은 누군가 필드를 추가하거나 순서를 바꾸면 깨지고, 다른 패키지의 구조체 타입에 쓰면 go vet이 경고합니다(예: net/url.Error struct literal uses unkeyed fields). Point{1, 2}처럼 아주 작은 지역 타입에는 위치 기반 형태도 괜찮습니다.

기본값 없음: 제로 값과 생성자

Go에는 필드 기본값도 없고 언어 차원의 생성자도 없습니다. 모든 필드는 타입의 제로 값(0, "", false, nil)에서 시작합니다. 두 가지 관용구가 그 빈자리를 채웁니다.

제로 값을 쓸모 있게 만드세요. sync.Mutex, bytes.Buffer, strings.Builder는 모두 초기화 없이 동작합니다. 그렇게 설계할 수 있다면 그렇게 하세요.

필드에 0이 아닌 기본값이 필요하거나 맵처럼 할당해야 하는 것이 있다면 NewX 함수를 작성하세요:

NewServer는 서버에 자신을 수정하는 메서드가 있고 공유될 것이므로 포인터를 반환합니다. 작고 불변인 타입은 구조체를 값으로 반환하는 것도 흔합니다(time.Datetime.Time을 반환합니다).

구조체 포인터

Go는 필드에 접근할 때 구조체 포인터를 자동으로 역참조합니다. p := &User{}라면 (*p).Name이 아니라 p.Name이라고 씁니다.

출력:

{11 12}
{11 12} {0 12}
{11 0}

구조체는 값입니다. 대입과 함수 인자는 구조체를 복사하고, 포인터는 하나를 공유합니다. 필드 단위 복사는 얕은 복사라서, 복사본의 슬라이스나 맵 필드는 여전히 같은 데이터를 가리킵니다. 무엇을 언제 쓸지는 포인터를 참고하세요.

익명 구조체

구조체 타입은 이름 없이 그 자리에서 쓸 수 있습니다. 일회성 묶음, 테이블 기반 테스트, 한 번만 읽는 JSON 디코딩에 유용합니다.

[]struct{ in string; want int } 테이블은 Go 테스트의 표준 형태입니다.

구조체 비교

모든 필드가 비교 가능하면 구조체는 ==로 비교할 수 있습니다. 같다는 것은 모든 필드가 같다는 뜻입니다. 비교 가능한 구조체는 맵 키로도 쓸 수 있습니다.

슬라이스나 맵 필드가 있는 구조체라면 Equal 메서드를 작성하거나 테스트에서 reflect.DeepEqual로 비교하세요.

구조체 태그

태그는 필드 타입 뒤에 오는 문자열 리터럴입니다. 언어는 태그를 무시하고, 라이브러리가 리플렉션으로 읽습니다. 가장 흔한 사용처는 encoding/json입니다:

이 코드는 {"id":7,"name":"Mug"}를 출력합니다. 태그가 필드 이름을 바꾸고, omitempty가 제로 값인 Price를 빼며, 비공개 필드는 인코더에 보이지 않습니다. 태그는 key:"value" 관례를 따르며, 키가 여럿이면 공백으로 구분합니다: `json:"name" db:"user_name"`. 형식이 잘못된 태그도 문제없이 컴파일되고 조용히 무시되는데, 이것은 go vet이 잡아 줍니다. 옵션은 json 페이지에서 다룹니다.

빈 구조체와 필드 순서

struct{}는 필드가 없고 0바이트를 차지합니다. 집합의 값(map[string]struct{})이나 이벤트만 중요한 채널의 신호(chan struct{})로 씁니다.

필드 순서는 메모리 배치에 영향을 줍니다. 컴파일러가 필드를 정렬 단위에 맞춰 채우기 때문입니다. 64비트 시스템에서 bool, int64, bool로 된 구조체는 24바이트를, int64, bool, bool은 16바이트를 차지합니다. 아주 많이 저장되는 타입에서만 의미가 있습니다.

흔한 실수

  • 복사본을 수정함. for _, u := range users { u.Age++ }는 매번 복사본을 바꿉니다. 인덱스를 쓰세요: users[i].Age++.
  • 맵 필드 초기화를 잊음. 구조체의 제로 값에는 nil 맵이 들어 있어서 쓰면 패닉이 납니다. 생성자에서 할당하세요.
  • sync.Mutex를 담은 구조체를 복사함. 복사본은 자기만의 잠금 상태를 갖습니다. 이런 구조체는 포인터로 넘기세요. go vet이 복사를 보고합니다.
  • 다른 패키지 타입에 위치 기반 리터럴을 씀. 필드가 추가되면 깨집니다.

자주 묻는 질문

Go에서 구조체는 어떻게 초기화하나요?

복합 리터럴을 씁니다. 보통은 필드 이름을 적는 형태입니다: u := User{Name: "Ana", Age: 31}. 생략한 필드는 제로 값을 가집니다. User{}는 모든 필드가 제로 값이고, &User{...}는 포인터를, new(User)는 제로 값 구조체의 포인터를 줍니다.

Go는 구조체 필드의 기본값을 지원하나요?

지원하지 않습니다. 모든 필드는 타입의 제로 값에서 시작합니다. 관용구는 생성자 함수 func NewServer() *Server { return &Server{Port: 8080} }를 쓰거나, sync.Mutexbytes.Buffer처럼 제로 값이 이미 쓸모 있도록 타입을 설계하는 것입니다.

Go에서 두 구조체는 어떻게 비교하나요?

모든 필드가 비교 가능하다면 ==로 비교합니다. 대응하는 필드가 모두 같으면 두 구조체 값은 같습니다. 슬라이스, 맵, 함수 필드가 있는 구조체는 ==로 비교할 수 없으므로(컴파일 오류) 필드를 직접 비교하거나 테스트에서 reflect.DeepEqual을 쓰세요.

Go의 구조체 태그란 무엇인가요?

`json:"name,omitempty"`처럼 필드 타입 뒤에 붙이는 문자열 리터럴입니다. 컴파일러는 무시하고, encoding/json, 데이터베이스 드라이버, 검증 라이브러리 같은 패키지가 리플렉션으로 읽어서 필드를 인코딩하거나 이름 붙이거나 검사하는 방식을 정합니다.

Go에서 구조체를 필드 이름과 함께 출력하려면 어떻게 하나요?

fmt.Printf("%+v\n", s)를 쓰면 {Name:Ana Age:31}이 출력됩니다. %v는 값만 출력하고, %#v는 타입 이름을 포함한 Go 문법으로 출력합니다: main.User{Name:"Ana", Age:31}.

Coddy programming languages illustration

Coddy로 코딩 배우기

시작하기