Go 포인터
Go 는 포인터를 유지하여 일정 수준 성능을 보장하면서도, 더 나은 GC 와 안전을 위해 포인터 사용을 제한합니다.
생성
포인터에는 두 가지常用的 연산자가 있습니다. 하나는 주소 연산자 & 이고, 다른 하나는 역참조 연산자 * 입니다. 변수의 주소를 취하면 해당 타입의 포인터를 반환합니다. 예를 들어:
func main() {
num := 2
p := &num
fmt.Println(p)
}포인터는 변수 num 의 주소를 저장합니다.
0xc00001c088역참조 연산자는 두 가지 용도가 있습니다. 첫 번째는 포인터가 가리키는 요소에 액세스하는 것, 즉 역참조입니다. 예를 들어
func main() {
num := 2
p := &num
rawNum := *p
fmt.Println(rawNum)
}p 는 포인터이며, 포인터 타입을 역참조하면 포인터가 가리키는 요소에 액세스할 수 있습니다. 또 다른 용도는 포인터를 선언하는 것입니다. 예를 들어:
func main() {
var numPtr *int
fmt.Println(numPtr)
}<nil>*int 는 해당 변수의 타입이 int 타입 포인터임을 나타냅니다. 하지만 포인터는 선언만 해서는 안 되며, 초기화해야 하고 메모리를 할당해야 합니다. 그렇지 않으면 빈 포인터가 되어 정상적으로 사용할 수 없습니다. 주소 연산자를 사용하여 다른 변수의 주소를 포인터에 할당하거나, 내장 함수 new 를 사용하여 수동으로 할당해야 합니다. 예를 들어:
func main() {
var numPtr *int
numPtr = new(int)
fmt.Println(numPtr)
}대부분 단변수를 더 많이 사용합니다.
func main() {
numPtr := new(int)
fmt.Println(numPtr)
}new 함수는 타입이라는 하나의 매개변수만 가지며 해당 타입의 포인터를 반환합니다. 함수는 해당 포인터에 메모리를 할당하며, 포인터는 해당 타입의 영값을 가리킵니다. 예를 들어:
func main() {
fmt.Println(*new(string))
fmt.Println(*new(int))
fmt.Println(*new([5]int))
fmt.Println(*new([]float64))
}
0
[0 0 0 0 0]
[]포인터 연산 금지
Go 에서는 포인터 연산을 지원하지 않습니다. 즉, 포인터는 오프셋할 수 없습니다. 먼저 C++ 코드를 보겠습니다.
int main() {
int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
int *p = &arr[0];
cout << &arr << endl
<< p << endl
<< p + 1 << endl
<< &arr[1] << endl;
}0x31d99ff880
0x31d99ff880
0x31d99ff884
0x31d99ff884배열의 주소와 첫 번째 요소의 주소가 일치하며, 포인터에 1 을 더한 연산 후 두 번째 요소를 가리키는 것을 알 수 있습니다. Go 의 배열도 이와 동일하지만, 포인터는 오프셋할 수 없다는 점이 다릅니다. 예를 들어
func main() {
arr := [5]int{0, 1, 2, 3, 4}
p := &arr
println(&arr[0])
println(p)
// 포인터 연산 시도
p++
fmt.Println(p)
}이러한 프로그램은 컴파일을 통과할 수 없으며, 오류는 다음과 같습니다.
main.go:10:2: invalid operation: p++ (non-numeric type *[5]int)TIP
표준 라이브러리 unsafe 는 로우 레벨 프로그래밍을 위한 많은 연산을 제공하며, 그중에는 포인터 연산도 포함됩니다. 세부사항은 표준 라이브러리 -unsafe 에서 확인하실 수 있습니다.
new 와 make
이전 몇 절에서 내장 함수 new 와 make 를 여러 번 언급했습니다. 두 함수는 비슷하지만 다르기도 합니다. 아래에서 복습하겠습니다.
func new(Type) *Type- 반환값은 타입 포인터입니다.
- 매개변수는 타입입니다.
- 포인터에 메모리 공간을 할당하는 데 전용됩니다.
func make(t Type, size ...IntegerType) Type- 반환값은 값이며 포인터가 아닙니다.
- 첫 번째 매개변수는 타입이며, 가변 길이는 전달되는 타입에 따라 다릅니다.
- 슬라이스, 맵, 채널에 메모리를 할당하는 데 전용됩니다.
아래는 몇 가지 예시입니다.
new(int) // int 포인터
new(string) // string 포인터
new([]int) // 정수 슬라이스 포인터
make([]int, 10, 100) // 길이 10, 용량 100 의 정수 슬라이스
make(map[string]int, 10) // 용량 10 의 맵
make(chan int, 10) // 버퍼 크기 10 의 채널