[Curso de AeED - Aula 01] - Ponteiros + Registros




A linguagem C permite que o programador manipule a memória por meio dos seus endereços. Estes endereços podem ser armazenados em variáveis denominadas ponteiros. A partir deste simples dado é possível que sejam feitas muitas operações.
Ao final desta aula você será capaz de:
  • ·         Entender o que é um ponteiro;
  • ·         Como declarar e manipular ponteiros;
  • ·         Entender o que são registros;
  • ·         Declarar, manipular e renomear registros;




Ponteiros

A linguagem C implementa o conceito de ponteiro. O ponteiro é um tipo de dado como int, char ou float. A diferença do ponteiro em relação aos outros tipos de dados é que uma variável que seja ponteiro guardará um endereço de memória.

Por meio deste endereço pode-se acessar a informação, dizendo que a variável ponteiro aponta para uma posição de memória. O maior problema em relação ao ponteiro é entender quando se está trabalhando com o seu valor, ou seja, o endereço, e quando se está trabalhando com a informação apontada por ele.

Operador & e *

O primeiro operador de ponteiro é &. Ele é um operador unário que devolve o endereço na memória de seu operando. Por exemplo: m = &count; põe o endereço na memória da variável count em m. Esse endereço é a posição interna da variável na memória do computador e não tem nenhuma relação com o valor de count. O operador & tem como significado o endereço de. O segundo operador é *, que é o complemento de &. O * é 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 count: q = *m; coloca o valor de count em q. O operador * tem como significado no endereço de.


A declaração de uma variável ponteiro é dada pela colocação de um asterisco (*) na frente de uma variável de qualquer tipo. Na linguagem C, é possível definir ponteiros para os tipos básicos ou estruturas. A definição de um ponteiro não reserva espaço de memória para o seu valor e sim para o seu conteúdo. Antes de utilizar um ponteiro, o mesmo deve ser inicializado, ou seja, deve ser colocado um endereço de memória válido para ser acessado posteriormente.

Um ponteiro pode ser utilizado de duas maneiras distintas. Uma maneira é trabalhar com o endereço armazenado no ponteiro e outro modo é trabalhar com a área de memória apontada pelo ponteiro. Quando se quiser trabalhar com o endereço armazenado no ponteiro, utiliza-se o seu nome sem o asterisco na frente. Sendo assim qualquer operação realizada será feita no endereço do ponteiro.

Como, na maioria dos casos, se deseja trabalhar com a memória apontada pelo ponteiro, alterando ou acessando este valor, deve-se colocar um asterisco antes do nome do ponteiro. Sendo assim, qualquer operação realizada será feita no endereço de memória apontado pelo ponteiro. O programa 1. demostra a utilização de ponteiros para acesso à memória.

#include <stdio.h>

int main (void){
int *piValor; /* ponteiro para inteiro */
int iVariavel = 1;
piValor = &iVariavel; /* pegando o endereço de memória da variável */

printf ("Endereco: %d\n", piValor);
printf ("Valor : %d\n", *piValor);

*piValor = 180118 ;
printf ("Valor alterado: %d\n", iVariavel);
printf ("Endereco : %d\n", piValor);
return 0;
}

Passando variáveis para funções por referência

O ponteiro é utilizado para passar variáveis por referência, ou seja, variáveis que podem ter seu conteúdo alterado por funções e mantêm este valor após o término da função. Na declaração de uma função, deve-se utilizar o asterisco antes do nome do parâmetro, indicando que está sendo mudado o valor naquele endereço passado como parâmetro.

#include <stdio.h>

void soma (int, int, int *);

int main (void){
int iValorA;
int iValorB;
int iResultado;

printf ("Entre com os valores:");
scanf ("%d %d", &iValorA, &iValorB);
printf("Endereco de iResultado = %d\n", &iResultado);

soma (iValorA, iValorB, &iResultado);/* está sendo passado o endereço de memória da variável, qualquer alteração estará sendo realizada na memória */
printf ("Soma : %d\n", iResultado);
return 0;
}


void soma (int piValorA, int piValorB, int * piResultado){
printf("Endereco de piResultado = %d\n", piResultado);
/* o valor está sendo colocado diretamente na memória*/
*piResultado = piValorA + piValorB;
return;
}


Registros

Na linguagem C, existem os tipos básicos (char, int, float, etc.) e seus respectivos ponteiros que podem ser usados na declaração de variáveis. Para estruturar dados complexos, nos quais as informações são compostas por diversos campos, necessitamos de mecanismos que nos permitam agrupar tipos distintos. Neste capítulo, apresentaremos os mecanismos fundamentais da linguagem C para a estruturação de tipos.

O tipo estrutura

Em C, podemos definir um tipo de dado cujos campos são compostos de vários valores de tipos mais simples. Para ilustrar, vamos considerar o desenvolvimento de programas que manipulam pontos no plano cartesiano. Cada ponto pode ser representado por suas coordenadas x e y, ambas dadas por valores reais. Sem um mecanismo para agrupar as duas componentes, teríamos que representar cada ponto por duas variáveis independentes.

float x;
float y;

No entanto, deste modo, os dois valores ficam dissociados e, no caso do programa manipular vários pontos, cabe ao programador não misturar a coordenada x de um ponto com a coordenada y de outro. Para facilitar este trabalho, a linguagem C oferece recursos para agruparmos dados. Uma estrutura, em C, serve basicamente para agrupar diversas variáveis dentro de um único contexto. No nosso exemplo, podemos definir uma estrutura ponto que contenha as duas variáveis. A sintaxe para a definição de uma estrutura é mostrada abaixo:

struct ponto {
float x;
float y;
};

