Prévia do material em texto
Ju d so n S an to s S an ti ag o PONTEIROS Alocação Dinâmica de Memória Introdução Para armazenar dados no computador um programa gerencia: Onde a informação esta armazenada Que tipo de informação é armazenada Que valor é mantido lá 00001100 00110110 Endereços de Memória 3126 (short) 0xCB20 0xCB21 0xCB22 0xCB23 0xCB24 0xCB25 Introdução A declaração de uma variável num programa realiza estes passos necessários para o armazenamento de dados short total; // declaração de variável total = 3126; // atribuição de valor 00001100 00110110 3126 = total 0xCB20 0xCB21 0xCB22 0xCB23 0xCB24 0xCB25 = total 0xCB20 0xCB21 0xCB22 0xCB23 0xCB24 0xCB25 short Declaração Atribuição Endereços de Variáveis Todo nome de variável esta associado a um endereço na memória O operador de endereço & pode ser usado para obter a localização de uma variável short total; // declaração de variável total = 3126; // atribuição de valor cout << total; // valor da variável cout << &total; // endereço da variável Endereços de Variáveis #include <iostream> using namespace std; int main() { int copos = 6; double cafe = 4.5; cout << "Valor de copos = " << copos << endl; cout << "Endereço de copos = " << &copos << endl; cout << "Valor de cafe = " << cafe << endl; cout << "Endereço de cafe = " << &cafe << endl; system("pause"); return 0; } Endereços de Variáveis Saída do programa: Execução 1 Execução 2 Valor de copos = 6 Endereço de copos = 0027FCF8 Valor de cafe = 4.5 Endereço de cafe = 0027FCE8 Valor de copos = 6 Endereço de copos = 0021F8FC Valor de cafe = 4.5 Endereço de cafe = 0021F8EC Ponteiros Existe outra estratégia para armazenar dados no computador: Alocar memória manualmente Guardar o endereço de memória em um ponteiro Usar o ponteiro para acessar e modificar os dados Para usar esta estratégia é necessário o conhecimento de ponteiros Ponteiros Um ponteiro é um tipo especial que guarda valores que são endereços de memória Da mesma forma que uma variável char tem um caractere como valor e um int tem um número como valor 2.6 0x27FCF8 120 G ch num ptr mult 0x27FCF8 0x27FCF9 0x27FCFD 0x27FD01 char ch = 'G'; int num = 120; float mult = 2.6; char * ptr = (char *) 0x27FCF8; 0x27FD05 0x27FD09 Ponteiros A declaração de um ponteiro segue o seguinte padrão: char * ptr; O tipo do elemento apontado Nome do ponteiro Operador de indireção Ponteiros Como o ponteiro contém um endereço de memória, diz-se que ele aponta para aquela posição de memória 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; Ponteiros O nome de um ponteiro representa uma localização na memória O operador de indireção * pode ser usado para obter o valor armazenado na memória // declaração do ponteiro char * ptr = (char *) 0x27FCF8; cout << ptr; // endereço armazenado cout << *ptr; // valor apontado 2.6 0x27FCF8 120 G ch num ptr mult 0x27FCF8 0x27FCF9 0x27FCFD 0x27FD01 0x27FD05 0x27FD09 Ponteiros #include <iostream> using namespace std; int main() { int total = 6; // declara uma variável int * pt; // declara um ponteiro pt = &total; // atribui endereço de total cout << "Valor de total = " << total << endl; cout << "Valor de pt = " << *pt << endl; cout << "Endereço de total = " << &total << endl; cout << "Endereço de pt = " << pt << endl; *pt = *pt + 1; // altera valor cout << "Agora total vale = " << total << endl; system("pause"); return 0; } Ponteiros Saída do Programa: A alteração de *pt mudou o valor da variável apontada pelo ponteiro Valor de total = 6 Valor de pt = 6 Endereço de total = 0034FBBC Endereço de pt = 0034FBBC Agora total vale = 7 *pt = *pt + 1; // altera valor cout << "Agora total vale = " << total << endl; Variável versus Ponteiro Ao usar uma variável: O valor é um elemento que possui um nome A localização do valor é um elemento derivado (&) Ao usar um ponteiro: A localização é um elemento que possui um nome O valor é um elemento derivado (*) int * pt = &total; // pt se refere ao endereço cout << *pt; // *pt se refere ao valor int total = 6; // total se refere ao valor cout << &total; // &total se refere ao endereço 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; char * pc = &ch; int * pi = # float * pf = &f; pointer p = &ch; // cout não sabe o tipo de *p cout << *p; Declaração de Ponteiros Na declaração de um ponteiro o uso de espaços ao redor do (*) é opcional Cuidado com declarações múltiplas int *ptr; // enfatiza que *ptr é um int int* ptr; // enfatiza que ptr é um ponteiro para int int * ptr; // estilo neutro // p1 é um ponteiro para int, p2 é um int int * p1, p2; // p1 e p2 são ponteiros para int int *p1, *p2; Cuidado com Ponteiros Ao declarar um ponteiro o computador não aloca automaticamente memória para guardar o valor apontado long * ptr; *ptr = 504; ptr 0x27FCF8 504 ptr long val; long * ptr = &val; *ptr = 504; val0x27FCF8 0x27FCF9 0x27FCFD 0x27FD01 0x27FD05 0x27FD09 0x27FCF8 0x27FCF9 0x27FCFD 0x27FD01 0x27FD05 0x27FD09 Atribuição de Valores Endereços de memória não são valores do tipo inteiro: Um endereço tem 4 bytes Um inteiro pode ter 2 bytes (antigo MS-DOS) É possível converter um inteiro para um endereço de memória usando um type cast int * p = 0x27FCF8; // inválido, mistura de tipos int * p = 0xB80000; // inválido, mistura de tipos int * p = (int *) 0x27FCF8; int * p = (int *) 0xB80000; Atribuição de Valores O type cast converte para um endereço e indica também o tipo do valor apontado Ao usar o operador & o tipo do endereço já está definido pelo tipo da variável char * p = (char *) 0x27FCF8; // endereço de um char char ch = 'G'; char * p = &ch; // endereço de um char Alocação de Memória Ponteiros tem sido usados para guardar endereços de variáveis já existentes Variáveis são memórias rotuladas durante o processo de compilação Neste caso os ponteiros fornecem apenas uma segunda forma de acesso as variáveis O verdadeiro poder dos ponteiros está em apontar para memória não rotulada, alocada durante a execução do programa Alocação de Memória A alocação de memória é feita com o operador new int * pn = new int; Operador new retorna o endereço da memória alocada Tipo de dado Ponteiro compatível com o tipo de dado requisitado Alocação de Memória #include <iostream> using namespace std; int main() { int * pi = new int; // aloca memória para um inteiro *pi = 1001; // guarda um valor lá cout << "Valor inteiro = " << *pi << endl; cout << "Localização = " << pi << endl << endl; double * pd = new double; // aloca memória para um double *pd = 500.35; // guarda um valor lá cout << "Valor ponto-flutuante = " << *pd << endl; cout << "Localização = " << pd << endl << endl; cout << "Tamanho de pi = " << sizeof(pi) << endl; cout << "Tamanho de *pi = " << sizeof(*pi)<< endl << endl; cout << "Tamanho de pd = " << sizeof(pd) << endl; cout << "Tamanho de *pd = " << sizeof(*pd) << endl; system("pause"); return 0; } Alocação de Memória Saída do programa: Valor inteiro = 1001 Localização = 00114CD0 Valor ponto-flutuante = 500.35 Localização = 00114DA8 Tamanho de pi = 4 Tamanho de *pi = 4 Tamanho de pd = 4 Tamanho de *pd = 8 Liberando Memória Toda memória alocada com new deve ser liberada ao final do seu uso O operador delete permite retornar a memória não mais usada para uso do sistema, ou de novas alocações int * ps = new int; // aloca memória com new ... // usa memória delete ps; // libera memória ao final Liberando Memória O operador delete libera a memória mas não destrói o ponteiro O mesmo ponteiro pode ser usado para receber outro endereço de memória int * ps = new int; // aloca memória com new *ps = 30; cout << *ps; delete ps; // libera memória ao final ps = new int; // aloca nova memória *ps = 50; cout << *ps; delete ps; // libera nova memória Liberando Memória Um uso de new deve ser sempre balanceado com um uso de delete Caso contrário tem-se um memory leak int * ps = new int; // aloca memória com new *ps = 30; cout << *ps; ps = new int; // memory leak *ps = 50; cout << *ps; delete ps; // libera última alocação Liberando Memória Não se pode liberar o mesmo bloco de memória duas vezes int * ps = new int; // aloca memória com new *ps = 30; cout << *ps; ... delete ps; // libera memória ao final delete ps; // resultado indefinido Liberando Memória Não se pode usar delete para liberar memória criada com a declaração de variáveis int val; // declaração de variável val = 30; cout << val; ... delete val; // inválido, não foi alocado com new Vetores Dinâmicos Um vetor criado por uma declaração de variável é chamado de vetor estático É preciso definir previamente o tamanho do vetor em tempo de compilação Usando new é possível criar um vetor com tamanho definido durante a execução do programa int vet[10]; // vetor de 10 inteiros Vetores Dinâmicos Para criar um vetor dinâmico com new basta passar o tipo e o número de elementos O ponteiro recebe o endereço do primeiro elemento do vetor int * vet = new int [20]; Operador new Tipo de dado Ponteiro compatível com o tipo de dado requisitado Quantidade de elementos Vetores Dinâmicos Para liberar a memória de um vetor dinâmico é preciso usar delete com uma notação especial delete [] vet; Operador delete Endereço da memória alocada Memória contém um vetor Vetores Dinâmicos O ponteiro de um vetor dinâmico pode ser usado como se fosse um vetor 5 15 0x27FD09 0x27FD0D 30 0x27FD11 28 0x27FD15 pvet int * pvet = new int [5]; pvet[0] = 15; pvet[1] = 5; pvet[2] = 30; pvet[3] = 28; pvet[4] = 40; cout << pvet[0]; // 15 cout << *pvet; // 15 40 0x27FD19 0x27FD1D 0x27FD01 0x27FD05 0x27FD09 Vetores Dinâmicos #include <iostream> using namespace std; int main() { double * p3 = new double [3]; // memória para três doubles p3[0] = 0.2; p3[1] = 0.5; p3[2] = 0.8; cout << "p3[1] = " << p3[1] << endl; p3 = p3 + 1; // incrementa o ponteiro cout << "Agora p3[0] = " << p3[0] << endl; cout << "Agora p3[1] = " << p3[1] << endl; p3 = p3 - 1; // retorna ao inicio delete [] p3; // libera a memória system("pause"); return 0; } Vetores Dinâmicos Saída do programa: Um ponteiro é uma variável e seu conteúdo pode ser modificado através de uma atribuição p3[1] = 0.5 Agora p3[0] = 0.5 Agora p3[1] = 0.8 p3 = p3 + 1; // incrementa o ponteiro p3 = p3 - 1; // decrementa o ponteiro Vetores Dinâmicos Um ponteiro pode ser usado como um vetor Um vetor pode ser usado como um ponteiro int * pvet = new int [10]; pvet[0] = 15; pvet[1] = pvet[0] + 5; cout << pvet[1]; int vet[10]; *vet = 15; // vet[0] = 15; *(vet + 1) = *vet + 5; // vet[1] = vet[0] + 5; cout << *(vet + 1); // cout << vet[1]; Vetores Dinâmicos Um vetor estático é um ponteiro constante para o primeiro elemento do vetor 5 15 0x27FD09 0x27FD0D 30 0x27FD11 28 0x27FD15 vet int vet[5]; vet[0] = 15; vet[1] = 5; vet[2] = 30; *(vet+3) = 28; *(vet+4) = 40; cout << vet[1]; // 5 cout << *(vet+1); // 5 vet = vet + 1; // inválido 40 0x27FD19 0x27FD1D 0x27FD01 0x27FD05 0x27FD09 Registros Dinâmicos O operador new também pode ser usado para criar registros dinâmicos jogador * pj = new jogador; Operador new Tipo de dado Ponteiro compatível com o tipo de dado requisitado struct jogador { char nome[40]; float salario; unsigned gols; }; Registros Dinâmicos O operador membro (.) não pode ser usado com um registro dinâmico C++ oferece o operador (->) para acessar membros de um registro dinâmico Usa-se (.) com variáveis tipo registro Usa-se (->) com ponteiros para registros jogador * pj = new jogador; cout << pj->nome; cout << pj->salario; cout << pj->altura; Registros Dinâmicos #include <iostream> using namespace std; struct jogador { char nome[40]; float salario; unsigned gols; }; int main() { jogador * pbeb = new jogador; strcpy(pbeb->nome, "Bebeto"); pbeb->salario = 200000; pbeb->gols = 600; cout << "Contratação para o próximo ano:\n" << pbeb->nome << " por " << pbeb->salario << " Reais\n"; delete pbeb; system("pause"); return 0; } Registros Dinâmicos Saída do programa: Atribuição a um registro dinâmico: Contratação para o próximo ano: Bebeto por 200000 Reais jogador * prom = new jogador; strcpy(prom->nome, "Romario"); prom->salario = 300000; prom->gols = 800; Vetores Dinâmicos O operador new também pode ser usado para criar vetores dinâmicos de registros jogador * pt = new jogador [22]; Operador new Tipo de dado Ponteiro compatível com o tipo de dado requisitado struct jogador { char nome[40]; float salario; unsigned gols; }; Quantidade de elementos Vetores Dinâmicos O operador (.) deve ser usado com a notação de vetor O operador (->) deve ser usado com a notação de ponteiro cout << pt->nome; // nome do primeiro jogador cout << (pt+1)->salario; // salario do segundo jogador cout << (pt+21)->altura; // altura do ultimo jogador cout << pt[0].nome; // nome do primeiro jogador cout << pt[1].salario; // salario do segundo jogador cout << pt[21].altura; // altura do ultimo jogador Vetores Dinâmicos #include <iostream> using namespace std; struct jogador { char nome[40]; float salario; unsigned gols; }; int main() { jogador * ptime = new jogador[22]; cout << "Digite o nome, salario e gols de dois jogadores: "; cin >> ptime[0].nome; cin >> ptime[0].salario; cin >> ptime[0].gols; cin >> ptime[1].nome; cin >> ptime[1].salario; cin >> ptime[1].gols; cout << "\nCusto da aquisição: R$" << ptime[0].salario + ptime[1].salario << "!\n"; delete [] ptime; system("pause"); return 0; } Vetores Dinâmicos Saída do programa: Digite o nome, salário e gols de dois jogadores: Bebeto 200000 600 Romario 300000 800 Custo da aquisição: R$500000! Conclusão Ponteiros são variáveis que armazenam endereços de memória A sua principal função é guardar o endereço de memória alocada dinamicamente com o operador new Permite alocar variáveis durante a execução Permite criar vetores dinâmicos Permite criar registrosdinâmicos