Baixe o app para aproveitar ainda mais
Prévia do material em texto
APOSTILA : LINGUAGENS DE PROGRAMAÇÃO C E C++ Angelo Passaro Página VI.1 Segunda revisão / 2002 VVII.. PPOONNTTEEIIRROOSS Ponteiro é um dos recursos mais importantes das linguagens C e C++. O uso de ponteiros permite o acesso direto ao conteúdo da memória do computador, a definição de variáveis (dos tipos intrínseco e derivado) em tempo de execução (armazenamento dinâmico) e passagem de parâmetros por referência (capítulos VII e VIII). No entanto, seu uso, sem uma clara compreensão de seu significado e conhecimento da forma correta de tratamento, pode levar a sérios danos na fase de execução de um programa, tais como, alterações de dados vitais, “corrupção” de programas e, até, de componentes do sistema operacional. Uma alteração inadvertida pode resultar em instabilidade do programa do usuário e/ou do sistema operacional, levando, neste último caso, ao “shutdown” forçado do computador. Neste capítulo, vamos tentar desmistificar os ponteiros, compreendendo seu significado e tentando indicar hábitos de programação que minimizam o uso incorreto de um recurso tão importante da linguagem. Ponteiro é um tipo de dado derivado não diferente, na sua natureza, de um número inteiro. Uma variável do tipo ponteiro armazena um número inteiro. Esse número inteiro corresponde a um endereço de memória (em microcomputadores com sistema operacional Windows de 32 bits, um ponteiro ocupa 4 bytes de memória). Quando o programador declara um identificador como do tipo ponteiro o programa passa a tratá-lo de forma diferenciada, apresentando seu conteúdo na notação hexadecimal e limitando o tipo de operações que podem ser executadas, como será visto na seção VI.C. O conteúdo de um ponteiro (ou seja, um endereço de memória) indica o local na memória no qual é armazenada a definição de uma outra variável ou uma função. Essa outra variável também pode ser do tipo ponteiro. Neste caso, tem-se a definição de um ponteiro para ponteiro, o que é denominado de indireção múltipla. Observações: Para que uma variável do tipo ponteiro contenha um endereço que tenha sentido no contexto da aplicação é necessário que esta seja inicializada convenientemente. A inicialização pode ser feita por atribuição direta, conforme apresentado neste capítulo (ver também o uso do operador &) ou pelo uso de funções, tais como, malloc e calloc, ou o operador new, apresentados no capítulo VII. Um ponteiro não inicializado é um erro de implementação muitas vezes difícil de ser localizado e que pode levar a graves erros durante a execução. Uma variável do tipo ponteiro não inicializada contém um endereço de memória que pode assumir qualquer valor. O uso da posição de memória indicada por tal variável, seja para efeito de leitura ou escrita, pode ter efeitos devastadores em tempo de execução (este tipo de problema não é identificado em tempo de compilação). Um programa que faz a leitura de dados de uma posição de memória qualquer estará utilizando um valor incorreto de uma variável. O uso dessa variável vai resultar em erros de cálculo. No caso do software de um sistema de controle pode resultar numa tomada de decisão incorreta. No caso de escrita, o problema também está presente. Uma gravação de dados numa região inapropriada de memória pode causar uma alteração em dados, no próprio programa, e/ou em componentes do sistema operacional. O exemplo a seguir procura explorar, a partir de declarações simples, o significado de um ponteiro e sua relação com o tipo de dado. Exemplo: short int id1 = 10; // define o identificador id1 de uma variável do tipo short int short int * p_id1; // define a variável p_id1 do tipo ponteiro para short int (consulte a forma // adequada para declaração de ponteiros na seção VI.A) p_id1 = &id1; // atribui o endereço da variável id1 à variável p_id1 (veja o // uso do operador &, posteriormente, na seção VI.B) APOSTILA : LINGUAGENS DE PROGRAMAÇÃO C E C++ Angelo Passaro Página VI.2 Segunda revisão / 2002 Vamos tentar compreender o que ocorre na memória do computador com o uso destas instruções. A variável id1, no exemplo anterior, ocupa dois bytes de memória já que é do tipo short int. A representação binária do valor de id1 é 0000 0000 0000 1010 Vamos assumir que estamos trabalhando com um sistema operacional no qual o tamanho de um ponteiro é de 4 bytes. Vamos assumir, também, que o sistema armazenou o conteúdo de id1 a partir do endereço de memória 0xABAC0105, e o conteúdo da variável p_id1 (o ponteiro) a partir da posição de memória 0xABAC010B. (Note que, na realidade, a atribuição de endereços às variáveis e funções é feita automaticamente pelo sistema). Desta forma, o conteúdo da variável ponteiro p_id1 será o endereço de memória no qual está armazenada a variável id1. Isto é representado como segue: primeiro byte segundo byte terceiro byte quarto byte notação hexadecimal : A B A C 0 1 0 5 notação binária 1010 1011 1010 1100 0000 0001 0000 0101 A tabela a seguir mostra uma representação do conteúdo da memória. Tabela VI.1 : Representação do conteúdo da memória entre os endereços 0xABAC0100 e 0xABAC0110 endereço de memória conteúdo da memória Comentários ..... 0xABAC0100 0xABAC0101 0xABAC0102 0xABAC0103 0xABAC0104 0xABAC0105 0000 0000 a variável short int id1 ocupa dois bytes de memória (ou seja 0xABAC0105 e 0xABAC0106): (primeiro byte) 0000 0000 0xABAC0106 0000 1010 (segundo byte) 0000 1010 0xABAC0107 0xABAC0108 0xABAC0109 0xABAC010A 0xABAC010B 1010 1011 primeiro byte do ponteiro (ponteiro de 4 bytes) 0xABAC010C 1010 1100 segundo byte do ponteiro 0xABAC010D 0000 0001 terceiro byte do ponteiro 0xABAC010E 0000 0101 quarto byte do ponteiro 0xABAC010F 0xABAC0110 ... primeiro byte segundo byte APOSTILA : LINGUAGENS DE PROGRAMAÇÃO C E C++ Angelo Passaro Página VI.3 Segunda revisão / 2002 VI.A. Declaração de uma variável do tipo ponteiro VI.A A declaração de um ponteiro apresenta a seguinte sintaxe: Sintaxe: tipo * ident_variavel; O uso do símbolo asterisco (*) na declaração é fundamental. Neste caso, ele atua como um indicador de que o identificador a seguir é um ponteiro para o tipo especificado (esta função do símbolo é diferente de seu uso como operador binário aritmético (de multiplicação – seção III.B.2) ou como operador unário de indireção. (seção III.A.5 e seção VI.B) Pode-se declarar uma variável ponteiro para qualquer tipo permitido em C e C++ (tipos intrínsecos e tipos derivados), inclusive tipos novos definidos por typedef (seçao I.B.3). Ponteiros para funções também podem ser declarados e definidos. Exemplos: // usa typedef para definição de um novo tipo de dado composto por um vetor de dois caracteres typedef char[2] WORD; int * p_var; // lê-se : p_var é o identificador de uma variável do tipo ponteiro para inteiros, // ou, p_var é um ponteiro para uma variável do tipo inteiro, ou para um // array de inteiros double * p_dvar; // p_dvar é um ponteiro para uma ou mais variáveis do tipo double // armazenadas em seqüência na memória(um array de doubles) Matriz * po_matriz; // po_matriz é um ponteiro para um ou mais objetos do tipo Matriz armazenados // em seqüência na memória (um vetor de Matrizes) WORD * p_word; // p_word é um ponteiro para uma variável do tipo WORD (ver typedef acima) VI.B. Operadores de Ponteiros VI.B São dois os operadores especiais para ponteiros definidos nas linguagens C e C++ . operador significado & O operador unário, aplicado à esquerda do operando, fornece o endereço, na memória, no qual o seu operando está armazenado. O valor obtido com a aplicação desse ponteiro pode ser associado somente a uma variável do tipo ponteiro. identificador da variável tipo válido (intrínseco ou derivado) na declaração, identifica uma variável ponteiro APOSTILA : LINGUAGENS DE PROGRAMAÇÃO C E C++ Angelo Passaro Página VI.4 Segunda revisão / 2002 * O operador unário de conteúdo é aplicado à esquerda do operando (uma variável ponteiro declarada conforme apresentado na seção VI.A) e fornece o conteúdo armazenado na posição de memória indicada pelo ponteiro. Exemplo: char incorreto = 0; char correto = !incorreto; // observe o uso do operador unário de negação lógica char * p_inc = & incorreto; // define o ponteiro p_inc com a atribuição do endereço de incorreto char * p_cor = & correto; // define o ponteiro p_cor com a atribuição do endereço de correto cout << “\nvalor da variavel incorreto : “ << incorreto; cout << “\nvalor da variavel correto : “ << correto; cout << “\nvalor do conteúdo apontado por p_inc : “ << *p_inc; cout << “\nvalor do conteúdo apontado por p_cor : “ << *p_cor; Observe com cuidado, também, o uso do operador & no exemplo apresentado no início deste capítulo. VI.C. Expressões com ponteiros VI.C No geral, as regras de expressões de ponteiros são as mesmas que a de qualquer outra expressão. Contudo, são impostas restrições quanto ao tipo de operação que pode ser efetuada sobre uma variável ponteiro. VI.C.1.a. ATRIBUIÇÃO VI.C.1.a A uma variável ponteiro pode ser atribuído um endereço: int x; int *p1, *p2; // p1 e p2 são ponteiros p1 = &x; // o endereço da variável x é atribuído à variável ponteiro p1 p2 = p1; // o conteúdo da variável ponteiro p1 (ou seja, o endereço de x) é atribuído à variável // ponteiro p2 VI.C.1.b. ARITMÉTICA São permitidas quatro operações aritméticas com ponteiros, a saber, incremento e decremento unários (++ e --), soma e subtração binários (+ e -). Estas operações são efetuadas de acordo com o tipo de dado com que o ponteiro está relacionado, ou seja, com a quantidade de memória que o tipo de dado ocupa, tabela VI.2. Tabela VI.2 : tipo de dado bytes ocupados número de bytes somados no incremento de 1 (ponteiro++) número de bytes somados no incremento de 5 (ponteiro = ponteiro + 5) char 1 1 1 * 5 = 5 short int 2 2 2 * 5 = 10 APOSTILA : LINGUAGENS DE PROGRAMAÇÃO C E C++ Angelo Passaro Página VI.5 Segunda revisão / 2002 long int 4 4 4 * 5 = 20 double 8 8 8 * 5 = 40 vetor com 10 short int 10 * 2 = 20 20 20 * 5 = 100 Exemplos: short int var = 10; short int *p_vari ; p_vari = &var; p_vari++; double dvar = 3.141592; double * p_vard; p_vard = &dvar; p_vard--; p_vari = p_vari – 8; p_vard = p_vard +2; Exercício: Assuma que as variáveis var e dvar são armazenadas nos endereços 0x00001000 e 0x00001500, respectivamente. Verifique qual o conteúdo dos ponteiros p_vari e p_vard linha a linha do exemplo acima. VI.C.1.c. COMPARAÇÃO ENTRE PONTEIROS É possível comparar logicamente dois ponteiros do mesmo tipo. Normalmente, comparações são utilizadas quando mais de um ponteiro faz referência a um mesmo dado. Exemplo : char * p_p, * p_q; ....... // conjunto de instruções if (p == q) { conjunto de instruções }; VI.D. Arrays e Ponteiros No capítulo V, os arrays foram apresentados como um agrupamento contínuo de um ou mais elementos de um mesmo tipo. Nesse capítulo foram apresentados, também, a forma de fazer acesso aos elementos do array por meio de índices e a forma como arrays são armazenados na memória. Sugere-se que o leitor faça uma revisão da seção V.A antes de continuar a leitura deste capítulo. O uso de ponteiros permite definir um segundo método de acesso a componentes de um array. Um ponteiro para um array é o ponteiro para o primeiro elemento de um array (ou seja, o ponteiro para um array aponta para o primeiro elemento do array). Observe atentamente os exemplos a seguir, acompanhando os comentários. Exemplo: int vet [10] = { 13,12,100,99,1000,999,1313,1212,21,31 }; // define o array identificado por vet int * p_vet = 0; // declara um ponteiro para inteiro, APOSTILA : LINGUAGENS DE PROGRAMAÇÃO C E C++ Angelo Passaro Página VI.6 Segunda revisão / 2002 // inicializando-o com zero; // Sim, um ponteiro pode ser inicializado // com zero, o que normalmente é feito // para indicar que o ponteiro não aponta // para nenhum endereço em especial int b, c; p_vet = &vet [0]; // atribui a p_vet o endereço do inicio do array vet p_vet = vet; // Esta instrução é equivalente à anterior. // Normalmente, o nome do array é um “alias” // (ou sinônimo) para o endereço do array; b = vet [5]; // atribui o sexto elemento de vet à variável b c = *(vet + 5); // atribui o conteúdo apontado por (vet + 5) a c if ( b == c ) cout << “b é igual a c”; else cout << “b e c são diferentes”; Exercícios: 1. Qual o conteúdo apontado por (vet+3) e qual o apontado por (vet+9)? 2. Qual o conteúdo apontado por (vet+10)? 3. Escreva um programa completo em C++ (que seja compilável e que gere um executável) com as instruções do exemplo acima. 4. Escreva um programa para soma dos dez primeiros números naturais armazenando-os primeiramente num array unidimensional. Utilize somente ponteiros para acesso ao array. Exemplo: double dvet [ 3 ] [ 3 ] = { { 13., 12., 100. }, // define o array bidimensional denominado { 99., 1000., 999. }, // dvet { 1313., 1212., 21. } // consultem a seção V.A.4 sobre o }; // armazenamento de arrays na memória. double * p_dvet = 0; // define o ponteiro nulo (ou zero) p_dvet double b, c;p_dvet = &dvet [ 0 ] [ 0 ]; // atribui o endereço do primeiro elemento do // array dvet ao ponteiro p_dvet cout << “conteúdo do sexto elemento de dvet é : “<< *(p_dvet + 5); Exercícios: 1. Escreva um programa completo em C++ (que seja compilável e que gere um executável) com as instruções do exemplo acima. 2. Modifique o programa para imprimir utilizando índices do array fornecidos pelo usuário (faça de duas formas, utilizando acesso por índices e por ponteiros). APOSTILA : LINGUAGENS DE PROGRAMAÇÃO C E C++ Angelo Passaro Página VI.7 Segunda revisão / 2002 VI.E. Structures e Ponteiros Na seção V.B foram apresentados a declaração de structures e operadores de acesso a seus dados membros. Nesta seção, vamos declarar ponteiros para structures e verificar como é feito o acesso a seus dados membros, utilizando o operador de acesso −>. Compare os exemplos a seguir com os apresentados na seção V.B. Para facilitar a compreensão, os primeiros dois exemplos fazem uso das structs data e endereço, respectivamente. O terceiro exemplo envolve o uso das structs data, endereço e pessoa. Aproveite para observar, também, a tabulação efetuada nas instruções de cada bloco. A adoção de tabulações diferentes para indicar instruções em blocos diferentes facilita a visualização da organização do código. Exemplo 1: #include <iostream.h> struct data { unsigned char dia, mes; unsigned short int ano;}; // fim da declaração da struct data void main(void) { // definicao da data com identificador dia_formatura data dia_formatura = {30,2,2000}; //---------------------------------------------------------------------------------------------------- // declaração e inicialização de ponteiro nulo para struct data //---------------------------------------------------------------------------------------------------- data * p_data = 0; // ponteiro para uma struct data // acessando os dados membros da struct data por meio dos ponteiros p_data = & dia_formatura; // atribui a p_data o endereço de dia_formatura // antes desta atribuição p_data não aponta para nada definido unsigned short int a; unsigned char d, m; d = p_data −> dia; m = p_data −> mes; a = p_data −> ano; // alteração dos dados membros da struct data a = 1998; d = 13, m = 4; p_data −> dia = d; p_data −> mes = m; p_data −> ano = a; cout << “\n data atualizada : “<< dia_formatura.dia << “/ “ << dia_formatura.mes << “/ “ << dia_formatura.ano; cout << “\n data atualizada com acesso por ponteiro : “<<p_data −> dia << “/ “ << p_data −> mes << “/ “ << p_data −> ano; APOSTILA : LINGUAGENS DE PROGRAMAÇÃO C E C++ Angelo Passaro Página VI.8 Segunda revisão / 2002 } // fim da função main Exemplo 2: #include <iostream.h> struct endereco { char rua [256]; unsigned int numero; char cidade [30]; unsigned long int cep; } ; // fim da declaração da struct endereco void main(void) { //---------------------------------------------------------------------------------------------------- // definição de structs //---------------------------------------------------------------------------------------------------- endereço end = { “Rua Balalaica”, 12222, “São Paulo”, 12222000}; endereco novo_end = { “Av. BlaBla da Silva”, 000, “Seilá”, 00000000}; //---------------------------------------------------------------------------------------------------- // declaração e inicialização de ponteiro nulo para struct endereço //---------------------------------------------------------------------------------------------------- endereço * p_end = 0; // ponteiro para uma struct endereço // acessando os dados membros da struct endereço por meio dos ponteiros p_end = &end; // atribui o endereço de end para o ponteiro p_end cout << “acesso a struct end por ponteiro : “<<p_end −> rua << “, “ << p_end −> numero << ‘\n’<<p_end −> cidade << ‘\n’ << p_end −> cep; p_end = &novo_end; // atribui o endereço de novo_end para o ponteiro p_end cout << “acesso a struct end por ponteiro : “<<p_end −> rua << “, “ << p_end −> numero << ‘\n’<<p_end −> cidade << ‘\n’ << p_end −> cep; // alteração dos dados membros da struct endereço char r [500] = “Rua X”, cid [500] = “Mogi”; unsigned int n = 123; unsigned long int cep = 01000100; p_end −> numero = n; p_end −> cep = cep; strcpy (p_end −> rua, r); // a função strcpy copia o conteúdo do array APOSTILA : LINGUAGENS DE PROGRAMAÇÃO C E C++ Angelo Passaro Página VI.9 Segunda revisão / 2002 // unidimensional de caracteres r para o membro rua strcpy (p_end −> cidade, cid); // a função strcpy copia o conteúdo do array // unidimensional de caracteres r para o membro rua } // fim da função main Exercício: 1. Qual das structures foi modificada (end ou novo_end)? 2. Implemente a saída de dados utilizando cout para confirmar sua resposta. Exemplo 3: #include <iostream.h> struct endereco { char rua [256]; unsigned int numero; char cidade [30]; unsigned long int cep; } ; // fim da declaração da struct endereco struct data { unsigned char dia, mes; unsigned short int ano;}; // fim da declaração da struct data struct pessoa { char nome [128]; endereco residencia; endereco comercial; data nascimento; }; // fim da declaração da struct pessoa void main(void) { unsigned short int a; unsigned char d, m; //---------------------------------------------------------------------------------------------------- // definição de structs //---------------------------------------------------------------------------------------------------- // definição de uma pessoa com identificador Joao_da_Silva pessoa Joao_da_Silva = { “João José da Silva”, “Av. BlaBla da Silva”, 0, “Seilá”, 0, “Rua 1”, 333, “Seilá”, 01000000, {28,02,1900}}; //---------------------------------------------------------------------------------------------------- // declaração e inicialização de ponteiros nulos para structs //---------------------------------------------------------------------------------------------------- endereço * p_end = 0; // ponteiro para uma struct endereço APOSTILA : LINGUAGENS DE PROGRAMAÇÃO C E C++ Angelo Passaro Página VI.10 Segunda revisão / 2002 data * p_data = 0; // ponteiro para uma struct data pessoa * p_pessoa = 0; // ponteiro para uma struct pessoa // acessando os dados membros da struct data por meio dos ponteiros p_end = & Joao_da_Silva .... residencia; // note que o operador unário de endereço, &, // fornece o endereço do dado membro// endereço, e não o endereço de João da Silva p_pessoa = & Joao_da_Silva ; // aqui, o operador de endereço fornece o // endereço da struct Joao_da_Silva. Observe a // diferença na sintaxe! p_data = & p_pessoa −> nascimento; // o operador de endereço fornece o endereço // da struct data nascimento (dado membro de // pessoa). Observe que, como p_pessoa é um // ponteiro, o acesso ao dado membro é feito // utilizando o operador −> // acessando os dados membros da struct endereço cout << “nome : “ << p_pessoa −> nome << ‘\n’; cout << “endereço residencial : “ << p_end −> rua << “, “ << p_end −> numero << ‘\n’<< p_end −> cidade << ‘\n’ << p_end −> cep << ‘\n’; cout << “endereço residencial : “ << Joao_da_Silva.residencia.rua << “, “ << Joao_da_Silva.residencia. numero << ‘\n’ << Joao_da_Silva.residencia. cidade << ‘\n’ << Joao_da_Silva.residencia. cep << ‘\n’; cout << “data de nascimento : “<< p_data −> dia << “/ “ << p_data −> mes << “/ “ << p_data −> ano; p_end = & p_pessoa −> comercial; cout << “\n\nendereço comercial : “ << p_end −> rua << “, “ << p_end −> numero << ‘\n’<< p_end −> cidade << ‘\n’ << p_end −> cep << ‘\n’; } // fim da função main Exercicio: 1. Modifique o programa do exemplo 3 para incluir a possibilidade do usuário, após ler os dados de pessoa, alterar o número e o CEP, tanto do endereço comercial como o da residência. Faça experimentos com ponteiros (consulte os exemplos anteriores neste capítulo e nos capítulos IV e V, se necessário). 2. Implemente também a possibilidade do usuário modificar a data de nascimento da pessoa. Observe que a declaração de ponteiros para struct segue exatamente os modelos apresentados nas seções anteriores. VI.F. Array de ponteiros APOSTILA : LINGUAGENS DE PROGRAMAÇÃO C E C++ Angelo Passaro Página VI.11 Segunda revisão / 2002 Vamos lembrar, inicialmente, que arrays são agrupamentos contínuos de dados de um mesmo tipo. Isto significa que cada elemento do array ocupa o mesmo número de bytes na memória. Em muitas aplicações, particularmente quando se trabalha com seqüência de caracteres (strings), o tamanho dos elementos individuais que comporiam o array podem assumir tamanhos variados. Uma forma de usar arrays neste caso, apesar da possível diferença de tamanho dos elementos, é identificar, dentre todos os elementos, aquele que apresenta o maior comprimento, em bytes, e reservar o mesmo espaço para todas as strings, mesmo que estas não venham a ocupar todo o espaço colocado à sua disposição. Exemplo: Queremos armazenar 3 strings : str1 = “Paulo VI”, str2 = “João Paulo II “, str3 = “Pio XI”, num array. A representação do array bidimensional será: ‘P’ ‘a’ ‘u’ ‘l’ ‘o’ ‘ ’ ‘V’ ‘I’ ‘\0’ ‘J’ ‘o’ ‘a’ ‘o’ ‘ ’ ‘P’ ‘a’ ‘u’ ‘l’ ‘o’ ‘ ’ ‘I’ ‘I’ ‘\0’ ‘P’ ‘i’ ‘o’ ‘ ’ ‘X’ ‘I‘ ‘\0’ #include <iostream.h> // é necessário incluir este cabeçalho para utilizar cout << #include <string.h> // é necessário incluir este cabeçalho para utilizar a função strcpy void main (void) { char str1[ ] ="Paulo VI"; // o compilador dimensiona, estaticamente, o array unidimen- // sional quando a inicialização ocorre com a declaração char str2[ ] ="João Paulo II"; char str3[ ] ="Pio XI"; char papas [ 3 ] [ 14 ] ; // declarado um array bidimensional do tipo char de 3 linhas por 14 // colunas strcpy (papas [0], str1); strcpy (papas [1], str2); strcpy (papas [2], str3); cout << "tamanho reservado a cada array de strings é 14 bytes\n"; // observe a seguir o uso do operador sizeof (seção III.A.8) cout << "tamanho realmente ocupado pela primeira string e : "<< sizeof ( str1 ) << "bytes\n"; cout << "tamanho realmente ocupado pela segunda string e : "<< sizeof ( str2 ) << "bytes\n"; cout << "tamanho realmente ocupado pela terceira string e : "<< sizeof ( str3 ) << "bytes\n"; cout << "primeira string : "<< papas[0] << "\n"; cout << "segunda string : "<< papas[1] << "\n"; cout << "terceira string : "<< papas[2] << "\n"; } Esta abordagem provoca um desperdício de memória que pode ser evitado com o uso de ponteiros, como apresentado no exemplo a seguir. Exemplo: #include <iostream.h> // é necessário incluir este cabeçalho para utilizar cout << #include <string.h> // é necessário incluir este cabeçalho para utilizar a função strcpy APOSTILA : LINGUAGENS DE PROGRAMAÇÃO C E C++ Angelo Passaro Página VI.12 Segunda revisão / 2002 void main (void) { char str1[ ] ="Paulo VI"; // o compilador dimensiona, estaticamente, o array unidimen- // sional quando a inicialização ocorre com a declaração char str2[ ] ="João Paulo II"; char str3[ ] ="Pio XI"; char *papas [ 3 ] ; // declarado um array unidimensional de 3 ponteiros para char. Preste // atenção na interpretação!! papas[0] = str1; // o endereço de str1 é armazenado em papas[0]. Note que papas[0] é // um ponteiro e str1 também é (str1 é equivalente a &str1[0]) papas[1] = str2; // str2 é equivalente a &str2[0] papas[2] = str3; // str3 é equivalente a &str3[0] cout << "tamanho reservado a cada array de strings é 14 bytes\n"; // observe a seguir o uso do operador sizeof (seção III.A.8) cout << "tamanho realmente ocupado pela primeira string e : "<< sizeof ( str1 ) << "bytes\n"; cout << "tamanho realmente ocupado pela segunda string e : "<< sizeof ( str2 ) << "bytes\n"; cout << "tamanho realmente ocupado pela terceira string e : "<< sizeof ( str3 ) << "bytes\n"; cout << "primeira string : "<< papas[0] << "\n"; cout << "segunda string : "<< papas[1] << "\n"; cout << "terceira string : "<< papas[2] << "\n"; } // fim da função main VI.G. Indireção múltipla Como já foi comentado anteriormente, variáveis do tipo ponteiro podem apontar para elementos que também são ponteiros. Isto é denominado indireção múltipla (do inglês multiple indirection). Teoricamente, não existe limites para o número de indireções, mas cada nível de indireção traz um maior nível de complexidade ao programa. Compare o esquema de armazenamento na memória apresentado na tabela VI.3 com o esquema apresentado na tabela VI.1. Exemplo : // declaração de um ponteiro que aponta para um ponteiro que aponta para um valor inteiro // (dois níveis de indireção) int ** p_p_id1; int * p_id1; int id1 = 10; p_id1 = & id1; p_p_id1 = & p_id1; // declaração de um ponteiro que aponta para um ponteiro que aponta para uma // struct pessoa (seção V.B.I) (dois níveis de indireção) pessoa ** p_p_pessoa; APOSTILA : LINGUAGENS DE PROGRAMAÇÃO C E C++ Angelo Passaro Página VI.13 Segunda revisão / 2002 A tabela VI.3 ilustra o que ocorre na memória quando da indireção em dois níveis de um inteiro. Os dados são os mesmos apresentados na tabela VI.1 . Tabela VI.3 : Representaçãodo conteúdo da memória entre os endereços 0x100 e 0x115 endereço de memória conteúdo da memória comentários ..... 0xABAC0100 1010 1011 Neste endereço está armazenada a variável p_p_id1 (AB) 0xABAC0101 1010 1100 ou seja, o ponteiro para um ponteiro que aponta para um inteiro (AC) 0xABAC0102 0000 0001 (01) 0xABAC0103 0000 1011 (0B) 0xABAC0104 0xABAC0105 0000 0000 a variável short int id1 ocupa dois bytes de memória (ou seja 0xABAC0105 e 0xABAC0106): (primeiro byte) 0000 0000 0xABAC0106 0000 1010 (segundo byte) 0000 1010 0xABAC0107 0xABAC0108 0xABAC0109 0xABAC010A 0xABAC010B 1010 1011 primeiro byte do ponteiro (ponteiro de 4 bytes) (AB) 0xABAC010C 1010 1100 segundo byte do ponteiro (AC) 0xABAC010D 0000 0001 terceiro byte do ponteiro (01) 0xABAC010E 0000 0101 quarto byte do ponteiro (05) 0xABAC010F ... VI.H. Saída formatada de ponteiros Para apresentar uma saída formatada em um dispositivo de saída padrão (monitor de vídeo), pode-se usar a função padrão do ANSI C printf. Observe a constante literal para definição do formato de impressão dos ponteiros do exemplo apresentado à seção VI.C.1.a: printf ((“p1 = %p, p2 = %p, \&x = % p” , p1, p2, &x); Neste caso, %p especifica que os parâmetros 1, 2 e 3 devem ser apresentados no formato de ponteiros.
Compartilhar