Como muitas outras linguagens, o Go tem ponteiros. Se você está começando com ponteiros, eles podem parecer um pouco misteriosos, mas depois que você se acostuma, são bem simples. Eles são uma parte crucial do Go, então reserve um tempo para entendê-los de verdade.
Antes de entrar nos detalhes, vale a pena entender para que servem os ponteiros. Ponteiros são uma forma de compartilhar memória com outras partes do nosso programa, o que é útil por dois motivos principais:
Digamos que temos uma variável inteira comum a:
var a int
Quando declaramos uma variável, o Go precisa encontrar um lugar na memória para guardar o valor dela. Isso é bastante abstraído para nós. Quando precisamos buscar o valor guardado naquele pedaço de memória, basta nos referirmos a ele pelo nome da variável.
Por exemplo, quando escrevemos a + 2, estamos na prática buscando o valor guardado na memória associada à variável a e somando 2 a ele.
Da mesma forma, quando precisamos mudar o valor no pedaço de memória de a, podemos usar o nome da variável para fazer uma atribuição:
a = 3
O pedaço de memória associado a a agora vai guardar o valor 3.
As variáveis nos permitem nos referir a valores na memória, mas às vezes é útil saber o endereço de memória para o qual a variável aponta. Os ponteiros guardam os endereços de memória desses valores. Você declara uma variável com um tipo de ponteiro colocando um asterisco antes do tipo base:
var p *int // 'p' contains the memory address of an integer
Aqui declaramos uma variável p do tipo "ponteiro para int" (*int).
Isso significa que p vai guardar o endereço de memória de um inteiro.
O valor zero dos ponteiros é nil, porque um ponteiro nil não guarda nenhum endereço de memória.
Para encontrar o endereço de memória do valor de uma variável, podemos usar o operador &.
Por exemplo, se quisermos encontrar e guardar o endereço de memória da variável a no ponteiro p, podemos fazer o seguinte:
var a int
a = 2
var p *int
p = &a // the variable 'p' contains the memory address of 'a'
Quando temos um ponteiro, pode ser que queiramos saber o valor guardado no endereço de memória que o ponteiro representa.
Podemos fazer isso com o operador *:
var a int
a = 2
var p *int
p = &a // the variable 'p' contains the memory address of 'a'
var b int
b = *p // b == 2
A operação *p busca o valor guardado no endereço de memória armazenado em p.
Essa operação costuma ser chamada de "desreferenciamento".
Também podemos usar o operador de desreferência para atribuir um novo valor ao endereço de memória referenciado pelo ponteiro:
var a int // declare int variable 'a'
a = 2 // assign 'a' the value of 2
var pa *int
pa = &a // 'pa' now contains to the memory address of 'a'
*pa = *pa + 2 // increment by 2 the value at memory address 'pa'
fmt.Println(a) // Output: 4
// 'a' will have the new value that was changed via the pointer!
Atribuir a *pa vai mudar o valor guardado no endereço de memória que pa contém.
Como pa guarda o endereço de memória de a, ao atribuir a *pa estamos na prática mudando o valor de a!
Um aviso, porém: sempre verifique se um ponteiro não é nil antes de desreferenciá-lo.
Desreferenciar um ponteiro nil vai fazer o programa quebrar em tempo de execução!
var p *int // p is nil initially
fmt.Println(*p)
// panic: runtime error: invalid memory address or nil pointer dereference
Até agora só vimos ponteiros para valores primitivos. Também podemos criar ponteiros para structs:
type Person struct {
Name string
Age int
}
var peter Person
peter = Person{Name: "Peter", Age: 22}
var p *Person
p = &peter
Também poderíamos ter criado uma Person nova e guardado imediatamente um ponteiro para ela:
var p *Person
p = &Person{Name: "Peter", Age: 22}
Quando temos um ponteiro para uma struct, não precisamos desreferenciar o ponteiro antes de acessar um dos campos:
var p *Person
p = &Person{Name: "Peter", Age: 22}
fmt.Println(p.Name) // Output: "Peter"
// Go automatically dereferences 'p' to allow
// access to the 'Name' field
Slices e maps são tipos especiais porque já têm ponteiros na sua implementação. Isso significa que, na maioria das vezes, não precisamos criar ponteiros para esses tipos para compartilhar o endereço de memória dos seus valores. Imagine que temos uma função que incrementa o valor de uma chave em um map:
func incrementPeterAge(m map[string]int) {
m["Peter"] += 1
}
Se criarmos um map e chamarmos essa função, as mudanças que a função fez no map continuam valendo depois que a função termina. É um comportamento parecido com o que teríamos se estivéssemos usando um ponteiro, mas repare que neste exemplo não usamos nenhuma referência, nenhuma desreferência, nem nenhuma sintaxe de ponteiro:
ages := map[string]int{
"Peter": 21
}
incrementPeterAge(ages)
fmt.Println(ages)
// Output: map[Peter:22]
// The changes the function 'incrementPeterAge' made to the map are visible after the function ends!
O mesmo vale quando alteramos um item que já existe em um slice.
No entanto, ações que retornam um novo slice, como append, são um caso especial e podem não modificar o slice fora da função.
Isso acontece por causa de como os slices funcionam internamente, mas não vamos cobrir isso em detalhe neste exercício, pois é um tópico mais avançado.
Se você estiver realmente curioso, pode ler mais sobre isso em Go Blog: Mechanics of 'append'
Uma escola perto de você tem um grêmio estudantil muito ativo. O grêmio estudantil é administrado por um presidente e, a cada 2 anos, são realizadas eleições para eleger um novo presidente.
Na eleição deste ano, decidiu-se que era necessário um novo sistema digital para contar os votos. A escola precisa da sua ajuda para construir esse novo sistema.
Uma das primeiras coisas de que o novo sistema de votação precisa é de um contador de votos. Esse contador é uma maneira de acompanhar os votos que um determinado candidato tem.
Crie uma função NewVoteCounter que aceita o número de votos iniciais de um candidato e retorna um ponteiro que se refere a um int, inicializado com o número de votos iniciais fornecido.
var initialVotes int
initialVotes = 2
var counter *int
counter = NewVoteCounter(initialVotes)
*counter == initialVotes // true
Agora você tem uma maneira de criar novos contadores! Mas você percebe que o novo sistema também precisará de uma maneira de obter o número de votos de um contador.
Crie uma função VoteCount que recebe um contador (*int) como argumento e retorna o número de votos no contador.
Se o contador for nil, você deve assumir que o contador não tem votos:
var votes int
votes = 3
var voteCounter *int
voteCounter = &votes
VoteCount(voteCounter)
// => 3
var nilVoteCounter *int
VoteCount(nilVoteCounter)
// => 0
Finalmente chegou a hora de contar os votos! Agora você precisa de uma maneira de incrementar os votos em um contador.
Crie uma função IncrementVoteCount que recebe um contador (*int) como argumento e um número de votos, e incrementa o contador nesse número de votos.
Você pode assumir que o ponteiro passado nunca será nil.
var votes int
votes = 3
var voteCounter *int
voteCounter = &votes
IncrementVoteCount(voteCounter, 2)
votes == 5 // true
*voteCounter == 5 // true
Com todos os votos contados, é hora de preparar o anúncio do resultado para a escola inteira. Para isso, você percebe que ter apenas contadores para os votos é insuficiente. É preciso haver uma maneira de associar o número de votos a um candidato específico.
Crie uma função NewElectionResult que recebe o nome de um candidato e o número de votos dele e
retorna um novo resultado de eleição.
var result *ElectionResult
result = NewElectionResult("Peter", 3)
result.Name == "Peter" // true
result.Votes == 3 // true
A struct de resultado da eleição já foi criada para você e está definida como:
type ElectionResult struct {
// Name of the candidate
Name string
// Votes of votes the candidate had
Votes int
}
É hora de anunciar o novo presidente para a escola!
O presidente será anunciado nos pequenos murais digitais de mensagens que a escola tem.
A mensagem deve mostrar o nome do novo presidente e os votos que ele recebeu, no seguinte formato: <candidate_name> (<votes>).
Este é um exemplo dessa mensagem: "Peter (51)".
Crie uma função DisplayResult que receberá um *ElectionResult como argumento e retornará uma string com a mensagem a ser exibida.
var result *ElectionResult
result = &ElectionResult{
Name: "John",
Votes: 32,
}
DisplayResult(result)
// => John (32)
Para garantir que os resultados finais estivessem corretos, os votos foram recontados. Na recontagem, descobriu-se que o número de votos de alguns candidatos estava com diferença de um.
Crie uma função DecrementVotesOfCandidate que recebe os resultados finais e o nome de um candidato cuja contagem de votos você deve decrementar.
Os resultados finais são fornecidos na forma de um map[string]int, em que as chaves são os nomes dos candidatos e os valores são o total de votos de cada um.
var finalResults = map[string]int{
"Mary": 10,
"John": 51,
}
DecrementVotesOfCandidate(finalResults, "Mary")
finalResults["Mary"]
// => 9
Crie sua conta no Exercism para aprender e dominar Go com 34 conceitos165 exercícios e mentoria humana de verdade, tudo de graça.