Prévia do material em texto
Ju d so n S an to s S an ti ag o REVISÃO DE PONTEIROS Estrutura de Dados I Introdução Um ponteiro é uma variável cujo valor é um endereço de memória Da mesma forma que uma variável char tem um caractere como valor e um int tem um número natural como valor 2.6 0x27FCF8 120 G ch num ptr mult 0x27FCF8 0x27FCF9 0x27FCFD 0x27FD01 0x27FD05 0x27FD09 char ch = 'G'; int num = 120; float mult = 2.6; char * ptr = (char *) 0x27FCF8; Introdução Como o ponteiro contém um endereço de memória, diz-se que ele aponta para aquela posição de memória char ch = 'G'; int num = 120; char * ptr = (char *) 0x27FCF8; float mult = 2.6; 2.6 0x27FCF8 120 G ch num ptr mult 0x27FCF8 0x27FCF9 0x27FCFD 0x27FD01 0x27FD05 0x27FD09 O Operador de Endereço Pode-se usar diretamente um endereço de memória, ou usar o operador de endereço & para obter o endereço de alguma variável para onde deseja-se apontar char ch = 'G'; char * ptr2 = (char *) 0x27FCF8; char * ptr1 = &ch; 0x27FCF8 0x27FCF8 G ch ptr1 ptr2 0x27FCF8 0x27FCF9 0x27FCFD 0x27FD01 0x27FD05 0x27FD09 O Operador de Indireção O operador de indireção * pode ser usado para obter o valor contido na memória apontada pelo um ponteiro char ch = 'G'; char * ptr = &ch; cout << *ptr; // G cout << ptr; // 0x27FCF8 0x27FCF8 G ch ptr 0x27FCF8 0x27FCF9 0x27FCFD 0x27FD01 0x27FD05 0x27FD09 Declaração de Ponteiros Por que não se declara um ponteiro da mesma forma que um int, char ou float? Não é suficiente dizer que uma variável é um ponteiro, é preciso também especificar para que tipo de dado ele aponta char ch = 'G'; int num = 120; float f = 2.1; pointer p = &ch; char * pc = &ch; int * pi = # float * pf = &f; Atribuição de Valores Por essa mesma razão, é preciso especificar o tipo de um endereço de memória usando um type cast (conversão explícita de tipos) Ao usar o operador &, o tipo do endereço já está definido pelo tipo da variável char ch = 'G'; char * p = (char *) 0x27FCF8; // endereço de um char char ch = 'G'; char * p = &ch; // endereço de um char Ponteiros e Funções Ponteiros podem ser usados como argumentos de funções para fazer a passagem de parâmetro por referência void modif(int n) { n = 3; } int main() { int a = 2; cout << a; // 2 modif(a); cout << a; // 2 } void modif(int * p) { *p = 4; } int main() { int a = 2; cout << a; // 2 modif(&a); cout << a; // 4 } Ponteiros e Funções A função não recebe a cópia de um valor, ela recebe o endereço de memória de onde se encontra o valor void modif(int n) { n = 3; } int main() { int a = 2; cout << a; // 2 modif(a); cout << a; // 2 } void modif(int & p) { p = 4; } int main() { int a = 2; cout << a; // 2 modif(a); cout << a; // 4 } Ponteiros e Funções Para que serve passar um parâmetro por referência? Para modificar o argumento da função no programa principal Para tratar tipos de dados que guardam um grande volume de informação (vetores, matrizes, registros, listas, árvores, etc.) Ao invés de passar para a função uma cópia desse grande volume de dados, passa-se apenas o endereço desses dados na memória Ponteiros e Vetores A notação de vetor [] é apenas um uso disfarçado de ponteiros O nome de um vetor é um ponteiro para o primeiro elemento do vetor int main() { int vet[5] = {12,7,3,45,52}; if (vet == &vet[0]) cout << "iguais"; // iguais cout << *vet; // 12 } Ponteiros e Vetores int soma (int * ar, int n) // int soma (int ar[], int n) { int total = 0; for (int i=0; i<n; i++) total += ar[i]; // total = total + ar[i] return total; } int main() { int vet[5] = {12,7,3,45,52}; int somavalor = soma(vet, 5); cout << somavalor; // 119 } Ponteiros e Registros Registros são estruturas usadas para agrupar informações sob um único nome struct data { int dia; int mes; int ano; }; int main() { data hoje; hoje.dia = 05; hoje.mes = 11; hoje.ano = 2009; } Ponteiros e Registro Ponteiros podem apontar para estruturas struct data { int dia; int mes; int ano; }; int main(void) { data hoje = {15, 04, 2008}; data * ptr = &hoje; cout << "Hoje são " << ptr->dia << "/" << ptr->mês << "/" << ptr->ano; } Ponteiros e Registro Registros podem conter ponteiros struct data { int * dia; int * mes; int * ano; }; int main() { int d = 15; int m = 04; int a = 2008; data hoje; hoje.dia= &d; hoje.mes= &m; hoje.ano= &a; } Ponteiros e Registro Registros podem conter ponteiros para registros struct data { int dia; int mes; int ano; data * prox; }; int main() { data hoje = {15, 04, 2008, NULL}; data amanha = {16, 04, 2008, NULL}; hoje.prox = &amanha; } Alocação Dinâmica de Memória A declaração de uma variável embute uma série de tarefas que são feitas automaticamente pelo compilador: Reservar (alocar) uma quantidade de memória suficiente para guardar um valor inteiro Usar o identificador para fazer referência a essa posição de memória, dentro do programa Liberar (desalocar) essa memória quando a variável sair de escopo ou quando o programa finalizar Alocação Dinâmica de Memória Podemos alocar memória a qualquer momento usando o operador new e desalocar usando o operador delete int main() { int * ptr; ptr = new int; *ptr = 5; cout << *ptr; // 5 delete ptr; } Alocação Dinâmica de Memória A função new recebe como argumento um tipo de dado e retorna o endereço da memória alocada int main() { int * ptr; ptr = new int; *ptr = 5; cout << *ptr; // 5 delete ptr; } Alocação Dinâmica de Memória A função delete recebe como argumento o ponteiro que aponta para a memória previamente alocada com new int main() { int * ptr; ptr = new int; *ptr = 5; cout << *ptr; // 5 delete ptr; } Vetor Dinâmico Como um vetor é um ponteiro, podemos alocar um vetor dinamicamente com new e usar o nome do ponteiro como vetor int main() { int * ptr; // aloca memória para 5 inteiros (vetor de 5 inteiros) ptr = new int[5]; ptr[0]=5; ptr[1]=15; ptr[2]=30; ptr[3]=21; ptr[4]=6; cout << ptr[0] << " " << ptr[1] << " " ptr[2]; // 5 15 30 delete [] ptr; } Vetor Dinâmico Da mesma forma que se cria um vetor dinâmico de inteiros, pode-se criar um vetor dinâmico de registros int main() { data * ptr; // aloca memória para 5 datas (vetor de 5 datas) ptr = new data[5]; ptr[0].dia = 20; ptr[0].mes = 4; ptr[0].ano = 2008; ptr[1].dia = 10; ptr[1].mes = 7; ptr[1].ano = 2008; ptr[2].dia = 17; ptr[2].mes = 3; ptr[2].ano = 2009; delete [] ptr; } struct data { int dia; int mes; int ano; }; Listas Encadeadas Pode-se usar ponteiros também para interligar registros: uma lista encadeada int main() { data * pt1, * pt2; pt1 = new data; pt2 = new data; pt1->dia=20; pt1->mes=4; pt1->ano=2008; pt1->prox= NULL; pt2->dia=17; pt2->mes=3; pt2->ano=2009; pt2->prox= NULL; pt1->prox = pt2; delete pt1; delete pt2; } struct data { int dia; int mes; int ano; data * prox; }; Conclusão Ponteiros são tipos de dados especiais que armazenam endereços de memória Eles permitem acessar a máquina em um nível mais baixo de abstração: Excelente para acessar o diretamente o hardware Permitem manipular os dados de forma mais eficiente Ponteiros são usados para organizar dados em listas, pilhas e filas encadeadas