constTodas as variáveis são guardadas na memória do
computador* em octetos com endereços
consecutivos e portanto têm um determinado endereço de memória. 
Se uma variável ocupar mais do que um octeto (os int, por
exemplo, se tiverem 32 bit, ocupam quatro octetos), então
o seu endereço é o menor dos endereços dos octetos
que a variável ocupa na memória.  Como é óbvio,
duas variáveis que existam num determinado instante de tempo ocupam
forçosamente zonas disjuntas de memória, pelo que têm
endereços diferentes (há uma excepção: uma
instância de uma classe e o seu primeiro atributo podem ter o mesmo endereço,
embora nesse caso se distingam forçosamente pelo tipo, pois uma classe não
pode ter atributos que sejam instâncias dela própria).
Um ponteiro é uma variável que contém o endereço de um objecto de um dado tipo, normalmente outra variável. A declaração de um ponteiro não reserva memória para a variável endereçada. Um ponteiro, quando não inicializado, contém um endereço arbitrário (lixo), geralmente inválido. Usar o conteúdo (a zona de memória) correspondente ao endereço contido num ponteiro não inicializado é um erro.
A definição/declaração de ponteiros faz-se
tal como para as outras variáveis, mas seguindo o nome do tipo de
*:
A inicialização do valor do ponteiro
int* p;
p, fazendo
com que aponte para a variável i definida anteriormente,
faz-se à custa do  operador endereço &:
Depois destas instruções
int i;int* p = &i;
p contém o endereço
da variável i, dizendo-se que p aponta para i.
O acesso ao valor da variável apontada por p (ou seja,
ao conteúdo do endereço), faz-se à custa do  operador
conteúdo (ou des-referenciação, com o significado
"de conteúdo da variável apontada por") *:
Os ponteiros permitem, assim, o acesso indirecto às variáveis apontadas.
cout << *p;
Os ponteiros contêm endereços de variáveis de um dado tipo, indicado na sua definição, e não se lhes pode atribuir o endereço de objectos de outros tipos:
Há duas excepções a esta regra. A primeira é que se pode sempre atribuir o endereço de um objecto de uma classe derivada a um ponteiro para uma classe base, assunto que se verá em pormenor em aulas posteriores. A segunda é que existe um tipo especial de ponteiros,
int i = 1;double d = 2.0;erro!int* pi = &d;//erro!double* pd = &i;//
void*, compatível com qualquer
outro ponteiro.  Este tipo de ponteiros era extremamente importante
na linguagem C, mas em C++ só é verdadeiramente útil
em raras, mesmo muito raras ocasiões.
Não se deve confundir a definição/declaração
de um ponteiro para uma variável com a utilização
do operador conteúdo, da mesma forma não se deve confundir
a declaração/definição de uma referência
com a utilização do operador endereço.  Os símbolos
*
e & têm significados completamente diferentes consoante
ocorram numa expressão ou numa declaração:
int i = 10;int& j = i; //definição de uma referência paraintinicializada comi(jfica
//sinónimo dei).int* p = &j; //definição de um ponteiro paraintinicializado com o endereço dej,
//ou seja, inicializada com o endereço dei.cout << *p; //acesso ao conteúdo do endereço emppara a escrita do valor dei.
* Excepto quando as variáveis podem existir apenas nos registos do processador, o que é uma optimização típica realizada pelos compiladores.
o endereço do elemento 4 (o quinto elemento) é igual ao endereço do elemento 3 somado de uma unidade. Ou seja,
int m[20];
&m[4]
= &m[3] + 1.  Claro está que, se os inteiros ocuparem
quatro bytes, estes endereços na realidade diferem de quatro
unidades básicas de memória, mas isso é transparente
para o programador: o compilador encarrega-se de calcular o tamanho dos
objectos do tipo apontado pelo ponteiro, neste caso int, de uma
forma automática.  De qualquer forma, o operador sizeof
pode ser usado para saber quantos bytes ocupa cada objecto de um
dado tipo, muito embora a sua utilidade em C++ seja reduzida, ao contrário do
que acontece na linguagem C.
Quando uma matriz ocorre numa expressão, é convertida
num ponteiro constante para o respectivo primeiro elemento (excepto,
por exemplo, quando a matriz ocorra como operando do operador sizeof). 
Por exemplo, sendo
a ocorrência de
int m[20];
m numa qualquer expressão é
normalmente interpretada como significando o mesmo que &m[0]. 
Por outro lado, todas as operações de indexação
do tipo m[i] são interpretadas como significando
*(m
+ i), desde que m não seja seja uma instância
de uma classe (e, como a adição é comutativa, também
a indexação o é, logo m[i] é o mesmo
que i[m]).
Por exemplo, dadas as seguintes definições:
ambas as instruções
int m[20];int* p = m; //ou, o que é o mesmo,int* p = &m[0].
são válidas e têm o mesmo efeito.
p[19] = 1;m[19] = 1;
O caso das matrizes de matrizes (ou matrizes multi-dimensionais) é idêntico, mas merece algumas observações. Seja a seguinte definição:
Quando a matriz
double m[3][2];
m é usada numa expressão é
convertida num ponteiro para o seu primeiro elemento.  Mas a matriz
m
é uma matriz com três elementos, cada uma dos quais é
uma matriz com dois double.  Assim, o nome m numa
expressão é interpretado como um ponteiro para uma matriz
com dois elementos double.  Isto é, é possível
escrever:
São necessários os parênteses dadas as regras de precedência do C++. Sem eles a definição seria interpretada como
double m[3][2];double (*p)[2] = m; //ou&m[0].
double*
(p[2]), com o significado totalmente diferente de "matriz com dois
elementos do tipo ponteiro para double".
A soma de ponteiros com inteiros e a subtracção de inteiros de ponteiros, incluindo os operadores de incrementação e decrementação, estão bem definidos para ponteiros, desde que o endereço resultante se refira a um elemento (incluindo o elemento fictício final) da mesma matriz (que pode ser dinâmica, como se verá em aulas posteriores). Por exemplo, sendo
ambas as instruções
int m[20];int* p = m; //ou, o que é o mesmo,int* p = &m[0].p += 3;
são válidas e têm o mesmo efeito.
p[16] = 1;m[19] = 1;
A subtracção de ponteiros também está bem definida desde que ambos os ponteiros se refiram a elementos válidos (incluindo o elemento fictício final) da mesma matriz (que pode ser dinâmica). Dado o código atrás
tem como resultado aparecer
cout << p - m << endl;
no ecrã.
3
O caso das matrizes de matrizes merece, mais uma vez, algumas observações. Sejam as seguintes definições:
Que acontece se se incrementar
double m[3][2] ={{1, 2},{3, 4},{5, 6},};double (*p)[2] = m; //ou&m[0].
p?  Qual o resultado do seguinte
código?
Dado que
++p:cout << p[0][0] << ' ' << p[0][1];
p é do tipo ponteiro para uma matriz com dois
elementos double, a sua incrementação leva o ponteiro
a apontar para a seguinte matriz de dois double, que é
o segundo elemento da matriz m.  Depois, a expressão
p[0][0]
é interpretada como (p[0])[0], ou seja, (*(p + 0))[0],
ou seja (*p)[0] .  Sendo p um ponteiro para o segundo
elemento de m, *p é o segundo elemento, ou seja,
é equivalente a m[1], pelo que p[0][0] é
o mesmo que m[1][0].  Logo, o que aparece no ecrã
são os dois elementos da matriz que é o segundo elemento
de m:
3 4
Isto significa que é sempre possível, na invocação de uma rotina, passar um ponteiro como argumento no lugar de um parâmetro especificado como uma matriz. Por exemplo, a função
pode ser invocada da seguinte forma:
int soma(int const m[], int const n)
//ouint soma(int const* m, int const n){
int soma = 0;
for(int i = 0; i != n; ++i)
soma += m[i];
return soma;
}
A função
int matriz[] = {1, 2, 3, 4};int *p = matriz; //ou seja,p = &matriz[0].int z = soma(p, 4);
soma() poderia ser implementada como se
segue:
A utilização de ponteiros tem algumas semelhanças com a utilização de referências, nomeadamente porque permite a alteração do valor apontado pelo ponteiro. Por exemplo, o procedimento
int soma(int const m[], int n)//ouint soma(int const* m, int n){int soma = 0;
for(; n != 0; n--) {soma += *m;++m;}
return soma;
}
permite trocar os valores apontados por dois ponteiros. O resultado de
inline void troca(int* x, int* y)
{int aux = *x;*x = *y;*y = aux;}
seria aparecer
int a = 10, b = 20;troca(&a, &b);cout << a << ' ' << b << endl;
no ecrã. Repare-se que a invocação do procedimento se faz usando o operador endereço (
20 10
&) explicitamente.
O procedimento também pode ser usado como se segue:
Note-se que
int a = 10, b = 20;int* pa = &a;int* pb = &b;troca(pa, pb);cout << *pa << ' ' << *pb << endl;
define
int* a, b;
a como ponteiro para int e b como int
simplesmente!  Para evitar estas ambiguidades convém separar
claramente as definições de cada variável.
Finalmente, note-se bem a diferença face à utilização de referências propriamente ditas na versão alternativa abaixo:
inline void troca(int& x, int& y)
{int aux = x;x = y;y = aux;}...
int a = 10, b = 20;troca(a, b);cout << a << ' ' << b << endl;
Qual prefere?
constconst  depois do tipo.  Por exemplo,
define uma constante de nome
double const pi = 3.14159265358979323846264338327950288419716939937510;
pi.  A notação
alternativa, com a palavra chave const antes do tipo, presta-se a ambiguidades no caso dos ponteiros.  Suponha-se a seguinte
definição:
Que significa a definição que se segue?
int m[5] = {9, 8, 7, 6, 5};
O que é constante? O ponteiro ou o valor apontado? Note-se que a variável apontada (o primeiro elemento da matriz
const int* p = m; //ou&m[0].
m)
não
é constante.  Um pequeno teste permite verificar rapidamente
o significado da definição:
Logo,
erro de compilação: não se pode alterar um*p = 0;//inttratado como constante!++p; //tudo bem,ppassa a apontar o segundo elemento da matriz.
p é um ponteiro não constante para um int
 tratado como constante (embora na realidade possa não o ser).  Se se pretendesse que fosse um ponteiro constante para
