Baixe o app para aproveitar ainda mais
Prévia do material em texto
1 LINGUAGEM E TÉCNICA DE PROGRAMAÇÃO I Profa. Gisele Busichia Baioco gisele@ceset.unicamp.br Algoritmos Estruturados e Linguagem de Programação Estruturada Ponteiros em C Parte I – Conceitos Básicos 1 Introdução Uma das mais poderosas características oferecidas pela linguagem C é o uso de ponteiros. Ponteiros proporcionam o acesso a variáveis sem referenciá-las diretamente, ou seja, por meio do endereço de memória das variáveis. Algumas razões para o uso de ponteiros: fornecem maneiras pelas quais as funções em C podem modificar os argumentos que recebem; usá-los no lugar de vetores, matrizes e strings para aumentar a efeciência; para alocação dinâmica de memória; para criar estruturas de dados complexas, como listas encadeadas, pilhas, árvores, etc. Além dos ponteiros serem uma das características mais fortes de C, também é a mais perigosa. O uso incorreto de ponteiros provoca desde erros difíceis de serem encontrados até uma queda no sistema. 2 O que são Ponteiros? Uma variável do tipo int guarda números inteiros. As variáveis do tipo float guardam números de ponto flutuante. Uma variável do tipo char guarda caracteres. Um ponteiro é uma variável que guarda endereços de memória. Ou seja, uma variável do tipo ponteiro contém o endereço de memória de uma outra variável e pode-se dizer que a primeira variável aponta para a segunda. Esquematicamente tem-se: Memória do Computador Endereço Conteúdo 1000 1003 1001 1002 1003 1004 1005 1006 ... ... 2 Assim, usado um ponteiro é possível acessar o conteúdo de memória de uma variável sem referenciá-la diretamente, o que é conhecido como endereçamento indireto. Além do endereçamento indireto, um ponteiro em C também pode ser utilizado em um programa para alocar memória em tempo de execução, o que é conhecido como alocação dinâmica de memória. Essas duas possibilidades de utilização de ponteiros serão abordadas com detalhes no decorrer deste texto. 3 Declarando e Utilizando Variáveis Ponteiro Assim como todas as variáveis, uma variável ponteiro também tem tipo. Em C, quando se declara uma variável ponteiro, deve-se informar para que tipo de variável irá apontar. Por exemplo, um ponteiro int aponta para uma variável do tipo inteiro, isto é, guarda o endereço de um inteiro. Sintaxe de declaração de variáveis ponteiro em C: tipo-de-dado *nome; onde: tipo-de-dado: é um tipo de dado válido em C; nome: é um identificador válido para a variável ponteiro. É o asterisco (*) que faz o compilador saber que a variável não vai armazenar um valor, mas sim um endereço de memória que contenha um valor do tipo especificado. Exemplos de declarações: int *p; /* declara um ponteiro para o tipo inteiro */ char *temp, *p2; /* declara dois ponteiros para o tipo caractere */ Os ponteiros p, temp e p2 ainda não foram inicializados (como toda variável do C que é apenas declarada). Isso significa que eles apontam para um lugar indefinido da memória. Esse lugar pode estar, por exemplo, na porção da memória reservada ao sistema operacional do computador. Usar o ponteiro nessas circunstâncias pode levar a um travamento do computador. Desse modo, um ponteiro deve ser inicializado (apontado para algum lugar conhecido) antes de ser usado. Para atribuir um valor a um ponteiro recém-criado a fim de inicializá-lo, bastaria igualá-lo a um valor de memória. Mas, como saber a posição na memória de uma variável do programa? Seria muito difícil saber o endereço de cada variável usada em um programa, mesmo porque esses endereços são determinados pelo compilador (em tempo de compilação) e realocados na execução. Pode-se então deixar que o compilador faça esse trabalho. Para saber o endereço de uma variável basta usar o operador &. Por exemplo: int cont; int *p; cont = 10; p = &cont; /* obtém o endereço de cont e atribui para p */ Nesse exemplo declarou-se uma variável do tipo inteiro cont e uma variável ponteiro para inteiro p. Em seguida atribuiu-se o valor 10 para cont. A expressão &cont fornece o endereço da variável cont, o qual é armazenado em p. Nesse momento, p está inicializado e pode, então, ser utilizado. 3 Deve-se observar no exemplo anterior que o valor de cont não foi alterado, ou seja, cont continua com o conteúdo 10. Pode-se, então, alterar o valor de cont usando p. Para tanto usa-se o operador *. Considerando o exemplo anterior, no momento em que se executa a instrução: p = &cont; A expressão *p passa a ser equivalente à própria variável cont. Isso significa que, pode-se alterar o valor de cont para 12, basta fazer: *p = 12; Observação: O símbolo utilizado para multiplicação e o símbolo que obtém o valor armazenado no endereço referenciado por ponteiros são iguais (*). Apesar disso, esses operadores não tem nenhuma relação entre si. Os operadores de ponteiros & e * têm precedência mais alta que os operadores aritméticos, exceto o menos unário, com o qual têm a mesma precedência. Exemplos do uso de variáveis ponteiro: #include <stdio.h> main() { int num, valor; int *p; num = 55; p = # /* Obtém o endereço de num */ valor = *p; /* a variável valor recebe o mesmo valor de num de maneira indireta */ printf("Valor = %d\n", valor); printf("Endereco para onde o ponteiro aponta: %p\n", p); printf("Valor da variavel apontada: %d\n", *p); } Deve-se observar que o código %p usado na função printf() indica que deve imprimir um endereço (em hexadecimal). #include <stdio.h> main() { int num,*p; num = 55; p = # /* Obtém o endereço de num */ printf("Valor inicial: %d\n", num); *p = 100; /* Altera o valor de num de maneira indireta */ printf("Valor final: %d\n", num); } 4 Expressões com Ponteiros Em geral, expressões que envolvem ponteiros obedecem as mesmas regras das expressões em C. 4 4.1 Atribuições com Ponteiros Considerando dois ponteiros p1 e p2 pode-se atribuir o conteúdo de p1 a p2 fazendo p1 = p2. A atribuição p1 = p2 faz com que p1 aponte para o mesmo endereço que p2. Deve- se observar que se fosse necessário fazer com que a variável apontada por p1 tenha o mesmo conteúdo da variável apontada por p2 o comando seria *p1=*p2. Por exemplo, o programa seguinte exibe o endereço de memória de x (em hexadecimal) por meio do ponteiro p2: #include <stdio.h> main() { int x; int *p1, *p2; p1 = &x; p2 = p1; /* atribui p1 para p2 */ printf("%p %p %p",&x, p1, p2); /* imprime 3 vezes o endereço de memória de x */ } 4.2 Aritmética com Ponteiros Em C, pode-se fazer apenas duas operações aritméticas em ponteiros: incremento (+) e decremento (-). Cada vez que o computador incrementa (ou decrementa) um ponteiro, ele aponta para o endereço de memória do próximo elemento (ou elemento anterior) de seu tipo. Por exemplo, quando o computador incrementar um ponteiro do tipo char, seu endereço será aumentado em 1 (o tipo char ocupa 1 byte na memória); porém, quando o computador incrementar um ponteiro do tipo int, seu endereço será aumentado em 2 (o tipo int ocupa 2 bytes de memória). Esquematicamente tem-se: char *c = 3000; Memória do Computador Endereço Conteúdo c 3000 c + 1 3001 c + 2 3002 c + 3 3003 c + 4 3004 c + 5 3005 ... ... int *i = 3000; Memória do Computador Endereço Conteúdo i 3000 3001 i + 1 3002 3003 i + 2 3004 3005 ... ... 5 Supondo que p é um ponteiro, as operações são escritas como: p++; /* ou p = p + 1 */ p--; /* ou p = p – 1 */ p = p + 5; /* ou p += 5 */ Mais uma vez vale lembrar queas operações são com os ponteiros e não com o conteúdo das variáveis para as quais eles apontam. Por exemplo, para incrementar o conteúdo da variável apontada pelo ponteiro p, faz-se: (*p)++; /* ou *p += 1 ou *p = *p + 1 */ Caso seja necessário usar o conteúdo do ponteiro 15 posições adiante, faz-se: *(p+15) Exemplos de operações válidas com ponteiros em C: 1) se p aponta para a variável x do tipo inteiro, então *p pode ocorrer em qualquer contexto em que x possa ocorrer, por exemplo: y = *p + 1; /* atribui a y o valor de x e soma um */ d = sqrt((double)*p); /* atribui a d a raiz quadrada de x, o qual é convertido em um double antes de ser usado por sqrt */ 2) referências a ponteiros podem ocorrer do lado esquerdo de atribuições. Por exemplo, tendo um ponteiro p que aponta a variável x do tipo inteiro, então: p = &x; /* p aponta para x */ *p = 0; /* atribui 0 a x */ (*p)++ /* incrementa o valor de x */ Entretanto, existem operações que não podem ser realizadas com ponteiros. Por exemplo, não se pode dividir ou multiplicar ponteiros, adicionar dois ponteiros, adicionar ou subtrair valores do tipo float e double de ponteiros. 4.3 Comparações entre ponteiros Pode-se comparar dois ponteiros usando operadores relacionais (<, <=, >, >=, ==, !=). Mas que informação é obtida quando compara-se dois ponteiros? Bem, em primeiro lugar, pode-se saber se dois ponteiros são iguais ou diferentes (== e !=), ou seja, se apontam ou não para o mesmo endereço de memória. No caso de operações do tipo <, <=, >, >= compara-se qual ponteiro aponta para uma posição mais alta (ou mais baixa) na memória. A comparação entre dois ponteiros se escreve como a comparação entre outras duas variáveis quaisquer, por exemplo: p1 > p2 6 5 Exercícios de Fixação 1) Explique a diferença entre: p++; (*p)++; *(p++); 2) Explique o significado de: *(p+10) 3) Explique o que você entendeu sobre comparação entre ponteiros. 4) Qual o valor de y no final do programa? Tente primeiro descobrir e depois verifique no computador o resultado. #include <stdio.h> main() { int y, *p, x; y = 0; p = &y; x = *p; x = 4; (*p)++; x--; (*p) += x; printf ("y = %d\n", y); }
Compartilhar