
E aí, Gopher, tudo certo?
Ultimamente venho dedicando meu tempo livre ao estudo de Golang e preciso admitir: tem sido uma jornada interessante e desafiadora.
Durante o processo de aprendizado, tive inicialmente uma certa “resistência” para entender o que de fato são ponteiros. Por coincidência, acompanhando alguns blogs e páginas sobre Go, percebi que muita gente passa pelo mesmo obstáculo — e isso é completamente normal.
Foi por isso que resolvi criar este espaço para escrever um pouco sobre eles.
Afinal, o que é um ponteiro?
Um ponteiro é simplesmente uma variável cujo conteúdo é um endereço de memória. Se você compartilhar esse ponteiro em outro lugar, conseguimos acessar diretamente o endereço em que o dado está armazenado e, a partir daí, manipular de alguma forma aquele espaço de memória.
Um resumo rápido de como a memória funciona:

Em resumo, a memória é composta por endereços que armazenam dados, chamados de “valores” na imagem acima. Cada variável criada no código recebe um endereço de memória, e o valor correspondente é armazenado nesse endereço.
package main
func main() {
a := 15
b := "foo"
}
No trecho de código acima, duas variáveis foram declaradas: uma chamada
acom o valor 15, e outra chamadabcom o valor “foo”.

A imagem acima mostra o endereço de memória atribuído a cada variável e seus respectivos valores.
Como declarar um ponteiro?
Um ponteiro é declarado usando * seguido do tipo de dado que se deseja declarar, como int, string, etc.
package main
func main() {
var p *int
/* Aqui, o ponteiro foi declarado,
mas nenhum endereço de memória foi atribuído a ele. */
}
Como atribuir um endereço de memória a um ponteiro?
Atribuímos o endereço de uma variável ao ponteiro usando & antes da variável (ex.: &a).
package main
import "fmt"
func main() {
var p *int
a := 15 // declarando uma variável do tipo int
p = &a // pegando o endereço da variável (a)
// e atribuindo ao ponteiro (*)
fmt.Println(p) // OUTPUT: endereço de memória atribuído, exemplo:
// 0x140000140a5
}
Como saber o valor armazenado no endereço de memória?
Para obter o valor armazenado no endereço de memória atribuído ao ponteiro, basta usar * antes do ponteiro (ex.: p).
package main
import "fmt"
func main() {
var p *int
a := 15
p = &a
fmt.Println(*p) // OUTPUT: 15 -> Valor armazenado no endereço.
}
Quando usar ponteiros?
É sabido que ponteiros ajudam quando o assunto é performance. Mas quando devemos usá-los?
Mutabilidade
A única maneira de modificar uma variável passada para uma função é passando um ponteiro. Por padrão, passar por valor significa que qualquer alteração é feita em uma cópia com a qual você está trabalhando. Como resultado, essas alterações não são refletidas na função chamadora. Veja um exemplo:
package main
import "fmt"
type dog struct {
name string
}
func main() {
d := dog{"Fifo"}
changeDogName(d)
fmt.Println(d)
}
func changeDogName(d dog) {
d.name = "Mike"
}
A saída do fmt.Println será “Fifo” e não “Mike”, porque o nome do cachorro está sendo alterado em uma “cópia” da variável. Para corrigir isso, basta usar um ponteiro.
package main
import "fmt"
type dog struct {
name string
}
func main() {
d := dog{"Fifo"}
changeDogName(&d)
fmt.Println(d)
}
func changeDogName(d *dog) {
d.name = "Mike"
}
Agora a saída será “Mike” 🙂
Existem outros casos em que o uso de ponteiros é recomendado, mas, na minha opinião, mutabilidade é o principal. Você encontra outros casos em um ótimo artigo que usei como referência nos meus estudos.
Enfim, é isso…
Só um lembrete: a ideia de escrever esse breve resumo sobre ponteiros surgiu principalmente para fins de aprendizado e, quem sabe, ajudar alguém com dúvidas sobre o assunto.
Usei este excelente artigo do Dylan Meeus como referência — recomendo muito a leitura. 😁