Baixe o app para aproveitar ainda mais
Prévia do material em texto
1 Profa. Dra. Ieda Hidalgo E-mail: iedahidalgo@ft.unicamp.br Ponteiros e Referências 2 Definição de Ponteiros Ponteiros são variáveis cujo conteúdo são endereços de memória. Estes endereços podem ser, por exemplo, a localização na memória de uma variável ou função. Exemplo: se x tiver o endereço de y, então é dito que x "aponta para" y. 3 Para que servem os ponteiros? � Alocação dinâmica de memória � Manipulação de elementos de uma matriz � Alteração do conteúdo de um argumento por uma função � Criação de estruturas de dados complexas, como: listas encadeadas e árvores binárias (onde um item deve conter referência a outro) � Aumento de eficiência em determinadas rotinas. Qual o endereço de uma variável? Todas as variáveis e funções ocupam uma certa localização na memória, e seu endereço é o do primeiro byte ocupado por ela. Quando carregados para a memória, elas (as variáveis e funções) começam em um endereço particular, e este endereço é chamado de endereço da variável ou da função. 5 Sintaxe da Declaração de Ponteiros tipo *ponteiro; tipo: determina que tipo de dados o ponteiro estará apontando ponteiro: é o nome da variável ponteiro 6 Exemplos Declarar uma variável ponteiro chamada Num que aponte para valores inteiros: int *Num; Na mesma instrução anterior, inicializar a variável ponteiro Num com o endereço de x: int *Num = &x; 7 Operadores de Ponteiros & É um operador unário que retorna o endereço de memória de seu operando. * É um operador unário que retorna o conteúdo da variável localizada no endereço especificado pelo seu operando. ptr = &total; val = *ptr; ptr recebe o endereço de memória da variável total val recebe o conteúdo da variável localizada no endereço ptr 8 Observações Como todas as variáveis, os ponteiros têm um endereço e um valor. O ato de usar um ponteiro é frequentemente chamado de indireção, porque você está acessando uma variável indiretamente através de outra variável. Tanto & quanto * têm uma precedência mais alta do que quaisquer dos operadores aritméticos, exceto o menos unário, com o qual eles têm precedência igual e são resolvidos da direita para a esquerda. 9 O tipo de um ponteiro É importante assegurar que os ponteiros sempre apontem para o tipo de dados correto. Por exemplo, o fragmento a seguir está incorreto: int *p; double f; //... p = &f; // erro de compilação 10 Coerção Usando coerção é possível anular (a seu próprio risco) a restrição de que dois ponteiros devem ter tipos compatíveis. Por exemplo, o fragmento a seguir é tecnicamente correto: int *p; double f; //... p = (int *) &f; // tecnicamente ok 11 Atribuição de valores para ponteiros É possível usar um ponteiro no lado esquerdo de uma instrução de atribuição para atribuir um valor ao local apontado pelo ponteiro. int *p; // no local apontado por p *p = 100; // coloque o conteúdo 100 12 Alterando o conteúdo É possível incrementar ou decrementar o conteúdo do local apontado por um ponteiro. int *p; // incremente o conteúdo (*p)++; // do local apontado por p 13 Operadores Aritméticos para Ponteiros Existem 4 operadores aritméticos que podem ser usados com ponteiros: ++ -- + - 14 Tipos de Operações Aritméticas para Ponteiros � Incremento � Decremento � Adição de um ponteiro e um número inteiro � Subtração de um ponteiro e um número inteiro � Subtração de dois ponteiros. O resultado da operação depende do tipo que ele aponta. 15 Incremento e Decremento de Ponteiros Seja p1 um ponteiro int com um valor atual de 2000, ou seja, ele contém o endereço 2000. p1++; Seu conteúdo será 2004 (não 2001) pois ele aponta para a localização da memória do próximo elemento do seu tipo base (assumindo números inteiros de 32 bits, isto é, 4 bytes de comprimento). p1--; Seu conteúdo será 1996. 16 Resumindo Sempre que um ponteiro for incrementado ele apontará para a localização de memória do próximo elemento do seu tipo base, sempre que ele for decrementado, apontará para a localização do elemento anterior de seu tipo base. 17 Adição e Subtração de um ponteiro e um número inteiro p1 = p1 + 9; p1 apontará para o nono elemento do tipo base de p1, após aquele para o qual ele está apontando atualmente. p1 = p1 – 9; p1 apontará para o nono elemento do tipo base de p1, anterior àquele para o qual ele está apontando atualmente. 18 Subtração de Dois Ponteiros É possível subtrair um ponteiro de outro (desde que ambos sejam do mesmo tipo base). O resultado será o número de elementos do tipo base que separa os dois ponteiros. Se um ponteiro p1 aponta para o endereço 1000, e outro p2 aponta para o endereço 1008, a diferença entre p2 e p1 será: � 8 se forem ponteiros do tipo char � 2 se forem ponteiros do tipo int � 1 se forem ponteiros do tipo double 19 Comparação entre Ponteiros Os ponteiros de mesmo tipo podem ser comparados usando-se operadores relacionais, como: == < <= > >= != Em geral, para que o resultado da comparação de ponteiros seja significativo, os dois ponteiros devem ter algum relacionamento. Por exemplo, ambos podem apontar para elementos de uma mesma matriz. 20 A Convenção do Ponteiro Nulo Quando um ponteiro é declarado (antes de ser atribuído a ele um valor) ele contém um valor arbitrário. Usar um ponteiro não inicializado é um risco sério, pois podemos estar acessando endereços ocupados por outros dados ou endereços não permitidos (de uso do SO, por exemplo). Para evitar esse tipo de erro, os programadores em C++ adotam a seguinte convenção: se o ponteiro tiver o valor nulo, assume-se que ele aponta para nada e não deve ser usado. 21 Exemplo float *p = 0; Para testar se um ponteiro é nulo, use uma instrução if: if (p) // ok se p não for nulo if (!p) // ok se p for nulo 22 Múltipla Indireção Um ponteiro para um ponteiro é uma forma de múltipla indireção. Nesse caso, um ponteiro contém o endereço do outro, que aponta para o local que contém o valor desejado. A múltipla indireção pode ser levada a qualquer dimensão, mas raramente é necessário mais de um ponteiro para um ponteiro. Ponteiros para ponteiros dão a C++ uma grande flexibilidade na criação e ordenação de classes complexas. 23 Sintaxe / Exemplo Sintaxe: int main() { int x, *p, **q; x = 10; p = &x; q = &p; cout << **q; return 0; } // Saída = 10 tipo ** ponteiro Exemplo: 24 Dicas para evitar erros com ponteiros Certifique-se que o ponteiro aponta para algo antes de usá-lo, ou seja, que ele não é nulo. Certifique-se que o tipo de objeto para o qual o ponteiro aponta seja o mesmo que o tipo base do ponteiro. Faça comparações apenas entre ponteiros que apontam para objeto comum. Não converta ponteiros apenas para compilar seu código (evite a coerção). 25 Ponteiros e Funções Há três maneiras de passar argumentos para uma função: � Por valor � Por referência � Por referência com ponteiros 26 Passando Argumentos por Valor Quando o envio de uma variável a uma função se dá por valor, a função chamada cria novas variáveis do mesmo tipo dos argumentos e copia nelas o valor dos argumentos passados. Ou seja, a função não tem acesso às variáveis originais, portanto não pode modificá-las. void inverte (int val) … inverte (x); 27 Passando Argumentos por Referência Quando o envio de uma variável a uma função se dá por referência, o operador de referência cria outro nome para uma variável já existente (não uma cópia). Nesse caso, o valor da variável enviada pode ser alterado dentro da função. void inverte (int &val)… inverte (x); 28 Passando Argumentos por Referência com Ponteiro Quando o envio de uma variável a uma função se dá por referência com ponteiro, é enviado o endereço da variável que será alterada o qual é recebido em um ponteiro na função. Nesse caso, o valor da variável enviada pode ser alterado dentro da função. void inverte (int *val) ... inverte (&x); • C - a passagem de parâmetros é sempre por valor; existe o tipo "ponteiro" que permite a você passar uma referência a um valor. • C++ - a passagem de parâmetros é sempre por valor; existem os tipos "ponteiro" e "referência" que permitem a você passar uma referência a um valor. 29 30 Retornando Ponteiros em uma Função O valor de retorno de uma função deve ser compatível com o tipo de seu retorno. Então, para retornar um ponteiro, uma função deve declarar seu tipo de retorno como sendo um ponteiro e o valor usado em sua instrução return também deve ser um ponteiro. char *match(char c, char *s) { while (c != *s && *s) s++; return(s); } 31 Ponteiros e Estruturas Os tipos definidos pelo programador podem ter ponteiros associados. A sintaxe segue a regra geral de ponteiros. tipo-estrutura *ponteiro; As sintaxes para acessar os membros de uma estrutura são: ponteiro->membro; *ponteiro.membro; 32 Exemplo struct Pessoa // declara a estrutura { char nome[40]; char endereço[60]; }; Pessoa *p; // declara um ponteiro para a estrutura Pessoa p = &Pessoa; // atribui o endereço da estrutura Pessoa ao ponteiro p p->nome; // acessa o membro nome da estrutura Pessoa p->endereço; // acessa o membro endereço da estrutura Pessoa 33 Vantagens em usar Ponteiros para Estruturas 1. Permite que uma função referencie os valores reais de uma estrutura, ao invés de uma cópia dos seus valores. 2. Quando um ponteiro para uma estrutura é passado para uma função apenas o endereço da estrutura é colocado/tirado da pilha. Isso contribui para chamadas rápidas a funções. 34 Ponteiros Void São ponteiros genéricos que podem ser formatados para qualquer tipo de dado em tempo de execução. void * ponteiro; 35 Conteúdo do Ponteiro Void O C++ permite que se acesse o conteúdo apontado por um ponteiro genérico somente após ele ser formatado. Pode-se criar outro ponteiro e fazer a conversão de tipo na atribuição. int i = 5, *pi; void *pv; pv = &i; pi = (int *) pv; cout << *pi; 36 Motivação para o uso de Ponteiros Void Ponteiros Void permitem especificar funções de propósito geral cujo parâmetro é um ponteiro a ser formatado em tempo de execução. Isto é, com eles é possível criar funções que retornem um ponteiro genérico e opere independente do tipo de dado apontado. 37 Ponteiro This Quando criamos objetos de uma classe, cada um deles tem sua própria cópia dos membros, mas as funções possuem um código compartilhado por todos os objetos da classe. Para o programa saber dentro do código de uma função o objeto que está sendo acessado existe um ponteiro, denominado THIS, implicitamente criado e associado à função. Este ponteiro aponta sempre para o objeto que chamou a função, ou seja, sempre aponta para o objeto sendo usado. Quando um objeto chama uma função, o ponteiro THIS é automaticamente ajustado para o endereço do objeto.
Compartilhar