(&
unário).*
unário).* e & em declarações
(ponteiro e referência) e em expressões (conteúdo e
endereço, quando unários).X[I] equivalente
a *(X + I).tipo nome[] equivalente a tipo* nome
na definição de parâmetros.(*ponteiro).membro
equivalente a ponteiro->membro.Quero também alertar para o facto de irmos descer um pouco o nível da nossa conversa. O nível de abstracção, bem entendido... Vamos falar de coisas que normalmente não nos preocupam. Vou fazê-lo apenas para que compreendam os conceitos. Depois de os aprenderem convém regressar a níveis de abstracção mais elevados e não ligar aos pormenores de implementação.
Quando se escreve o código:
Cria-se uma variável i, do tipo
int i = 10;
int, com valor 10.  Onde está
a variável  i quando o programa é executado?
Discutir. Concluir que está guardada na memória.
O que é a memória?
Discutir.
Do nosso ponto de vista, ou melhor, do ponto de vista do C++ a memória é uma sequência de bytes, com números sucessivos. Aos números chama-se endereços. Os bytes são a unidade básica de memória (do ponto de vista do C++) e normalmente têm 8 bits.
Ok.  E quantos bits têm as variáveis do tipo 
int nas máquinas que costumam usar?
Esperar... (É típico ninguém responder)
32. Ou seja, quando uma variável é criada ocupa quantos octetos? 4! Exacto! Cada um com um endereço.
Suponha-se que a variável  i ocupava os endereços 12, 13,
14 e 15.
Desenhar memória como matriz de octetos.  Agrupar os
quatro da variável i.  A variável deve ficar dentro de
um rectângulo UML (diagrama de objectos).  Usar sempre UML.
Se quisermos dizer onde está  i que endereço dizemos?
Discutir.
Exacto: o que é lógico é dizer 12, o mais baixo dos endereços dos octetos ocupados pela variável.
Perfeito.  Que podemos fazer com o endereço de uma variável? 
Guardá-lo noutra variável!  De que tipo?  int? 
O compilador não deixa.  Para o C++ um endereço é
um endereço.  Mais, um endereço de um  int não
é o mesmo que o endereço de um double!  Então
temos de conseguir dizer algo como "esta variável guarda o endereço
de uma variável do tipo X".
Se quisermos definir uma variável que guarda endereços
de variáveis do tipo int, escrevemos:
A sintaxe é sempre esta:
int* p;
tipo, depois um *, e
finalmente o nome
da variável.  O que mudou foi o *.  Diz-se que  p é
um ponteiro.  Um ponteiro para int.
E que endereço contém p?
Discutir. Concluir que tem lixo.
Lixo não serve de muito.  Convinha poder colocar um endereço
específico em p.  Qual?
Discutir.  Lembrar que  p é um ponteiro para int, e portanto
deve conter o endereço de uma variável do tipo int.
Neste caso só há um int: i.  Logo, gostávamos
de poder colocar em  p o endereço de i.  Para isso
usa-se o 
operador endereço:
Note-se que o símbolo
int* p = &i;
& também pode significar referência,
ou "e" lógico, ou "e" bit-a-bit.  Depende do contexto. 
Já lá vamos...
Costuma-se representar os ponteiros com setas dirigidas para o local de que possuem o endereço.
Desenhar  p na memória.  Colocar lá dentro o endereço
de i.  Desenhar seta até i.
Perfeito. Agora p tem um endereço válido. Que podemos fazer com ele?
É possível usar  p para aceder a  i indirectamente. 
Para isso usa-se o  operador conteúdo:
Como
cout << *p << endl;*p = 20;
 p é um ponteiro para o  int i, então
 *p é o  int i!  Diz-se que é o conteúdo do inteiro apontado por
p.  Ou, abusando da linguagem, é o conteúdo de p. 
No fundo, por isso *p é uma forma alternativa de escrever i:
é um sinónimo de i.
Assim, o código começa por mostrar 10 e depois altera
indirectamente o valor de  i para 20.
É preciso cuidado com o significado dos símbolos  * e
 &. 
O significado é totalmente diferente consoante os símbolos
surjam numa declaração ou numa expressão:
Explicar muito bem. Dar exemplo do português ("A nota, se não me engano, foi 16,5.", utilização da vírgula). Dizer para passarem para o caderno.
int i = 10;int& j = i;int* p = &j;int k = *p;
Os ponteiros, como vistos até agora, só servem para confundir
as coisas: permitem acessos indirectos a objectos aos quais se pode aceder
directamente.  Para quê usar  p para alterar indirectamente
 i
