Ir para o conteúdo

CCT-UFCA/Ciência da Computação/Introdução à Programação/Vetores, Matrizes e Estrutura (struct, enum)

De Wikiversidade

Vetores, Matrizes e Estrutura (struct, enum)

[editar | editar código]

A construção de programas robustos e organizados em C passa necessariamente pelo entendimento profundo das estruturas compostas oferecidas pela linguagem. Dentre essas, destacam‐se as structs e as enums, que permitem ao programador agrupar dados heterogêneos e codificar constantes simbólicas de forma clara e legível. A seguir, apresentamos uma exposição detalhada desses dois conceitos, ilustrando seus pontos‐chave com exemplos práticos e mostrando como eles se relacionam com recursos como ponteiros e alocação dinâmica.

Structs em C

[editar | editar código]

Uma struct (ou “estrutura”) é um tipo de dado composto que reúne, sob um mesmo identificador, vários campos (ou “membros”) de tipos diferentes. Enquanto variáveis simples armazenam dados de um único tipo, as structs permitem representar entidades mais complexas — por exemplo, uma pessoa, um produto ou uma tarefa — de modo fiel ao mundo real.

Definição de uma struct

[editar | editar código]

A sintaxe básica para declarar uma struct em C é:

struct NomeDaStruct {
    tipo1 campo1;
    tipo2 campo2;
    ...
    tipoN campoN;
};

Por exemplo, imagine que precisamos modelar, em um sistema acadêmico, as informações de um aluno: seu nome (uma cadeia de caracteres), seu número de matrícula (inteiro) e sua média final (ponto flutuante). Isso pode ser feito da seguinte forma:

struct Aluno {
    char nome[50];
    int matricula;
    float media_final;
};

Neste exemplo, o tipo struct Aluno agrupa três campos de tipos distintos. Cada objeto conterá as informações de um único aluno.

Declaração e inicialização

[editar | editar código]

Após a definição, declara‐se uma variável do tipo struct Aluno assim:

struct Aluno a1;

Para atribuir valores, utiliza‐se o operador ponto (.), lembrando que vetores de caracteres não podem ser atribuídos diretamente por "=", mas precisam de funções como strcpy ou inicializadores literais no momento da declaração. Por exemplo:

#include <string.h>

strcpy(a1.nome, "Ana Silva");
a1.matricula = 2023101;
a1.media_final = 8.75f;

Também é possível inicializar todos os campos de uma só vez, ao declarar a variável:

struct Aluno b1 = { "Bruno Souza", 2023123, 9.2f };

Nesse caso, o compilador preenche b1.nome com a string fornecida (até o caractere nulo), b1.matricula com 2023123 e b1.media_final com 9.2.

Arrays de structs (lista estática)

[editar | editar código]

Quando trabalhamos com arrays de structs (também chamados de “listas estáticas de structs”), estamos combinando duas ideias básicas: (1) o conceito de array, que armazena vários elementos de um mesmo tipo em posições de memória contíguas, e (2) o conceito de struct, que agrupa diversos campos de tipos possivelmente diferentes em uma única entidade. A seguir, veremos com mais detalhes como isso funciona em C:

struct Aluno turma[30];  // Turma com até 30 alunos

Cada elemento de turma é uma variável do tipo struct Aluno. Para acessar o nome do segundo aluno, por exemplo:

strcpy(turma[1].nome, "Carla Ferreira");
turma[1].matricula = 2023150;
turma[1].media_final = 7.8f;

Também podemos complementar nosso exemplo criando uma função para mostrar os dados de um aluno:

void mostra_aluno(struct Aluno aluno) {
    printf("Nome: %s\n", aluno.nome);
    printf("Matrícula: %d\n", aluno.matricula);
    printf("Média: %.2f\n", aluno.media_final);
}

Observe que essa função recebe um aluno (do tipo struct Aluno) e mostra seus dados utilizando a notação ponto. Os dados do aluno recebidos na função serão uma cópia do aluno passado. Isso pode ser melhorado utilizando ponteiros, de forma que será passada a referência para um aluno, em vez de copiar seus dados, que faria ocupar quase o dobro da memória necessária. Veja:

void mostra_aluno(struct Aluno *ptr_aluno) {
    printf("Nome: %s\n", (*ptr_aluno).nome);
    printf("Matrícula: %d\n", (*ptr_aluno).matricula);
    printf("Média: %.2f\n", (*ptr_aluno).media_final);
}

Agora, recebendo uma referência para a variável com os dados do aluno, podemos acessar os valores utilizando o conteúdo do ponteiro, como visto em cada printf. (*ptr_aluno) representa o conteúdo para o qual o ponteiro ptr_aluno está apontando. Podemos fazer uma pequena melhoria na sintaxe desse código utilizando um operador que abstrai esse acesso ao ponteiro: o operador ->. Veja como isso simplifica:

void mostra_aluno(struct Aluno *aluno) {
    printf("Nome: %s\n", aluno->nome);
    printf("Matrícula: %d\n", aluno->matricula);
    printf("Média: %.2f\n", aluno->media_final);
}

Por fim, veja como ficaria o programa completo, já com alocação dinâmica para uma turma:

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

struct Aluno {
    char nome[50];
    int matricula;
    float media_final;
};

