for문은 초기화 구문, 조건 표현, 사후 구문으로 구성되는데, 초기화 구문과 사후 구문을 생략하면 (타 언어의) while문과 같아지고, 조건 표현을 생략하면 무한 루프가 된다.switch 문을 이용해서 복잡한 if-else 구문 중첩을 표현할 수 있다.defer문은 자신을 둘러싼 함수가 종료할 때까지 어떠한 함수의 실행을 연기한다.구조체 리터럴은 필드 값을 나열하여 새로 할당된 구조체 값을 나타낸다.
type Vertex struct {
X, Y int
}
var (
v1 = Vertex{1, 2} // has type Vertex
v2 = Vertex{X: 1} // Y:0 is implicit
v3 = Vertex{} // X:0 and Y:0
p = &Vertex{1, 2} // has type *Vertex
)
[n]T 타입은 타입이 T인 n 값들의 배열이다.a[low : high]슬라이스는 어떤 데이터도 저장할 수 없고 기본 배열의 한 영역을 나타낼 뿐이다.
슬라이스의 요소를 변경하면 기본 배열의 해당 요소가 수정된다.
동일한 기본 배열을 공유하는 다른 슬라이스는 이런 변경사항을 볼 수 있다.
상한 또는 하한을 생략하면, 슬라이싱할 때 기본 값을 사용할 수 있다. 하한의 경우 기본 값은 0이고, 상한의 경우 슬라이스의 길이다.
슬라이스의 zero value는 nil이다.
nil 슬라이스의 길이와 용량은 0이며, 기본 배열을 가지고 있지 않다.
슬라이스는 make 함수로 생성할 수 있다. (동적 크기의 배열 생성)
make 함수는 0으로 이루어진 배열을 할당한다. 그리고 해당 배열을 참조하는 슬라이스를 반환한다.
a := make([]int, 0, 5) // len(a)=0, cap(a)=5
append 함수를 이용하여 슬라이스에 요소를 추가할 수 있다.
func append(s []T, vs ...T) []T
s는 슬라이스의 타입 T이다.T의 값들은 슬라이스에 추가할 값들이다.for에서 range는 슬라이스 또는 맵의 요소들을 순회한다.
슬라이스에서 사용하면 각 순회마다 두 개의 값(인덱스와, 해당 인덱스의 값 복사본)이 반환된다.
(인덱스만을 원하면 두 번째 변수를 생략할 수 있다. 아니면 _를 할당하거나)
var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
for i, v := range pow {
fmt.Printf("2**%d = %d\n", i, v)
}
맵은 키를 값에 매핑한다.
맵의 zero value는 nil이며 여기엔 키도 없고 키를 추가할 수도 없다.
make 함수를 통해 주어진 타입의 초기화되고 사용 준비가 된 맵을 반환할 수 있다.
type Vertex struct {
Lat, Long float64
}
var m map[string]Vertex
func main() {
m = make(map[string]Vertex)
m["Bell Labs"] = Vertex{
40.68433, -74.39967,
}
fmt.Println(m["Bell Labs"])
}
맵 리터럴은 구조체 리터럴과 같지만 키가 필요하다.
var m = map[string]Vertex{
"Bell Labs": Vertex{
40.68433, -74.39967,
},
"Google": Vertex{
37.42202, -122.08408,
},
}
Map Mutation은 다음과 같이 할 수 있다.
// 추가 및 업데이트
m[key] = elem
// 요소 검색
elem, ok := m[key]
// 요소 제거
delete(m, key)
Golang에선 함수들도 값이다. 인수나 반환 값 등으로 전달될 수 있다.
func compute(fn func(float64, float64), float64) float64 {
return fn(3, 4)
}
Go 함수들은 클로저일 수도 있다. 클로저는 함수의 외부로부터 오는 변수를 참조하는 함수 값이다.
Go는 클래스를 가지지 않는다. 하지만 그와 같은 타입의 메소드를 정의할 수 있다.
그 메소드는 특별한 'receiver' 인자가 있는 함수이다.
type Vertex struct {
X, Y float64
}
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
* 메소드는 종종 리시버를 수정해야 하기 때문에 포인터 리시버가 값 리시버보다 일반적이다.
포인터 리시버가 있는 메소드는 호출될 때 값이나 포인터를 리시버로 받아들인다.
var v Vertex
v.Scale(5) // OK
p := &v
p.Scale(10) // OK
Scale은 포인터 리시버를 가진 메소드이기 때문에 편의상 Go가 v.Scale(5)를 (*v).Scale(5)로 해석하기 때문이다.
포인터 리시버를 사용하는 데에는 두 가지 이유가 있다.
interface type은 메소드 시그니처 집합으로 정의된다.
인터페이스 유형의 값은 해당 메소드를 전부 구현하는 모든 값을 보유할 수 있다.
암시적 인터페이스는 인터페이스의 정의를 구현으로부터 분리하며, 이는 사전 정렬 없이 어떠한 패키지에 등장할 수 있다.
타입 선언을 직렬로 허용하는 구조
switch v := i.(type) {
case T:
// here v has type T
case S:
// here v has type S
default:
// no match; here v has the same type as i
}
Go 프로그램은 error 값으로 오류 상태를 표현한다.
error 타입은 fmt.Stringer 와 유사한 내장 인터페이스이다.
type error interface {
Error() string
}
함수는 종종 error 값을 반환하며, 호출 코드는 오류가 nil 과 같은지 테스트하여 오류를 처리해야한다.
(nil error 는 성공을 나타낸다.)
goroutine은 Go 런타임에 의해 관리되는 lightweight thread이다.
go f(x, y, z)
f와 인자 x, y, z의 evaluation은 현재 goroutine에서 일어나고, f의 실행은 새로운 goroutine에서 일어난다.
goroutine은 같은 주소의 공간에서 실행되고, 따라서 공유된 메모리는 synchronous 해야한다.
sync 패키지는 유용한 기본형을 제공한다.
Channel은 채널 연산자 <-을 통해 값을 주고 받을 수 있는 하나의 분리된 통로이다.
(데이터는 화살표의 방향대로 흐른다)
ch <- v // 채널 ch에 v를 전송한다.
v := <-ch // ch로부터 값을 받고, 값을 v에 대입한다.
channel은 map과 slice처럼 사용 전에 생성되어야 한다.
ch := make(chan int)
기본적으로 전송과 수신은 다른 한 쪽이 준비될 때까지 block 상태이다.
이는 명시적인 lock이나 조건 변수 없이 goroutine이 synchronous하게 작업될 수 있도록 한다.
전송자는 더 이상 보낼 데이터가 없다는 것을 암시하기 위해 channel을 close할 수 있다.
수신자는 수신에 대한 표현에 두 번째 매개변수를 할당함으로써 채널이 닫혔는지 확인할 수 있다.
v, ok := <-ch
만약 더 수신할 값이 없고, channel이 닫혀있다면 ok는 false이다.
for i := range c 반복문은 channel이 닫힐 때까지 반복해서 channel에서 값을 수신한다.
select 문은 goroutine이 다중 커뮤니케이션 연산에서 대기할 수 있게 한다.
select는 case들 중 하나가 실행될 때까지 block된다. 그리고나서 해당하는 case문을 수행한다.
다수의 case가 준비되는 경우에는 select가 무작위로 하나를 선택한다.
독특한 것은 Go의 select문이 송신과 수신 모두를 지원한다는 것이다.
이것은 채널 중심의 동시성 모델을 효율적으로 구현하기 위해 설계된 기능이다.
충돌을 피하기 위해 오직 하나의 goroutine만이 어떤 순간에 어떤 변수에 접근할 수 있도록 하려면? (mutual exclusion)
Go의 표준 라이브러리는 sync.Mutex와 두 가지 method를 통해 mutual exclusion을 제공한다.
바로 Lock과 Unlock이 그것이다.
type SafeCounter struct {
mu sync.Mutex
v map[string]int
}
// Inc increments the counter for the given key.
func (c *SafeCounter) Inc(key string) {
c.mu.Lock()
// Lock so only one goroutine at a time can access the map c.v.
c.v[key]++
c.mu.Unlock()
}
// Value returns the current value of the counter for the given key.
func (c *SafeCounter) Value(key string) int {
c.mu.Lock()
// Lock so only one goroutine at a time can access the map c.v.
defer c.mu.Unlock()
return c.v[key]
}