본문으로 건너뛰기

구조체(Struct)

구조체란?

구조체는 서로 다른 타입의 필드를 하나로 묶어 새로운 복합 타입을 정의 하는 수단입니다. Go에는 클래스가 없지만, 구조체에 메서드를 정의하여 객체지향 스타일로 코딩할 수 있습니다.

구조체도 배열과 마찬가지로 값 타입 입니다. 대입하거나 함수에 전달하면 전체가 복사됩니다.


구조체 선언과 초기화

package main

import "fmt"

// 구조체 타입 정의
type Person struct {
Name string
Age int
Email string
}

func main() {
// 1. 필드명 지정 초기화 (권장)
p1 := Person{
Name: "Alice",
Age: 30,
Email: "alice@example.com",
}

// 2. 순서 기반 초기화 (필드 순서에 의존 — 비권장)
p2 := Person{"Bob", 25, "bob@example.com"}

// 3. 제로값으로 선언 후 필드 설정
var p3 Person
p3.Name = "Carol"
p3.Age = 28

// 4. new() — 포인터 반환
p4 := new(Person)
p4.Name = "Dave"

fmt.Println(p1, p2, p3, *p4)
}

구조체 메서드

구조체에 메서드를 정의해 객체지향 스타일을 구현합니다. 메서드는 Ch6에서 자세히 다루지만, 기본 사용법을 알아봅니다.

package main

import (
"fmt"
"math"
)

type Circle struct {
X, Y float64 // 중심 좌표
Radius float64
}

// 값 리시버 — 읽기 전용
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}

func (c Circle) Perimeter() float64 {
return 2 * math.Pi * c.Radius
}

// 포인터 리시버 — 구조체 수정
func (c *Circle) Scale(factor float64) {
c.Radius *= factor
}

func (c Circle) String() string {
return fmt.Sprintf("Circle(center=(%.1f,%.1f), r=%.1f)", c.X, c.Y, c.Radius)
}

func main() {
c := Circle{X: 0, Y: 0, Radius: 5}
fmt.Println(c)
fmt.Printf("넓이: %.2f\n", c.Area())
fmt.Printf("둘레: %.2f\n", c.Perimeter())

c.Scale(2)
fmt.Printf("스케일 후: %s\n", c)
// Circle(center=(0.0,0.0), r=10.0)
}

중첩 구조체

구조체 안에 다른 구조체를 필드로 포함할 수 있습니다.

package main

import "fmt"

type Address struct {
Street string
City string
Zip string
}

type Company struct {
Name string
Address Address // 중첩 구조체
}

type Employee struct {
Name string
Age int
Company Company // 중첩
}

func main() {
emp := Employee{
Name: "Alice",
Age: 30,
Company: Company{
Name: "Gopher Inc.",
Address: Address{
Street: "123 Main St",
City: "Seoul",
Zip: "06234",
},
},
}

// 필드 접근 (점 체인)
fmt.Println(emp.Name)
fmt.Println(emp.Company.Name)
fmt.Println(emp.Company.Address.City)
}

익명 구조체

타입 이름 없이 즉석에서 정의하는 구조체입니다. 일회성 데이터 묶음, 테스트, JSON 임시 파싱에 유용합니다.

package main

import (
"encoding/json"
"fmt"
)

func main() {
// 익명 구조체 변수
point := struct {
X, Y int
}{X: 10, Y: 20}
fmt.Println(point) // {10 20}

// 슬라이스 of 익명 구조체 — 테이블 주도 테스트에 자주 사용
cases := []struct {
input int
expected string
}{
{1, "one"},
{2, "two"},
{3, "three"},
}
for _, tc := range cases {
fmt.Printf("input=%d expected=%s\n", tc.input, tc.expected)
}

// JSON 임시 파싱
data := `{"name":"Alice","age":30}`
var result struct {
Name string `json:"name"`
Age int `json:"age"`
}
json.Unmarshal([]byte(data), &result)
fmt.Printf("%s is %d years old\n", result.Name, result.Age)
}

구조체 임베딩 (Embedding)

Go는 상속 대신 임베딩 으로 다른 구조체의 필드와 메서드를 그대로 사용할 수 있습니다. 타입 이름을 필드명 없이 포함시키면 됩니다.

package main

import "fmt"

type Animal struct {
Name string
}

func (a Animal) Speak() string {
return a.Name + " speaks"
}

type Dog struct {
Animal // Animal 임베딩 — 필드명 없음
Breed string
}

func (d Dog) Fetch() string {
return d.Name + " fetches the ball!" // Animal.Name 직접 접근
}

type GuideDog struct {
Dog // Dog 임베딩 (Dog는 Animal을 임베딩)
Owner string
}

func main() {
d := Dog{
Animal: Animal{Name: "Rex"},
Breed: "Labrador",
}

// 임베딩된 필드/메서드를 직접 사용
fmt.Println(d.Name) // Rex (Animal.Name 승격)
fmt.Println(d.Speak()) // Rex speaks (Animal.Speak 승격)
fmt.Println(d.Fetch()) // Rex fetches the ball!

// 다층 임베딩
gd := GuideDog{
Dog: Dog{Animal: Animal{Name: "Buddy"}, Breed: "Golden"},
Owner: "Alice",
}
fmt.Println(gd.Name) // Buddy (Animal.Name이 두 단계 승격)
fmt.Println(gd.Speak()) // Buddy speaks
fmt.Printf("%s owned by %s\n", gd.Name, gd.Owner)
}

구조체 태그(Struct Tag)

구조체 필드에 메타데이터를 붙이는 문자열입니다. JSON, DB, 유효성 검사 등 여러 라이브러리가 리플렉션으로 태그를 읽어 동작합니다.

package main

import (
"encoding/json"
"fmt"
)

type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email,omitempty"` // 비어있으면 필드 생략
Password string `json:"-"` // JSON에서 완전히 제외
Age int `json:"age,string"` // 숫자를 문자열로 직렬화
}

func main() {
u := User{ID: 1, Name: "Alice", Password: "secret", Age: 30}

// 구조체 → JSON
data, _ := json.MarshalIndent(u, "", " ")
fmt.Println(string(data))
// {
// "id": 1,
// "name": "Alice",
// "age": "30"
// (Email 생략, Password 제외)
// }

// JSON → 구조체
jsonStr := `{"id":2,"name":"Bob","email":"bob@example.com","age":"25"}`
var u2 User
json.Unmarshal([]byte(jsonStr), &u2)
fmt.Printf("%+v\n", u2)
}

실전 예제 — 학생 성적 관리 시스템

package main

import (
"fmt"
"sort"
)

type Score struct {
Subject string
Point int
}

type Student struct {
ID string
Name string
Scores []Score
}

func (s Student) Total() int {
total := 0
for _, sc := range s.Scores {
total += sc.Point
}
return total
}

func (s Student) Average() float64 {
if len(s.Scores) == 0 {
return 0
}
return float64(s.Total()) / float64(len(s.Scores))
}

func (s Student) Grade() string {
avg := s.Average()
switch {
case avg >= 90:
return "A"
case avg >= 80:
return "B"
case avg >= 70:
return "C"
default:
return "F"
}
}

type Classroom struct {
Students []Student
}

func (c *Classroom) Add(s Student) {
c.Students = append(c.Students, s)
}

func (c Classroom) RankByAverage() []Student {
sorted := make([]Student, len(c.Students))
copy(sorted, c.Students)
sort.Slice(sorted, func(i, j int) bool {
return sorted[i].Average() > sorted[j].Average()
})
return sorted
}

func main() {
classroom := Classroom{}
classroom.Add(Student{
ID: "s001", Name: "Alice",
Scores: []Score{{"Math", 95}, {"English", 88}, {"Science", 92}},
})
classroom.Add(Student{
ID: "s002", Name: "Bob",
Scores: []Score{{"Math", 72}, {"English", 80}, {"Science", 68}},
})
classroom.Add(Student{
ID: "s003", Name: "Carol",
Scores: []Score{{"Math", 85}, {"English", 90}, {"Science", 88}},
})

fmt.Println("=== 성적 순위 ===")
for rank, s := range classroom.RankByAverage() {
fmt.Printf("%d위 [%s] %s — 평균: %.1f점 (%s)\n",
rank+1, s.ID, s.Name, s.Average(), s.Grade())
}
}