Aula 11 - Referências e Apontadores
Conceitos-Chave:
- apontador, variável que aponta para outra variável, contendo o endereço desta
- endereço, local de uma variável na memória. Pensem como se fosse a morada de uma variável no computador
- referência, o "nickname" de uma variável
- operador endereço de (&), serve para obter o endereço de uma variável
- operações com o valor de de variáveis apontadas por apontadores, como aceder ao valor de outra variável, através do uso de apontadores
Conceito de endereço e de valor - revisão
Na aula anterior (em que falei de memória) já dei uma breve explicação de endereço e de valor de uma variável. Nesta aula vou rever os conceitos, pois para aprenderem apontadores e referências é muito mais fácil se tiverem estes conceitos em mente. Se tivessemos um programa a correr, a memória pareceria alguma coisa como:
| Valor | ||||||||||||||
| Endereço | 1164 | 1165 | 1166 | 1167 | 1168 | 1169 | 1170 | 1171 | 1172 | 1173 | 1174 | 1175 | 1176 | 1177 |
Se o programa encontrasse uma linha de código do género: int x = 25; o programa iria introduzir o valor 25 num endereço aleatório:
| Valor | 25 | |||||||||||||
| Endereço | 1164 | 1165 | 1166 | 1167 | 1168 | 1169 | 1170 | 1171 | 1172 | 1173 | 1174 | 1175 | 1176 | 1177 |
Apontadores - breve explicação
Apontadores são uma variável como qualquer outra só que, em vez de conterem valores, contêm endereços de variáveis. Os apontadores são definidos através de um * (asterisco) antes da variável ( int *pointer; ).
Há várias utilidades para os apontadores. Nesta lição vou só ensinar algumas operações básicas com apontadores (nomeadamente com o valor da variável apontada).
Segue-se, por enquanto, uma explicação sobre referências e o operador endereço de (que irá ser importante para o uso com apontadores)
Operador referência (&) e operador endereço de (&)
Se leram o título acima devem pensar que estou enganado, pois os dois operadores são representados com o mesmo símbolo. Não é erro e os dois operadores são mesmo representados pelo mesmo símbolo (&) e a única forma de distinguirmos um do outro é pelo contexto, mas não se preocupem porque, como vão ver, isso é muito simples.
Comecemos pelo operador referência. Quando o símbolo & é utilizado ao definirmos uma variável esta, em vez de ser uma variável normal, irá passar a ser uma referência para outra variável, ou seja, irá ser como um "nickname" (alcunha) para esta:
#include <iostream>
using namespace std;
int main()
{
int i;
int &i_ref = i;
i = 20;
cout << "Variavel i: " << i << endl;
cout << "Referencia para i: " << i << endl;
i_ref = 10;
cout << "\nVariavel i: " << i << endl;
cout << "Referencia para i: " << i << endl;
cin.get();
return 0;
}
Nota:
Quando declaramos uma referência temos que atribuir logo a variável que queremos referenciar:
int &ref;
ref = i; // errado!
---
int &ref = i; //certo!
Como podem ver pelo programa tanto i como i_ref possuem os mesmos valores e, se alterarmos o valor a uma delas, a outra irá necessariamente ser alterada para o mesmo valor. Como já devem ter percebido não há muitas vantagens na utilização de referências neste caso, mas se as utilizarmos como argumentos para funções, verão a sua verdadeira utilidade:
#include <iostream>
using namespace std;
// Função Swap
// Desc: troca os valores de 2 variáveis inteiras
void swap(int &x, int &y); //passamos os argumentos como referência
int main()
{
int a = 20;
int b = 10;
cout << "A:" << a << endl;
cout << "B:" << b << endl;
swap(a, b);
cout << "\n\nA:" << a << endl;
cout << "B:" << b << endl;
cin.get();
return 0;
}
void swap(int &x, int &y)
{
int temp; //variável temporária, para não perdermos o valor do x
temp = x;
x = y;
y = temp;
}
Resumindo, para que possamos alterar os 2 valores em simultâneo (sem recurso a variáveis globais) usamos referências. Poderíamos usar variáveis globais para isso, mas com projectos enormes teríamos uma lista enorma de variáveis globais.
Agora vou discutir o uso do operador endereço de.
Operador endereço de (&)
O operador endereço de (&) serve para obter o endereço de uma variável. Vejamos o seguinte programa:
#include <iostream>
using namespace std;
int main()
{
int var = 30;
cout << "Endereco da variavel var: " << &var << endl;
cout << "Valor da variavel var: " << var << endl;
cin.get();
return 0;
}
Como podem observar, se correrem o programa, irão obter um número estranho quando fazemos &var. Esse número é o endereço da variável var na memória e está expresso numa base hexadecimal (por exemplo: 0x22ff31).
Falta dizer que se quiserem guardar o endereço de uma variável terão que o guardar através de um apontador. Assim:
int x = 10;
int v = &x; //errado
int *p= &x; //correcto
Operações com apontadores - introdução
Para demonstrar uma introdução às operações com apontadores segue-se um programa de exemplo:
#include <iostream>
using namespace std;
int main()
{
int x = 20;
int *pointer; //apontador para uma variável do tipo int
pointer = &x; //o apontador assume o valor do endereço da variável
cout << "Variavel x:" << endl;
cout << "\nValor de x: " << x << endl;
cout << "Endereco de x: " << &x << endl;
cout << "\n\nVariavel pointer:" << endl;
cout << "\nValor de pointer: " << pointer << endl;
cout << "Endereco de pointer: " << &pointer << endl;
cout << "Valor da variavel apontada por pointer (x): " << *pointer << endl;
cin.get();
return 0;
}
Não se assustem para já com a quantidade de coisas que ainda não sabem! Ao correr o programa deu-me o seguinte output (que será diferente dos vossos, uma vez que o endereço não vai ser o mesmo):
Output
Variavel x:
Valor de x: 20
Endereco de x: 0x22ff74
Variavel pointer:
Valor de pointer: 0x22ff74
Endereco de pointer: 0x22ff70
Valor da variavel apontada por pointer (x): 20
Para melhor compreenderem é melhor "desmontarmos" o programa :
Variável X:
Valor: 20
Endereço: 0x22ff74 (neste caso)
| Valor | 20 | -- | -- |
| Endereço | 0x22ff74 | 0x22ff75 | ... |
Variável pointer (o apontador para a variável x):
Valor: 0x22ff74 (o valor de um apontador é, então, o endereço da variável por ele apontada)
Endereço: 0x22ff70 (o endereço de pointer. Reparem que está 4 "posições" antes do que X; isto acontece porque um int ocupa 4 bytes)
Valor da variável apontada por pointer (x): 20
| Valor | 0x22ff74 | ... | 20 |
| Endereço | 0x22ff70 | ... | 0x22ff74 |
Como vêm é importante testarmos os exemplos por nós mesmos, alterando os valores e experimentando coisas novas, pois é com os erros que aprendemos.
Operações com apontadores - operações com o valor
Há dois tipos de operações com apontadores. Nesta lição vou só dar as operações com o valor, visto que é o mais fácil de aprender (e porque isto já vai muito longo :).
De modo a "mexermos" com o valor da variável apontada pelo apontador temos que utilizar o operador desreferência (*):
#include <iostream>
using namespace std;
int main()
{
int x = 10;
int *pointer = &x;
cout << "Valor de X: " << x << endl;
cout << "Valor apontado por pointer: " << *pointer << endl;
x += 10;
cout << "\nValor de X: " << x << endl;
cout << "Valor apontado por pointer: " << *pointer << endl;
*pointer += 10;
cout << "\nValor de X: " << x << endl;
cout << "Valor apontado por pointer: " << *pointer << endl;
++*pointer; //ou ++(*pointer) se quiserem
cout << "\nValor de X: " << x << endl;
cout << "Valor apontado por pointer: " << *pointer << endl;
cin.get();
return 0;
}
Vejamos agora como ficaria a função swap com apontadores:
#include <iostream>
using namespace std;
// Função Swap -> uso de apontadores
// Desc: troca os valores de 2 variáveis inteiras
void swap(int *x, int *y); //passamos os argumentos como apontadores
int main()
{
int a = 20;
int b = 10;
cout << "A:" << a << endl;
cout << "B:" << b << endl;
swap(&a, &b); /* não se esqueçam que estamos a utilizar apontadores, logo
temos que utilizar o operador endereço de para atribuir o
endereço das variáveis aos apontadores */
cout << "\n\nA:" << a << endl;
cout << "B:" << b << endl;
cin.get();
return 0;
}
void swap(int *x, int *y)
{
int temp; //variável temporária, para não perdermos o valor do x
temp = *x; //temp é igual ao valor da variável apontada por x
*x = *y; //valor da variável apontada por x vai ser igual ao valor da
//variável apontada por y
*y = temp; //valor da variável apontada por y vai ser igual ao valor de temp
}
Para quê isto tudo?
Se leram isto tudo devem estar a perguntar-se para que é que servem realmente os apontadores, pois existem referências que, além de serem muito mais fáceis de usar e de compreender, têm exactamente o mesmo efeito. Não vos culpo. Há várias razões:
1 - As referências são uma evolução do uso dos apontadores. Em C referências não existiam era tudo feito através de apontadores (até aqui vocês continuam com razão...)
2 - A verdadeira utilidade dos apontadores em C++ (e acreditem que são mesmo úteis) está com a memória dinâmica e com as operações com o endereço, algo que optei não dar nesta lição (por falta de espaço e porque exige mais conhecimentos).
3 - Ficaram com uma excelente introdução aos apontadores. Estes conhecimentos de memória e apontadores vão ser muito importantes para compreenderem a 100% o uso de apontadores
Próxima aula -> C++ 12 - Tratamento de Caracteres
Fazer o download do código fonte dos exemplos da aula
Fazer o download da aula em PDF (Brevemente)
Ir para o topo da página