se posso usar  i directamente?  A grande utilidade dos ponteiros só
será bem evidente quando se falar de variáveis dinâmicas,
na próxima aula.  Paciência...
Os ponteiros têm uma relação muito interessante com as matrizes em C++. Sejam as seguintes instruções:
Que se está a passar? Vamos desenhar isto na memória:
int m[5] = {6, 7, 8, 9, 10};int* p = &m[0];p = p + 1;*p = 20;
Desenhar no quadro a memória com  m e p.  Pôr
m no endereço 20!  Pôr seta de  p para o primeiro
elemento de m.  Explicar tudo calmamente.  Explicar que os elementos
são colocados em endereços crescentes!
Que endereço tem p?  20!  Que acontece a esse endereço
quando se lhe soma 1?
Discutir. Concluir que o que nos interessa é que passe para o próximo elemento da matriz.
O compilador sabe quantos octetos ocupam os objectos de cada tipo e
dá os saltos apropriados!  Isto dá muito jeito! 
Podemos e devemo-nos abstrair de pormenores como a dimensão exacta
dos int.
Então o endereço em  p passa para... 24!  É
o endereço do segundo elemento da matriz.  Assim, a última
instrução altera o segundo elemento da matriz.
Moral da história: podemos somar ou subtrair inteiros de ponteiros. Sempre? E se eu tivesse:
Não se pode obter o endereço de elementos inexistentes da matriz... Com uma excepção! Pode-se obter um ponteiro para o elemento fictício final. Podia-se ter:
int m[5] = {6, 7, 8, 9, 10};int* p = &m[0];p = p + 6;*p = 20;
Mas nesse caso a atribuição seria um erro.
int m[5] = {6, 7, 8, 9, 10};int* p = &m[0];p = p + 6;*p = 20;
Infelizmente não se passa o mesmo no caso do elemento fictício inicial...
Mas as relações interessantes entre ponteiros e matrizes
não se ficam por aqui.  Que significa m[i]?  Indexação
de matriz?  Não.  Bem, sim, mas a coisa é mais
complicada do que parece.  A regra é:
desde que
X[I]é o mesmo que*(X + I)
 X não seja uma instância duma classe.
Mas então, e código inocente como:
?
int m[5];m[2] = 10;
Quer isto dizer que o C++ interpreta a coisa como
?
int m[5];*(m + 2) = 10;
Exacto!
Mas que significa  m + 2?  É que há outra regra. 
Salvo raras excepções, se uma matriz ocorrer numa expressão
é convertida num ponteiro para o primeiro elemento!  I.e.,
é o mesmo que
int m[5];int* p = m;
Mas então, que significa
int m[5];int* p = &m[0];
?
int m[5];m[2] = 10;
É o mesmo que
que é o mesmo que
int m[5];*(m + 2) = 10;
Conclusão: desde o primeiro semestre que trabalham com ponteiros sem o saberem!
int m[5];*(&m[0] + 2) = 10;
E se for:
?
int m[5];int* p = m + 1;p[3] = 10;
É o mesmo que
Que é o mesmo que
int m[5];int* p = &m[0] + 1;*(p + 3) = 10;
que é o mesmo que
int m[5];int* p = &m[0] + 1;*(&m[0] + 1 + 3) = 10;
E se fosse
int m[5];int* p = &m[0] + 1;m[4] = 10;
 p[-1] = 20?  Onde ficava o 20?
Mas, se as matrizes numa expressão são convertidas num ponteiro para o seu primeiro elemento que sucede se forem passadas como argumento?
Explicar o que faz. Discutir quanto escreve no ecrã.
int soma(int const m[], int const n){int soma = 0;for(int i = 0; i != n; ++i)soma += m[i];return soma;}
int main(){int matriz[] = {2, 4, 6, 8};
cout << soma(matriz, 4) << endl;}
Reparem na instrução:
pelo que disse, é o mesmo que
cout << soma(matriz, 4) << endl;
ou ainda
cout << soma(&matriz[0], 4) << endl;
O que é passado é um ponteiro!
int* p = matriz; // ou &matriz[0]cout << soma(p, 4) << endl;
Mas a função diz que recebe uma matriz! Que se passa? É que o C++, quando um parâmetro é declarado como matriz, converte logo a declaração para a declaração de um ponteiro! Logo,
é o mesmo que
int soma(int const m[], int n)
e portanto a função pode-se escrever:
int soma(int const* m, int n)
Discutir rapidamente. Dizer que é por isso que dissemos no primeiro semestre que as matrizes eram sempre passadas por referência! Uma mentirita piedosa.
int soma(int const* m, int n){int soma = 0;for(int i = 0; i != n; ++i)soma += m[i];return soma;}
Apresentar rapidamente caso das matrizes multidimensionais (se não houver tempo saltar para classes).
Explicar linha a linha. Notar importância dos
int m[3][2];int (*p)[2] = m;++p;p[0][0] = 10;
().
Finalmente, como funcionam os ponteiros para classes? Simplesmente:
Os parênteses são fundamentais, pois de outra forma o C++ interpreta a primeira expressão como:
class Aluno {
public:
Aluno(int número, int nota);
int número() const;
int nome() const;
private:int número_;int nota_;};...
int main(){Aluno a(12345, 20);Aluno* p = &a;cout << (*p).número() << endl<<(*p).nota() << endl;}
*(p.número()),
o que não faz qualquer sentido.
A notação é aborrecida... parênteses asterisco ponteiro parênteses ponto... O C++ fornece uma abreviatura:
int main(){Aluno a;Aluno* p = &a;cout << p->número() << endl<<p->nota() << endl;}
onde p->número() se lê "a operação número()
da instância de  Aluno apontada por p". 
Esta notação pode ser usada tanto para invocar operações, como acima, como
para aceder a atributos, desde que estejam acessíveis.