Aula 27b — Structs Parte 2 de 2

Operador seta, structs aninhadas, arrays de structs, sizeof e padding

Esta é a continuação da Aula 27a. Os exemplos aqui assumem familiaridade com struct, typedef e passagem por ponteiro — temas da Parte 1.

6. Operador Seta (->) vs. Ponto (.)

Existem dois operadores de acesso a campos de struct — qual usar depende se você tem a variável diretamente ou um ponteiro para ela:

Situação Operador Exemplo Equivalente
Variável é a própria struct . (ponto) aluno.nota —
Variável é um ponteiro para struct -> (seta) ptr->nota (*ptr).nota
typedef struct { int x; int y; } Ponto;

Ponto  p  = {3, 4};
Ponto *pp = &p;

/* Estas três expressões acessam o mesmo campo: */
printf("%d\n", p.x);        // variável — usa ponto
printf("%d\n", pp->x);     // ponteiro — usa seta
printf("%d\n", (*pp).x);   // desreferência explícita + ponto (verboso)

/* ❌ Erros comuns: */
// printf("%d\n", p->x);   — p é struct, não ponteiro: use ponto
// printf("%d\n", pp.x);   — pp é ponteiro: use seta
Java — sempre ponto, mesmo com referência
Aluno a = new Aluno();
a.nota = 8.5f;    // Java usa . sempre

// O objeto em Java é sempre
// acessado por referência,
// mas a sintaxe é sempre ponto
C — ponto ou seta, dependendo do tipo
Aluno  a;         // struct direta
Aluno *p = &a;   // ponteiro para struct

a.nota  = 8.5f;  // ponto
p->nota = 8.5f;  // seta (modifica o mesmo a)
Regra mnemônica: "Ponto para variável, seta para ponteiro." Ou: se o tipo na declaração tem *, use ->. Se não tem *, use ..

7. Structs Aninhadas

Um campo de uma struct pode ser outra struct — o equivalente de um atributo cujo tipo é outra classe em Java:

#include <stdio.h>
#include <string.h>

typedef struct {
    int dia;
    int mes;
    int ano;
} Data;

typedef struct {
    char  nome[60];
    char  cpf[15];
    Data  nascimento;    // campo do tipo Data (struct dentro de struct)
    float salario;
} Funcionario;

void exibir(const Funcionario *f) {
    printf("Nome:         %s\n",   f->nome);
    printf("CPF:          %s\n",   f->cpf);
    printf("Nascimento:   %02d/%02d/%04d\n",
           f->nascimento.dia,      // acesso: seta + ponto
           f->nascimento.mes,
           f->nascimento.ano);
    printf("Salário:      R$ %.2f\n\n", f->salario);
}

int main() {
    Funcionario f1 = {
        .nome        = "Carlos Mota",
        .cpf         = "123.456.789-00",
        .nascimento  = {.dia = 15, .mes = 8, .ano = 1990},
        .salario     = 5800.0f
    };

    Funcionario f2;
    strcpy(f2.nome, "Beatriz Lima");
    strcpy(f2.cpf,  "987.654.321-00");
    f2.nascimento.dia = 22;
    f2.nascimento.mes = 3;
    f2.nascimento.ano = 1995;
    f2.salario = 4300.0f;

    exibir(&f1);
    exibir(&f2);

    return 0;
}
Nome: Carlos Mota
CPF: 123.456.789-00
Nascimento: 15/08/1990
Salário: R$ 5800.00

Nome: Beatriz Lima
CPF: 987.654.321-00
Nascimento: 22/03/1995
Salário: R$ 4300.00
Acesso a struct aninhada: f->nascimento.dia — seta para o primeiro nível (f é ponteiro para Funcionario), ponto para o segundo (nascimento é um campo direto, não ponteiro). Em Java seria f.nascimento.dia sempre.

8. Arrays de Structs

Arrays de structs são extremamente comuns — é a forma natural de representar coleções de entidades como turmas, catálogos ou filas:

#include <stdio.h>
#include <string.h>

typedef struct {
    char  nome[40];
    float preco;
    int   estoque;
} Produto;

/* Encontra o produto mais barato — retorna ponteiro para ele */
Produto *maisBarato(Produto *cat, int n) {
    Produto *menor = &cat[0];
    for (int i = 1; i < n; i++)
        if (cat[i].preco < menor->preco)
            menor = &cat[i];
    return menor;
}

/* Ordena por preço (selection sort) */
void ordenarPorPreco(Produto *cat, int n) {
    for (int i = 0; i < n - 1; i++) {
        int minIdx = i;
        for (int j = i + 1; j < n; j++)
            if (cat[j].preco < cat[minIdx].preco)
                minIdx = j;
        if (minIdx != i) {
            Produto temp  = cat[i];    // structs podem ser copiadas por =
            cat[i]        = cat[minIdx];
            cat[minIdx]   = temp;
        }
    }
}

