Struct

No desenvolvimento de programas, precisamos constantemente armazenar informações que estão fortemente relacionadas. Você deve se lembrar quando, no início dos estudos de programação, precisou desenvolver um programa que armazenava notas de diferentes alunos. A estratégia que tínhamos para isso era usar vetores/matrizes ou criar várias variáveis distintas, como nos exemplos abaixo:

float aluno1_nota1, aluno2_nota1..., alunoN_nota1;
float aluno2_nota1, aluno2_nota2..., alunoN_nota2;
float nota1[];
float nota2[];

Acontece que, se a nota pertence a um aluno que também possui nome e matrícula, esses dados precisam estar vinculados de forma mais clara. Para isso, contaremos com o uso de estruturas de dados heterogêneas. Diferentemente de vetores e matrizes, essas estruturas conseguem armazenar valores de diferentes tipos.

💡 O conceito de Struct

Na linguagem de programação C, podemos usar structs para agrupar dados relacionados. Uma estrutura pode reunir variáveis de diferentes tipos. Para declararmos uma struct em C, usamos o seguinte formato:

struct nomeDaStruct{
    tipo1 variavel1;
    tipo2 variavel2;
    tipo3 variavel3;
};

Aplicando ao caso do estudante com várias notas, teríamos:

struct estudante{
    char rga[15];
    char nome[100];
    float nota1;
    float nota2;
    float nota3;
};

No exemplo acima:

  • estudante é chamada de tag da struct;
  • As variáveis que formam uma struct são chamas de membros da struct;
  • Em uma struct, cada membro deve ter um nome exclusivo.

Veremos a seguir as diferentes estratégias para a declaração de uma struct em nosso programa.

📍 Local de declaração

Embora seja usualmente declaradas antes da função main(), uma struct pode ser declarada dentro da função main() ou de qualquer outra função/procedimento existente no programa. A diferença estará no escopo em que a struct existe.

Declaração global

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

struct estudante{
    char rga[15];
    char nome[100];
    float nota1;
    float nota2;
    float nota3;
};

int main(void){
    return 0;
}

Declaração local

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

int main(void){
    struct estudante{
        char rga[15];
        char nome[100];
        float nota1;
        float nota2;
        float nota3;
    };

    return 0;
}

💫 Criação de variáveis da estrutura

Usando a palavra struct

A criação de variáveis a partir de uma estrutura ocorre da seguinte forma:

struct tag nome;

No nosso exemplo, seria:

struct estudante joao,maria,jose;

Na definição da struct

Podemos aproveitar a criação da struct para já criar variáveis, como no exemplo a seguir:

struct estudante{
    char rga[15];
    char nome[100];
    float nota1;
    float nota2;
    float nota3;
} joao, maria;

Criando um tipo de dado

Para não termos que repetir o termo struct todas as vezes, podemos definir uma estrutura como um tipo de variável:

typedef struct tag nomeDoTipo;
nomeDoTipo nome;

Ou, para simplificar ainda mais, podemos já definir o tipo junto com a estrutura:

typedef struct {
    char nome[30];
    int idade;
} Pessoa;

📅 Acessando os valores de uma estrutura

Podemos usar acessar os valores de uma estrutura apenas inserindo um . para separar o nome da estrutura ao nome da variável.

#include<stdio.h>

typedef struct{
    char nome[30];
    int idade;
} Pessoa;

int main(void){
    Pessoa p = {"José", 10};
    printf("Nome: %s \n Idade: %d", p.nome, p.idade);
}

🏹 Passando struct via ponteiro

Quando passamos uma struct como ponteiro para uma função, podemos alterar os valores de forma bem simples, com o operador ->:

#include<stdio.h>

typedef struct{
    char nome[30];
    int idade;
} Pessoa;

void editar(Pessoa *pes){
    pes->idade = 150;
}

int main(void){
    Pessoa p = {"José", 10};
    editar(&p);
    printf("Nome: %s \n Idade: %d", p.nome, p.idade);
}

📦 Estruturas aninhadas

Uma das principais vantagens que encontramos ao usarmos estruturas consiste está no aninhamento de structs. Essa abordagem nos possibilita uma melhor representação dos dados que manipularemos. Por exemplo:

#include<stdio.h>

typedef struct{
    char nome[30];
    char unidade[10];
} Curso;

typedef struct{
    char nome[30];
    Curso curso;
} Estudante;

int main()
{
    Curso engcomp = {"Eng. Computacao", "FAENG"};
    Curso engcaut = {"Eng. Con. Aut.", "FAENG"};
    Estudante joao = {"Joao", engcomp};
    Estudante maria = {"Maria", engcaut};
    
    printf("Nome: %s\n", joao.nome);
    printf("Curso: %s\n", joao.curso.nome);

    printf("Nome: %s\n", maria.nome);
    printf("Curso: %s\n", maria.curso.nome);

    return 0;
}

🗄️ Alocação de memória de struct

Ao declararmos uma struct em C, precisamos estar cientes de que a ordem das variáveis que a compõem pode afetar o seu tamanho. Observe as estruturas A, B e C a seguir:

typedef struct{
    int a;
    int b;
    char c;
    char d;
} A;

typedef struct{
    int a;
    char b;
    char c;
    int d;
} B;

typedef struct{
    char a;
    int b;
    char c;
    int d;
} C;

Embora cada estrutura contenha dois variáveis inteiras e duas variáveis do tipo char, as estruturas A e B possuem tamanho diferente da estrutura C. Para verificar isso, podemos consultar o tamanho ocupado por cada estrutura:

printf("A: %ld B: %ld C: %ld", sizeof(A), sizeof(B), sizeof(C));

A saída será: 12 para A e B e 16 para C. Isso acontece por conta de uma operação chamada struct padding, que busca otimizar o acesso aos dados na memória.