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 [2023/08/31 13:01] – maziero | c:ponteiros [2024/10/01 14: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. | ||