Diferenças
Aqui você vê as diferenças entre duas revisões dessa página.
Ambos lados da revisão anterior Revisão anterior Próxima revisão | Revisão anterior | ||
c:ponteiros [2024/10/01 17:16] – maziero | c:ponteiros [2024/10/01 17:33] (atual) – maziero | ||
---|---|---|---|
Linha 1: | Linha 1: | ||
+ | ====== Ponteiros ====== | ||
+ | |||
+ | Em um programa, cada variável tem um **endereço**, | ||
+ | |||
+ | Variáveis do tipo **ponteiro** armazenam **endereços** de memória de outras variáveis; em outras palavras, ponteiros são variáveis que **referenciam** outras variáveis. Ponteiros podem ser bem úteis em determinadas situações, | ||
+ | |||
+ | <note tip> | ||
+ | **Ponteiro** é uma variável cujo conteúdo é um **endereço** na memória. | ||
+ | </ | ||
+ | |||
+ | ===== Declaração ===== | ||
+ | |||
+ | Um ponteiro é declarado através do modificador '' | ||
+ | |||
+ | < | ||
+ | |||
+ | Exemplo: | ||
+ | |||
+ | <code c> | ||
+ | int *pi ; // pi é um ponteiro para inteiros | ||
+ | float *pf ; // pf é um ponteiro para floats | ||
+ | char *pc ; // pc é um ponteiro para caracteres | ||
+ | </ | ||
+ | |||
+ | No exemplo acima, '' | ||
+ | |||
+ | <note important> | ||
+ | É importante lembrar que o ponteiro também é uma variável: ele tem **tipo**, **tamanho**, | ||
+ | </ | ||
+ | |||
+ | ===== Uso ===== | ||
+ | |||
+ | Ponteiros são usados sobretudo de três formas: | ||
+ | |||
+ | * **Atribuir**: | ||
+ | * **Ler**: ler o valor armazenado no ponteiro | ||
+ | * **Desreferenciar**: | ||
+ | |||
+ | Eis um exemplo mais elaborado dessas operações: | ||
+ | |||
+ | <code c ponteiros.c> | ||
+ | #include < | ||
+ | |||
+ | int main () | ||
+ | { | ||
+ | int *p ; // ponteiro para inteiros | ||
+ | int a = 231 ; | ||
+ | int b = 7680 ; | ||
+ | |||
+ | printf ("& | ||
+ | printf ("& | ||
+ | printf ("& | ||
+ | |||
+ | printf ("p vale %p\n", p) ; // valor de p (leitura) | ||
+ | |||
+ | p = &a ; // atribuir valor a p | ||
+ | printf ("p vale %p\n", p) ; // ler valor de p | ||
+ | printf ("*p vale %d\n", *p) ; // desreferenciar p | ||
+ | |||
+ | p = &b ; | ||
+ | printf ("p vale %p\n", p) ; | ||
+ | printf ("*p vale %d\n", *p) ; | ||
+ | |||
+ | *p = 500 ; // desreferenciar p | ||
+ | printf ("b vale %d\n", b) ; | ||
+ | |||
+ | return 0 ; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | A execução do código acima gera a seguinte saída ('' | ||
+ | |||
+ | &a vale 0x7ffe2b852890 | ||
+ | &b vale 0x7ffe2b852894 | ||
+ | &p vale 0x7ffe2b852898 | ||
+ | | ||
+ | p vale (nil) | ||
+ | | ||
+ | p vale 0x7ffe2b852890 | ||
+ | *p vale 231 | ||
+ | | ||
+ | p vale 0x7ffe2b852894 | ||
+ | *p vale 7680 | ||
+ | | ||
+ | b vale 500 | ||
+ | |||
+ | A tabela a seguir mostra o conteúdo da memória em diversos momentos da execução do código: | ||
+ | |||
+ | ^ variável ^ a ^ b ^ p ^ | ||
+ | | endereço | 0x7ffe2b852890 | 0x7ffe2b852894 | 0x7ffe2b852898 | | ||
+ | | valor inicial | ||
+ | | valor após '' | ||
+ | | valor após '' | ||
+ | | valor após '' | ||
+ | |||
+ | Observe que os endereços das variáveis não mudam, apenas seus valores. | ||
+ | |||
+ | ===== Ponteiros nulos ===== | ||
+ | |||
+ | Um ponteiro nulo é aquele que aponta para nada, ou seja nenhum endereço válido. A macro '' | ||
+ | |||
+ | A tentativa de desreferenciar um ponteiro nulo resulta em **erro de acesso à memória**, que geralmente leva à interrupção da execução com uma mensagem de // | ||
+ | |||
+ | <code c ptr-errado.c> | ||
+ | #include < | ||
+ | |||
+ | int main () | ||
+ | { | ||
+ | int *p ; | ||
+ | |||
+ | // na linha abaixo, qual o valor apontado por p? | ||
+ | printf ("p vale %p e *p vale %d\n", p, *p) ; | ||
+ | |||
+ | return 0 ; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | A forma correta de tratar esse problema é **testar cada ponteiro antes de usá-lo**, como mostra o exemplo abaixo: | ||
+ | |||
+ | <code c ptr-correto.c> | ||
+ | #include < | ||
+ | |||
+ | int main () | ||
+ | { | ||
+ | int *p ; | ||
+ | |||
+ | if (p) // (equivale a "if (p != NULL)" | ||
+ | printf ("p vale %p e *p vale %d\n", p, *p) ; | ||
+ | |||
+ | return 0 ; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Ponteiros não-inicializados (contendo " | ||
+ | |||
+ | ===== Representação gráfica ===== | ||
+ | |||
+ | As estruturas de dados que usam ponteiros podem ser tornar grandes e complexas, como listas, árvores e grafos. Por isso, muitas vezes é interessante poder representá-las graficamente. Uma variável de tipo ponteiro é usualmente representada como um quadrado com uma seta, que indica para onde a variável aponta. | ||
+ | |||
+ | Na figura a seguir está representado o ponteiro declarado no código abaixo. Também é mostrada a representação de um ponteiro nulo, que não aponta para um conteúdo válido. | ||
+ | |||
+ | <code c> | ||
+ | int a = 231 ; | ||
+ | int *p = &a ; | ||
+ | ... | ||
+ | p = NULL ; | ||
+ | </ | ||
+ | |||
+ | {{ ponteiros.png |Exemplos de ponteiros}} | ||
+ | |||
+ | ===== Ponteiros e vetores ===== | ||
+ | |||
+ | Um vetor pode ser visto como um ponteiro para uma área de memória que contém uma sequência de valores do mesmo tipo. Por exemplo: | ||
+ | |||
+ | <code c> | ||
+ | short int valor[5] ; | ||
+ | </ | ||
+ | |||
+ | O nome '' | ||
+ | |||
+ | Dessa forma, o nome de um vetor pode ser visto como um ponteiro para dados do tipo definido no vetor e aponta para o endereço do primeiro elemento do vetor. Em consequência, | ||
+ | |||
+ | <code c> | ||
+ | int *ptr ; // ponteiro para inteiros | ||
+ | int ptr[] ; // endereço de um vetor de inteiros (não alocado) | ||
+ | </ | ||
+ | |||
+ | ===== Aritmética de ponteiros ===== | ||
+ | |||
+ | Ponteiros são valores numéricos e portanto podem sofrer algumas operações aritméticas simples. Considerando //T// o **tamanho** em bytes do tipo apontado por um ponteiro, temos: | ||
+ | |||
+ | * '' | ||
+ | * '' | ||
+ | * '' | ||
+ | * '' | ||
+ | |||
+ | Além disso, ponteiros podem ser comparados (<, >, >=, <=, ==, !=, etc). | ||
+ | |||
+ | Exemplo: | ||
+ | |||
+ | <code c> | ||
+ | int nota[5] = { 45, 78, 92, 73, 87 } ; | ||
+ | int *p ; | ||
+ | |||
+ | p = nota ; // p aponta para nota[0] | ||
+ | printf ("p: %p, *p: %d\n", p, *p) ; | ||
+ | |||
+ | p++ ; // p aponta para nota[1] | ||
+ | printf ("p: %p, *p: %d\n", p, *p) ; | ||
+ | |||
+ | p += 3 ; // p aponta para nota[4] | ||
+ | printf ("p: %p, *p: %d\n", p, *p) ; | ||
+ | </ | ||
+ | |||
+ | Resultado da execução: | ||
+ | |||
+ | p: 0x7ffd28f319a0, | ||
+ | p: 0x7ffd28f319a4, | ||
+ | p: 0x7ffd28f319b0, | ||
+ | |||
+ | A evolução do ponteiro '' | ||
+ | |||
+ | {{ ponteiros-vetor.png |}} | ||
+ | |||
+ | Dessa forma, a manipulação de vetores pode ser feita usando aritmética de ponteiros: | ||
+ | |||
+ | <code c> | ||
+ | #define SIZE 100 | ||
+ | |||
+ | int v[SIZE] ; | ||
+ | int i, *p ; | ||
+ | |||
+ | // preenchimento usando [] | ||
+ | for (i = 0 ; i < SIZE ; i++) | ||
+ | v[i] = 0 ; | ||
+ | |||
+ | // preenchimento usando aritmética de ponteiros | ||
+ | for (p = v ; p < v + SIZE ; p++) | ||
+ | *p = 0 ; | ||
+ | |||
+ | // ou ainda... | ||
+ | for (i = 0 ; i < SIZE ; i++) | ||
+ | *(v + i) = 0 ; | ||
+ | </ | ||
+ | |||
+ | ===== Ponteiros para ponteiros ===== | ||
+ | |||
+ | Como visto acima, um ponteiro é uma variável que pode conter o endereço (" | ||
+ | |||
+ | A declaração e uso de ponteiros indiretos é simples: | ||
+ | |||
+ | <code c pontpont.c> | ||
+ | #include < | ||
+ | |||
+ | int main () | ||
+ | { | ||
+ | int a = 231 ; | ||
+ | int *pd ; // ponteiro direto | ||
+ | int **pi ; // ponteiro indireto, equivale a int *(*p) | ||
+ | |||
+ | pd = &a ; // pd recebe o endereço de um int | ||
+ | pi = &pd ; // pi recebe o endereço de um ponteiro para int | ||
+ | |||
+ | printf (" | ||
+ | printf ("pd está em %p e vale %p\n", &pd, pd) ; | ||
+ | printf ("pi está em %p e vale %p\n", &pi, pi) ; | ||
+ | |||
+ | printf (" | ||
+ | printf (" | ||
+ | printf ("**pi vale %d\n", **pi) ; | ||
+ | |||
+ | return 0 ; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | O resultado da execução do código acima é: | ||
+ | |||
+ | a está em 0x7ffda4afa4ac e vale 231 | ||
+ | pd está em 0x7ffda4afa4b0 e vale 0x7ffda4afa4ac | ||
+ | pi está em 0x7ffda4afa4b8 e vale 0x7ffda4afa4b0 | ||
+ | *pd vale 231 | ||
+ | *pi vale 0x7ffda4afa4ac | ||
+ | **pi vale 231 | ||
+ | |||
+ | Graficamente, | ||
+ | |||
+ | {{ : | ||
+ | |||
+ | ===== Ponteiros void ===== | ||
+ | |||
+ | Um ponteiro de tipo '' | ||
+ | |||
+ | <code c> | ||
+ | void *ptr ; | ||
+ | </ | ||
+ | |||
+ | Por não terem um tipo predefinido, | ||
+ | |||
+ | Exemplo: | ||
+ | |||
+ | <code c void.c> | ||
+ | #include < | ||
+ | |||
+ | int main () | ||
+ | { | ||
+ | int a = 34 ; | ||
+ | int b ; | ||
+ | void *p ; | ||
+ | |||
+ | p = &a ; | ||
+ | b = *p ; // erro de compilação! | ||
+ | |||
+ | printf ("p vale %p\n", p) ; | ||
+ | p++ ; | ||
+ | printf ("p vale %p\n", p) ; | ||
+ | |||
+ | return (0) ; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ===== Ponteiros e vetores II ===== | ||
+ | |||
+ | O uso de ponteiros pode facilitar muito a escrita de código envolvendo vetores. Um exemplo clássico é a implementação | ||
+ | |||
+ | Implementação vetorial básica: | ||
+ | |||
+ | <code c> | ||
+ | void strcpy (char *s, char *t) | ||
+ | { | ||
+ | int i ; | ||
+ | i = 0 ; | ||
+ | while (t[i] != ' | ||
+ | { | ||
+ | s[i] = t[i] ; | ||
+ | i++ ; | ||
+ | } | ||
+ | s[i] = t[i] ; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Implementação vetorial melhorada: | ||
+ | |||
+ | <code c> | ||
+ | void strcpy (char *s, char *t) | ||
+ | { | ||
+ | int i ; | ||
+ | i = 0 ; | ||
+ | while ((s[i] = t[i]) != ' | ||
+ | i++; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Versão usando ponteiros: | ||
+ | |||
+ | <code c> | ||
+ | void strcpy (char *s, char *t) | ||
+ | { | ||
+ | while ((*s = *t) != ' | ||
+ | { | ||
+ | s++; | ||
+ | t++; | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Podemos mudar os operadores de incremento para dentro da condição: | ||
+ | |||
+ | <code c> | ||
+ | void strcpy (char *s, char *t) | ||
+ | { | ||
+ | while ((*s++ = *t++) != ' | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Podemos retirar a comparação '' | ||
+ | |||
+ | <code c> | ||
+ | void strcpy (char *s, char *t) | ||
+ | { | ||
+ | while (*s++ = *t++) ; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ===== Exercícios ===== | ||
+ | |||
+ | - Assistir o vídeo [[https:// | ||
+ | - Escreva um programa que leia 10 inteiros da entrada padrão, armazene-os em um vetor e os escreva na saída padrão na ordem contrária; todos os acessos ao vetor devem ser feitos usando somente ponteiros, sem usar índices (vet[i], etc). | ||
+ | - Mude o programa anterior para ordenar o vetor usando o algoritmo da bolha. | ||
+ | - Escreva um programa para calcular o tamanho de uma string usando somente ponteiros. | ||
+ | - Escreva um programa para concatenar duas strings usando somente ponteiros. | ||