본 게시물은 Tucker의 Go언어 프로그래밍을 참고해 작성한 게시물입니다.
http://www.yes24.com/Product/Goods/99108736
Tucker의 Go 언어 프로그래밍 - YES24
게임 회사 서버 전문가가 알려주는 Go 언어를 내 것으로 만드는 비법구글이 개발한 Go는 고성능 비동기 프로그래밍에 유용한 언어이다. 『Tucker의 Go 언어 프로그래밍』은 Go 언어로 ‘나만의 프로
www.yes24.com
선언 및 기본 사용
여러 필드를 묶어서 하나의 구조체를 만든다. 배열이 같은 타입의 값들을 변수 하나로 묶어줬던 것과 달리 구조체는 다른 타입 값들을 변수 하나로 묶어주는 기능이다.
구조체 정의 하기
type 타입명 struct {
필드명 타입
...
필드명 타입
}
- type 키워드를 적어 새로운 사용자 정의 타입을 정의할 것임을 알린다.
- 타입명을 적는다.
- 타입명 첫번째 글자가 대문자이면 패키지 외부로 공개되는 타입이다.
- 타입 종류인 struct를 적는다.
- 중괄호 안에 구조체에 속한 필드들을 적는다. 필드는 필드명과 타입을 적는다.
// 학생 구조체
type student struct [
Name string
Class int
No int
Score float64
}
학생의 정보를 나타내는 구조체 예시
const (
Computer int = iota
English
Art
Soccer
)
type student struct { // student 구조체 정의
Name string
Class int
No int
Score float64
}
func ClassToString(class int) string {
if class == Computer {
return "Computer"
} else if class == English {
return "English"
} else if class == Art {
return "Art"
} else if class == Soccer {
return "Soccer"
}
return ""
}
func main() {
var jenny student // 구조체 변수 선언
jenny.Name = "jenny" // 각 필드값 초기화
jenny.Class = 0
jenny.No = 12345000
jenny.Score = 100
fmt.Printf("이름 : %s\n", jenny.Name) // 필드값을 출력
fmt.Printf("전공 : %s\n", ClassToString(jenny.Class))
fmt.Printf("학번 : %d\n", jenny.No)
fmt.Printf("점수 : %f\n", jenny.Score)
}
구조체 변수 초기화
구조체 변수를 선언하고 각 필드를 초기화하는 방법을 알아보겠다.
초깃값 생략, 모든 필드 초기화, 일부 필드 초기화 방법이 있다.
초깃값 생략
초깃값을 생략하면 모든 필드가 기본값으로 초기화된다.
var jenny student
모든 필드 초기화
var jenny student = student{
"jenny",
0,
12345000,
100, // 여러줄로 초기화할 때는 제일 마지막 값 뒤에 쉼표를 달아야한다.
}
일부 필드 초기화
일부 필드값만 초기화할 때는 '필드명 : 필드값' 형식으로 초기화한다. 초기화하지 않은 나머지 변수에는 기본값이 할당된다.
var jenny student = student{ Name: "jenny", Class: 0}
구조체를 포함하는 구조체
구조체의 필드로 다른 구조체를 포함할 수 있다.
일반적인 내장 타입처럼 포함하는 방법과 포함된 필드 방식이 있다.
내장 타입처럼 포함하는 방식
예를 들어 일반 고객 정보를 나타내는 User 구좇체와 VIP 고객 정보를 나타내는 VIPUser구조체가 있다. VIP 고객도 고객이므로 이름, ID, 연령 정보를 입력할 변수를 각각 선어하지 않고 이미 만어 사용하는 일반 고객 정보용 User 구조체를 활용할 수 있다.
type User struct { // 일반 고객용 구조체
Name string
ID string
Age int
}
type VIPUser struct { // VIP 고객용 구조체
UserInfo User
VIPLevel int
Price int
}
var user = User{"chaewon", "chean", 24}
vip := VIPUser{
user,
3,
280,
}
fmt.Printf("[일반 유저] 이름: %s, 닉네임: %s, 나이: %d\n", user.Name, user.ID, user.Age)
fmt.Printf("[VIP 유저] 이름: %s, 닉네임: %s, 나이: %d, 레벨: %d, 가격: %d만원",
vip.UserInfo.Name,
vip.UserInfo.ID,
vip.UserInfo.Age,
vip.VIPLevel,
vip.Price)
Name 필드는 vip 변수의 UserInfo 필드 안에 속하기 때문에 vip.UserInfo.Name으로 접근해야 한다.
포함된 필드 방식
vip에서 Name이나 ID와 같이 UserInfo 안에 속한 필드를 접근하려면 vip.UserInfo.Name과 같이 두 단계를 걸쳐 접근해야한다. 구조체에서 다른 구조체 필드로 포함할 때 필드명을 생략하면 .을 한 번만 직어 접근할 수 있다.
type User struct {
Name string
ID string
Age int
}
type VIPUser struct {
User // 필드명 생략
VIPLevel int
Price int
}
func main() {
var user = User{"chaewon", "chean", 24}
vip := VIPUser{
user,
3,
280,
}
fmt.Printf("[일반 유저] 이름: %s, 닉네임: %s, 나이: %d\n", user.Name, user.ID, user.Age)
fmt.Printf("[VIP 유저] 이름: %s, 닉네임: %s, 나이: %d, 레벨: %d, 가격: %d만원",
vip.Name, // . 하나로 접근 가능
vip.ID,
vip.Age,
vip.VIPLevel,
vip.Price)
}
필드 중복 해결
구조체 User와 VIPUser 모두 Level 필드를 가지고 있다.
구조체 VIPUser가 같은 Level 필드명을 가진 User를 포함된 필드로 갖는다. 이름이 겹칠 경우 현재 변수 타입에 해당하는 구조체의 필드에 접근한다.
type User struct {
Name string
ID string
Age int
Level int // User의 Level 필드
}
type VIPUser struct {
User // Level 필드를 갖는 구조체
Price int
Level int // VIPUser의 Level 필드
}
func main() {
var user = User{"chaewon", "chean", 24, 10}
vip := VIPUser{
user,
250,
3,
}
fmt.Printf("[일반 유저] 이름: %s, 닉네임: %s, 나이: %d\n", user.Name, user.ID, user.Age)
fmt.Printf("[VIP 유저] 이름: %s, 닉네임: %s, 나이: %d, 가격: %d만원, VIP 레벨: %d, 유저 레벨: %d",
vip.Name, // . 하나로 접근 가능
vip.ID,
vip.Age,
vip.Price,
vip.Level, // VIPUser의 Level
vip.User.Level, // 포함된 구조체명을 쓰고 접근
)
}
구조체 크기
구조체 변수가 선언되면 컴퓨터는 구조체 필드를 모두 담을 수 있는 메모리 공간을 할당한다.
구조체 크기를 구하는 방법을 알아보자.
type User struct {
Age int
Score float64
}
var user User
User 구조체의 user 변수가 선언되면 컴퓨터는 Age와 Score 필드를 연속되게 담을 수 있는 메모리 공간을 찾아 할당한다.
8바이트(Age) + 8바이트(Score) = 16바이트 즉, 구조체 변수 user의 크기는 16바이트가 된다.
구조체 값 복사
구조체 변숫값을 다른 구조체에 대입하면 모든 필드값이 복사된다.
type Student struct {
Age int
No int
Score float64
}
func PrintStudent(s Student) {
fmt.Printf("나이: %d 번호: %d, 점수: %f\n", s.Age, s.No, s.Score)
}
func main() {
var student = Student{15, 23, 64.3}
student2 := student // student 구조체 모든 필드가 student2로 복사된다.
PrintStudent(student) // 나이: 15 번호: 23, 점수: 64.300000
PrintStudent(student2) // 나이: 15 번호: 23, 점수: 64.300000
student2.No = 34
PrintStudent(student) // 나이: 15 번호: 23, 점수: 64.300000
PrintStudent(student2) // 나이: 15 번호: 34, 점수: 64.300000
}
student의 모든 필드값이 student2로 복사된다.
Age, No, Score의 모든 필드값이 복사된다.
Go 내부에서는 필드 각각이 아닌 구조체 전체를 한번에 복한다. 대입 연산자가 우변 값을 좌변 메모리 공간에 복사할 때 '복사되는 크기'는 '타입 크기'와 같다.
필드 배치 순서에 따른 구조체 크기 변화
type User struct {
Age int32 // 4바이트
Score float64 // 8바이트
}
func main() {
user := User{23, 55.7}
fmt.Println(unsafe.Sizeof(user))
}
-------------------------------------
16
unsafe.Sizeof() 함수는 해당 변수의 메모리 공간 크기를 반환한다. 앞서 배운대로라면 User 구조체의 크기는 12바이트이어야 하지만 16바이트로 출력된다. 바로 메모리 정렬 때문이다.
메모리 정렬에 대해 알아보자.
메모리 정렬
메모리 정렬이란 컴퓨터가 데이터에 효과적으로 접근하고자 메모리를 일정 크기 간격으로 정렬하는 것을 말한다.
레지스터는 실제 연산에 사용되는 데이터가 저장되는 곳인데, 레지스터 크기가 4바이트인 컴퓨터를 32비트 컴퓨터라 부르고 레지스터 크기가 8바이트인 컴퓨터를 64비트 컴퓨터라고 부른다.
레지스터 크기가 8바이트라는 얘기는 한 번 연산에 8바이트 크기를 연산할 수 있다는 뜻이다. 따라서 데이터가 레지스터 크기와 똑같은 크기로 정렬되어 있으면 더욱 효율적으로 데이터를 읽어올 수 있다.
예를 들어 64비트 컴퓨터에서 데이터를 메모리에서 읽어올 때 8의 배수인 메모리 주소에 데이터를 할당해야 성능 손해를 방지할 수 있다.
type User struct {
Age int32
Score float64
}
var user User
Age는 4바이트, Score는 8바이트이다. user의 시작주소가 240번지라고 가정하자.
Age는 4바이트 공간을 차지하기 때문에 바로 붙여서 Score를 할당하면 Score의 시작 주소가 244번지가 되서 8의 배수가 아니기 때문에 성능 손해를 본다.
따라서 프로그램 언어에서 User 구조체를 할당할 때 Age와 Score 사이를 4바이트만큼 띄워서 할당한다.
성능 손해를 피하기 위해 필드 사이에 공간을 띄우는 것을 메모리 패딩이라고 한다.
메모리 패딩을 고려한 필드 배치 방법
type User struct {
A int8 // 1바이트
B int // 8바이트
C int8 // 1바이트
D int // 8바이트
E int8 // 1바이트
}
func main() {
user := User{1, 2, 3, 4, 5}
fmt.Println(unsafe.Sizeof(user))
}
------------------------------------
40
User 구조체는 1바이트 x 3 + 8바이트 x 2 = 19바이트 크기이지만, 실제 구조체 크기는 메모리 패딩 때문에 40바이트가 된다.
1바이트 변수 A,C,E 모두 7바이트 패딩된다.
8바이트보다 작은 필드를 8바이트 크기를 고려해 몰아서 배치함으로써 메모리 낭비를 줄일 수 있다.
type User struct {
A int8 // 1바이트
C int8 // 1바이트
E int8 // 1바이트
B int // 8바이트
D int // 8바이트
}
func main() {
user := User{1, 2, 3, 4, 5}
fmt.Println(unsafe.Sizeof(user))
}
------------------------------------
24
프로그래밍에서 구조체의 역할
구조체는 관련 데이터들을 묶어서 응집도를 높이고 재사용성을 증가시킨다.
구조체를 이용하면 개별 데이터의 조작/연산보다는 구조체 간의 관계와 상호작용 중심으로 프로그래밍할 수 있다.
구조체에 메서드, 인터페이스가 추가되면서 객체지향 프로그래밍으로 발전했다.
'Programming language > Golang' 카테고리의 다른 글
[Go] ch15 문자열 (0) | 2022.01.18 |
---|---|
[Go] ch14 포인터 (0) | 2022.01.15 |
[Go] 12 배열 (0) | 2022.01.15 |
[Go] 11 for문 (0) | 2022.01.15 |
[Go] 10 switch (0) | 2022.01.12 |