Desta forma, a estrutura ponto passa a ser um tipo e podemos então declarar variáveis deste tipo. struct ponto p; Esta linha de código declara p como sendo uma variável do tipo struct ponto. Os elementos de uma estrutura podem ser acessados através do operador de acesso “ponto” (.). Assim, é válido escrever:

ponto.x = 10.0;
ponto.y = 5.0;

Manipulamos os elementos de uma estrutura da mesma forma que variáveis simples. Podemos acessar seus valores, atribuir-lhes novos valores, acessar seus endereços, etc.

Exemplo: Capturar e imprimir as coordenadas de um ponto. Para exemplificar o uso de estruturas em programas, vamos considerar um exemplo simples em que capturamos e imprimimos as coordenadas de um ponto qualquer.

/* Captura e imprime as coordenadas de um ponto qualquer */
struct ponto {
float x;
float y;
};

int main (void) {
struct ponto p;
printf("Digite as coordenadas do ponto(x y): ");
scanf("%f %f", &p.x, &p.y);
printf("O ponto fornecido foi: (%.2f,%.2f)\n", p.x, p.y);
return 0;
}

A variável p, definida dentro de main, é uma variável local como outra qualquer. Quando a declaração é encontrada, aloca-se, na pilha de execução, um espaço para seu armazenamento, isto é, um espaço suficiente para armazenar todos os campos da estrutura (no caso, dois números reais). Notamos que o acesso ao endereço de um campo da estrutura é feito da mesma forma que com variáveis simples: basta escrever &(p.x), ou simplesmente &p.x, pois o operador de acesso ao campo da estrutura tem precedência sobre o operador “endereço de”.

Ponteiro para estruturas

Da mesma forma que podemos declarar variáveis do tipo estrutura:

struct ponto p;

Podemos também declarar variáveis do tipo ponteiro para estrutura:

struct ponto *pp;

Se a variável pp armazenar o endereço de uma estrutura, podemos acessar os campos dessa estrutura indiretamente, através de seu ponteiro:

(*pp).x = 12.0;

Neste caso, os parênteses são indispensáveis, pois o operador “conteúdo de” tem precedência menor que o operador de acesso. O acesso de campos de estruturas é tão comum em programas C que a linguagem oferece outro operador de acesso, que permite acessar campos a partir do ponteiro da estrutura. Este operador é composto por um traço seguido de um sinal de maior, formando uma seta (->). Portanto, podemos reescrever a atribuição anterior fazendo:

pp->x = 12.0;

Em resumo, se temos uma variável estrutura e queremos acessar seus campos, usamos o operador de acesso ponto (p.x); se temos uma variável ponteiro para estrutura, usamos o operador de acesso seta (pp->x). Seguindo o raciocínio, se temos o ponteiro e queremos acessar o endereço de um campo, fazemos &pp->x!

Passagem de estruturas para funções

Para exemplificar a passagem de variáveis do tipo estrutura para funções, podemos reescrever o programa simples, mostrado anteriormente, que captura e imprime as coordenadas de um ponto qualquer. Inicialmente, podemos pensar em escrever uma função que imprima as coordenadas do ponto. Esta função poderia ser dada por:

void imprime (struct ponto p) {
printf("O ponto fornecido foi: (%.2f,%.2f)\n", p.x, p.y);
}

A passagem de estruturas para funções se processa de forma análoga à passagem de variáveis simples, porém exige uma análise mais detalhada. Da forma como está escrita no código acima, a função recebe uma estrutura inteira como parâmetro. Portanto, faz-se uma cópia de toda a estrutura para a pilha e a função acessa os dados desta cópia. Existem dois pontos a serem ressaltados. Primeiro, como em toda passagem por valor, a função não tem como alterar os valores dos elementos da estrutura original (na função imprime isso realmente não é necessário, mas seria numa função de leitura). O segundo ponto diz respeito à eficiência, visto que copiar uma estrutura inteira para a pilha pode ser uma operação custosa (principalmente se a estrutura for muito grande). É mais conveniente passar apenas o ponteiro da estrutura, mesmo que não seja necessário alterar os valores dos elementos dentro da função, pois copiar um ponteiro para a pilha é muito mais eficiente do que copiar uma estrutura inteira. Um ponteiro ocupa em geral 4 bytes, enquanto uma estrutura pode ser definida com um tamanho muito grande. Desta forma, uma segunda (e mais adequada) alternativa para escrevermos a função imprime é:

void imprime (struct ponto* pp) {
printf("O ponto fornecido foi: (%.2f,%.2f)\n", pp->x, pp->y);
}

Podemos ainda pensar numa função para ler a hora do evento. Observamos que, neste caso, obrigatoriamente devemos passar o ponteiro da estrutura, caso contrário não seria possível passar ao programa principal os dados lidos:

void captura (struct ponto* pp) {
printf("Digite as coordenadas do ponto(x y): ");
scanf("%f %f", &p->x, &p->y);
}

Com estas funções, nossa função main ficaria como mostrado abaixo.

int main (void) {
struct ponto p;
captura(&p);
imprime(&p);
return 0;
}


Confira o código completo:

#include <stdio.h>
#include <stdlib.h>

struct ponto {
    float x;
    float y;
};

void imprime(struct ponto* pp){
    printf("O ponto fornecido foi: (%.2f,%.2f)\n", pp->x, pp->y);
}

void captura(struct ponto* pp){
    printf("Digite as coordenadas do ponto(x y): ");
    scanf("%f %f", &pp->x, &pp->y);
}

int main(){
    struct ponto p;
    captura(&p);
    imprime(&p);
    system("pause");
    return 0;

}
[Curso de AeED - Aula 01] - Ponteiros + Registros [Curso de AeED - Aula 01] - Ponteiros + Registros Reviewed by Vinicius dos Santos on 16:28:00 Rating: 5

Nenhum comentário

Escreve ai sua opinião!