Programar em C++/Ponteiros
Ponteiros
editarEm linguagem "C", podemos definir variáveis ponteiro, ou seja, variáveis que armazenam o endereço de outras variáveis. Este recurso é largamente explorado pela linguagem, embora que deva ser usado com cautela por iniciantes devido ao seu poder destrutivo. Como linguagem "irmã mais nova" o C++ também permite o uso de ponteiros, o que a distingue de muitas outras linguagens orientadas a objeto. Embora seja muito difundida a ideia da criação de linguagens que não suportem acesso a ponteiros, basicamente pressupondo que todos os programadores são inexperientes, a falta deste recurso limita as capacidades de interação de programas com o hardware. Em outras palavras, a falta de um meio de manipular ponteiros faz a linguagem limitada ou dependente de fabricantes de bibliotecas que acessem o hardware.
A disponibilidade do uso de ponteiros em C++ agrega um poder a mais ao conjunto da linguagem, porém implica a necessidade de cautela na elaboração de programas que usam deste recurso. Certamente, nem todos os programadores precisam ser considerados inaptos, a priori, através da supressão ou inserção de complicadores de recursos criados explicitamente para forçá-los a não usar dos recursos. Por isso, a linguagem C++ disponibiliza o recurso para quem deseja utilizá-lo e também apresenta diversos outros recursos que são alternativas ao uso de ponteiros quando eles não são imprescindíveis.
O operador *
editarO operador *, chamado de apontador, funciona em C++ da mesma forma que em C. Considerando que tenhamos uma variável ponteiro p:
- Em p armazena-se o endereço de memória que queiramos manipular. Na maioria das vezes obtemos o endereço de outra variável e colocamos em p;
- Se p é um ponteiro, *p é o valor apontado por p, ou seja, o valor que está armazenado no endereço de memória que queremos ler ou alterar.
Na declaração de variáveis, uma variável declarada com * é um ponteiro.
Exemplo:
int *px;
Muitas vezes, iniciantes podem se sentir confusos porque quando declaramos um ponteiro usamos o * e quando atribuímos endereços a ele não usamos o *. A conceituação básica é a seguinte:
- Declaramos o ponteiro com *, para que o compilador identifique que a variável é um ponteiro;
- Usamos o ponteiro sem *, para acessar o endereço que ele aponta na memória;
- Usamos o ponteiro com *, para acessar o valor do dado armazenado na posição de memória;
O operador &
editarNa linguagem "C", o operador & tem três funções básicas, funciona como operador da função lógica AND como operador binário bit-a bit AND e como operador de leitura de endereços. Para operações com vetores, isso é usado da seguinte forma:
int a = 12;
int *pa;
...
...
pa = &a;
...
...
*pa = 100;
Ou seja, declaramos a variável a, depois declaramos um ponteiro pa, através do operador & obtemos o endereço de a e atribuímos o valor 100 à variável usando o ponteiro ao invés da variável a. Desta forma alteramos o valor de a indiretamente.
Um outro uso de & (que não tem similar em "C") pode ser visto mais adiante, em Referências de dados, mas, para isto, é necessário estudar o que são Funções.
O ponteiro "this"
editarImagine que tenhamos criado um objeto qualquer de uma classe X, se quisermos ter acesso ao ponteiro que contém a posição de memória onde está armazenado este objeto basta chamar o ponteiro "this". O ponteiro "this" é uma das características dos objetos em C++ e algumas outras linguagens que suportam orientação a objetos. Ele é um membro inerente a todos os objetos que instanciamos em programas escritos em C++.
Faremos uma breve explanação a respeito de objetos para esclarecer este tópico. Objeto são parecidos com estruturas, uma diferença básica é que estes possuem "habilidades específicas" representadas por funções que estão dentro do seu escopo. Vejamos um exemplo:
struct Data
{ int x,y;
int get_x(){ return x;}
int get_y(){ return y;}
int set_x(int a){ return x=a;}
int set_y(int b){ return y=b;}
};
Observe que a estrutura acima apresenta dois inteiros e duas funções para cada um deles, uma que atribui o valor e outra que lê o valor de uma das mesmas. Detalhes das implicações a respeito desse modo de operar os valores serão dados nos capítulos seguintes que tratam de objetos. Por ora vamos nos ater a um conceito fundamental importante para a noção de ponteiros em C++, a identidade de um objeto.
Veja, temos uma estrutura de dados que está na memória, os dados estão lá (variáveis x e y), porém as funções não estarão lá, pois se tivéssemos que copiar uma função para cada estrutura que criássemos o programa tomaria um tamanho monstruoso. O que se faz é apenas guardar o endereço da estrutura em um ponteiro especial, o ponteiro this. Assim, o compilador poderá criar uma única cópia de função para todas as estruturas que criarmos e depois quando a função quiser manipular os dados de uma estrutura em particular, o fará através do ponteiro this.
Examinemos os detalhes mais de perto... Digamos que instanciemos um objeto "A" da classe Data:
Data A;
A.set_x(2);
A.set_y(7);
Para acessar estas funções o compilador fará:
Data A;
A.set_x(2);
// { Data *this = &A;
// return this->x = 2;
// }
A.set_y(7);
// { Data *this = &A;
// return this->y = 7;
// }
Desta forma podemos perceber como diferentes conjuntos de dados podem ser manipulados pela mesma função. Quando declaramos uma função dentro de uma estrutura de dados esta rotina recebe um ponteiro com o endereço do conjunto de dados que deve tratar toda vez que for invocada pelo programa. Assim, sempre acessará os dados através deste ponteiro, o this. Como todos os objetos precisam ser identificados por esse ponteiro, ele é definido para qualquer objeto com o mesmo nome: this.