Advertisement

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);
}


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;

}

Nenhum comentário

Conta pra mim sua opinião!

Fale comigo