12장 배열
- 배열은 같은 타입의 데이터들로 이루어진 저장소이다. 배열을 이루는 각 값은 요소라고 하고, 요소를 가리키는 위치값을 인덱스라고 한다.
- 아래의 소스는 배열을 사용한 간단한 예제이다.
//ch12/ex12.1/ex12.1.go
package main
import "fmt"
func main() {
var t [5]float64 = [5]float64{24.0, 25.9, 27.8, 26.9, 26.2} // ❶
for i := 0; i < 5; i++ { // ❷
fmt.Println(t[i]) // ❸
}
}
- 배열은 선언시 개수는 항상 상수여야한다. 아래의 소스에서 a배열선언문은 오류가 발생한다. 배열의 길이를 지정할때 상수가 아닌값으로 지정하였기때문이다.
//ch12/ex12.2/ex12.2.go
package main
const Y int = 3 // ❶ 상수
func main() {
x := 5 // ❷ 변수
a := [x]int{1, 2, 3, 4, 5} // ❸
b := [Y]int{1, 2, 3} // ➍
var c [10]int // ➎
}
- 아래소스는 for문을 사용하여, 배열을 순회하는 예제이다. len()을 사용하여 배열의 길이를 구해서 해당 길이만큼 순회하여, 각각의 배열요소를 출력하는 소스이다.
//ch12/ex12.3/ex12.3.go
package main
import "fmt"
func main() {
nums := [...]int{10, 20, 30, 40, 50} // ❶
nums[2] = 300 // ❷
for i := 0; i < len(nums); i++ { // ❸
fmt.Println(nums[i]) // ➍
}
}
- for문에서 range라는 키워드를 사용하여, 위와는 다른 방법으로 배열을 순회할수있다. i는 배열의 인덱스 , v는 해당 배열의 요소값이다. 인덱스값이 필요없을경우 _ 로 교체가능하다. (i,v를 선언하고 i를 사용안하면 오류가발생한다.)
//ch12/ex12.4/ex12.4.go
package main
import "fmt"
func main() {
var t [5]float64 = [5]float64{24.0, 25.9, 27.8, 26.9, 26.2} // ❶
for i, v := range t { // ❷
fmt.Println(i, v) // ❸
}
}
// --------------------------
// ch12/ex12.1/ex12.1.2.go
// ch12/ex12.1/ex12.1.2.go
package main
import "fmt"
func main() {
t := [...]float64{24.0, 25.9, 27.8, 26.9, 26.2} // ❶
for i := 0; i < len(t); i++ { // ❷
fmt.Println(t[i]) // ❸
}
for i, v := range t {
fmt.Println(i, v, t[i])
}
}
- 아래소스는 배열을 복사하는 소스이다. b = a 문장만으로 a의 모든 값을 b에 복사할수있다. 배열복사는 같은 타입, 같은 크기만이 복사할수있다. 사이즈가 다르거나 배열의 변수타입이 다르면 복사할때 오류가 발생한다.
//ch12/ex12.5/ex12.5.go
package main
import "fmt"
func main() {
a := [5]int{1, 2, 3, 4, 5}
b := [5]int{500, 400, 300, 200, 100}
for i, v := range a { // ❶ 배열 a 원소 출력
fmt.Printf("a[%d] = %d\n", i, v)
}
fmt.Println() // ❷ 개행
for i, v := range b { // ❸ 배열 b 원소 출력
fmt.Printf("b[%d] = %d\n", i, v)
}
b = a // ❹ a 배열을 b변수에 복사
fmt.Println() // 개행
for i, v := range b { // ➎ 배열 b 원소 출력
fmt.Printf("b[%d] = %d\n", i, v)
}
}
- 아래는 다중배열의 소스이다. 다중배열은 배열을 요소로 가지는 배열이다.
//ch12/ex12.6/ex12.6.go
package main
import "fmt"
func main() {
a := [2][5]int{ // ❶ 이중 배열 선언
{1, 2, 3, 4, 5},
{5, 6, 7, 8, 9}, // ❷ 여러 줄에 걸쳐 초기화할 때는 쉼표를 찍자!
}
for _, arr := range a { // ❸ arr값은 순서대로 a[0]의 배열 a[1]의 배열
for _, v := range arr { // ❹ v값은 순서대로 a[0]과 a[1] 배열의 각 원소
fmt.Print(v, " ") // ➎ v값 출력
}
fmt.Println()
}
}
13장 구조체
- 12장의 배열에서 보면 배열은 해당 배열의 요소들이 같은 타입이여야만한다. 구조체는 서로 다른 타입의 값들을 하나의 변수로 묶어줄수가 있다.
- 아래의 소스에서는 Address, Size, Price, Type을 House라는 이름의 구조체로 정의하였다. 문자열,정수, 실수등의 값을 하나의 값으로 묶었다.
//ch13/ex13.1/ex13.1.go
package main
import "fmt"
type House struct { // ❶ House 구조체를 정의합니다.
Address string
Size int
Price float64
Type string
}
func main() {
var house House // ❷ House 구조체 변수를 선언합니다.
house.Address = "서울시 강동구 ..." // ❸ 각 필드값을 초기화합니다.
house.Size = 28
house.Price = 9.8
house.Type = "아파트"
fmt.Println("주소:", house.Address) // ❹ 필드값을 출력합니다.
fmt.Printf("크기 %d평\n", house.Size)
fmt.Printf("가격: %.2f억원\n", house.Price) // ➎ 소수점 2자리까지 출력합니다.
fmt.Println("타입:", house.Type)
}
- 구조체변수의 멤버를 구조체로 다시 지정할수있다. 아래의 소스에서 보면 User라는 이름의 구조체가 있는데, VIPUSER에서 멤버로 User를 사용하고 있다.
//ch13/ex13.2/ex13.2.go
package main
import "fmt"
type User struct { // 일반 고객용 구조체
Name string
ID string
Age int
}
type VIPUser struct { // VIP 고객용 구조체
UserInfo User
VIPLevel int
Price int
}
func main() {
user := User{"송하나", "hana", 23}
vip := VIPUser{
User{"화랑", "hwarang", 40},
3,
250, // 여러 줄로 초기화할 때는 제일 마지막 값 뒤에 꼭 쉼표를 달아주세요.
} // ❶ User를 포함한 VIPUser 구조체 변수를 초기화합니다.
fmt.Printf("유저: %s ID: %s 나이 %d\n", user.Name, user.ID, user.Age)
fmt.Printf("VIP 유저: %s ID: %s 나이 %d VIP 레벨: %d VIP 가격: %d만원\n",
vip.UserInfo.Name, // ❷ UserInfo 안의 Name
vip.UserInfo.ID, // ❸ UserInfo 안의 ID
vip.UserInfo.Age,
vip.VIPLevel, // ❹ VIPUser의 VIPLevel
vip.Price, // ➎ 마지막에 쉼표
)
}
- 위의소스에서 VIPUser구조체로 vip를 생성하였는데, Name에 접근하려면 vip.UserInfo.Name 이렇게 두단계를 거쳐서 접근해야한다.
- 하지만 아래 소스처럼 VIPUser구조체를 선언할때, User필드명을 생략하는 경우 vip.Name이렇게 바로 접근이 가능하다.
//ch13/ex13.3/ex13.3.go
package main
import "fmt"
type User struct { // 일반 고객용 구조체
Name string
ID string
Age int
}
type VIPUser struct { // VIP 고객용 구조체
User // ❶ 필드명 생략
VIPLevel int
Price int
}
func main() {
user := User{"송하나", "hana", 23}
vip := VIPUser{
User{"화랑", "hwarang", 40},
3,
250,
}
fmt.Printf("유저: %s ID: %s 나이 %d\n", user.Name, user.ID, user.Age)
fmt.Printf("VIP 유저: %s ID: %s 나이 %d VIP 레벨: %d VIP 가격: %d만원\n",
vip.Name, // ❷ . 하나로 접근할 수 있습니다.
vip.ID,
vip.Age,
vip.VIPLevel,
vip.Price, // 여러 줄로 초기화할 때는 제일 마지막 값 뒤에 꼭 쉼표를 달아주세요.
)
}
- 만약에 상위구조체와 하위구조체의 필드명이 겹치면 어떻게 될것인가?
- 아래의 소스를 보면 VIPUser구조체에 Level필드가 있고, 해당구조체의 하위구조체인 User구조체에도 Level필드가 있다.
- 이럴경우 vip.Level은 VIPUser의 Level을 사용하게 된다. User구조체의 Level을 사용하려면 vip.User.Level이렇게 하면 사용할수있다.
//ch13/ex13.4/ex13.4.go
package main
import "fmt"
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() {
user := User{"송하나", "hana", 23, 10}
vip := VIPUser{
User{"화랑", "hwarang", 40, 10},
250,
3, // 여러 줄로 초기화할 때는 제일 마지막 값 뒤에 꼭 쉼표를 달아주세요.
}
fmt.Printf("유저: %s ID: %s 나이 %d\n", user.Name, user.ID, user.Age)
fmt.Printf("VIP 유저: %s ID: %s 나이 %d VIP 레벨: %d 유저 레벨:%d\n",
vip.Name,
vip.ID,
vip.Age,
vip.Level, // ➍ VIPUser의 Level
vip.User.Level, // ➎ 포함된 구조체명을 쓰고 접근
)
}
- 구조체 값의 복사는 배열과 마찬가지로 대입연산자를 사용하여 복사할수있다.
//ch13/ex13.5/ex13.5.go
package main
import "fmt"
type Student struct {
Age int // ❶ 대문자로 시작하는 필드는 외부로 공개됩니다.
No int
Score float64
}
func PrintStudent(s Student) {
fmt.Printf("나이:%d 번호:%d 점수:%.2f\n", s.Age, s.No, s.Score)
}
func main() {
var student = Student{15, 23, 88.2}
// ❷ student 구조체 모든 필드가 student2 로 복사됩니다.
student2 := student
PrintStudent(student2) // ❸ 함수 호출시에도 구조체가 복사됩니다.
}
- 아래의 소스를 보면 구조체의 각 요소의 크기는 4바이트,8바이트이다. 사이즈를 출력하면 12가 될것같지만 실제로는 16이 출력된다. 레지스터 크기가 8바이트인 컴퓨터는 한번의 연산에 8바이트 크기를 연산할수있다. 그래서 데이터 크기와 똑같은 크기로 정렬되어있으면 효율적으로 데이터를 읽어올수있다.
- 아래의 경우에 Age는 4바이트이기때문에 8바이트로 정렬된다. 그래서 값이 16으로 출력된다.
//ch13/ex13.6/ex13.6.go
package main
import (
"fmt"
"unsafe"
)
type User struct {
Age int32 // ❶ 4바이트
Score float64 // 8바이트
}
func main() {
user := User{23, 77.2}
fmt.Println(unsafe.Sizeof(user))
}
- 아래의 경우에는 사이즈가 19바이트이지만 실제로 출력하면, 40바이트가 출력된다. 메모리낭비가 21바이트나 낭비된다.
//ch13/ex13.7/ex13.7.go
package main
import (
"fmt"
"unsafe"
)
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))
}
- 아래와 같이 1바이트짜리 변수들을 한군데로 몰아서 코드를 작성하면 24바이트의 메모리만 사용하게 된다.
//ch13/ex13.8/ex13.8.go
package main
import (
"fmt"
"unsafe"
)
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))
}
14장 포인터
- 포인터는 메모리 주소를 값으로 갖는 타입이다.
- 아래 그림과 같이 어떤변수가 있을때 그 변수가 메모리에 저장되는데 저장된 위치도 숫자값이기때문에 해당 숫자값을 갖는 변수를 포인터 변수라고 한다.
- 아래 소스에서 a라는 변수가 500의 값을 가지고 있고, p라는 포인터 변수는 a의 주소값을 가지고 있다.
- *p = 100 의 명령을 사용하여 p가 가리키고 있는 메모리에 100을 저장한다. 따라서 a의 값을 출력하면 100이 출력된다.
- var p *int에서 p의 기본값은 nil 값을 가진다.
//ch14/ex14.1/ex14.1.go
package main
import "fmt"
func main() {
var a int = 500
var p *int // ❶ int 포인터 변수 p 선언
p = &a // ❷ a의 메모리 주소를 변수 p의 값으로 대입(복사)
fmt.Printf("p의 값: %p\n", p) // ❸ 메모리 주솟값 출력
fmt.Printf("p가 가리키는 메모리의 값: %d\n", *p) // ❹ p가 가리키는 메모리의 값 출력
*p = 100 // ➎ p가 가리키는 메모리 공간의 값을 변경합니다.
fmt.Printf("a의 값: %d\n", a) // ➏ a값 변화 확인
}
- 아래의 소스에서 p1,p2는 모두 a변수의 주소값을 가지고 있다. 그래서 p1과 p2는 서로 같은 값이다.
//ch14/ex14.2/ex14.2.go
package main
import "fmt"
func main() {
var a int = 10
var b int = 20
var p1 *int = &a // ❶ p1은 a의 메모리 공간을 가리킵니다.
var p2 *int = &a // ❷ p2는 a의 메모리 공간을 가리킵니다.
var p3 *int = &b // ❸ p3는 b의 메모리 공간을 가리킵니다.
fmt.Printf("p1 == p2 : %v\n", p1 == p2)
fmt.Printf("p2 == p3 : %v\n", p2 == p3)
}
- 위의 소스에서 a 값과 p1,p2의 값은 같은 주소를 공유한다. a를 변경하면 p1,p2의 값도 변경된다.
- 아래 소스에서 보면 ChangeData()에서는 data를 받는데, 해당 함수가 호출할때마다 Data구조체가 생성되어 해당값이 복사되어 전달된다(Call by value). 그래서 함수내에서 어떤값을 바꾸더라도 main()에 있는값은 바뀌지 않는데, 해당 함수는 호출할때마다 Data구조체를 생성하는데 총 1608바이트가 새로 생성된다.
- 만약 해당 함수가 반복문에서 자주 호출되는 함수라면 메모리가 낭비되고, 성능이 저하될수있다.
//ch14/ex14.3/ex14.3.go
package main
import "fmt"
type Data struct { // ❶ Data형 구조체
value int
data [200]int
}
func ChangeData(arg Data) { // ❷ 파라미터로 Data를 받습니다.
arg.value = 999
arg.data[100] = 999
}
func main() {
var data Data
ChangeData(data) // ❸ 인수로 data를 넣습니다.
fmt.Printf("value = %d\n", data.value)
fmt.Printf("data[100] = %d\n", data.data[100]) // ❹ data 필드 출력
}
- 아래의 소스는 ChangeData() 함수에서 파라미터를 포인터 변수로 받는다.
- 이렇게 받을경우 해당 함수를 호출할때마다 4바이트만 사용되기때문에, 여러번 호출해도 성능이 떨어지지 않는다.
- 포인터변수로 받기때문에 해당 함수에서 값을 변경하면 main()함수에도 해당 변경된값이 영향을 받는다.
//ch14/ex14.4/ex14.4.go
package main
import "fmt"
type Data struct {
value int
data [200]int
}
func ChangeData(arg *Data) { // ❶ 파라미터로 Data 포인터를 받습니다.
arg.value = 999 // ❸ arg 데이터 변경
arg.data[100] = 999
}
func main() {
var data Data
ChangeData(&data) // ❷ 인수로 data의 주소를 넘깁니다.
fmt.Printf("value = %d\n", data.value) // ❹ data 필드값 출력
fmt.Printf("data[100] = %d\n", data.data[100])
}
- 아래 소스에서 NewUser()함수에서 User를 생성하여 리턴한다. 해당 값은 사라지지 않는다.
package main
import "fmt"
type User struct {
Name string
Age int
}
func NewUser(name string, age int) *User {
var u = User{name, age}
return &u // 1 탈출 분석으로 u 메모리가 사라지지 않음
}
func main() {
userPointer := NewUser("AAA", 23)
fmt.Println(userPointer)
}
'Go' 카테고리의 다른 글
[묘공단] Tucker의 Go 언어 프로그래밍 20장~21장 (0) | 2024.02.03 |
---|---|
[묘공단] Tucker의 Go 언어 프로그래밍 18장~19장 (0) | 2024.02.03 |
[묘공단] Tucker의 Go 언어 프로그래밍 15장~17장 (0) | 2024.01.29 |
[묘공단] Tucker의 Go 언어 프로그래밍 8장~11장 (0) | 2024.01.23 |
[묘공단] Tucker의 Go 언어 프로그래밍 3장~7장 (0) | 2024.01.21 |