A Tour of Go 3

태그: ,

카테고리: ,

출처 Go Tour

🚫 아래 내용은 주관적인 생각이므로 사실과 다를 수 있습니다.



Pointers

package main

import "fmt"

func main() {
	i, j := 42, 2701

	p := &i         // p는 i의 포인터
	fmt.Println(*p) // 포인터를 통해 i를 읽어온다
	*p = 21         // 포인터를 통해 i를 초기화 한다
	fmt.Println(i)  // 초기화 된 i의 새 값을 확인

	p = &j         // p는 j의 포인터
	*p = *p / 37   // 포인터를 통해 j의 값을 37로 나눈 값으로 초기화
	fmt.Println(j) // 초기화 된 j의 새 값을 확인
}
// 42
// 21
// 73

Go에는 포인터가 있다
포인터에는 해당 값이 저장된 메모리의 주소 값이 저장된다
*T 타입은 T 값의 포인터이고, 기본값은 nil이다

var p *int // p는 int 포인터 타입이다

& 연산자는 피연산자의 포인터를 생성한다

i := 42
p = &i

* 연산자는 포인터가 가리키는 주소에 저장된 값을 가져온다

fmt.Println(*p) // 포인터 p를 통해 i를 읽어온다
*p = 21         // 포인터 p를 통해 i를 초기화 한다

이런 참조 방식을 ‘역참조’ 또는 ‘간접 참조’라고 한다
C언어와는 다르게 Go에서는 포인터 연산은 되지 않는다


Structs

package main

import "fmt"

type Vertex struct {
	X int
	Y int
}

func main() {
	fmt.Println(Vertex{1, 2})
}
// {1 2}

struct(구조체)는 필드의 집합체이다


Struct Fields

package main

import "fmt"

type Vertex struct {
	X int
	Y int
}

func main() {
	v := Vertex{1, 2}
	v.X = 4
	fmt.Println(v.X)
}
// 4

struct(구조체)의 필드들은 .을 통해 접근이 가능하다


Pointers to structs

package main

import "fmt"

type Vertex struct {
	X int
	Y int
}

func main() {
	v := Vertex{1, 2}
	p := &v
	p.X = 1e9
	fmt.Println(v)
}
// {1000000000 2}

struct(구조체)의 필드들은 구조체 포인터를 통해서도 접근이 가능하다

구조체의 포인터 p가 있는 경우, (*p).X와 같은 방식으로 구조체의 필드 X에 접근할 수 있다
그러나 너무 번거롭기 때문에 Go는 명시적 역참조 없이 그냥 p.X와 같은 방식도 허용한다


Struct Literals

package main

import "fmt"

type Vertex struct {
	X, Y int
}

var (
	v1 = Vertex{1, 2}  // 일반 Vertex 인스턴스
	v2 = Vertex{X: 1}  // Y는 int 타입의 기본값인 0으로 초기화된다
	v3 = Vertex{}      // X는 0, Y는 0이 된다 (기본값이 적용됨)
	p  = &Vertex{1, 2} // Vertex 포인터 p
)

func main() {
	fmt.Println(v1, p, v2, v3)
}
// {1 2} &{1 2} {1 0} {0 0}

구조체 초기화와 동시에 필드명을 명시해 직접적으로 필드 값을 초기화 할 수 있다

변수명 = 구조체{필드2: 필드2값, 필드1: 필드1값...} // 순서는 상관없다

다른 변수와 마찬가지로 &연산자를 사용하면 포인터가 반환된다


Arrays

package main

import "fmt"

func main() {
	var a [2]string
	a[0] = "Hello"
	a[1] = "World"
	fmt.Println(a[0], a[1])
	fmt.Println(a)

	primes := [6]int{2, 3, 5, 7, 11, 13}
	fmt.Println(primes)
}
// Hello World
// [Hello World]
// [2 3 5 7 11 13]

[n]T는 길이 n의 T 타입 배열이다
표기법은 아래와 같다

var a [10]int

길이 10의 int배열 변수 a의 선언이다

배열의 길이는 배열 타입의 일부분이라 변경될 수 없다
제한적인것 같지만 Go에는 이를 위한 기능들이 있다