um inteiro não constante, teria de se definir p como:
Acontece que a palavra chave
int* const p = m; //ou&m[0].
const se aplica sempre ao tipo que
está à sua esquerda, excepto se não existir nada à
esquerda, caso em que se aplica ao tipo à sua direita.  Isto
significa que, para manter uma certa coerência de utilização,
as constantes simples se devem definir com a palavra chave const
após o tipo, como se tem feito até aqui.
Assim, têm-se as seguintes alternativas para a definição de um ponteiro:
Note-se que é proibido inicializar um ponteiro simples com o endereço de uma constante:
int* p1 = m; //ponteiro não-constante parainttratado como
//não-constante.int const* p2 = m; //ponteiro não-constante parainttratado como
//constante.int* const p3 = m; //ponteiro constante parainttratado como
//não-constante.int const * const p4 = m; //ponteiro constante parainttratado como
//constante.
Se assim não fosse, poder-se-ia sempre alterar uma constante através de um ponteiro, o que seria um constra-senso!
int const número_de_alunos = 50;erro!int* p = &número_de_alunos;//
Atente-se agora no seguinte exemplo, um pouco mais complicado:
A operação
typedef int* Item; // Itemé um sinónimo de "ponteiro paraint".
void f(Item const& i) //passagem por referência constante.{erro!++i;//++(*i); //ok!}
void g(int const* p) //passagem por valor de um ponteiro não constante
//para uminttratado como constante.{erro!f(p);//}
++i é um erro porque i
é uma referência para um ponteiro tratado como constante (e não
uma referência para um ponteiro para um int  tratado como constante). 
Assim, o ponteiro não pode ser alterado.  Mas o seu conteúdo
pode, daí que a operação ++(*i) seja totalmente
válida.
Finalmente, a invocação de f() a partir de g()
é um erro.  Essa invocação implica a inicialização
de uma referência para um ponteiro constante para um int
(Item const& i) a partir de um ponteiro
não constante para um int  tratado como constante (int const*). 
Esta inicialização, a ser feita, descartaria o const
do valor apontado pelo ponteiro p, sendo esta operação
proibida pelo C++!
Note-se na utilização de parênteses envolvendo
#include <iostream>#include <string>
using namespace std;
class Aluno {public:Aluno(string const& nome = "", int const número = 0);
string nome() const;
int número() const;
void alteraNomePara(string const& nome);
void alteraNúmeroPara(int const& número);
private:string nome_;int número_;};
Aluno::Aluno(string const& nome = "", int const número = 0): nome_(nome), número_(número)
{}
stringAluno::nome() const
{return nome_;}
void Aluno::alteraNomePara(string const& nome)
{nome_ = nome;}
intAluno::número() const
{return número_;}
voidAluno::alteraNúmeroPara(int const& número)
{número_ = número;}
int main(){Aluno a("Ambrósio Amaral", 1234);Aluno* p = &a;cout << (*p).nome() << endl;}
*p. 
Os parênteses são necessários porque o operador * (conteúdo)
tem menor precedência que o operador . (selecção
de membro).  Para evitar esta notação complicada, o
C++ fornece um operador -> de selecção de membro
de uma instância apontada por um ponteiro:
cout << p->nome() << endl;