구조체

여러 필드를 묶어서 사용하는 타입으로 C의 structure와 비슷하며, go에서는 별도의 클래스를 키워드를 제공하지 않지만 구조체를 이용해서 클래스를 정의할 수 있다.


1. 선언

/*
type 이름 sturct{
  ... 필드명
}
*/
type School struct{
  Name string
  CntTeacher int
}

type Student struct {
  school School
  Name string
  score float64
}

//Student형 변수 선언
var student Student
student.Name = "홍길동"
student.score = 87.1

struct을 이용해서 구조체를 선언하고 해당 형식의 구조체를 특정 이름이라는 타입으로 부르도록 하겠다라는 의미로 type을 이용해서 선언을 해주면 된다.


//student라는 패키지에 선언
type Student struct {
  schoolInfo School
  Name string
  score float64
}

//main 패키지
import (
	"goStudy/student"
)

func main() {
	student := student.Student{}
  student.score = 98.1  //error
}

구조체내부의 필드로 다른 구조체 타입을 포함할 수 도 있다. 이때 go만의 특징으로 go는 특정 문법을 강제하는 강타입 언어인데 구조체 내부의 필드를 패키지 외부에서 접근하기 위해서는 이름은 반드시 대문자로 시작을 해야 한다.
소문자로 시작을 한다면 같은 패키지 내부에서만 접근 할 수 있다.



2. 초기화

var student Student  //zero value를 갖는 구조체 변수

var stdeunt Student = Student{"홍길동", 12.1}
student := Student{"홍길동", 12.1} //타입 추론



3. 포함된 필드방식(Embedded field)

type School struct{
  Name string
  CntTeacher int
}

type Student struct {
  School        //Embedded field
  Name string
  score float64
}

구조체 내부에 변수명 없이 타입만 설정한 field로 다른 기본형 타입은 사용이 불가능하고 구조체타입만 사용이 가능하다. 해당 필드방식으로 사용하면 School 구조체타입의 내부 필드가 Student 필드로 들어가게 되어 변수명으로 접근을 하지 않고 바로 접근을 할 수 있다.

var stdeunt Student

student.schoolInfo.Name = "길동고등학교" //기존의 방식
student.CntTeacher = 4  //Embedded field사용한 경우
student.School.Name = "길동고등학교" //필드명이 중복이 될 경우에는 구조체 타입을 이용해서 구분해주어야 한다.



4. 구조체 크기

type User struct{
  Age int
  Score float64
}

var user User

size


각 필드 타입의 크기를 모두 더한만큼 변수의 크기가 설정 된기 때문에 user는 8Byte(int) + 8Byte(float64) = 16byte가 된다.



5. 구조체 복사

type User struct{
  Age int
  Score float64
}

var user1 User = User{23, 87.1}

var user2 User = user1

대입연산자를 이용해 새로운 변수에 값을 할당 해주면 go는 메모리의 값을 그대로 새로운 메모리에 할당을 해준다. 이는 Java의 class 변수를 =연산자로 할당해주면 reference를 넘겨주는 것과는 다르게 동작하기 때문에 헷갈리지 말자. (자바는 얕은 복사, go는 깊은 복사)


user2.Age = 56

fmt.Println(user1) // {23 87.1}
fmt.Println(unsafe.Sizeof(user1)) //16

fmt.Println(user2) // {56 87.1}
fmt.Println(unsafe.Sizeof(user2)) //16

user3 := &user
user3.Age = 1
fmt.Println(user1) // {1 87.1}

Reference를 넘겨주고 싶으면 포인터 타입(*)과 메모리주소를 넘겨주기 위한 키워드 &를 사용하면 된다. (C와 비슷하다.)


+추가)

go의 표준 패키지로 unsafe라는 패키지가 있는데 이는 이름 그대로 안전하지 않은 함수를 제공한다. 그 중에 SizeOf()는 변수가 차지하고 있는 메모리 크기(Byte)를 반환하는 함수 이다.

type User struct{
  Age int32
  Score float64
}

var user1 User
fmt.Println(unsafe.Sizeof(user1)) //16

위에서 설명한대로라면 int32는 4byte, float64는 8byte이기 때문에 user는 12byte를 가져야 하는데 실제로는 16byte의 크기를 갖는다. 이는 컴퓨터(go)가 컴퓨터의 사양(32bit컴퓨터/64bit 컴퓨터)에 따라 관리하기 편한 방법으로 메모리를 정렬하기 때문이다.

현재에는 대부분 64bit컴퓨터로 8byte단위로 값을 가져오는데 합이 12byte로 4byte가 끊기기 때문에 조금 더 효율적인 관리를 위해 4byte만큼을 추가 메모리를 할당(Memory padding)해서 관리를 한다. 이는 Age가 8Byte를 갖는 것이 아니라 4byte의 빈공간을 만들어 관리하는 것이다.

한마디로 성능을 위해서 메모리를 희생하게 된다.


type User struct{
  a int8
  b float64
  c int8
}

var user1 User
fmt.Println(unsafe.Sizeof(user1)) //24

이는 위에서 설명한대로 8byte씩 끊어서 관리하기 때문에 24byte만큼 할당이 되었는데 8byte보다 작은 필드라면 앞으로 몰아서 필드를 선언하는 것이 메모리 패딩을 줄여 메모리를 절약할 수 있다.

type User struct{
  a int8
  c int8
  b float64
}

var user1 User
fmt.Println(unsafe.Sizeof(user1)) //16



6. 구조체의 역할

결합도는 낮게 응집도는 높게

함수는 비슷한 코드를, 배열은 같은 타입의 데이터를 묶어 응집도를 높이는 것이라면 구조체는 관련된 데이터를 묶어 응집도를 높여 재사용성을 증가시키는데 사용한다.

go는 처음에 설명한 것처럼 class가 존재하지 않고 structure객체로써 사용되기 위한 방법으로 사용된다.





Reference

『Tucker의 Go 언어 프로그래밍』 스터디 요약 노트

Tucker의 Go 강좌