Slices

package main

import "fmt"

func main() {
	primes := [6]int{2, 3, 5, 7, 11, 13}

	var s []int = primes[1:4]
	fmt.Println(s)
}
// [3 5 7]

길이가 고정되어 있는 배열과는 다르게 슬라이스의 길이는 동적이다
슬라이스를 통해 배열의 요소들을 유연하게 볼 수 있다
[]T는 T 타입 요소들의 슬라이스다
슬라이스의 형태는 콜론(:)을 기준으로 시작(포함)값과 끝(미포함)값을 가진다

a[시작 : ]

half-open 범위 규격으로
시작값은 인덱스에 포함이지만, 끝값은 인덱스에 포함되지 않는다

아래의 슬라이스는 a의 인덱스 1부터 3까지의 요소를 가진다

a[1:4]

Slices are like references to arrays

package main

import "fmt"

func main() {
	names := [4]string{
		"John",
		"Paul",
		"George",
		"Ringo",
	}
	fmt.Println(names)

	a := names[0:2]
	b := names[1:3]
	fmt.Println(a, b)

	b[0] = "XXX"
	fmt.Println(a, b)
	fmt.Println(names)
}
// [John Paul George Ringo]
// [John Paul] [Paul George]
// [John XXX] [XXX George]
// [John XXX George Ringo]

슬라이스는 어떤 데이터도 저장하지 않는다
슬라이스는 해당되는 배열의 범위만을 나타낸다

슬라이스의 요소를 변경하면, 그에 해당하는 배열의 요소가 변경된다
같은 배열을 가리키는 슬라이스들이라면 모두 그 변경사항을 공유한다


Slice literals

package main

import "fmt"

func main() {
	q := []int{2, 3, 5, 7, 11, 13}
	fmt.Println(q)

	r := []bool{true, false, true, true, false, true}
	fmt.Println(r)

	s := []struct {
		i int
		b bool
	}{
		{2, true},
		{3, false},
		{5, true},
		{7, true},
		{11, false},
		{13, true},
	}
	fmt.Println(s)
}
// [2 3 5 7 11 13]
// [true false true true false true]
// [{2 true} {3 false} {5 true} {7 true} {11 false} {13 true}]

슬라이스 리터럴은 길이를 뺀 배열 리터럴과 같다

아래는 배열 리터럴이다

[3]bool{true, true, false}

아래는 위와 같은 배열을 생성하고, 그에 대한 슬라이스를 만드는 코드다

[]bool{true, true, false}

Slice defaults

package main

import "fmt"

func main() {
	s := []int{2, 3, 5, 7, 11, 13}

	s = s[1:4]
	fmt.Println(s)

	s = s[:2]
	fmt.Println(s)

	s = s[1:]
	fmt.Println(s)
}
// [3 5 7]
// [3 5]
// [5]

슬라이스를 할 때, 인덱스 시작값이나 끝값을 생략하면 기본값이 적용된다
기본값은 각각 시작값은 0, 끝값은 배열의 길이(마지막 인덱스 + 1)와 같다

var a [10]int

위 배열에 대한 아래의 슬라이스들은 모두 같다

a[0:10]
a[:10]
a[0:]
a[:]

Slice length and capacity

package main

import "fmt"

func main() {
	s := []int{2, 3, 5, 7, 11, 13}
	printSlice(s)

	// Slice the slice to give it zero length.
	s = s[:0]
	printSlice(s)

	// Extend its length.
	s = s[:cap(s)]
	printSlice(s)

	// Drop its first two values.
	s = s[2:]
	printSlice(s)
}

