Prática: Tipos Personalizados

Você já aprendeu o suficiente — agora é hora de construir algo. Como aprender a cozinhar: só ler receitas não basta; você tem que entrar na cozinha. Combine structs, memória dinâmica e E/S de arquivos para criar programas funcionais.

Projeto 1: Sistema de Gerenciamento de Notas

Requisitos

Design da Estrutura de Dados

C
typedef struct {
    char name[32];
    char id[16];
    int chinese;
    int math;
    int english;
} Student;

typedef struct {
    Student *data;
    int count;
    int capacity;
} StudentList;

StudentList gerencia alunos com um array dinâmico — capacidade inicial é 4, expandindo quando cheio.

Implementação Principal

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

typedef struct {
    char name[32];
    char id[16];
    int chinese;
    int math;
    int english;
} Student;

typedef struct {
    Student *data;
    int count;
    int capacity;
} StudentList;

void list_init(StudentList *list) {
    list->capacity = 4;
    list->count = 0;
    list->data = (Student *)malloc(sizeof(Student) * list->capacity);
}

void list_free(StudentList *list) {
    free(list->data);
    list->data = NULL;
    list->count = 0;
    list->capacity = 0;
}

void list_expand(StudentList *list) {
    if (list->count < list->capacity) return;
    list->capacity *= 2;
    Student *tmp = (Student *)realloc(list->data, sizeof(Student) * list->capacity);
    if (tmp) list->data = tmp;
}

void list_add(StudentList *list, const Student *s) {
    list_expand(list);
    list->data[list->count++] = *s;
}

int total_score(const Student *s) {
    return s->chinese + s->math + s->english;
}

double avg_score(const Student *s) {
    return total_score(s) / 3.0;
}

Student *find_by_id(StudentList *list, const char *id) {
    for (int i = 0; i < list->count; i++) {
        if (strcmp(list->data[i].id, id) == 0) {
            return &list->data[i];
        }
    }
    return NULL;
}

int cmp_by_total(const void *a, const void *b) {
    int ta = total_score((const Student *)a);
    int tb = total_score((const Student *)b);
    return tb - ta;
}

void sort_by_total(StudentList *list) {
    qsort(list->data, list->count, sizeof(Student), cmp_by_total);
}

void print_student(const Student *s) {
    printf("%-6s %-10s Portugues:%-3d Matematica:%-3d Ingles:%-3d Total:%-4d Media:%.1f\n",
           s->id, s->name, s->chinese, s->math, s->english,
           total_score(s), avg_score(s));
}

void print_all(const StudentList *list) {
    printf("Matr  Nome        Portugues Matematica Ingles Total Media\n");
    printf("----------------------------------------------------------\n");
    for (int i = 0; i < list->count; i++) {
        print_student(&list->data[i]);
    }
}

int save_to_file(const StudentList *list, const char *filename) {
    FILE *fp = fopen(filename, "w");
    if (!fp) return -1;
    fprintf(fp, "%d\n", list->count);
    for (int i = 0; i < list->count; i++) {
        Student *s = &list->data[i];
        fprintf(fp, "%s %s %d %d %d\n", s->id, s->name,
                s->chinese, s->math, s->english);
    }
    fclose(fp);
    return 0;
}

int load_from_file(StudentList *list, const char *filename) {
    FILE *fp = fopen(filename, "r");
    if (!fp) return -1;
    int n;
    fscanf(fp, "%d", &n);
    for (int i = 0; i < n; i++) {
        Student s;
        fscanf(fp, "%s %s %d %d %d", s.id, s.name,
               &s.chinese, &s.math, &s.english);
        list_add(list, &s);
    }
    fclose(fp);
    return 0;
}

Exemplo

C
int main(void) {
    StudentList list;
    list_init(&list);

    Student s1 = {"001", "Zhang", 85, 92, 78};
    Student s2 = {"002", "Li", 90, 88, 95};
    Student s3 = {"003", "Wang", 72, 65, 80};

    list_add(&list, &s1);
    list_add(&list, &s2);
    list_add(&list, &s3);

    printf("--- Todos os Alunos ---\n");
    print_all(&list);

    sort_by_total(&list);
    printf("\n--- Ordenados por Total ---\n");
    print_all(&list);

    Student *found = find_by_id(&list, "001");
    if (found) {
        printf("\nEncontrado 001: %s Total=%d\n", found->name, total_score(found));
    }

    save_to_file(&list, "students.dat");
    printf("\nSalvo em students.dat\n");

    list_free(&list);
    return 0;
}
▶ Experimente
TEXT
--- Todos os Alunos ---
Matr  Nome        Portugues Matematica Ingles Total Media
----------------------------------------------------------
001    Zhang       Portugues:85  Matematica:92  Ingles:78  Total:255  Media:85.0
002    Li          Portugues:90  Matematica:88  Ingles:95  Total:273  Media:91.0
003    Wang        Portugues:72  Matematica:65  Ingles:80  Total:217  Media:72.3