int main() {
    Produto catalogo[] = {
        {"Caderno",   12.90f, 50},
        {"Caneta",     2.50f, 200},
        {"Mochila",   89.90f, 15},
        {"Borracha",   1.80f, 100},
        {"Régua",      4.50f, 75}
    };
    int n = 5;

    Produto *barato = maisBarato(catalogo, n);
    printf("Mais barato: %s (R$ %.2f)\n\n", barato->nome, barato->preco);

    ordenarPorPreco(catalogo, n);

    printf("Catálogo ordenado por preço:\n");
    printf("%-15s %8s %8s\n", "Produto", "Preço", "Estoque");
    printf("-------------------------------\n");
    for (int i = 0; i < n; i++)
        printf("%-15s R$%6.2f %8d\n",
               catalogo[i].nome,
               catalogo[i].preco,
               catalogo[i].estoque);

    return 0;
}
Mais barato: Borracha (R$ 1.80)

Catálogo ordenado por preço:
Produto Preço Estoque
-------------------------------
Borracha R$ 1.80 100
Caneta R$ 2.50 200
Régua R$ 4.50 75
Caderno R$ 12.90 50
Mochila R$ 89.90 15
Cópia de structs com =: Ao contrário de arrays (que decaem para ponteiro), structs em C podem ser copiadas com = — o compilador copia campo a campo automaticamente. Isso é equivalente a criar um novo objeto com os mesmos valores em Java. Por isso o swap na função de ordenação funciona corretamente com uma variável Produto temp.

9. sizeof e Padding

O tamanho de uma struct não é necessariamente a soma dos tamanhos dos seus campos. O compilador pode inserir bytes de preenchimento (padding) entre campos para garantir alinhamento de memória eficiente:

#include <stdio.h>

typedef struct {
    char  c;      // 1 byte
    int   i;      // 4 bytes — alinhado em múltiplo de 4
    char  c2;     // 1 byte
} Exemplo;

typedef struct {
    int   i;      // 4 bytes
    char  c;      // 1 byte
    char  c2;     // 1 byte
} ExemploOtimizado;

int main() {
    printf("sizeof(char)              = %zu\n", sizeof(char));
    printf("sizeof(int)               = %zu\n", sizeof(int));
    printf("sizeof(Exemplo)           = %zu\n", sizeof(Exemplo));
    printf("sizeof(ExemploOtimizado)  = %zu\n", sizeof(ExemploOtimizado));
    return 0;
}
sizeof(char) = 1
sizeof(int) = 4
sizeof(Exemplo) = 12 ← padding: 1+3pad+4+1+3pad
sizeof(ExemploOtimizado) = 8 ← 4+1+1+2pad
Para minimizar padding: Declare os campos em ordem decrescente de tamanho — maior para menor. Isso reduz o espaço desperdiçado. Em Java esse problema não existe — a JVM organiza os campos internamente de forma otimizada.

10. Comparação Geral — C vs. Java

Aspecto C (struct) Java (class)
Sintaxe de declaração struct Nome { campos; }; class Nome { campos; métodos; }
Métodos ❌ Não — funções ficam fora da struct ✅ Sim — métodos são parte da classe
Construtor ❌ Não — usar função de inicialização ✅ Sim — construtores automáticos
Encapsulamento ❌ Todos os campos são públicos ✅ private, protected, public
Herança ❌ Não suportada ✅ extends
Passagem para função Por valor (cópia) ou por ponteiro (explícito) Referência automática (implícita)
Acesso a campos var.campo ou ptr->campo Sempre obj.campo
Cópia com = ✅ Copia todos os campos ❌ Copia apenas a referência
Padding de memória ⚠️ Visível — ordem dos campos afeta sizeof ✅ Transparente — JVM gerencia
Resumo mental para quem vem do Java: Pense em struct como uma classe Java que só tem atributos públicos, sem construtores, sem métodos e sem herança. As "funções membro" ficam fora da struct e recebem um ponteiro como primeiro parâmetro — é o padrão que as aulas de listas lineares (Aulas 29+) vão explorar extensivamente.

Quiz — Aula 27b

1. Dado Aluno *p = &turma[0];, como acessar o campo nota corretamente?

2. Dado Funcionario *f onde nascimento é um campo do tipo Data (não ponteiro), como acessar ano?

3. Por que o sizeof de uma struct pode ser maior que a soma dos tamanhos dos seus campos?

4. Em Java, toda classe tem modificadores como private. Em C, structs: