Ponteiros e Struct
Os ponteiros e struct são conceitos bastante importantes dentro do curso de
estrutura de dados. Geralmente essa disciplina ensina ponteiros em C, visto
que essa linguagem é uma das únicas que permite que o programador manipule
os endereços de memória. A struct em c também faz parte dos conceitos
básicos necessários para compreender a lógica por trás dos códigos
apresentados na disciplina de algoritmos e estrutura de dados. Nessa aula
você irá encontrar a definição de ponteiro e struct, bem como alguns
exemplos de como usar esses conceitos na linguagem C.
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;
Em diversas universidades você vai encontrar pessoas traumatizadas
com esse assunto. Realmente, as vezes esse pequeno conceito pode dar um nó
gigantesco na sua lógica. Porém, fique calmo... não é tão ruim assim.
Ponteiros
Atualmente, é difícil encontrar alguma linguagem de alto nível (C#, Java, Python,
Javascript, etc.) que permite que o programador manipule os endereços de
memória (ponteiros). A linguagem C é uma das únicas que permite
trabalhar o conceito de ponteiro. O ponteiro é um simples tipo de dado,
assim como int, char ou float que você tanto usa. A grande diferença é essa variável do tipo ponteiro pode apenas
armazenar um endereço de memória (em Hexadecimal).
Portanto, por meio deste tipo de variável é possível que o programador
armazene os endereços de memória de outras variáveis. Para simplificar,
geralmente dizemos que a variável ponteiro aponta para uma posição
de memória. O maior problema que os alunos tem ao utilizar ponteiros é
compreender que não estamos trabalhando o valor, mas sim com o
endereço.
Operadores: & e *
Assim como os inteiros são passiveis de ser somados, divididos,
subtraídos, multiplicados, etc. Os ponteiros também possuem operações que
são válidas apenas para esse tipo. O primeiro operador disponível para o
tipo ponteiro é o &. Ele é considerado unário e devolve o endereço de
memória de uma variável. Por exemplo, quando dizemos:
a = &b;
A linguagem compreende que deve-se inserir em a o endereço na memória da
variável b. Esse endereço consiste na posição interna da variável na memória
RAM do computador. Para facilitar as coisas, você pode ler o código dizendo
que o operador & tem o significado: "endereço de".
Outro operador válido para o tipo ponteiro é o asterisco (*). Na verdade,
esse operador é um complemento do operador &. O asterisco também é
considerado unário e o valor da variável localizada no endereço (ou
seja, faz o trabalho inverso de &).
Outro ponto importante é que para realizar a declaração de uma variável do
tipo ponteiro é necessário a inserção de um asterisco (*) na frente de uma
variável de qualquer tipo. Na linguagem C, podemos definir ponteiros para
tipos básicos ou estruturas. Ao definir um ponteiro, não é reservado um
espaço de memória o valor, mas sim para o endereço hexadecimal. Lembre-se
que antes de utilizar um ponteiro, devemos sempre inicializá-lo, ou seja,
colocar um endereço de memória válido para ser acessado
posteriormente.
Os ponteiros podem ser utilizados de duas maneiras: (1) trabalhando com o
endereço armazenado no ponteiro, e (2) trabalhar com a área de memória
apontada pelo ponteiro. Quando você quiser usar o endereço armazenado no
ponteiro, use apenas o seu nome sem o asterisco na frente. Assim, qualquer
operação que for realizada, na verdade será feita no endereço do ponteiro. No entanto, na maioria das vezes, desejamos trabalhar com a memória apontada
pelo ponteiro, seja alterando ou apenas acessando este valor. Para fazer
isso, devemos inserir um asterisco antes do nome do ponteiro. Assim, toda operação que for feita afetará o valor presente no endereço de memória que foi apontado pelo ponteiro. Veja um exemplo de como fazer isso :
#include <stdio.h>
int main (void){
int *p;
int i = 1;
p = &i ; /* pegando o endereço de memória da variável */
printf ("Endereco: %d\n", p);
printf ("Valor : %d\n", *p);
*p = 1992;
printf ("Valor alterado: %d\n", i);
printf ("Endereco : %d\n", p);
return 0;
}
Como passar variáveis para funções usando ponteiros (por referência)
O ponteiro também pode ser utilizado para passar variáveis por referência, ou seja, as variáveis declaradas em outro escopo podem ter seu conteúdo alterado dentro das funções. Para realizar uma passagem por referência é preciso que na declaração de uma função, deve-se
utilize-se o asterisco antes do nome do parâmetro, assim, você indica que o valor naquele endereço será passado como parâmetro.
#include <stdio.h>
void soma (int, int, int *);
int main (void){
int iA;
int iB;
int iR;
printf ("Entre com os valores:");
scanf ("%d %d", &iA, &iB);
printf("Endereco de iR = %d\n", &iR);
soma (iA, iB, &iR);/* passando o endereço de memória*/
printf ("Soma : %d\n", iR);
return 0;
}
void soma (int piA, int piB, int * piR){
printf("Endereco de piR = %d\n", piR);
/* Nesse ponto o valor é colocado diretamente na memória*/
*piR = piA + piB;
return;
}
Struct (registros)
Ao usar a linguagem C, sabemos que os tipos básicos de dados - também chamados de tipos primitivos (char, int, float, etc.) e seus
respectivos ponteiros que podem ser usados para declarar variáveis. Estruturar tipos de dados complexos significa que os dados são informações são compostas
por diversos campos, para isso precisamos de mecanismos que nos permitam agrupar
tipos diferentes.
O tipo estrutura (struct)
A linguagem C permite definir um tipo de dado no qual os campos são compostos de
vários valores de tipos mais simples. Por exemplo, vamos considerar o
desenvolvimento de um software que manipula pontos no plano cartesiano.
Cada um dos pontos poderiam ser representados por suas coordenadas x e y, sendo que elas são dadas
por valores reais. Sem um mecanismo que possa agrupar as coordenadas,
teríamos que representar os pontos com duas variáveis independentes.
float x;
float y;
A grande desvantagem é que os valores ficam dissociados e cabe ao programador não misturar a
coordenada x de um ponto com a coordenada y de outro. Visando facilitar este
trabalho, um dos recursos da linguagem C é o agrupamento de dados em estruturas/registros (struct). Uma
estrutura visa agrupar diversas variáveis dentro
de um único contexto, por exemplo, podemos definir uma estrutura
ponto que contenha as duas variáveis (x e y). Veja um exemplo de como fazer isso:
struct ponto {
float x;
float y;
};
Dessa forma, a podemos dizer que a estrutura "ponto" se torna um tipo e podemos declarar variáveis deste tipo:
struct ponto p;
Esta linha de código
declara a variável com nome de "p" do tipo struct ponto. Você pode acessar os elementos de
uma estrutura usando do operador de acesso “ponto”
(.). Veja um exemplo:
ponto.x = 10.0;
ponto.y = 5.0;
Os elementos dessa estrutura são manipulados da mesma forma que variáveis
simples. É possível acessar seus valores, atribuir novos valores, acessar endereços, etc. Veja um exemplo que captura e imprime as coordenadas de um ponto.
/* Aqui você define a estrutura */
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;
}
No código apresentado, a variável p é uma variável local como outra
qualquer. Sendo assim, quando a declaração é encontrada pelo compilador é feita a alocação na pilha de execução contendo espaço suficiente para
armazenar todos os campos da estrutura (nesse caso, dois números reais). Quando desejamos acessar o endereço de uma estrutura isso é feito da mesma
forma que com variáveis simples: você escreve &p.x.
Ponteiro para estruturas
Da mesma forma que podemos declarar variáveis do tipo estrutura, também é possível declarar variáveis do tipo ponteiro para estrutura, veja um exemplo:
struct ponto *pp;
Quando a variável pp estiver armazenando um endereço de uma estrutura, será possível acessar os
campos dessa estrutura indiretamente usando seu ponteiro:
(*pp).x = 4.0;
Note que nesse caso em específico os parênteses são indispensáveis, visto que o operador “conteúdo de”
tem precedência menor que o operador de acesso.
Fazer acesso aos campos de
estruturas é muito comum em programas C, para facilitar esse acesso, a linguagem oferece outro operador que permite acessar diretamente os campos a partir do ponteiro da estrutura. Pode parecer estranho, mas este operador é composto por um traço seguido de um sinal de maior, formando algo como uma seta (->). Assim, você pode reescrever a operação anterior como:
pp->x = 4.0;
Passagem de estruturas para funções
Existe ainda a possibilidade de passar variáveis do tipo estrutura para funções. Assim, o programa simples que mostramos anteriormente poderia ser reescrito adicionando uma função para acesso do ponto e impressão:
void imprime (struct ponto p) {
printf("Ponto: (%.2f,%.2f)\n", p.x, p.y);
}
Passar estruturas para funções é bem parecido com a passagem de variáveis simples, porém, é preciso se atentar para alguns detalhes. Observando o código apresentado acima, a função recebe uma estrutura
inteira como parâmetro, portanto, na execução do programa será feita uma cópia de toda a estrutura para
a pilha de execução.
Existem dois pontos que precisam de atenção: (1) como em toda passagem por valor, a função não altera os valores dos elementos da estrutura original (2) copiar uma estrutura
inteira para a pilha pode ser uma operação que custa muito processamento e memória.
Uma alternativa para isso é utilizar a passagem por referência:
void imprime (struct ponto* pp) {
printf("O ponto fornecido foi: (%.2f,%.2f)\n", pp->x, pp->y);
}
#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;
}
Post a Comment