--- Ordenados por Total ---
Matr  Nome        Portugues Matematica Ingles Total Media
----------------------------------------------------------
002    Li          Portugues:90  Matematica:88  Ingles:95  Total:273  Media:91.0
001    Zhang       Portugues:85  Matematica:92  Ingles:78  Total:255  Media:85.0
003    Wang        Portugues:72  Matematica:65  Ingles:80  Total:217  Media:72.3

Encontrado 001: Zhang Total=255

Salvo em students.dat

Projeto 2: Gerenciamento de Contatos

Requisitos

Design da Estrutura de Dados

C
typedef struct {
    char name[32];
    char phone[16];
    char email[40];
    char group[16];
} Contact;

typedef struct {
    Contact *data;
    int count;
    int capacity;
} ContactList;

Implementação Principal

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

typedef struct {
    char name[32];
    char phone[16];
    char email[40];
    char group[16];
} Contact;

typedef struct {
    Contact *data;
    int count;
    int capacity;
} ContactList;

void clist_init(ContactList *list) {
    list->capacity = 4;
    list->count = 0;
    list->data = (Contact *)malloc(sizeof(Contact) * list->capacity);
}

void clist_free(ContactList *list) {
    free(list->data);
    list->data = NULL;
    list->count = 0;
    list->capacity = 0;
}

void clist_expand(ContactList *list) {
    if (list->count < list->capacity) return;
    list->capacity *= 2;
    Contact *tmp = (Contact *)realloc(list->data, sizeof(Contact) * list->capacity);
    if (tmp) list->data = tmp;
}

void clist_add(ContactList *list, const Contact *c) {
    clist_expand(list);
    list->data[list->count++] = *c;
}

int clist_remove(ContactList *list, const char *name) {
    for (int i = 0; i < list->count; i++) {
        if (strcmp(list->data[i].name, name) == 0) {
            list->data[i] = list->data[list->count - 1];
            list->count--;
            return 0;
        }
    }
    return -1;
}

Contact *clist_find(ContactList *list, const char *name) {
    for (int i = 0; i < list->count; i++) {
        if (strcmp(list->data[i].name, name) == 0) {
            return &list->data[i];
        }
    }
    return NULL;
}

void clist_filter_by_group(const ContactList *list, const char *group) {
    printf("--- Grupo: %s ---\n", group);
    for (int i = 0; i < list->count; i++) {
        if (strcmp(list->data[i].group, group) == 0) {
            printf("%s  %s  %s\n", list->data[i].name,
                   list->data[i].phone, list->data[i].email);
        }
    }
}

void print_contact(const Contact *c) {
    printf("%-8s %-14s %-24s %s\n", c->name, c->phone, c->email, c->group);
}

void print_all_contacts(const ContactList *list) {
    printf("%-8s %-14s %-24s %s\n", "Nome", "Telefone", "Email", "Grupo");
    printf("---------------------------------------------------------\n");
    for (int i = 0; i < list->count; i++) {
        print_contact(&list->data[i]);
    }
}

int save_contacts(const ContactList *list, const char *filename) {
    FILE *fp = fopen(filename, "w");
    if (!fp) return -1;
    fprintf(fp, "%d\n", list->count);
    for (int i = 0; i < list->count; i++) {
        Contact *c = &list->data[i];
        fprintf(fp, "%s %s %s %s\n", c->name, c->phone, c->email, c->group);
    }
    fclose(fp);
    return 0;
}

int load_contacts(ContactList *list, const char *filename) {
    FILE *fp = fopen(filename, "r");
    if (!fp) return -1;
    int n;
    fscanf(fp, "%d", &n);
    for (int i = 0; i < n; i++) {
        Contact c;
        fscanf(fp, "%s %s %s %s", c.name, c.phone, c.email, c.group);
        clist_add(list, &c);
    }
    fclose(fp);
    return 0;
}

Exemplo

