Percursos
/
Go
Go
/
Exercícios
/
Dia de eleições
Dia de eleições

Dia de eleições

Exercício de aprendizagem

Introdução

Tal como muitas outras linguagens, o Go tem ponteiros. Se és novo no que toca a ponteiros, podem parecer um pouco misteriosos, mas assim que te habituares a eles vão parecer-te bastante simples. São uma parte crucial do Go, por isso dedica algum tempo a compreendê-los bem.

Antes de entrar nos detalhes, vale a pena perceber para que servem os ponteiros. Os ponteiros são uma forma de partilhar memória com outras partes do nosso programa, o que é útil por duas grandes razões:

  1. Quando temos grandes quantidades de dados, fazer cópias para passar entre funções é muito ineficiente. Ao passar em vez disso a localização de memória onde os dados estão guardados, conseguimos reduzir drasticamente o consumo de recursos dos nossos programas.
  2. Ao passar ponteiros entre funções, conseguimos aceder e modificar diretamente a única cópia dos dados, o que significa que quaisquer alterações feitas por uma função ficam imediatamente visíveis para as outras partes do programa quando a função termina.

Variáveis e memória

Digamos que temos uma variável inteira normal a:

var a int

Quando declaramos uma variável, o Go tem de encontrar um lugar na memória para guardar o seu valor. Isto está em grande parte abstraído de nós: quando precisamos de obter o valor guardado nesse pedaço de memória, basta-nos usar o nome da variável.

Por exemplo, quando escrevemos a + 2, estamos efetivamente a obter o valor guardado na memória associada à variável a e a somar-lhe 2.

Do mesmo modo, quando precisamos de alterar 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 passa agora a guardar o valor 3.

Ponteiros

Embora as variáveis nos permitam referir valores na memória, por 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. Declaras uma variável com um tipo ponteiro ao colocar um asterisco antes do tipo subjacente:

var p *int // 'p' contains the memory address of an integer

Aqui declaramos uma variável p do tipo "ponteiro para int" (*int). Isto 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.

Obter um ponteiro para uma variável

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'

Aceder ao valor através de um ponteiro (desreferenciação)

Quando temos um ponteiro, podemos querer saber o valor guardado no endereço de memória que o ponteiro representa. Podemos fazê-lo 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 obtém o valor guardado no endereço de memória que está em p. A esta operação dá-se muitas vezes o nome de "desreferenciação".

Também podemos usar o operador de desreferenciação 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 altera o valor guardado no endereço de memória que pa contém. Como pa contém o endereço de memória de a, ao atribuir a *pa estamos efetivamente a alterar o valor de a!

Um aviso, no entanto: verifica sempre se um ponteiro não é nil antes de o desreferenciares. Desreferenciar um ponteiro nil faz o programa rebentar 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

Ponteiros para structs

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 podíamos ter criado um novo Person e guardado logo um ponteiro para ele:

var p *Person
p = &Person{Name: "Peter", Age: 22}

Quando temos um ponteiro para uma struct, não precisamos de desreferenciar o ponteiro antes de aceder a 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 mapas já são ponteiros

Os slices e os mapas são tipos especiais porque já têm ponteiros na sua implementação. Isto significa que, na maioria das vezes, não precisamos de criar ponteiros para estes tipos para partilhar o endereço de memória dos seus valores. Imagina que temos uma função que incrementa o valor de uma chave num mapa:

func incrementPeterAge(m map[string]int) {
	m["Peter"] += 1
}

Se criarmos um mapa e chamarmos esta função, as alterações que a função fez ao mapa mantêm-se depois de a função terminar. É um comportamento semelhante ao que teríamos se usássemos um ponteiro, mas repara que neste exemplo não estamos a usar qualquer referência/desreferenciação nem qualquer outra sintaxe de ponteiros:

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 se aplica quando alteramos um elemento já existente num slice.

No entanto, as ações que devolvem um novo slice, como append, são um caso especial e podem não modificar o slice fora da função. Isto deve-se à forma como os slices funcionam internamente, mas não vamos abordar isto em detalhe neste exercício, porque é um tópico mais avançado. Se estiveres mesmo curioso, podes ler mais sobre isto em Go Blog: Mechanics of 'append'

