Ponteiros do Go
Go manteve os ponteiros, garantindo até certo ponto a performance, ao mesmo tempo em que limitou o uso de ponteiros para melhor GC e segurança.
Criação
Sobre ponteiros existem dois operadores comumente usados, um é o operador de endereço &, o outro é o operador de desreferenciação *. Ao obter o endereço de uma variável, retorna-se um ponteiro do tipo correspondente, por exemplo
func main() {
num := 2
p := &num
fmt.Println(p)
}O ponteiro armazena o endereço da variável num
0xc00001c088O operador de desreferenciação tem dois usos, o primeiro é acessar o elemento para o qual o ponteiro aponta, ou seja, desreferenciação, por exemplo
func main() {
num := 2
p := &num
rawNum := *p
fmt.Println(rawNum)
}p é um ponteiro, ao desreferenciar o tipo ponteiro pode-se acessar o elemento para o qual o ponteiro aponta. Outro uso é declarar um ponteiro, por exemplo
func main() {
var numPtr *int
fmt.Println(numPtr)
}<nil>*int significa que o tipo dessa variável é um ponteiro do tipo int, mas o ponteiro não pode apenas ser declarado, também precisa ser inicializado, precisa-se alocar memória para ele, caso contrário é um ponteiro nulo, não pode ser usado normalmente. Ou se usa o operador de endereço para atribuir o endereço de outra variável a esse ponteiro, ou se usa a função incorporada new para alocar manualmente, por exemplo
func main() {
var numPtr *int
numPtr = new(int)
fmt.Println(numPtr)
}Mais comum é usar declaração curta de variável
func main() {
numPtr := new(int)
fmt.Println(numPtr)
}A função new tem apenas um parâmetro que é o tipo, e retorna um ponteiro do tipo correspondente, a função vai alocar memória para esse ponteiro, e o ponteiro aponta para o valor zero do tipo correspondente, por exemplo
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]
[]Operações com Ponteiros Proibidas
Em Go não há suporte para operações com ponteiros, ou seja, o ponteiro não pode ser deslocado, primeiro vejamos um código 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
0x31d99ff884Pode-se ver que o endereço do array é consistente com o endereço do primeiro elemento do número, e após a operação de adição de um ao ponteiro, o elemento para o qual ele aponta é o segundo elemento do array. Arrays em Go também são assim, mas a diferença é que o ponteiro não pode ser deslocado, por exemplo
func main() {
arr := [5]int{0, 1, 2, 3, 4}
p := &arr
println(&arr[0])
println(p)
// tentando fazer operação com ponteiro
p++
fmt.Println(p)
}Esse programa não vai compilar, com o seguinte erro
main.go:10:2: invalid operation: p++ (non-numeric type *[5]int)TIP
A biblioteca padrão unsafe fornece muitas operações para programação de baixo nível, incluindo operações com ponteiros, vá para biblioteca padrão - unsafe para entender os detalhes.
new e make
Nas seções anteriores já mencionamos muitas vezes as funções incorporadas new e make, ambos são um pouco similares, mas também têm diferenças, vamos revisar abaixo.
func new(Type) *Type- O valor de retorno é um ponteiro do tipo
- O parâmetro recebido é o tipo
- Especificamente usado para alocar espaço de memória para ponteiros
func make(t Type, size ...IntegerType) Type- O valor de retorno é o valor, não um ponteiro
- O primeiro parâmetro recebido é o tipo, parâmetros de comprimento variável são diferentes dependendo do tipo passado
- Especificamente usado para alocar memória para slices, mapas e canais
Alguns exemplos abaixo
new(int) // ponteiro de int
new(string) // ponteiro de string
new([]int) // ponteiro de slice de inteiros
make([]int, 10, 100) // slice de inteiros com comprimento 10 e capacidade 100
make(map[string]int, 10) // mapa com capacidade 10
make(chan int, 10) // canal com tamanho de buffer 10