Passando structs por referência para funções - O operador ->

Antes de mais nada, vamos deixar bem claro que, à rigor, não existe passagem por referência em linguagem C.
Mas por que vemos tanto falarem sobre isso, se não existe?

Na verdade, o que existe é uma espécie de 'truque', que é passar o endereço de memória, através de ponteiros, para funções, simulando uma passagem por referência.

Então não há problema em falar de passagem por referência em C, apenas use seu bom senso.


Como alterar uma struct em uma função

Ok, ler os dados de uma struct através de funções é bem simples e é feito exatamente da mesma maneira que fizemos com outros tipos de dados.

Porém, só ler nem sempre é tão útil assim. E quando quisermos alterar uma struct?
Vamos ter que alterar na main() ?
Claro que não! Já explicamos que entupir a função main() é um péssimo hábito de programação. Temos que dividir bem as tarefas de um aplicativo em C em diversas funções, cada uma fazendo uma tarefa bem específica.

Se você lembrar bem, para alterar o valor de uma variável, fizemos uso da passagem por referência, onde enviamos o endereço de memória de uma variável, em vez de uma cópia de seu valor (como é feito na passagem por valor).

Para fazer, a função deve ser declarada de modo a esperar receber, como argumento, um endereço de memória. Ou seja, o parâmetro é um ponteiro. A nossa função preenche seria:
void Preenche(CARRO *car);

E para passarmos a struct ? Temos que passar o endereço de memória dessa estrutura, e isso é feito colocando o operador & antes do nome da struct:
Preenche(&fusca);

Até aí, tudo ok.
O problema vem quando vamos tratar a estrutura dentro da função, pois devemos ter um cuidado especial e é aqui onde os iniciantes costumam se confundir e se complicar.

A função vai receber um ponteiro car de uma estrutura.
Para alterarmos os membros da estrutura, temos que usar o asterisco antes do ponteiro, lembra? O ponteiro em si é só um endereço de memória,  o valor para qual esse ponteiro aponta é obtido quando colocamos um asterisco antes do nome do ponteiro.
Por exemplo, para preenchermos o campo modelo:
gets( (*car).modelo);

E para acessar o elemento que armazena a potência do motor?
(*car).potenciaMotor

E para alterar esse valor, dentro da função scanf(), por exemplo?
scanf("%f", &(*car).potenciaMotor);

Então o código de nossa função que recebe e altera os membros de uma struct fica:
void Preenche(CARRO *car)
{
    printf("Modelo do carro: ");
    gets( (*car).modelo );
 
    printf("Motor: ");
    scanf("%f", &(*car).potenciaMotor);
 
    printf("Ano: ");
    scanf("%d", &(*car).anoFabricacao);
 
    printf("Numero de portas: ");
    scanf("%d", &(*car).numPortas);
}



O operador ->


Embora tenhamos explicado passo por passo como chegar na função anterior, temos que assumir que fica um pouco confuso e complexo nosso código:
&(*car).numPortas

Note também que colocamos o asterisco e o ponteiro em parêntesis separado.
Consegue imaginar o motivo disso?

Vamos imaginar que em vez de: (*car).numPortas
Usássemos: *car.numPortas

Podemos interpretar isso de duas maneiras:
1.       Estamos tentando acessar o elemento ‘numPortas’, que é um membro da struct que é apontada pelo ponteiro ‘car’. Que é o que vínhamos fazendo, usando parêntesis.
2.       Estamos tentando acessar o ponteiro ‘numPortas’, da estrutura ‘car’.

Mas ‘car’ não é uma struct, muito menos tem o membro ‘numPortas’.
Sabemos que ‘car’ é apenas um ponteiro pra uma struct, ou seja, ele é um endereço de memória. E é isso o que acontece quando não colocamos os parêntesis, pois para o C, o ponto final (.) tem procedência maior que o asterisco (*).

Para evitar problemas, usamos o parêntesis em volta do ponteiro ‘car’.
Mas ainda assim fica confuso, e em programação C, ficar confuso não é legal. Por isso, criaram um operador que vai facilitar isso, o ->

-> é simplesmente um atalho que substitui o asterisco e os parêntesis (é fácil esquecer e se confundir com estes), e com o ‘->’ a coisa se torna bem óbvia.
Por exemplo, em vez de: *(ponteiro).
Escrevemos: ponteiro->

No nosso exemplo do carro, as duas linhas seguintes são equivalentes:
scanf("%d", &(*car).anoFabricacao);
scanf("%d", &car->anoFabricacao);

Fica bem fácil de entender o: car -> anoFabricação

Exemplo: Alterando e exibindo structs através de funções em C
Crie um programa na linguagem C que define a estrutura de um carro, altere seus dados através de uma função (use passagem por referência e o operador  ->) bem como use outra função para exibir os membros da struct.

Nosso código, agora fazendo uso do operador -> , será:

#include <stdio.h>
// Curso C Progressivo: www.cprogessivo.net
// O melhor curso de C! Online e gratuito !
// Apostila online, tutorial completo sobre
// a linguagem de programação C !
 
