Docsity
Docsity

Prepare-se para as provas
Prepare-se para as provas

Estude fácil! Tem muito documento disponível na Docsity


Ganhe pontos para baixar
Ganhe pontos para baixar

Ganhe pontos ajudando outros esrudantes ou compre um plano Premium


Guias e Dicas
Guias e Dicas

(cap8) - Programação em C - Ponteiros, Notas de estudo de Engenharia Telemática

Apontadores ou ponteiros

Tipologia: Notas de estudo

2010

Compartilhado em 27/11/2010

samuel-santos-22
samuel-santos-22 🇧🇷

4.6

(41)

262 documentos

Pré-visualização parcial do texto

Baixe (cap8) - Programação em C - Ponteiros e outras Notas de estudo em PDF para Engenharia Telemática, somente na Docsity! Linguagem de Programação C Professora Isabel Harb Manssour 45 8. PONTEIROS O correto entendimento e uso de ponteiros é muito importante para um programador C, pois: eles fornecem os meios pelos quais as funções podem modificar seus argumentos; eles são usados para suportar as rotinas de alocação dinâmica de C; e eles podem aumentar a eficiência de certas rotinas. Ponteiros são um dos aspectos mais fortes e mais perigosos de C, porque quando são utilizados de maneira incorreta podem ocasionar erros que são muito difíceis de encontrar. Um ponteiro é uma variável que contém um endereço de memória. Esse endereço é normalmente a posição de uma outra variável de memória. Se uma variável contém o endereço de outra, então a primeira variável é dita para apontar para a segunda, como mostra a figura 8.1. Endereço na Variável na Memória Memória 1000 1003 1001 1002 1003 1004 1005 1006 Memória Figura 8.1 - Uma variável que aponta para outra [SCH 96] 8.1. Declaração e Manipulação Se uma variável irá conter um ponteiro, ela deve ser declarada como tal. Uma declaração de ponteiro consiste no tipo de base, um * e o nome da variável. A forma geral para declarar uma variável ponteiro é: <tipo> *<nome>; onde <tipo> é qualquer tipo válido em C e <nome> é o nome da variável ponteiro. O tipo base do ponteiro define que tipo de variáveis o ponteiro pode apontar. Tecnicamente, qualquer tipo de ponteiro pode apontar para qualquer lugar na memória. No entanto, toda a aritmética de ponteiros é feita por meio do tipo base, assim é importante declarar o ponteiro corretamente. Existem dois operadores especiais para ponteiros: * e & O & é um operador unário que devolve o endereço na memória do operando. Por exemplo: int *m, cont=100, q; m = &cont; É colocado em "m” o endereço da memória que contém a variável “cont”. O endereço não tem relação alguma com o valor de “cont”. O operador & pode ser imaginado como retornando “o endereço de”. Assim, o comando de atribuição anterior significa "m recebe o endereço de cont". Linguagem de Programação C Professora Isabel Harb Manssour 46 O segundo operador de ponteiro, *, é o complemento de &. É um operador unário que devolve o valor da variável localizada no endereço que o segue. Por exemplo, se “m" contém o endereço da variável “cont”, q="m; coloca o valor de “cont" em "q". Portanto, "q" terá o valor 100. O operador * pode ser imaginado como “no endereço". Nesse caso, o comando anterior significa “q recebe o valor que está no endereço m". Alguns iniciantes em C podem confundir o sinal de multiplicação e o símbolo “no endereço de" (*), porém esses operadores não têm nenhuma relação um com o outro. Tanto & como * têm uma precedência maior do que todos os operadores aritméticos, exceto o menos unário, com o qual eles se parecem. As variáveis ponteiros sempre devem apontar para o tipo de dado correto. Por exemplo, quando um ponteiro é declarado como sendo do tipo int, o ponteiro assume que qualquer endereço que ele contenha aponta para uma variável inteira. Como C permite a atribuição de qualquer endereço a uma variável ponteiro, o fragmento de código a seguir compila sem nenhuma mensagem de erro (ou apenas uma advertência - warning), mas não produz o resultado desejado. main () £ float x, y; int*p; X; !/ p aponta para um float Y=*p;//0 valor atribuído para y não é o esperado J Neste caso, como "p" é declarado como um ponteiro para inteiros, apenas dois bytes de informação são transferidos para “y", não os 8 bytes que normalmente formam um número em ponto flutuante. Em geral, expressões envolvendo ponteiros concordam com as mesmas regras de qualquer outra expressão de C. Algumas diferenças: * Atribuição: um ponteiro pode ser usado no lado direito de uma comando de atribuição para passar seu valor para um outro ponteiro. Por exemplo: main() £ intx; int *p1, “p2; pi = &x p2=p1; // agora p1 e p2 apontam para x printf('%p”, p2); // escreve o endereço de x e não o seu valor J * Aritmética: existem apenas duas operações aritméticas que podem ser usadas com ponteiros, que são adição e subtração. Para entender o que ocorre na aritmética de ponteiros, considere “pl” um ponteiro para um inteiro, que ocupa 2 bytes, com o valor atual 2000. Após a expressão "p1++*, p1 conterá 2002, e não 2001. Cada vez que “p1" é incrementado, ele aponta para o próximo inteiro. O mesmo ocorre nos decrementos. Generalizando a partir do exemplo, cada vez que um ponteiro é incrementado, ele aponta para a posição de memória do próximo elemento do seu tipo base. E cada vez que é decrementado, ele aponta para a posição do elemento anterior. Em outras palavras, os ponteiros são incrementados e decrementados relativamente ao tamanho do tipo base, de forma que ele sempre aponta para o próximo elemento ou para o elemento anterior, respectivamente. Também é possível somar e subtrair inteiros de ponteiros. Assim, a expressão “p1=p1+12" faz “p1" apontar para o décimo segundo elemento do tipo "p1" adiante do elemento que ele está atualmente apontando. Além de adição e subtração entre um ponteiro e um inteiro, nenhuma outra operação aritmética pode ser efetuada com ponteiros. » Comparação: É possível comparar dois ponteiros em uma expressão relacional. Por exemplo, dados dois ponteiros "p" e "q", o comando a seguir é perfeitamente válido: if(p < q) printf(ip aponta para uma memória mais baixa que q In”); Linguagem de Programação C Professora Isabel Harb Manssour 49 Se for necessário passar uma matriz de ponteiros para uma função, pode ser usado o mesmo método que é utilizado para passar outras matrizes - simplesmente chame a função com o nome da matriz sem qualquer índice. Por exemplo, a seguinte função recebe a matriz "x" como parâmetro: void display array (int gl) [ intt; for (t=0; t<10; t++) printf (“%d ”, alt); J Neste caso, é importante lembrar que “q” não é um ponteiro para inteiros; "q" é um ponteiro para uma matriz de ponteiros para inteiros. Portanto, é necessário declarar o parâmetro "q" como uma matriz de ponteiros para inteiros, como apresentado no código anterior. Ela não pode ser simplesmente declarada como um ponteiro para inteiros. Matrizes de ponteiros são usadas normalmente como ponteiros para strings. Assim, é possível criar uma função que exiba uma mensagem de erro quando é dado seu número de código, como mostrado a seguir: void syntax error (int num) £ static char *errl] = ( “Arquivo não pode ser aberto In”, “Erro de leitura In”, “Erro de escrita In”, “Falha da mídia In” y printi('%s”, errnum); No exemplo anterior, a matriz "err" contém ponteiros para cada string. Como pode-se observar, “printf()" dentro de "syntax. error" é chamada com um ponteiro de caracteres que aponta para uma das várias mensagens de erro indexadas pelo número de erro passado para a função. Por exemplo, se for passado o valor 1 a mensagem “Erro de leitura” é apresentada, se for passado o valor 2, a mensagem “Erro de escrita” é apresentada, e assim por diante. Outro exemplo de matriz de ponteiros a caracteres é o argumento da linha de comandos “argv" (seção 4.4) [SCH 96]. 8.3. Alocação Dinâmica Ponteiros fornecem o suporte necessário para o poderoso sistema de alocação dinâmica de C. Alocação dinâmica é o meio pelo qual um programa pode obter memória enquanto está em execução. Variáveis globais têm o armazenamento alocado em tempo de compilação e variáveis locais usam a pilha. No entanto, nem variáveis globais, nem locais podem ser acrescentadas durante o tempo de execução. Porém, haverá momentos em que um programa precisará usar quantidades de armazenamento variáveis. Por exemplo, um processador de texto ou um banco de dados aproveita toda a RAM de um sistema. Porém, como a quantidade de RAM varia entre computadores tais programas não poderão usar variáveis normais, por isso alocam memória conforme o necessário. A memória alocada pelas funções de alocação dinâmica de C é obtida do heap (região de memória livre). Embora o seu tamanho seja desconhecido, o heap geralmente contém uma quantidade razoavelmente grande de memória livre. A alocação dinâmica em C baseia-se nas funções malloc(), para alocar memória, e free(), para liberar a memória alocada. Apesar de existirem outras funções de alocação dinâmica, estas são as mais importantes. Elas operam em conjunto, usando a região de memória livre para estabelecer e manter uma lista de armazenamento disponível. Os protótipos destas funções, que estão descritos na stdlib.h, são: void “malloc(size t<número de bytes>); Linguagem de Programação C Professora Isabel Harb Manssour 50 void free(void *p); Aqui, "<número. de bytes>" é o número de bytes de memória que deve ser alocado, e o tipo “size +" é definido em stdlib.h como (mais ou menos) um inteiro sem sinal. A função malloc() devolve um ponteiro do tipo void, o que significa que este pode ser atribuído a qualquer tipo de ponteiro. Após uma chamada bem-sucedida, malloc() devolve um ponteiro para o primeiro byte da região de memória alocada do heap. Se não há memória disponível para satisfazer a requisição de malloc(), ele devolve um nulo. Exemplos de utilização destas funções: char “p1; int “p2; if(p1!=malloc(1000)) ) f // Se a alocação dos 1000 bytes retornar nulo printf(*Sem memória disponível. In”); exit(1); ! Aborta a execução do programa; Poderia ter um tratamento de erro. J p2 = malloc(50"sizeof(int)); ! usando a função “sizeof” há uma garantia de portabilidade free(p 1); free(p2); E importante salientar que o ponteiro enviado para a função free(), deve ser um ponteiro para memória alocada anteriormente por ma/loc(). Nunca deve-se usar free() com um argumento inválido, pois isso destruiria a lista de memória livre. Algumas vezes pode ser necessário alocar memória usando malloc(), mas operar na memória como se ela fosse uma matriz, usando indexação de matrizes. Em outras palavras, é possível criar uma matriz alocada dinamicamente. Como qualquer ponteiro pode ser indexado como se fosse uma matriz unidimensional, isso não representa nenhum problema, como mostra o próximo exemplo [SCH 96]. ! Aloca espaço para uma string dinamicamente, solicita a entrada do usuário e, em seguida, ! imprime a string de trás para frente. include <stdlib.h> include <stdio.h> include <string.h> void main(void) f char *s; register int t; s=malloc(80); its) ( printf("Falha na solicitação de memória In"); exit(1); J gets(s); for(t=strlen(s)-1; t>=0; t--) putchar(s[t]); free(s); Acessar memória alocada como se fosse uma matriz unidimensional é simples. No entanto, matrizes dinâmicas multidimensionais levantam alguns problemas. Como as dimensões da matriz não foram definidas no programa, não é possível indexar diretamente um ponteiro como se ele fosse uma matriz multidimensional. Para conseguir uma matriz alocada dinamicamente, é necessário passar o ponteiro como um parâmetro a uma função. Dessa forma, a função pode definir as dimensões do parâmetro que recebe o ponteiro, permitindo, assim, a indexação normal de matriz. O exemplo a seguir, que constrói uma tabela dos números de 1 a 10 elevados a primeira, à segunda, à terceira e à quarta potência, mostra como isso funciona [SCH 96]. /* Apresenta as potências dos números de 1 a 10. Nota: muito embora esse programa esteja correto, alguns compiladores C apresentarão uma mensagem de advertência, ou até erro, com relação aos argumentos para as funções table() e show(). Se forem apenas advertências, ignore. */ Linguagem de Programação C Professora Isabel Harb Manssour include <stdio.h> include <stdlib.h> int pwr(int a, int b); void table(int p[4Jt 0); void show(int p[4][10)); void main(voia) £ int*p; p= (int *) malloc(40*sizeof(int)); into) ( printf("Falha na solicitação de memória. In"); exit(1); J ! aqui, p, é simplesmente um ponteiro table(p); show(p); ! Constrói a tabela de potências void table(int p[4J10]) [ // agora o compilador tem uma matriz para trabalhar register int i, j; forlj=1; j<11;j++) for(i=1; i<5; i++) p[i- 1-1] = pwr( à); ! Exibe a tabela de potências inteiras void showf(int p[4J[10)) £ // agora o compilador tem uma matriz para trabalhar register int i, j; printi("%10s %10s %10s %1Osin", "Nº, "NZD", "NAS", "NM4); forlj=1; j<11; jr+) ( for(i=1; i<5; i++) printf('% 10d *, pfi-1j-1); printf"); / Eleva um inteiro a uma potência especificada pwrfint a, int b) f register int t=1; for(; b; b--) t=t'a; return t; J 51
Docsity logo



Copyright © 2024 Ladybird Srl - Via Leonardo da Vinci 16, 10126, Torino, Italy - VAT 10816460017 - All rights reserved