Instruções

Uma escola local perto de ti tem uma associação de estudantes muito ativa. A associação de estudantes é gerida por um presidente e, de 2 em 2 anos, realizam-se eleições para eleger um novo presidente.

Nas eleições deste ano, decidiu-se que era necessário um novo sistema digital para contar os votos. A escola precisa da tua ajuda para construir este novo sistema.

1. Cria um contador de votos

Uma das primeiras coisas de que o novo sistema de votação precisa é um contador de votos. Este contador serve para acompanhar os votos que um determinado candidato tem.

Cria uma função NewVoteCounter que aceita o número de votos iniciais de um candidato e devolve um apontador para um int, inicializado com o número de votos iniciais indicado.

var initialVotes int
initialVotes = 2

var counter *int
counter = NewVoteCounter(initialVotes)
*counter == initialVotes // true

2. Obtém o número de votos de um contador

Já tens forma de criar novos contadores! Mas agora reparas que o novo sistema também vai precisar de uma forma de obter o número de votos de um contador.

Cria uma função VoteCount que recebe um contador (*int) como argumento e devolve o número de votos do contador. Se o contador for nil, deves 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

3. Incrementa os votos de um contador

É finalmente a altura de contar os votos! Agora precisas de uma forma de incrementar os votos de um contador.

Cria uma função IncrementVoteCount que recebe um contador (*int) como argumento e um número de votos, e incrementa o contador com esse número de votos. Podes assumir que o apontador recebido nunca será nil.

var votes int
votes = 3

var voteCounter *int
voteCounter = &votes

IncrementVoteCount(voteCounter, 2)

votes == 5          // true
*voteCounter == 5   // true

4. Cria os resultados das eleições

Com todos os votos contados, é altura de preparar o anúncio dos resultados para toda a escola. Para isso, reparas que ter apenas contadores de votos não é suficiente. É preciso uma forma de associar o número de votos a um determinado candidato.

Cria uma função NewElectionResult que recebe o nome de um candidato e o seu número de votos e devolve um novo resultado eleitoral.

var result *ElectionResult
result = NewElectionResult("Peter", 3)

result.Name == "Peter"  // true
result.Votes == 3       // true

A struct do resultado eleitoral já foi criada para ti e está definida assim:

type ElectionResult struct {
    // Name of the candidate
    Name    string
    // Votes of votes the candidate had
    Votes   int
}

5. Anuncia os resultados

É altura de anunciar o novo presidente à escola! O presidente será anunciado nos pequenos painéis de mensagens digitais que a escola tem. A mensagem deve mostrar o nome do novo presidente e os votos que teve, no seguinte formato: <candidate_name> (<votes>). Este é um exemplo dessa mensagem: "Peter (51)".

Cria uma função DisplayResult que recebe um *ElectionResult como argumento e devolve uma string com a mensagem a apresentar.

var result *ElectionResult
result = &ElectionResult{
    Name: "John",
    Votes: 32,
}

DisplayResult(result)
// => John (32)

6. Recontagem dos votos

Para garantir que os resultados finais estavam corretos, os votos foram recontados. Na recontagem, descobriu-se que o número de votos de alguns candidatos estava errado por um.

Cria uma função DecrementVotesOfCandidate que recebe os resultados finais e o nome de um candidato cujo número de votos deves decrementar. Os resultados finais são dados na forma de um map[string]int, em que as chaves são os nomes dos candidatos e os valores são o seu total de votos.

var finalResults = map[string]int{
    "Mary":  10,
    "John":  51,
}

DecrementVotesOfCandidate(finalResults, "Mary")

finalResults["Mary"]
// => 9
Editar via GitHub A ligação abre numa nova janela ou separador
Go Exercism

Estás pronto para começar Dia de eleições?

Inscreve-te no Exercism para aprenderes e dominares Go com 34 conceitos165 exercícios, e mentoria humana real, tudo grátis.