func printSlice(s []int) {
	fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
// len=6 cap=6 [2 3 5 7 11 13]
// len=0 cap=6 []
// len=6 cap=6 [2 3 5 7 11 13]
// len=4 cap=4 [5 7 11 13]

슬라이스는 길이값과 용량값을 모두 가지고 있다
길이값은 슬라이스에 포함된 요소들의 수다
용량값은 슬라이스가 가리키는 배열에서
슬라이스의 첫번째 인덱스부터 계산한 배열 요소들의 수다

슬라이스 s의 길이값과 용량값은 각각 len(s), cap(s)를 통해 얻을수 있다

용량값이 충분한 슬라이스의 길이를 늘리려면
인덱스 끝값을 늘려 다시 슬라이스를 하면 된다

인덱스 시작값을 늘리면 용량값이 줄어들기 때문에 되돌릴 수 없다


Nil slices

package main

import "fmt"

func main() {
	var s []int
	fmt.Println(s, len(s), cap(s))
	if s == nil {
		fmt.Println("nil!")
	}
}
// [] 0 0
// nil!

슬라이스의 기본값은 nil이다

nil 슬라이스의 길이값과 용량값은 각각 0이며,
가리키는 배열 또한 없다


Creating a slice with make

package main

import "fmt"

func main() {
	a := make([]int, 5)
	printSlice("a", a)

	b := make([]int, 0, 5)
	printSlice("b", b)

	c := b[:2]
	printSlice("c", c)

	d := c[2:5]
	printSlice("d", d)
}

func printSlice(s string, x []int) {
	fmt.Printf("%s len=%d cap=%d %v\n",
		s, len(x), cap(x), x)
}
// a len=5 cap=5 [0 0 0 0 0]
// b len=0 cap=5 []
// c len=2 cap=5 [0 0]
// d len=3 cap=3 [0 0 0]

빌트인 함수 make를 통해 슬라이스를 만들수도 있다

아래는 동적인 길이를 가진 배열을 생성하는 방법이다

a := make([]int, 5) // len(a)=5

make 함수는 두 번째 파라미터로 전달받은 값의 길이를 가진,
기본값으로 초기화된 배열을 할당하고 그 배열을 가리키는 슬라이스를 반환한다

슬라이스의 용량값을 지정하려면 make 함수의 세 번째 파라미터로 전달하면 된다

b := make([]int, 0, 5) // len(b)=0, cap(b)=5

b = b[:cap(b)] // len(b)=5, cap(b)=5
b = b[1:]      // len(b)=4, cap(b)=4

Slices of slices

package main

import (
	"fmt"
	"strings"
)

func main() {
	// Create a tic-tac-toe board.
	board := [][]string{
		[]string{"_", "_", "_"},
		[]string{"_", "_", "_"},
		[]string{"_", "_", "_"},
	}

	// The players take turns.
	board[0][0] = "X"
	board[2][2] = "O"
	board[1][2] = "X"
	board[1][0] = "O"
	board[0][2] = "X"

	for i := 0; i < len(board); i++ {
		fmt.Printf("%s\n", strings.Join(board[i], " "))
	}
}
// X _ X
// O _ X
// _ _ O

슬라이스는 다른 슬라이스를 포함한 모든 타입을 담을 수 있다


Appending to a slice

package main

import "fmt"

func main() {
	var s []int
	printSlice(s)

	// append works on nil slices.
	s = append(s, 0)
	printSlice(s)

	// The slice grows as needed.
	s = append(s, 1)
	printSlice(s)

	// We can add more than one element at a time.
	s = append(s, 2, 3, 4)
	printSlice(s)
}

func printSlice(s []int) {
	fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
// len=0 cap=0 []
// len=1 cap=1 [0]
// len=2 cap=2 [0 1]
// len=5 cap=6 [0 1 2 3 4]

슬라이스에 요소를 추가하려면 빌트인 함수 append를 쓰면 된다
빌트인 함수 append가 기술된 문서에는 아래와 같이 설명되어 있다

func append(s []T, vs ...T) []T

append 함수의 첫번째 파라미터는 요소를 추가할 슬라이스이고
나머지는 슬라이스에 추가할 요소들이다
반환값으로는 첫번째 파라미터로 넘겨진 슬라이스에 나머지 파라미터들이 더해진 슬라이스이다

만약 반환값이 기존 슬라이스의 용량값을 초과한다면,
새로운 배열을 할당하고 그 배열을 가리키는 슬라이스를 반환한다
자세한 내용은 관련 문서를 보자


댓글남기기