DEV Community

loading...
Cover image for Drops #04: Desmistificando ponteiros no Golang!

Drops #04: Desmistificando ponteiros no Golang!

William Queiroz
Salvando o mundo quando não estou jogando video-game 👾
Updated on ・5 min read

Introdução

E ae dev, tudo bem com você?

Agora que FINALMENTE finalizei o MBA, aproveitei o tempo livre para dedicá-lo aos estudos do Golang.

Confesso que eu estou apaixonado pela linguagem - ainda dando os primeiros passos claro - mas creio que já dá pra compartilhar uma parada muito massa da linguagem (e como ela implementa) que são os ponteiros!

Bora pro post?

Ah! mas antes disso... Esse post faz parte de uma série de artigos "drops" que tenho aqui! Veja a lista:


Afinal, o que é um ponteiro?

Um ponteiro (ou apontador) nada mais é do que uma variável que, ao invés de armazenar um valor (true, "hello world"), ela armazena um endereço que está alocado na memória.

Memória

Vamos entender a imagem a seguir:

Representação da Memória

Basicamente, bem a grosso modo, a memória é constituída por elementos que armazenam informações.

O endereço é uma posição onde os dados serão colocados (geralmente expressos em números hexadecimais). Eles podem conter apenas uma única informação.

O dado, por sua vez, é a informação presente em cada posição na memória.

Quando criamos uma variável, ela recebe um endereçamento na memória e através dessa variável podemos armazenar valores que serão alocados nesse endereço:

Criação de variável x memória

Aqui podemos ler o seguinte:

A variável a que possui o endereço de memória 0xc000192020 está armazenando o dado 10 na posição deste endereço.

Ponteiros no Go

É muito simples definir um ponteiro, basta adicionar o * junto ao tipo do ponteiro que estamos criando:

package main

import "fmt"

func main() {
    var p *int

    fmt.Println(p) // output: <nil>
}
Enter fullscreen mode Exit fullscreen mode

💡 O tipo do ponteiro indica qual é o tipo de dado que esse ponteiro irá manipular. No caso acima, criamos um ponteiro que pode "apontar" para endereços na memória de variáveis que armazenam dados do tipo inteiro.

No Go temos o conceito de zero value que, ao definir uma variável sem atribuir um valor para ela, o Go irá atribuir um valor padrão para essa variável. Cada tipo possui o seu valor padrão (0 para int, false para bool...), no caso do nosso ponteiro, o zero value será nil dado que não atribuímos nenhum valor para ele. Ou melhor dizendo: nenhum endereço!

E como podemos definir um endereço para o ponteiro? 🤔

Primeiro, criaremos outra variável no nosso código, e então, vamos atribuir o endereço dela para o ponteiro dessa forma:

package main

import "fmt"

func main() {
    var p *int

    i := 10

    p = &i // atribuindo o endereço de i para o ponteiro

    fmt.Println(p) // output: algo como 0xc000192020
}
Enter fullscreen mode Exit fullscreen mode

💡 O & usado antes da variável, indica que queremos obter o endereço na memória daquela variável.

A representação gráfica do que fizemos aqui está na imagem a seguir:

Atribuição de endereço

💡 Note que o ponteiro que criamos, também possui o seu próprio endereço na memória!

Beleza, entendi! Até aqui o ponteiro já tá "apontando" pra um endereço... mas como eu faço pra saber o que esse endereço aí tá armazenando? 🤔

Basta utilizar o operador * junto ao ponteiro (*p). Esse é o processo de desreferenciar o ponteiro para que, ao invés dele retornar o endereço armazenado, ele ir lá naquele endereço e a partir de lá retornar o valor:

package main

import "fmt"

func main() {
    var p *int

    i := 10

    p = &i

    fmt.Println(p)  // output: 0xc000192020
    fmt.Println(*p) // output: 10
}
Enter fullscreen mode Exit fullscreen mode

Uma vez que desreferenciamos o ponteiro, podemos manipular o dado que está armazenado naquele endereço:

package main

import "fmt"

func main() {
    var p *int

    i := 10

    p = &i

    fmt.Println(p)  // output: 0xc000192020
    fmt.Println(*p) // output: 10

    *p = 20

    fmt.Println(i)  // output: 20
    fmt.Println(*p) // output: 20
}
Enter fullscreen mode Exit fullscreen mode

A representação gráfica do que fizemos aqui está na imagem a seguir:

Manipulando dado pelo ponteiro

Quando usar ponteiros?

No Go, por padrão, tudo é Pass By Value. Isso significa que, quando passamos uma variável como parâmetro de uma função, essa variável é "duplicada" na memória e o que fazemos dentro do escopo da função acontece apenas no escopo da função:

package main

import "fmt"

func increment(a int) int {
    a++

    fmt.Println(a) // output: 11

    return a
}

func main() {
    x := 10

    increment(x)

    fmt.Println(x) // output: 10
}
Enter fullscreen mode Exit fullscreen mode

Como é realizada uma "cópia" da variável na memória, isso tem um custo, que pode se tornar problemático quando lidamos com um grande volume de dados nessa variável. Para economizarmos essa operação, é possível trabalhar com Pass By Reference com a ajuda de ponteiros:

package main

import "fmt"

func increment(a *int) {
    *a++

    fmt.Println(*a) // output: 11
    fmt.Println(a)  // output: 0xc0000180b0
}

func main() {
    x := 10

    increment(&x)

    fmt.Println(x)  // output: 11
    fmt.Println(&x) // output: 0xc0000180b0
}
Enter fullscreen mode Exit fullscreen mode

Perceba que a função increment não retorna mais o valor manipulado e que além disso, passou a exigir um ponteiro/endereço de memória como parâmetro (a *int).

Dessa maneira, o parâmetro da função increment não foi duplicado na memória e passamos a trabalhar com a referência da variável x (que está fora do escopo da função increment).

A grosso modo, podemos trabalhar com ponteiros uma vez que lidamos com um volume grande de dados ou simplesmente querer trabalhar com Pass By Reference ao invés de Pass By Value.

É importante entender que existem pontos positivos e negativos ao trabalhar com ponteiros. Nós podemos e devemos usá-los mas precisamos ter certeza do que estamos fazendo.

Ou seja: depende! HAHA Cabe a você avaliar os melhores cenários.

Finalizando...

Bem, é isso, por hoje, é só!

Quero te agradecer por chegar até aqui, e queria lhe pedir também para me encaminhar as suas dúvidas, comentários, críticas, correções ou sugestões sobre a publicação.

Deixe seu ❤️ se gostou ou um 🦄 se esse post te ajudou de alguma maneira! Não se esqueça de ver os posts anteriores e me siga para maaaais conteúdos.

Até!

Discussion (0)