CES-10 INTRODUÇÃO À COMPUTAÇÃO Capítulo IX Ponteiros Capítulo IX – Ponteiros 9.1 – Introdução 9.2 – Relação entre ponteiros e variáveis indexadas 9.3 – Alocação dinâmica de memória 9.4 – Variáveis indexadas, ponteiros e estruturas como parâmetros e elementos de retorno 9.5 – Subprogramas como parâmetros 9.6 – Encadeamento de estruturas 9.1 – Introdução 9.1.1 – Constantes e variáveis do tipo ponteiro As variáveis dos programas apresentados nos capítulos anteriores são usadas para guardar e manipular valores de determinados tipos Endereços também podem ser manipulados em C Sendo a uma variável declarada em um programa, &a é seu endereço Ponteiros são endereços Assim como existem constantes e variáveis do tipo int, float, char, cadeias de caracteres, vetores, matrizes e estruturas também existem constantes e variáveis do tipo ponteiro O endereço de uma variável a declarada num bloco qualquer permanece o mesmo durante a execução do bloco Então &a é uma constante do tipo ponteiro Variável do tipo ponteiro armazena um endereço que pode ser alterado durante a execução do programa Variáveis-ponteiros podem guardar endereços de outras variáveis Diz-se que elas apontam para essas outras variáveis Na declaração de uma variável-ponteiro, deve-se especificar o tipo das variáveis para as quais ela pode apontar Exemplo: na declaração float *p; p é uma variável-ponteiro destinada a guardar endereços de variáveis do tipo float, ou a apontar para elas Abreviadamente, p é um ponteiro para o tipo float Exemplo: seja trecho de programa: int c = 237, b = 15, *q; double a = 13.5, *p; p = &a; q = &b; printf ("Endereco(p) = %d\n", &p); printf (" p = %d\n", p); printf ("Endereco(a) = %d\n", &a); printf ("Endereco(q) = %d\n", &q); printf (" q = %d\n", q); printf ("Endereco(b) = %d\n", &b); printf ("Endereco(c) = %d\n", &c); printf (" a = %g\n", a); printf (" b = %d\n", b); printf (" c = %d\n", c); Resultado Endereco(p) p Endereco(a) Endereco(q) q Endereco(b) Endereco(c) a b c = = = = = = = = = = 1638204 1638208 1638208 1638216 1638220 1638220 1638224 13.5 15 237 Com este resultado, pode-se desenhar o mapa da memória a seguir int c = 237, b = 15, *q; double a = 13.5, *p; p = &a; q = &b; Endereco(p) p Endereco(a) Endereco(q) q Endereco(b) Endereco(c) a b c Resultado = = = = = = = = = = 1638204 1638208 1638208 1638216 1638220 1638220 1638224 13.5 15 237 1638204 1638208 p 13.5 a 1638220 q 15 b 237 c 1638208 1638216 1638220 1638224 int c = 237, b = 15, *q; double a = 13.5, *p; p = &a; q = &b; 1638204 p 13.5 a 1638220 q 15 b 237 c 1638208 &a é uma constante-ponteiro para double &b é uma constante-ponteiro para int 1638208 1638216 1638220 1638224 int c = 237, b = 15, *q; double a = 13.5, *p; p = &a; q = &b; 1638204 p 13.5 a 1638220 q 15 b 237 c 1638208 q é um ponteiro para o tipo int p é um ponteiro para o tipo double p recebe o endereço de a q recebe o endereço de b 1638216 p aponta para a q aponta para b 1638220 a é apontada por p b é apontada por q 1638208 1638224 int c = 237, b = 15, *q; double a = 13.5, *p; p = &a; q = &b; 1638204 1638208 p 13.5 a 1638220 q 15 b 237 c 1638208 Endereços, isto é ponteiros, ocupam 4 bytes Não é recomendável fazer: p = &b , pois 1638216 b é um int e p é um ponteiro para o tipo double 1638220 Alguns compiladores não aceitam isso 1638224 int c = 237, b = 15, *q; double a = 13.5, *p; p = &a; q = &b; 1638204 13.5 15 b ou simplesmente: p q 13.5 a 1638220 q 15 b 237 c a 1638216 q p 1638208 Duas formas de representação gráfica: p 1638208 1638220 13.5 a 15 b 1638224 Endereços são números inteiros não-negativos Então, esses números podem ser atribuídos às variáveis ponteiros, desde que sejam convertidos para endereços usando-se fator de conversão Exemplo: seja a declaração double *p; muitos compiladores não permitem p = 48237; O lado esquerdo deveria receber um endereço e não um inteiro No entanto, a seguinte atribuição é aceita: p = (double *) 48237; O inteiro 48237 é convertido no endereço 48237 Muitos compiladores também não aceitam atribuição entre ponteiros de tipos diferentes Exemplo: pelas declarações double *p; int *q; muitos compiladores não aceitam p = q; É necessário fazer p = (double *)q; nem e q = p; q = (int *)p; Se alguns compiladores aceitam certas irregularidades, mas outros não, para melhorar portabilidade, é bom não cometê-las A palavra ponteiro poderá ser usada tanto para variáveis como para valores do tipo ponteiro Usando ponteiros, o endereço zero de memória é considerado de forma muito especial Em C há uma constante simbólica para ele: NULL As seguintes atribuições são equivalentes: p = 0; e p = NULL; Diz-se que p está aterrado, ou então que aponta para lugar nenhum É diferente de dizer que p está indefinido, pois tal lugar é perfeitamente definido Aqui, lugar nenhum é diferente de nenhum lugar Representações gráficas para o aterramento de ponteiros: p p p • p NULL 9.1.2 – Acesso ao local apontado por um ponteiro Seja a declaração: int a, b, *p; p O local apontado por p é referenciado por *p a *p b *p Executando-se p = &a; então *p passa a coincidir com a Depois, executando-se p = &b; coincidir com b Seja a declaração int a, *p = &a; então *p passa a Correto: É errado pdizer: é ponteiro local apontado porcom inicializado p recebe o o endereço endereço dede aa Exemplo: Seja o seguinte trecho de programa: int a, b = 2, *p; p = &a; *p = 1; b = *p; a 1 Inicialmente Prosseguindo b 21 p Exemplo: Seja a seguinte declaração: int a = 2, b = 5, *p = &a, *q = &b; Inicialmente: Voltando Fazendo Fazendo*p àp situação ==q; *q; inicial: a 52 p b 5 q Apesar dos conteúdos de *p e *q serem iguais, o conteúdo de p é diferente do conteúdo de q (*p == *q) e (p ≠ q) Exemplo: Seja o seguinte trecho de programa: int *p, a; p = (int *)134; a = *p; printf ("*p = %d", a); Erro de execução: p Região protegida pelo sistema operacional a O endereço 134 é protegido pelo sistema operacional O comando a = *p tenta copiar o valor ali guardado RAM 134 O endereço zero também é protegido pelo sistema operacional Portanto, ao se aterrar um ponteiro, a simples referência ao local apontado por ele gera o mesmo tipo de erro Então a sequência de comandos p = NULL; a = *p; não pode ser usada Sejam p e q dois ponteiros quaisquer A divisão entre os conteúdos dos locais apontados por eles não pode ser expressa por *p/*q A sequência de caracteres “/*” inicia um comentário Deve-se deixar pelo menos um espaço em branco no meio dessa sequência: *p/ *q Nos atuais ambientes de programação esse problema é facilmente detectado Ali os comentários são escritos com tipo e coloração das letras distintos do resto do texto dos programas Em ambientes mais antigos, isso já causou muitos atrasos na programação As principais utilidades dos ponteiros são: Alocação dinâmica de variáveis indexadas Passagem por referência de argumentos a parâmetros Encadeamento de estruturas Os dois primeiros itens eram realizados não por intermédio de ponteiros nas linguagens de programação antes de C Para o terceiro sim, tais linguagens já usavam ponteiros Aliás, o encadeamento de estruturas foi a principal razão para a criação de ponteiros Ele é abordado no último tópico deste capítulo e usado intensamente em Estruturas de Dados Capítulo IX – Ponteiros 9.1 – Introdução 9.2 – Relação entre ponteiros e variáveis indexadas 9.3 – Alocação dinâmica de memória 9.4 – Variáveis indexadas, ponteiros e estruturas como parâmetros e elementos de retorno 9.5 – Subprogramas como parâmetros 9.6 – Encadeamento de estruturas 9.2 – Relação entre Ponteiros e Variáveis Indexadas 9.2.1 – O ponteiro-nome de variável indexada Em C, diferentemente das linguagens anteriores, há uma forte relação entre ponteiros e variáveis indexadas O nome de uma variável indexada é o endereço do primeiro de seus elementos É uma constante-ponteiro apontando para seu elemento zero Não tem um local exclusivo na memória O ponteiro-nome de variável indexada aponta para um local fixo (elemento de índice zero) Então ele tem valor constante durante a execução do programa (ponteiro de valor fixo) Não pode ser alterado como um ponteiro comum Seja a declaração int A[8]; Representação gráfica de A: int A[8]; O nome A não tem local exclusivo; &A é o endereço do início do vetor A na memória O valor de A (ou seja, A, simplesmente) é o endereço de A[0] (&A[0]) A escrita do valor do local apontado por A (ou seja, de *A) resulta no conteúdo de A[0] O nome de uma variável indexada pode ser atribuído a um ponteiro de mesmo tipo Exemplo: seja o seguinte trecho de programa: int i, A[8], B[5], *p; p = A; p = B; Inicialmente: Após p = B; A; A Proibidos: p tem localAexclusivo = p; B = p; A e B não têm A = B; B = A; A = &i; B = &i; A[0] A[1] A[2] A[3] A[4] A[5] A[6] A[7] p ? B p tornou-se equivalente a B: A: B[0] B[1] B[2] B[3] B[4] p[0] B[0], A[0], p[1] A[1], B[1], ... , p[7] B[4] p[4] A[7] *A A[0] e *B B[0] p se tornou variável indexada 9.2.2 – Aritmética de endereços e ponteiros Seja a seguinte declaração: int *p, i; A expressão p+i é o endereço do iésimo inteiro após o inteiro apontado por p (p+i = &p[i]) *p *(p+1) *(p+i) p p[i] p[0] p[1] Seja a seguinte declaração: int A[10], i; Analogamente, A+i = &A[i] e *(A+i) A[i] *A *(A+1) *(A+i) A A[0] A[1] A[i] Exemplo: seja o seguinte p = 1638204; &A[0] = 1638204; de programa: q trecho = 1638212; &A[1] = 1638212; p A[0] r = 1638220; &A[2] = 1638220; q-p = 1; r-p = 2; Diferenças de índices endereços ender(q)-ender(p) = 8; ender(r)-ender(p) = 16; q No vídeo A[1] double *p, *q, *r, A[3]; p = A; q = p+1; r = p+2; r printf ("p = %d; &A[0] = %d;", p, &A[0]); A[2] printf ("\nq = %d; &A[1] = %d;", q, &A[1]); printf ("\nr = %d; &A[2] = %d;", r, &A[2]); printf ("\n\nq-p = %d;\nr-p = %d;", q-p, r-p); printf("\n\nender(q)-ender(p) = %d;\nender(r)-ender(p) = %d;", (int)q-(int)p, (int)r-(int)p); Relações entre ponteiros e variáveis indexadas e entre subscritos e o operador unário ‘*’ possibilitam várias alternativas para realizar certas operações Por exemplo, considerando-se a seguinte declaração: int i, A[10] = {1,2,3,4,5,6,7,8,9,10}, *p, soma; A soma dos elementos de A pode ser feita por: 1) 2) 3) 4) 5) for for for for for (soma (soma (soma (soma (soma = = = = = 0, i = 0; 0, i = 0; 0, p = A; 0, p = A, 0,p = A,i i i p i = < 10; i++) soma += A[i]; < 10; i++) soma += *(A+i); < &A[10]; p++) soma += *p; = 0; i < 10; i++) soma += p[i]; 0; i < 10; i++) soma += *(p+i);