REVISÃO DE C-ANSI – Parte 4
Esta aula dará continuação à revisão dos conceitos básicos do padrão C-ANSI. Na aula de hoje serão revisados conceitos sobre ponteiros.
O QUE SÃO PONTEIROS?
Ponteiros são variáveis dedicadas para armazenar um endereço de memória. Usualmente, ponteiros contém o endereço de outra variável. Essa variável pode ser desde um simples caractere até uma imensa estrutura de dados.
OPERADORES DE PONTEIROS
Como brevemente mencionado na primeira aula de revisão, existem dois operadores dedicados à manipulação de ponteiros. São operadores unários, e são apresentados na Tabela 1.
Tabela 1 Operadores de ponteiro.
Operador Descrição
& Operador de ponteiro. Devolve o endereço do operando.
* Operador de ponteiro. Devolve o valor apontado pelo operando.
O operador & devolve o endereço do operando, i.e., quando se escreve:
y = &x;
Estamos atribuindo a y o endereço
O operador *, por sua vez, devolve o valor armazenado no endereço apontado pelo operando. No exemplo abaixo,
da variável x, não seu valor.
y = *p;
Estamos atribuindo a y o valor armazenado no endereço apontado por p. Estude atentamente o Programa 1.
#include <stdio.h> #include <stdlib.h>
int main(void) {
int a, b; int *p;
a = 10; //atribuo o valor 10 a variável a
p = &a; //p agora aponta para a (p recebe o endereço de a)
b = *p; //atribuo a b o valor do endereço apontado por p (i.e., a) printf("%d\n", b); //b no final recebe o valor de a...
printf("No endereco de memoria: %#x esta armazenado: %#x\n", p, *p); *p = 20; //atribuo 20 no endereço apontado por p.
printf("%d\n", a); //p aponta para a... system("PAUSE");
return 0; }
Note que quando realizamos a operação p = &a; estamos atribuindo o endereço da variável a ao ponteiro p. Após, a operação b = *p; atribui a b o valor apontado por p, i.e., o valor armazenado no endereço de memória armazenado na variável p. Mais adiante no programa encontramos o comando *p = 20; nele o programa solicita que seja atribuído o valor 20 ao endereço apontado por p. A Figura 1 ilustra esse processo.
Figura 1 Funcionamento dos ponteiros.
PONTEIROS PARA VARIÁVEIS
Note no Programa 1 que uma variável ponteiro é declarada de modo muito similar ao modo como se declara variáveis triviais. A única diferença é o identificador * que precede o nome da variável. O tipo de dado informado não é o tipo do ponteiro em si, e sim o tipo de dado para o qual o ponteiro apont
Programa 2
a. Isso é necessário quando se trabalha com aritmética de ponteiros. Quando um ponteiro de um determinado tipo é incrementado ou decrementado, esta ação é realizada de acordo com o tamanho do tipo de dado para qual o ponteiro aponta (1 para char, 2 para short int, 4 para float e etc). Por isso faz-se necessário informar o tipo de dado apontado. Altere os tipos de dados das variáveis a e p no para ver como isto ocorre.
#include <stdio.h> #include <stdlib.h>
int main(void) {
int i = 10;
char a[10]; //não irei atribuir nada a essa variável propositadamente
char *p;
p = &a[0]; //o ponteiro aponta para a primeira posição da matriz
do {
printf("No end. de mem.: %#x esta armazenado: %#x\n", p, *p); p++;
}while(--i); system("PAUSE"); return 0;
}
Programa 2 Tipos de dados em ponteiros.
PONTEIROS, STRINGS E MATRIZES
Matrizes e ponteiros estão intimamente relacionados em C. Isto porque a indexação de matrizes pode ser realizada tanto com índices quanto por aritmética de ponteiros. Veja bem, ao ser criada, uma matriz ocupa um espaço contíguo de memória (vide saída do Programa 2).
Ao se passar uma matriz para uma função, como fora falado em aulas anteriores, é passado o endereço de um termo da matriz (geralmente o primeiro). O próprio compilador é o responsável por organizar a indexação da matriz declarada como parâmetro da função... Ou seja, não foi “passada” uma matriz para a função, e sim o endereço da matriz. O Programa 3 prova isso, estude-o cestude-om cuidadestude-o:
#include <stdio.h> #include <stdlib.h>
void func(int mat[10]); void funcb(int i);
int main(void) {
int i = 10,m[10] = {0,1,2,3,4,5,6,7,8,9}; funcb(i);
printf("%d\n ",i); //i não foi alterado func(m);
for (i = 0;i < 10;i++) printf("%d ",m[i]); //a matriz é alterada.... putchar('\n');
system("PAUSE"); return 0;
}
void func(int mat[10]) { mat[5] = 0; }
//alterar mat[5] altera a matriz passada pois se trabalha com endereços!
void funcb(int i) { i = 0; }
//alterar o i declarado como parâmetro não altera variável passada... Programa 3 O C já trata um parâmetro matriz como ponteiro.
Observe a saída do programa... Houve alteração na matriz passada a função. Como C trata as matrizes declaradas como parâmetros com o uso de ponteiros, a matriz “enviada” pode ser alterada.
Na verdade, é possível facilmente trabalhar com uma matriz utilizando um ponteiro. Observe o Programa 4:
#include <stdio.h> #include <stdlib.h>
int main(void) {
int *p, i, m[10] = {0,1,2,3,4,5,6,7,8,9}; for (i = 0;i < 10;i++) printf("%d ",m[i]); putchar('\n');
p = &m[0]; //poderia ser apenas p = m;
for (i = 0;i < 10;i++) printf("%d ",*(p+i)); putchar('\n');
system("PAUSE"); return 0;
}
No Programa 4 uma matriz é impressa utilizando indexação e utilizando aritmética de ponteiros. É muito importante testar os limites quando se trabalha com ponteiros, de modo a evitar que áreas fora do limite da matriz sejam acessadas. Vale lembrar que matrizes multidimensionais também são alocadas contiguamente, assim é possível imprimir matrizes multidimensionais utilizando um ponteiro. Estude o exemplo:
#include <stdio.h> #include <stdlib.h>
int main(void) {
int *p, i, j, m[2][3] = { 0,1,2, 3,4,5 }; for (i = 0;i < 2;i++)
{
for (j = 0;j < 3;j++) printf("%d ",m[i][j]); //imprimo indexando m
putchar('\n'); }
printf("\n");
p = &m[0][0]; //p aponta para primeiro termo
for (i = 0;i < (2*3);i++) {
if(!(i%3)) putchar('\n'); //a cada três termos eu quebro uma linha...
printf("%d ",*(p+i)); //imprimo utilizando aritmética de ponteiros
}
putchar('\n'); system("PAUSE"); return 0;
}
Programa 5 Matrizes multidimensionais e ponteiros.
Observe como a matriz é alocada contiguamente, mesmo sendo multidimensional. O compilador é que indexa a matriz de acordo com os índices fornecidos quando a matriz é declarada.
Uma string é um vetor de caracteres onde o último caractere é nulo. Assim, é possível escrever uma string sem que haja necessidade de realizar o controle através de uma variável auxiliar. Observe o exemplo:
#include <stdio.h> #include <stdlib.h>
int main(void) {
char *p = "Ola... isso e' uma string...";
while(*p != '\0') //enquanto valor apontado por p não é nulo...
{
putchar(*p); p++;
}
putchar('\n'); system("PAUSE"); return 0;
}
No Programa 6 o ponteiro foi incrementado e o valor para o qual apontava testado. O loop continua a imprimir caractere por caractere até encontrar o marcador nulo (‘\n’). Assim que encontra o programa sai do loop while.
PONTEIROS PARA ESTRUTURAS
Pode-se definir um ponteiro para uma estrutura do mesmo modo como um ponteiro para qualquer variável, basta adicionar o asterisco na frente da variável.
Quando um ponteiro para estrutura é utilizado, os campos da estrutura devem ser referenciados através do operador seta (->).Examine o Programa 7:
#include <stdio.h> #include <stdlib.h>
struct capacitor {
char tipo[20]; float valor; };
int main(void) {
struct capacitor c, *p; //cria uma variável e um ponteiro para estrutura
strcpy(c.tipo,"Tantalo"); c.valor = 1.0e-6;
p = &c; //p recebe o endereço da variável c
printf("Tipo: %s, valor: %e\n", p->tipo, p->valor); //usando ->
system("PAUSE"); return 0;
}
Programa 7 Ponteiros para estruturas.
Uma vantagem em utilizar ponteiros reside no fato de que agora podemos utilizar funções para modificar variáveis do programa principal.
PASSANDO VARIÁVEIS PARA FUNÇÕES UTILIZANDO PONTEIROS
#include <stdio.h> #include <stdlib.h>
void altera(float *f) {
*f = 3.45; };
int main(void) {
float aux = 0.76;
printf("Antes: %f\n",aux); altera(&aux);
printf("Depois: %f\n",aux); system("PAUSE");
return 0; }
Programa 8 Passando variáveis para funções utilizando ponteiros.
Um uso comum é a leitura de dados. Estude com cuidado o Programa 9:
#include <stdio.h> #include <stdlib.h>
typedef struct capacitor {
char tipo[20]; float valor; }cap;
void le_dados(cap *p) {
printf("Entre com tipo: "), gets(p->tipo);
printf("Entre com valor: "), scanf("%f",&p->valor); };
int main(void) {
cap c1,c2; le_dados(&c1);
printf("Tipo: %s valor: %e\n", c1.tipo, c1.valor); le_dados(&c2);
printf("Tipo: %s valor: %e\n", c2.tipo, c2.valor); system("PAUSE");
return 0; }
Programa 9 Exemplo de função que recebe o endereço de uma variável estrutura.
Observe que ponteiros são utilizados para modificar meus dados dentro da função, atribuindo valores utilizando o operador seta. Para ler os dados, basta chamar le_dados(&c);
FUNÇÕES QUE RETORNAM PONTEIROS
retornado deve ser de uma variável que exista, preferencialmente, fora da função. Examine com cuidado o Programa 10.
#include <stdio.h> #include <stdlib.h>
char *depo_virg(char *s) {
while(*s != '\0') //enquanto não encontrar o caractere nulo
{ //testa existência de ',' se achou retorna ponteiro
if(*s == ',') return s+1; //para próxima posição
s++; }
return NULL; //se não encontrar retorna um ponteiro nulo
};
int main(void) {
char *p1, *p2, a[1024];
printf("Digite um pequeno texo (max 1024 caracteres):"); gets(a);
p1 = &a[0]; //p1 aponta para primeiro espaço definido pela matriz a[]
p2 = depo_virg(p1); //recebe ponteiro
if (p2 == NULL) printf("Sem virgulas na frase\n"); //testa se há ,
else printf("Imprimindo so' depois da primeira virgula: %s\n",p2); system("PAUSE");
return 0; }
Programa 10 Retornando ponteiro.
No Programa 10 é retornado um ponteiro para uma área conhecida do programa, i.e., o ponteiro retornado estará dentro da área reservada para a matriz a, que foi declarada no início da função principal. Reiterando, é muito importante retornar ponteiros para variáveis externas à função. Avalie o Programa 11:
#include <stdio.h> #include <stdlib.h>
char *teste(void) {
char a = 'o';
return &a; //retornando end. de var. local... gera warning mas não erro...
}
void f(void) //esta função não faz nada... apenas declara uma variável
{
char a = 'i'; }
int main(void) {
char *str;
str = teste(); //cuidado!!!!!
printf("%c\n",*str); f();
printf("%c\n",*str); system("PAUSE"); return 0;
}
EXERCÍCIOS
Exercício 1 Faça uma função que calcula e retorna a média dos termos da matriz de números
inteiros n x m utilizando, obrigatoriamente
Exercício 2 Faça uma função para realizar a leitura de uma matriz de números inteiros n x m. A
função deverá receber o endereço da matriz. O protótipo da função é dado:
void le_matriz(int *aux); Utilize o mesmo #define do exercício anterior.
, aritmética de ponteiros. A função deverá receber explicitamente o endereço da matriz. Utilize #define para definir o tamanho n x m da matriz.
Exercício 3 Faça uma função que retorna o valor máximo de um vetor de inteiros positivos de
tamanho definido por você. O protótipo da função é dado: int max(int *aux);
Exercício 4 Faça um programa que teste as funções criadas.
Exercício 5 Defina uma estrutura de dados simples com três campos. Defina uma variável do tipo