typedef struct
{
    char modelo[30];
    float potenciaMotor;
    int anoFabricacao,
        numPortas;
}CARRO;
 
void Exibe(CARRO car)
{
    printf("\n\tExibindo carro\n");
    printf("Modelo: %s\n", car.modelo);
    printf("Motor: %.1f\n", car.potenciaMotor);
    printf("Ano: %dn", car.anoFabricacao);
    printf("%d portas\n", car.numPortas);
}
 
void Preenche(CARRO *car)
{
    printf("Modelo do carro: ");
    gets( car->modelo );
 
    printf("Motor: ");
    scanf("%f", &car->potenciaMotor);
 
    printf("Ano: ");
    scanf("%d", &car->anoFabricacao);
 
    printf("Numero de portas: ");
    scanf("%d", &car->numPortas);
}
 
int main(void)
{
    CARRO fusca;
    Preenche(&fusca);
    Exibe(fusca);
 
    return 0;
}





Bem mais fácil e óbvio de entender, não acham?
É importante entender bem o conceito do operador -> e da passagem de estruturas para funções, pois faremos uso desses conhecimentos mais a frente em nossa apostila de C, na seção de “Estrutura de dados III: Listas, Filhas e Pilhas”.

8 comentários:

Unknown disse...

Cara você utiliza o gcc como compilador ? Pois pelo que eu saiba o gets não pertence mais a biblioteca padrão e deve se utilizar fgets em vez do gets. Pelo menos é o que vi em alguns sites, como esses:
http://gcc.gnu.org/ml/gcc-help/1999-10n/msg00077.html

Apostila C Progressivo disse...

Olá Unknown,

Cara, essas coisas de 'função perigosa', 'não indicada', 'uso errado', 'uso não recomendado' etc, é complicado.

Se formos ser rigorosos, vamos acabar programando da maneira como se programa em C pro Kernel do Linux, ou seja, de uma maneira bem otimizada e altamente rígida.

Nosso objetivo aqui no curso é ensinar iniciantes, para quem está começando mesmo a aprender.
Creio que se formos ensinar todos esses detalhes e pormenores, vai acabar ficando complicado e desestimulante.

Mas informação nunca é demais, por isso agradecemos e vamos deixar seu comentário aí.
Sinta-se a vontade para nos dar outras dicas como esse e sua opinião.

Nick Arcos disse...

Amigo o código:

void transfereTempPos( Pilha **pPos, Pilha **pTemp, Pilha *aux )
aux = (*(*pTemp)).prox; //(*p).x = 7.0; ou p->x = 7.0;
(*(*pTemp)).prox = *pPos;
*pPos = *pTemp;
*pTemp = aux;


Poderia me explicar o uso de dois asterisco **pPos e (*(pTemp)).prox = *Pos?

Ta meio obscuro isso para mim ainda.

Obrigado

Anônimo disse...

Olá,

Muito bom sua apostila. Observei ali que uma função recebe por referencia uma estrutura. Como seria se esta função recebesse por referencia uma estrutura vetor ?

forte a todos.
obrigado.

Apostila C Progressivo disse...

Olá Anônimo,

O que você quer dizer com 'estrutura vetor' ? Não entendi bem.

lixo virtual disse...

eu nao entendi bem porque usar
&(*car).anoFabricacao, tipo, o asterisco serve pra dizer que queremos o valor, e o "&", pra dizer que queremos o endereço, entao oque voce fez foi pegar o valor do ponteiro car, e depois pegar o endereço do valor do ponteiro. Nao da pra simplesmente colocar car.anoFabricacao, sem o "&" nem o "*"?

João Lucas disse...

lixo virtual,

Ao usar (*car).anoFabricacao na função printf("Ano de Fabricaco: %d, (*car).anoFabricacao); , estamos mostrando o valor do campo anoFabricacao da struct CARRO.

Usando &(*car).anoFabricacao na funcao scanf("%d",&(*car).anoFabricacao); , estamos armazenando um valor que o usuário informou no campo anoFabricaco da struct CARRO.

Não sei se ficou claro, mas é isso.

Guilherme0890 disse...

Tambem estou com a duvida de um amigo acima que não foi respondido porque sua pergunta não foi entendida, mas é o seguinte, no caso do exemplo de criação de carro temos a struct:
typedef struct
{
char modelo[30];
int ano, poras;
}CARRO;
daí se o enunciado pedisse pra cadastrar mais de um carro
CARRO cars[3];
quando eu fosse preencher o modelo do primeiro carro por exemplo na função, como ficaria?
Eu tentei
fgets(cars[cont]->modelo,30,stdin);
porem deu errado,
mudei pra "gets" e tambem deu errado
apresentando o erro "invalid type argument '->'".

Como seria a forma correta?

Gostou desse tutorial de C?
Sabia que o acervo do portal C Progressivo é o mesmo, ou maior que, de um livro ou curso presencial?
E o melhor: totalmente gratuito.

Mas para nosso projeto se manter é preciso divulgação.
Para isso, basta curtir nossa página no Facebook e/ou clicar no botão +1 do Google.
Contamos e precisamos de seu apoio.