C
int main(void) {
    ContactList list;
    clist_init(&list);

    Contact c1 = {"Zhang", "13800001111", "zhangsan@mail.com", "Colega"};
    Contact c2 = {"Li", "13900002222", "lisi@mail.com", "Amigo"};
    Contact c3 = {"Wang", "15000003333", "wangwu@mail.com", "Colega"};
    Contact c4 = {"Zhao", "18600004444", "zhaoliu@mail.com", "Familia"};

    clist_add(&list, &c1);
    clist_add(&list, &c2);
    clist_add(&list, &c3);
    clist_add(&list, &c4);

    printf("--- Todos os Contatos ---\n");
    print_all_contacts(&list);

    clist_filter_by_group(&list, "Colega");

    Contact *found = clist_find(&list, "Li");
    if (found) {
        printf("\nEncontrado Li: %s %s\n", found->phone, found->email);
    }

    clist_remove(&list, "Wang");
    printf("\n--- Apos Remover Wang ---\n");
    print_all_contacts(&list);

    save_contacts(&list, "contacts.dat");
    printf("\nSalvo em contacts.dat\n");

    clist_free(&list);
    return 0;
}
▶ Experimente
TEXT
--- Todos os Contatos ---
Nome     Telefone       Email                   Grupo
---------------------------------------------------------
Zhang    13800001111    zhangsan@mail.com       Colega
Li       13900002222    lisi@mail.com           Amigo
Wang     15000003333    wangwu@mail.com         Colega
Zhao     18600004444    zhaoliu@mail.com        Familia

--- Grupo: Colega ---
Zhang  13800001111  zhangsan@mail.com
Wang  15000003333  wangwu@mail.com

Encontrado Li: 13900002222 lisi@mail.com

--- Apos Remover Wang ---
Nome     Telefone       Email                   Grupo
---------------------------------------------------------
Zhang    13800001111    zhangsan@mail.com       Colega
Li       13900002222    lisi@mail.com           Amigo
Zhao     18600004444    zhaoliu@mail.com        Familia

Salvo em contacts.dat

Revisão dos Padrões-Chave

O Padrão de Array Dinâmico

Ambos os projetos usam o mesmo padrão de array dinâmico:

  1. Struct contém ponteiro de dados + contagem + capacidade
  2. init aloca memória inicial
  3. add verifica a capacidade e usa realloc para expandir quando cheio
  4. free libera a memória e define como NULL

Esta é a forma mais comum de implementar coleções dinâmicas em C. Domine uma vez e você pode reutilizar em qualquer cenário.

O Padrão de E/S de Arquivo

C
fprintf(fp, "%d\n", count);
for (int i = 0; i < count; i++) {
    fprintf(fp, "campo1 campo2 ...\n", ...);
}

Ao ler, primeiro leia a contagem, depois percorra os registros. Note que %s para no espaço em branco — garanta que os campos não contenham espaços, ou use fscanf com formato fixo.

O Truque de Remoção

Ao remover um elemento do meio de um array, sobrescreva-o com o último elemento, depois count--. Isso evita mover muitos elementos mas não preserva a ordem. Se a ordem importa, use memmove para deslocar os elementos subsequentes para frente.

❓ Perguntas Frequentes

P: Por que dobrar a capacidade em vez de adicionar 1? R: Dobrar é uma estratégia de custo amortizado O(1) — o número total de cópias é muito menor do que incrementar 1 a cada vez. Este é um resultado clássico da análise de algoritmos, e o vector do C++ usa a mesma estratégia.

P: Devo salvar arquivos em formato binário ou texto? R: Formato texto é legível, editável manualmente e multiplataforma. Binário é mais compacto e rápido para ler/escrever, mas não é legível por humanos. Texto é mais intuitivo para projetos de aprendizado.

P: E se eu precisar preservar a ordem ao usar a remoção por troca-com-o-último? R: Use memmove para deslocar elementos para frente: memmove(&data[i], &data[i+1], (count-i-1)*sizeof(T)). Ou mude para uma estrutura de lista ligada.**

P: E se um nome contiver espaços? R: scanf/fscanf com %s para no espaço em branco. Use fgets para ler uma linha inteira, ou concorde em usar sublinhados em vez de espaços, ou use um formato de largura fixa como %31[^\n].**

📖 Resumo

📝 Exercícios

  1. Adicione um recurso de "editar notas" ao sistema de gerenciamento de notas: digite uma matrícula e novas notas para atualizar o registro do aluno correspondente
  2. Adicione um recurso de "buscar por número de telefone" ao gerenciador de contatos, e mude o armazenamento para manter os contatos ordenados por nome (mantenha a ordem na inserção)
  3. Divida ambos os projetos em estruturas multi-arquivo: cada projeto deve ter pelo menos um arquivo de declaração .h e um arquivo de implementação .c, com um Makefile correspondente
100%