void mostra_aluno(struct Aluno *aluno) {
    printf("Nome; %s\n", aluno->nome);
    printf("Matrícula; %d\n", aluno->matricula);
    printf("Média; %.2f\n", aluno->media_final);
}

int main() {
    
    int n;
    
    printf("Quantos alunos deseja cadastrar? ");
    scanf("%d", &n);
    getchar();
    
    // cria uma turma de 'n' alunos
    struct Aluno *turma = malloc(n * sizeof(struct Aluno));
    if (turma == NULL) {
        fprintf(stderr, "Erro de alocação de memória.\n");
        return 1;
    }

    // Preenchendo dados de cada aluno
    for (int i = 0; i < n; i++) {
        printf("Aluno %d:\n", i + 1);
        printf("  Nome: ");
        fgets(turma[i].nome, sizeof(turma[i].nome), stdin);
        // Remove eventual '\n' do final da string
        turma[i].nome[strcspn(turma[i].nome, "\n")] = '\0';
        
        printf("  Matrícula: ");
        scanf("%d", &turma[i].matricula);
        printf("  Média final: ");
        scanf("%f", &turma[i].media_final);
        getchar(); // consome o \n
    }

    // Exibindo informações
    for (int i = 0; i < n; i++) {
        mostra_aluno(&turma[i]);
        puts("");
    }

    return 0;
}

Enums em C

[editar | editar código]

O enum (ou “enumeração”) é um tipo de dado que associa nomes simbólicos a valores inteiros. Seu principal benefício é tornar o código mais legível e autocontido, evitando “números mágicos” (constantes literais sem significado explícito) e facilitando a manutenção.

Declaração básica de enum

[editar | editar código]

A sintaxe para declarar um enum é:

enum NomeDoEnum {
    CONSTANTE1,
    CONSTANTE2,
    ...,
    CONSTANTEM
};

Por padrão, CONSTANTE1 recebe o valor inteiro 0, CONSTANTE2 recebe 1, e assim por diante. Pode‐se, no entanto, atribuir valores iniciais e específicos:

enum DiaSemana {
    DOMINGO = 0,
    SEGUNDA = 1,
    TERCA = 2,
    QUARTA = 3,
    QUINTA = 4,
    SEXTA = 5,
    SABADO = 6
};

Ou mesmo:

enum StatusPedido {
    PENDENTE = 1,
    PROCESSANDO = 2,
    ENVIADO = 4,     // Às vezes definem valores não sequenciais
    ENTREGUE = 8
};

Nesse último caso, pode‐se usar constantes que facilitam operações de “bitmask” (máscaras de bits) ou sinalizam valores específicos.

Uso de enums

[editar | editar código]

Após a definição, declara‐se uma variável do tipo enum:

enum DiaSemana hoje;
hoje = QUARTA;

if (hoje == QUARTA) {
    printf("Hoje é quarta‐feira.\n");
}

Internamente, enum é tratado como inteiro, mas o compilador faz checagem de tipo quando atribui ou compara com literais. Evita‐se, assim, comparar diretamente com valores numéricos (por exemplo, if (hoje == 3) perde clareza: deveria ser if (hoje == QUARTA)).

Enum em conjunto com struct

[editar | editar código]

A combinação de enum e struct é bastante comum para representar estados ou categorias de um objeto complexo. Por exemplo, num sistema de pedidos de uma loja, temos:

enum StatusPedido {
    PENDENTE,
    PROCESSANDO,
    ENVIADO,
    ENTREGUE
};

struct Pedido {
    int numero;
    char cliente[100];
    float valor_total;
    enum StatusPedido status;
};

Isso permite que cada instância de Pedido mantenha seu estado de forma clara:

struct Pedido p1;
p1.numero = 1234;
strcpy(p1.cliente, "Maria Oliveira");
p1.valor_total = 250.75f;
p1.status = PROCESSANDO;

...

if (p1.status == PROCESSANDO) {
    printf("O pedido %d de %s está em processamento.\n", p1.numero, p1.cliente);
}

Se, em vez de enum, utilizássemos inteiros puros para representar “pendente=0, processando=1, ...”, perderíamos a clareza do código, pois a equipe de desenvolvimento teria de memorizar o significado de cada número.

Definindo valores explícitos

[editar | editar código]

Em algumas aplicações, é conveniente que o enum tenha valores predefinidos que não sejam sequenciais ou que comecem de 1 ou outro offset. Por exemplo:

enum NívelAcesso {
    VISITANTE = 1,
    COMUM = 2,
    MODERADOR = 5,
    ADMINISTRADOR = 10
};

Dessa forma, ao comparar permissões, pode‐se usar laços aritméticos ou intervalos: alguém com nível ≥ 5 já tem privilégios de moderador, etc. Noutras situações, define‐se valores que servem de “flags” para operações bit a bit:

enum AtributosArquivo {
    LEITURA   = 1 << 0, // 0001
    ESCRITA   = 1 << 1, // 0010
    EXECUCAO  = 1 << 2, // 0100
    OCULTO    = 1 << 3  // 1000
};

Assim, para representar um arquivo com atributos de “leitura” e “execução”, combinam‐se bits:

int atributos = LEITURA | EXECUCAO; // 0101

Para testar:

if (atributos & EXECUCAO) {
    printf("Arquivo executável.\n");
}