[Tucker의 Go 언어 프로그래밍] 13장 구조체

Coen·2023년 10월 11일
1

tucker-go

목록 보기
10/18
post-thumbnail

이 글은 골든래빗 《Tucker의 Go 언어 프로그래밍》의 13장 써머리입니다.

13장 구조체

13.1 선언 및 기본 사용

  • 여러 필드를 묶어 하나의 구조체를 만든다.
type 타입명 struct {
	필드명 타입
   	필드명 타입
    ...
}
  • 이름, 반, 번호, 성적 등으로 학생 데이터를 만들 때 각각을 변수로 선언하는 것보다 학생이라는 구조체로 묶으면 더 쉽게 다룰 수 있다.
type Student struct {
	Name string
    Class int
    No int
    Score float64
}
  • Student 구조체를 변수로 선언
  • Student의 필드에 접근하려면 .을 사용하면 된다.
var a Student
a.Name = "Coen"
a.Class = 1
a.No = 10
a.Score = 100.0

13.2 구조체 변수 초기화

  • 구조체 변수를 선언하고 필드를 초기화 하는 방법은 초깃값 생략, 모든 필드 초기화, 일부 필드 초기화 방법이 있다.

13.2.1 초기값 생략

  • 초기값을 생략하면 모든 필드가 기본값으로 초기화 된다.
var a Student
fmt.Println(a.Name) //""
fmt.Println(a.Class) //0
fmt.Println(a.No) //0
fmt.Println(a.Score) //0.0

13.2.2 모든 필드 초기화

  • 모든 필드값을 중괄호 사이에 넣어서 초기화 한다.
var a Student{"Coen", 1, 10, 100.0}
fmt.Println(a.Name) //"Coen"
fmt.Println(a.Class) //1
fmt.Println(a.No) //10
fmt.Println(a.Score) //100.0

13.2.3 일부 필드 초기화

  • 일부 필드값만 초기화할 때는 필드명: 필드값 형식으로 초기화한다. 나머지 필드값은 기본값이 할당된다.
var a Student{Name: "Coen", Score: 100.0}
fmt.Println(a.Name) //"Coen"
fmt.Println(a.Class) //0
fmt.Println(a.No) //0
fmt.Println(a.Score) //100.0

13.3 구조체를 포함하는 구조체

  • 구조체의 필드로 다른 구조체를 포함할 수 있다.

13.3.1 내장 타입처럼 포함하는 방식

type User struct {
	Name string
    ID   string
    Age  int
}
type VIPUser struct {
	UserInfo User
    VIPLevel int
    Price    int
}

func main() {
	user := User{"Coen", "coen90", 34}
    vip := VIPUser{user, 1, 1000}
    
    fmt.Println(vip.UserInfo.Name) //"Coen"
    fmt.Println(vip.VIPLevel) //1
}

13.3.2 포함된 필드 방식

  • vip에서 UserInfo 안에 속한 필드를 접근하려면 vip.UserInfo.Name과 같이 두 단계를 걸쳐 접근해야 한다.
  • 필드명을 생략하면 포함하는 구조체의 필드를 내 필드처럼 사용할 수 있다.
type User struct {
	Name string
    ID   string
    Age  int
}
type VIPUser struct {
	User
    VIPLevel int
    Price    int
}

func main() {
	user := User{"Coen", "coen90", 34}
    vip := VIPUser{user, 1, 1000}
    
    fmt.Println(vip.Name) //"Coen"
    fmt.Println(vip.VIPLevel) //1
}

필드 중복

  • 13.3.2의 방식을 사용했을 때 필드명이 중복되는 경우 아래의 방식을 따른다.
type User struct {
	Name  string
    ID    string
    Age   int
    Level int
}
type VIPUser struct {
	User
    VIPLevel int
    Price    int
    Level    int
}

func main() {
	user := User{"Coen", "coen90", 34, 1111}
    vip := VIPUser{user, 1, 1000, 9999}
    
    fmt.Println(vip.Level)      //9999
    fmt.Println(vip.User.Level) //1111
}
  • 같은 필드명의 경우 현재 변수 타입 구조체 필드에 우선 합니다.

13.4 구조체 크기

  • 구조체 변수가 선언되면 컴퓨터는 구조체 필드를 모두 담을 수 있는 메모리 공간을 할당한다.
type User struct {
	Age   int //8byte
    Score int //8byte
}
var user User //16byte

13.4.1 구조체 값 복사

  • Golang 내부에서 필드 각각이 아닌 구조체 전체를 한 번에 복사한다.
  • 주소값 복사가 아닌 값복사이다.
type User struct {
	Age   int
    Score int
}
user := User{20, 5}
user2 := user

13.4.2 필드 배치 순서에 따른 구조체 크기 변화

type User struct {
	Age   int32   //4byte
	Score float64 //8byte
}
var user User
t.Log(unsafe.Sizeof(user.Age))   //4
t.Log(unsafe.Sizeof(user.Score)) //8
t.Log(unsafe.Sizeof(user))       //16?!
  • int32는 4byte float64는 8byte인데 User는 16byte이다?!
  • 이유는 메모리 정렬 때문이다.

13.4.3 메모리 정렬

  • 메모리 정렬이란 컴퓨터가 데이터에 효과적으로 접근하고자 메모리를 일정 크기 간격으로 정렬하는 것.
  • 64비트 컴퓨터에서 int64 데이터의 시작주소가 100번지일 경우 100은 8의 배수가 아니기 때문에 레지스터 크기 8에 맞게 정렬되어 있지 않는다.
  • 이럴 경우 데이터를 메모리에서 읽어올 때 성능을 손해보기 때문에 처음부터 8의 배수인 메모리 주소에 데이터를 할당한다.

  • 이렇게 메모리 정렬을 위해 필드 사이에 공간을 띄우는 것을 Memory Padding이라고 한다.

13.4.4 메모리 패딩을 고려한 필드 배치 방법

type User struct {
	A int  //8byte
	B int8 //1byte
	C int  //8byte
	D int8 //1byte
	E int  //8byte
}
var user User
t.Log(unsafe.Sizeof(user)) //40
type User2 struct {
	B int8 //1byte
	D int8 //1byte
	A int  //8byte
	C int  //8byte
	E int  //8byte
}
var user2 User2
t.Log(unsafe.Sizeof(user2)) //32

13.5 프로그래밍에서 구조체의 역할

  • 프로그래밍 역사는 결합도를 낮추고 데이터간 응집도를 올리는 객체지향으로 흘러왔다.
  • 구조체는 관련 데이터를 묶어 응집도를 높이고 재사용성을 증가시킨다.
  • 구조체를 사용함으로써 개별 데이터에 집중하지 않고 더 큰 범위에서 생각할 수 있게 되었다.

✅추가: struct tag

struct에서 자주 사용하는 struct tag라는 기능이 있다.

  • json을 marshal unmarshal할 때 사용한다.
  • struct to JSON
type User struct {
	Name string `json:"user_name"`
	Age  int    `json:"user_age"`
}
user := User{"Coen", 34}
indent, err := json.MarshalIndent(user, "", "    ")
if err != nil {
	t.Fatal(err)
}
t.Log(string(indent))
  • 결과
{
    "user_name": "Coen",
    "user_age": 34
}
  • JSON to struct
type User struct {
	Name string `json:"user_name"`
	Age  int    `json:"user_age"`
}
jsonString := "{\"user_name\":\"Coen\",\"user_age\":34}"
var user User
err := json.Unmarshal([]byte(jsonString), &user)
if err != nil {
	t.Fatal(err)
}
t.Log(user) //{Coen 34}
  • struct tag값을 받아오기(아마 json패키지 내부에서 이 방식으로 돌아가지 않을까 싶다)
type User struct {
	Name string `json:"user_name"`
	Age  int    `json:"user_age"`
}
user := User{"Coen", 34}
tsReflect := reflect.TypeOf(user)
name, _ := tsReflect.FieldByName("Name")
get := name.Tag.Get("json")
t.Log(get) //user_name
profile
백엔드 프로그래머

0개의 댓글