Baixe o app para aproveitar ainda mais
Prévia do material em texto
Universidade Federal de Itajubá BAC004 - Informática Profa. Claudia Akemi Izeki Prof. Walter Aoiama Nagai Tópico Complementar: Ponteiros 1. Definição, Declaração e Operadores Um ponteiro é uma variável cujo valor é um endereço de memória. Na Figura 1, xPtr é um ponteiro para um valor do tipo float. Figura 1. xPtr referencia indiretamente a variável x cujo valor é 10.0 O valor do ponteiro xPtré o endereço da variável xque, por sua vez, contém o valor 10.0. Assim, o nome de variável x referencia diretamente um valor, sendo que o ponteiro xPtr referencia indiretamente um valor. No exemplo da Figura 2 é apresentado um programa que declara um ponteiro de nome xPtre utiliza os seguintes operadores: ● & - operador de endereço: obtém o endereço na memória de uma variável; ● * - operador de indireção, também chamado de derreferência: obtém o valor da variável apontada. Na linha 4 é declarada uma variável chamada xPtrcomo sendo do tipo float*,ou seja, um ponteiro para um valor do tipo float. Lê-se "xPtré um ponteiro para float" ou "xPtraponta para uma varíavel do tipo float". 1 int main() 2 { 3 float x; // x é uma variável do tipo float 4 float *xPtr; // xPtr é um ponteiro para um float 5 6 x = 10.0; 7 xPtr = &x; // xPtr recebe o endereço de x 8 9 cout << "O valor de x é: " << x << endl; 10 cout << "O endereço de x é: " << &x << endl; 11 cout << "O valor de xPtr é: " << xPtr << " que é o mesmo endereço de x" << endl; 12 cout << "O valor de *Ptr é " << *xPtr << " que é o mesmo valor de x" << endl; 13 cout << "O endereço de xPtr é: " << &xPtr; 14 return 0; 15} Figura 2. Primeiro exemplo com ponteiros e operadores de endereço e de indireção. 1 Na Figura 3 são apresentadas as impressões na tela do código da Figura 2. O valor de x é: 10.0 O endereço de x é: 0x22ff0c O valor de xPtr é: 0x22ff0c que é o mesmo endereço de x O valor de *Ptr é: 10.0 que é o mesmo valor de x O endereço de xPtr é: 0x22ff08 Figura 3. Saída do programa da Figura 2. 2. Inicialização de variáveis ponteiros Existem 3 formas de inicializar um ponteiro: ● Com o valor 0 ou a constante NULL: não aponta para nada. Em C++ é preferível usar a constante 0. Exemplos: int *x = 0; int *y = NULL; ● Com algum endereço de memória de uma variável já existente. Exemplo: int a = 10; int *x = &a; ● Com a atribuição de um endereço de memória retornado pelo comando new() de alocação dinâmica de memória. Exemplo: int *y = new int(); // Este comando será explicado posteriormente 3. Argumentos ponteiros na passagem de parâmetro por referência Ponteiros na chamada por referência podem ser usados para modificar uma ou mais variáveis da função chamadora, ou para evitar sobrecarga através da passagem por valor de dados grandes. Quando se chama uma função com parâmetros que devem ser modificados, os endereços dos parâmetros devem ser passados. Existem duas fomas de passar os endereços dos parâmetros: ● empregando-se o operador de endereços & ao nome da variável cujo valor será modificado; ou ● escrevendo-se o nome do próprio vetor ou matriz, pois seu nome é sua posição inicial na memória. Na Figura 4 é apresentada a execução passo a passo de uma chamada à função que troca dois valores utilizando ponteiros. Antes da chamada à função troca()... 2 Função troca() sendo chamada... Após realizar a troca de valores... Figura 4. Passo a passo de uma chamada por referência típica usando ponteiro como parâmetro. 4. Aritmética de ponteiros Com a finalidade de apresentar aritmética de ponteiros, é apresentado o exemplo da Figura 6, no qual o ponteiro vPtr aponta para o vetor v (ilustração na Figura 5). Figura 5. Um vetor v e uma variável ponteiro vPtr apontando para v. 1 int main() 2 { 3 int v[]={10, 20, 30, 40, 50}; // v é um vetor de inteiros 4 int *vPtr; // vPtr é um ponteiro para inteiro 5 6 vPtr = &v[0]; // ou vPtr = v, pois o nome do vetor é o endereço do primeiro elemento 7 cout << "O valor de *vPtr é o mesmo que v[0], que é: " << *vPtr << "\n\n"; 8 cout << "Operação vPtr++..." << endl; 9 vPtr++; // incrementa o ponteiro de 1 unidade (tamanho de um inteiro) 10 cout << "O valor de *vPtr é o mesmo que v[1], que é: " << *vPtr << "\n\n"; 3 11 cout << "Operação vPtr = vPtr + 2..." << endl; 12 vPtr = vPtr + 2; // incrementa o ponteiro de 2 unidades (tamanho de 2 inteiros) 13 cout << "O valor de *vPtr é o mesmo que v[3], que é: " << *vPtr << "\n\n"; 14 cout << "Operação vPtr" << endl; 15 vPtr; // decrementa em uma unidade o valor do ponteiro (tamanho de um inteiro) 16 cout << "O valor de *vPtr é o mesmo que v[2], que é: " << *vPtr << "\n\n"; 17 18 int *v2Ptr = &v[3]; 19 int q; 20 q = v2Ptr vPtr; 21 cout << "Subtração entre dois ponteiros: v2Ptr vPtr = " << q; 22 return 0; 23 } Figura 6. Um programa com várias operações aritméticas de ponteiros. A saída do programa é apresentada na Figura 7. Inicialmente, vPtrestá com o valor 2000 (endereço do primeiro elemento do vetor). Note que a instrução vPtr++resulta em 2004 e não em 2001, já que somar uma unidade de um ponteiro significa somar uma unidade do tipo ao qual ele aponta (um inteiro, no caso, possui 4 bytes). Sendo o endereço 2004 o do segundo elemento do vetor, então v[1] corresponde ao valor 20. A instrução da linha 20 (subtração entre dois ponteiros) segue o mesmo raciocínio, no qual v2Ptr possui o endereço do quarto elemento, no caso 2012 e vPtr o do terceiro elemento, a subtração v2PtrvPtr resulta em 1 e não em 4 (2012-2008). O valor de *vPtr é o mesmo que v[0], que é: 10 Operação vPtr++... O valor de *vPtr é o mesmo que v[1], que é: 20 Operação vPtr = vPtr + 2... O valor de *vPtr é o mesmo que v[3], que é: 40 Operação vPtr O valor de *vPtr é o mesmo que v[2], que é: 30 Subtração entre dois ponteiros: v2Ptr vPtr = 1 Figura 7. Saída do programa da Figura 6. 5. Relação entre ponteiros e vetores (arrays) Ponteiros e vetores estão intimamente relacionados: o nome de um vetor é um ponteiro constante ; e os 1 ponteiros podem ser usados para fazer qualquer operação envolvendo indexação de vetores. No exemplo da Figura 8 são apresentados quatro tipos de notação de acesso aos elementos de um vetor: 1. Uso de subscritos com array; 2. Ponteiro/deslocamento com o nome do array como ponteiro; 3. Uso de subscritos com ponteiros; 4. Ponteiro/deslocamento com um ponteiro; Seja vPtr o mesmo ponteiro dos exemplos das Figuras 5 e 6: int v[]={10, 20, 30, 40, 50}; 1Um ponteiro constante é aquele cujo valor não pode ser modificado. 4 int *vPtr = v; Verifique na Tabela 1 um exemplo de como referenciar o quarto elemento de um array com os 4 tipos de notação descritos acima. 1 - Uso de subscritos com array v[3] 2 - Ponteiro/deslocamento com o nome do array como ponteiro *(v+3) 3 - Uso de subscritos com ponteiros vPtr[3] 4 - Ponteiro/deslocamento com um ponteiro *(vPtr+3) Note que a expressão v = v+3é incorreta porque vé um ponteiro constante, seu valor não deverá ser modificado, sempre deverá apontar para o primeiro elemento do array. 6. Arrays de ponteiros Arrays de ponteiros são arrays cujos elementos são ponteiros. Um uso muito comum de arrays de ponteiros são os arrays de strings. No programa da Figura 8 é apresentado um array de strings que armazena os dias da semana. Na declaração da variável diaSemana, a parte diaSemana[7]indica que é um vetor de 7 elementos. A partechar * indica que cada elemento do vetor é do tipo "ponteiro para caracter". 1 int main() 2 { 3 char *diaSemana[7] = {"Domingo", "Segundafeira", "Tercafeira", 4 "Quartafeira", "Quintafeira", "Sextafeira", 5 "Sabado"} 6 int i; 7 8 for(i=0; i<7; i++) 9 cout << diaSemana[i] << endl; 10 11 return 0; 12 } Figura 8. Um programa com várias operações aritméticas de ponteiros. Figura 9. Representação gráfica do array diaSemana. 7. Operadores new e delete O comando newé utilizado para obter memória do sistema operacional em tempo de execução. Ele retorna um ponteiro para o início do novo bloco de memória que foi alocado. Uma vez alocada, esta memória continua ocupada até que seja desalocada explicitamente pelo operador delete. 5 Exemplo 1: alocando memória para um inteiro int main() { int *intPtr; intPtr = new int(); // aloca memória para 1 inteiro e retorna o // endereço inicial da memória alocada *intPtr = 10; cout << *intPtr; delete intPtr; // liberando a memória alocada return 0; } Figura 10. Esquema correpondente ao Exemplo1 Exemplo 2: alocando memória para um vetor de inteiros de n posições int main() { int *arrayPtr, i, n; cin >> n; arrayPtr = new int[n]; for(i=0; i<n; i++) cin >> arrayPtr[i]; delete [] arrayPtr; return 0; } Figura 11. Esquema correpondente ao Exemplo2 6 Exemplo 3: alocando memória para um vetor de caracteres #include <iostream> #include <cctype> // islower() e toupper() #include <cstring> // strlen() #include <cstdio> // gets() using namespace std; int main() { char *stringPtr, str[40]; int i; gets(str); stringPtr = new char[strlen(str)+1]; for(i=0; i<strlen(str); i++) { if(islower(str[i])) stringPtr[i] = toupper(str[i]); else stringPtr[i] = str[i]; } stringPtr[i] = '\0'; cout << "String: " << stringPtr; delete [] stringPtr; return 0; } Exemplo 4: alocando memória para um registro simples (tipo Aluno) #include<iostream> #include<cstdio> using namespace std; struct Aluno { int matricula; char nome[40]; }; int main() { Aluno *student = new Aluno(); cout << "Matricula: "; cin >> (*student).matricula; cin.ignore(); cout << "Nome.....: "; gets((*student).nome); 7 cout << "Matricula digitada: " << student>matricula << endl; cout << "Nome digitado.....: " << student>nome << endl; delete student; return 0; } Figura 12. Esquema correpondente ao Exemplo4 Exemplo 5: alocando memória para um registro simples (tipo Aluno) com funções #include<cstdio> #include<iostream> using namespace std; struct Aluno { int matricula; char nome[40]; }; void leia_aluno (Aluno *a) { cout << "Matricula: "; cin >> a>matricula; cin.ignore(); cout << "Nome.....: "; gets(a>nome); } void imprime_aluno (Aluno *a) { cout << "Matricula digitada: " << a>matricula << endl; cout << "Nome digitado.....: " << a>nome << endl; } int main() { Aluno *student = new Aluno(); leia_aluno (student); imprime_aluno (student); delete student; return 0; } 8 Exemplo 6: alocando memória para um vetor de registro (tipo Aluno) #include<iostream> using namespace std; const int MAX = 5; struct Aluno { int matricula; char nome[40]; }; int main() { Aluno *students = new Aluno[MAX]; for (int i = 0; i < MAX; i++) { cout << "Matricula: "; cin >> students[i].matricula; cin.ignore(); cout << "Nome.....: "; cin.getline (students[i].nome, 40); } for (int i = 0; i < MAX; i++) { cout << "\nMatricula digitada: " << students[i].matricula << endl; cout << "Nome digitado.....: " << students[i].nome << endl; } delete [] students; return 0; } Figura 13. Esquema correpondente ao Exemplo6 Exemplo 7: alocando memória para um vetor de registro (tipo Aluno) com funções - versão 1 #include<iostream> using namespace std; const int MAX = 5; 9 struct Aluno { int matricula; char nome[40]; }; void leia_aluno (Aluno *a) { cout << "Matricula: "; cin >> a>matricula; cin.ignore(); cout << "Nome.....: "; cin.getline (a>nome, 40); cout << endl; } void imprime_aluno (Aluno a) { cout << "\nMatricula digitada: " << a.matricula << endl; cout << "Nome digitado.....: " << a.nome << endl; } int main() { Aluno *students = new Aluno[MAX]; for (int i = 0; i < MAX; i++) leia_aluno(&students[i]); for (int i = 0; i < MAX; i++) imprime_aluno(students[i]); delete [] students; return 0; } Exemplo 8: alocando memória para um vetor de registro (tipo Aluno) com funções - versão 2 #include<iostream> using namespace std; const int MAX = 5; struct Aluno { int matricula; char nome[40]; }; 10 void leia_alunos (Aluno *alunos) { for(int i=0; i<MAX; i++) { cout << "Matricula: "; cin >> alunos[i].matricula; cin.ignore(); cout << "Nome.....: "; cin.getline (alunos[i].nome, 40); cout << endl; } } void imprime_alunos (Aluno *alunos) { for(int i=0; i<MAX; i++) { cout << "\nMatricula digitada: " << alunos[i].matricula << endl; cout << "Nome digitado.....: " << alunos[i].nome << endl; } } int main() { Aluno *students = new Aluno[MAX]; leia_alunos(students); imprime_alunos(students); delete [] students; return 0; } Exemplo 9: alocando memória para um vetor de registro (tipo Aluno) com funções - versão 3 #include<iostream> using namespace std; const int MAX = 5; struct Aluno { int matricula; char nome[40]; }; 11 void aloca_leAlunos(Aluno **alunos){ *alunos = new Aluno[MAX]; for(int i=0; i<MAX; i++) { cout << "Matricula: "; cin >> (*alunos)[i].matricula; cin.ignore(); cout << "Nome.....: "; cin.getline ((*alunos)[i].nome, 40); cout << endl; } } void imprime_alunos (Aluno *a) { for(int i=0; i<MAX; i++) { cout << "\nMatricula digitada: " << a[i].matricula << endl; cout << "Nome digitado.....: " << a[i].nome << endl; } } int main() { Aluno *students; aloca_leAlunos(&students); imprime_alunos(students); delete [] students; return 0; } Figura 14. Esquema correpondente ao Exemplo9 12
Compartilhar