Baixe o app para aproveitar ainda mais
Prévia do material em texto
Algoritmos e Técnicas de Programação Ponteiros (Apontadores) em C (Prof. Wellington Lima dos Santos - UFGD) Preâmbulo: Variáveis x Memória � Na memória residem dados e instruções que manipulam os primeiros. � Dados são armazenados e manipulados de forma mais conveniente em variáveis. � Variável é um nome atribuído a um bloco de memória (alocado pelo compilador), no qual armazenamos e recuperamos o valor da variável durante a execução do programa. � O número de bytes que o compilador reserva para a variável na memória depende do tipo da variável: � char c; //1 byte � int i; //4 bytes num SO de 32Bits � float x; //4 bytes � float v[5]; //5 * 4 = 20 bytes � float m[5][6]; //5 * 6 * 4 = 120 bytes Ponteiros - Introdução � Ponteiro é uma variável, cujos valores são endereços de memória, ou seja, posições dos bytes da memória. � Estes endereços são inteiros 32 ou 64 bits (conforme o S.O.), porém tratados distintamente do tipo int para que operações matemáticas indevidas sejam evitadas. � Ponteiros são usados para armazenar o endereço de: � Outra variável, de qualquer tipo, incluindo ponteiros. � Bloco de memória dinâmica alocada com malloc. � Uma função, permitindo passá-la como parâmetro para funções. � Ponteiros permitem livre acesso à memória, portanto Cuidado! � Ponteiros permitem representar estruturas de dados dinâmicas muito úteis como listas encadeadas e árvores. Ponteiros - Introdução � “O correto entendimento e uso de ponteiros é crítico para uma programação bem-sucedida em C.” (SCHILDT, H. C Completo e Total) � Em C, ponteiros têm quatro usos importantes: � Passagem de parâmetros por referência para funções. � Suporte à alocação dinâmica de memória e ao uso de estruturas de dados complexas. � Passagem de funções como parâmetros para outras funções. � Otimização de algumas rotinas, aumentando sua eficiência. Declaração de Ponteiros � PONTEIRO TIPADO: faz com que compilador interprete a memória apontada como o primeiro byte de uma variável associada ao tipo do ponteiro. � Se pensarmos em um tipo como uma máscara de bits para a memória ocupada por uma variável desse tipo, então um ponteiro tipado funciona como uma máscara “deslizante” sobre os bytes da memória, possibilitando interpretar seus bits conforme os seus significados para o tipo. � Sintaxe de declaração: � tipobase * NomeVar; � Exemplos: � int * i; //ponteiro para um dado int � char * s; //ponteiro para um dado char � float * x; //ponteiro para um dado float Declaração de Ponteiros � PONTEIRO TIPADO: faz com que compilador interprete a memória apontada como o primeiro byte de uma variável associada ao tipo do ponteiro. � Um TIPO de dado define como devem ser interpretados os bits na memória ocupada por uma variável desse tipo. De outro modo, um tipo funciona como uma máscara de bits. � Assim, um ponteiro tipado funciona como uma máscara “deslizante” sobre os bytes da memória, dando significados aos seus bits conforme o tipo do ponteiro. � Sintaxe de declaração: � TipoVar * NomeVar; � Exemplos: � int * i; //ponteiro para um int � char * s; //ponteiro para um char � float * x; //ponteiro para um float Declaração de Ponteiros � PONTEIRO GENÉRICO: Nenhum tipo é associado ao ponteiro, portanto o compilador não tem informação sobre como interpretar os bits da memória apontada. � Desta forma conhecemos apenas o endereço inicial do bloco de memória mas não o seu significado. � Comumente é usado para armazenar endereços de grandes blocos de memória dinâmica obtida com malloc, nos quais se escrevem informações heterogêneas. � Sintaxe de declaração: � void * NomeVar; � Exemplos: � void * p; //ponteiro genérico � void * temp; //ponteiro genérico Operadores de Ponteiros: & e * � & ⇒⇒⇒⇒ Retorna o endereço da variável que o segue. � Exemplo: � No que k e x (e os demais, se existissem) são parâmetros passados por referência! int k; float x; scanf(“ %d , %f ” , &k , &x ); formata a 1a entrada como inteiro e armazene-a no endereço de k formata a 2a entrada como float e armazena-a no endereço de x Operadores de Ponteiros: & e * � * (asterisco) ⇒⇒⇒⇒ Permite acessar o conteúdo (geralmente referido como valor apontado) da variável que o sucede. � Exemplo: #include <stdio.h> #include <conio.h> int main(int argc, char* argv[]) { int k; int * p; p = &k; //faz p apontar para o endereço de k k = 25; printf("%d\n", *p); //imprime o valor 25 *p = *p + 20; //faz: k = k + 20 printf("%d\n", k); //imprime o valor 45 } Aritmética de Ponteiros � Considerando as declarações em C: � TipoVar * p; � int k; � São possíveis as seguintes operações: � p++⇒ incrementa o endereço de p em (sizeof(TipoVar)) bytes. � p-- ⇒ decrementa o endereço de p em (sizeof(TipoVar)) bytes. � p ± k⇒ retorna outro ponteiro cujo endereço é deslocado de (±k * sizeof(TipoVar)) bytes. Aritmética de ponteiros: Exemplo1 #include <stdio.h> void main() { int v[] = {111, 222, 333, 444, 555, 666}; int * p; p = &v[1]; //p aponta para o 2o elemento: 222 printf("%d\n", *p); //imprime 222 p++; //avança para v[2]: 333 printf("%d\n", *p); //imprime 333 printf("%d\n", *(p+3)); //imprime 666 p = p - 2; //volta p/ v[2-2=0]:111 printf("%d\n", *p); //imprime 111 } Aritmética de ponteiros: Exemplo2 void InverteVetor (int v[], int n) { int *e, *d, t; e = v; //o mesmo que: e = &v[0] d = &v[n-1]; //aponta para o último elemento while (e < d) //enquanto “e” não cruzar “d” { t = *e; *e = *d; *d = t; e++; //avança um int para a direita d--; //recua um int para a esquerda } } � Inversão da ordem de um vetor: 0 1 2 3 n-2 n-1 ••• ⇑ ⇑ e d Alocação Dinâmica: Exemplo � Uma função genérica para trocar o conteúdo de duas variáveis do mesmo tipo //memcpy requer <mem.h> void TrocaVars(void * a, void * b, int sz) { void * c; c = malloc(sz); //aloca "sz" bytes memcpy(c, a, sz); //copia de "a" para "c" memcpy(a, b, sz); //copia de "b" para "a" memcpy(b, c, sz); //copia de "c" para "b" free(c); //desaloca a memória } Alocação Dinâmica: Exemplo (Cont.) � Uso da Função TrocaVars #include <stdio.h> #include <stdlib.h> #include <mem.h> void main() { char r = 'A', s = 'Z'; int x = 25, y = 80; printf("r = %c s = %c \n", r, s); TrocaVars(&r, &s, sizeof(r)); //troca os valores de "r" e "s" printf("r = %c s = %c \n", r, s); printf("x = %d y = %d \n", x, y); TrocaVars(&x, &y, sizeof(x)); //troca os valores de "r" e "s" printf("x = %d y = %d \n", x, y); } Ponteiros para funções � Um ponteiro para uma função pode ser declarado da seguinte forma: TipoRet (* NomePonteiro) (tipo1, tipo2, ..., tipoN) � TipoRet⇒ tipo retornado pela função � NomePonteiro⇒ nome da variável ponteiro precedido de *, ambos entre ( ), obrigatoriamente. � tipo1, tipo2, ..., tipoN⇒ tipos de cada um dos N parâmetros formais da função. � Como num protótipo, os nomes dos parâmetros não interessam. � Assim, para definir um ponteiro para uma função basta copiar o seu protótipo e substituir nele o nome da função por ( * NomePonteiro) Ponteiros para funções - Exemplo int Soma(int a, int b) { return a + b;} int Mult(int a, int b) { return a * b; } int Executa(int x, int y, int (* func) (int, int)) { return func(x, y); } void main() { //declara um ponteiro compatível com “Soma” e “Mult” int (*func)(int, int); //o endereço é obtido pelo nome sem parâmetros e sem & func = Mult; printf("%d", func(3, 7)); //imprime 3 x 7 = 21 func = Soma; //atribui a func o endereço de Soma printf("%d", Executa(4, 5, Soma)); //imprime 4 + 5 = 9 } Exercício 1: � Quais são os dois valores exibidos na tela pelo programa abaixo? void main() { int a = 30;int * p; p = &a; a = a + 15; printf("%d\n", *p); *p = *p + 5; a++; printf("%d\n", a); } Exercício 2: � Que valor é exibido na tela pelo programa abaixo? void main() { float x[] = {2.5, 3.6, 1.2, -99.9}; float * y; y = &x[2]; y++; *y = *(y-2) + *(y-1); printf("%.1f\n", x[3]); } Exercício 3: � Quais são os dois valores exibidos na tela pelo programa abaixo? void main() { int v[] = {000, 111, 222, 333, 444, 555, 666}; int *i; int **j; j = &i; i = v + 5; // mesmo que: i = &v[0] + 5 *j = *j - 3; printf("%d\n", *i); i++; printf("%d\n", **j); }
Compartilhar