Recursos educativos para dominar a linguagem que está na base de sistemas operativos, microcontroladores e toda a computação moderna.
Antes de escrever código, é essencial perceber porque C continua a ser a linguagem mais importante da computação moderna.
O C é uma linguagem de programação de propósito geral criada por Dennis Ritchie nos laboratórios Bell entre 1969 e 1973. Nasceu para reescrever o Unix — e esta origem moldou tudo: C é eficiente, próxima do hardware, mas ainda assim portável.
Hoje, o kernel do Linux, o macOS, o Windows, o Python, o MySQL, o SQLite, o Redis e praticamente todos os sistemas operativos e infraestruturas críticas estão escritos em C ou em linguagens derivadas de C.
Código C compilado compete com Assembly. Zero overhead de runtime, sem garbage collector.
Tu decides quando alocar e libertar memória. Poder absoluto — e responsabilidade absoluta.
O mesmo código corre em x86, ARM, RISC-V, microcontroladores de 8 bits ou supercomputadores.
C++, Java, C#, Python, JavaScript, PHP, Rust — todas beberam da sintaxe e filosofia de C.
Acesso a registos, endereços de memória e periféricos. O Arduino usa C/C++ por uma razão.
Quem entende C, entende como qualquer linguagem funciona por baixo. É o melhor investimento.
Segue esta ordem para uma progressão lógica — cada módulo constrói sobre o anterior.
Ficheiros .c e .h, directivas #include, função main(), compilar com gcc, ciclo editar-compilar-executar.
int, float, double, char, void. Tamanhos com sizeof(). Operadores aritméticos, relacionais e lógicos.
Formatos %d, %f, %s, %c. Leitura de valores do utilizador. Operador & com scanf.
if/else, switch/case, operador ternário. Ciclos: while, do-while, for. break e continue.
Definição, protótipos, parâmetros e retorno. Passagem por valor vs por referência. Recursividade.
Arrays uni e multidimensionais. Strings como arrays de char. Funções de string.h: strlen, strcpy, strcat.
O que é um ponteiro, operadores * e &, aritmética de ponteiros, ponteiros e arrays, ponteiros para funções.
Criar tipos compostos com struct. Enumerações com enum. Simplificar declarações com typedef.
malloc, calloc, realloc, free. Gestão de memória no heap. Leitura e escrita de ficheiros com FILE, fopen, fclose.
Os padrões mais usados no dia a dia de qualquer programador C.
#include <stdio.h> int main() { /* ── Tipos básicos ── */ int inteiro = 42; float decimal = 3.14f; double preciso = 3.14159265; char letra = 'A'; char texto[] = "Olá, C!"; /* ── Tamanho em bytes ── */ printf("int: %zu bytes\n", sizeof(int)); /* ── Leitura do utilizador ── */ int n; printf("Introduz um número: "); scanf("%d", &n); /* & = endereço de n */ printf("Escreveste: %d\n", n); /* ── Condições ── */ if (n > 0) { printf("Positivo\n"); } else if (n < 0) { printf("Negativo\n"); } else { printf("Zero\n"); } /* ── Ciclo for ── */ for (int i = 0; i < 5; i++) { printf("i = %d\n", i); } return 0; }
| Especificador | Tipo | Exemplo |
|---|---|---|
%d ou %i | int | printf("%d", 42) → 42 |
%f | float / double | printf("%.2f", 3.14) → 3.14 |
%c | char | printf("%c", 'A') → A |
%s | string (char*) | printf("%s", "oi") → oi |
%p | ponteiro (endereço) | printf("%p", &x) → 0x7ffd... |
%zu | size_t (sizeof) | printf("%zu", sizeof(int)) → 4 |
%ld | long int | printf("%ld", 1000000L) |
%lf | double (scanf) | scanf("%lf", &x) |
#include <stdio.h> /* Protótipo — declara antes de main */ int somar(int a, int b); void trocar(int *a, int *b); int fatorial(int n); int main() { int x = 5, y = 3; printf("%d + %d = %d\n", x, y, somar(x, y)); trocar(&x, &y); /* passa endereços! */ printf("Depois da troca: x=%d y=%d\n", x, y); printf("5! = %d\n", fatorial(5)); return 0; } /* Passagem por valor — recebe cópias */ int somar(int a, int b) { return a + b; } /* Passagem por referência — altera os originais */ void trocar(int *a, int *b) { int temp = *a; *a = *b; *b = temp; } /* Recursividade */ int fatorial(int n) { if (n <= 1) return 1; return n * fatorial(n - 1); }
#include <stdio.h> #include <string.h> int main() { /* ── Arrays ── */ int nums[5] = {10, 20, 30, 40, 50}; for (int i = 0; i < 5; i++) printf("nums[%d] = %d\n", i, nums[i]); /* ── Array 2D (matriz) ── */ int mat[2][3] = {{1,2,3},{4,5,6}}; printf("mat[1][2] = %d\n", mat[1][2]); // 6 /* ── Strings ── */ char nome[20] = "Alice"; char apelido[] = "Silva"; char completo[40]; strcpy(completo, nome); // copia nome → completo strcat(completo, " "); // concatena espaço strcat(completo, apelido); // concatena apelido printf("Nome completo: %s\n", completo); printf("Comprimento: %zu\n", strlen(completo)); printf("Comparação: %d\n", strcmp(nome, "Alice")); // 0 = iguais return 0; }
#include <stdio.h> #include <string.h> /* typedef + struct em conjunto */ typedef struct { char nome[50]; int idade; float nota; } Aluno; void imprimir(Aluno a) { printf("%-20s %2d anos %.1f\n", a.nome, a.idade, a.nota); } int main() { /* Inicialização direta */ Aluno a1 = {"Ana Martins", 17, 18.5f}; /* Atribuição por campo */ Aluno a2; strcpy(a2.nome, "João Costa"); a2.idade = 16; a2.nota = 14.2f; /* Array de structs */ Aluno turma[2] = {a1, a2}; printf("%-20s %-7s %s\n", "Nome", "Idade", "Nota"); printf("%-20s %-7s %s\n", "----", "-----", "----"); for (int i = 0; i < 2; i++) imprimir(turma[i]); return 0; }
#include <stdio.h> #include <stdlib.h> /* malloc, calloc, free */ int main() { int n; printf("Quantos inteiros? "); scanf("%d", &n); /* malloc — aloca n * 4 bytes no HEAP */ int *arr = (int *) malloc(n * sizeof(int)); if (arr == NULL) { fprintf(stderr, "Erro: sem memória!\n"); return 1; } for (int i = 0; i < n; i++) arr[i] = i * 10; for (int i = 0; i < n; i++) printf("%d ", arr[i]); printf("\n"); free(arr); /* SEMPRE libertar! memory leak se não o fizeres */ arr = NULL; /* boas práticas: evitar dangling pointer */ return 0; }
#include <stdio.h> int main() { /* ── Escrever num ficheiro ── */ FILE *fp = fopen("dados.txt", "w"); if (!fp) { perror("Erro ao abrir"); return 1; } fprintf(fp, "Linha 1: C é incrível!\n"); fprintf(fp, "Linha 2: valor = %d\n", 42); fclose(fp); /* ── Ler do ficheiro ── */ fp = fopen("dados.txt", "r"); char linha[100]; while (fgets(linha, sizeof(linha), fp) != NULL) printf("%s", linha); fclose(fp); return 0; } /* Modos fopen: "r" — ler (ficheiro deve existir) "w" — escrever (cria ou trunca) "a" — acrescentar ao fim "r+" — ler e escrever "b" — modo binário (ex: "rb", "wb") */
Ponteiros são o conceito mais poderoso — e mais temido — de C. Visualizados corretamente, ficam simples.
Um ponteiro é uma variável que guarda um endereço de memória. Em vez de guardar o valor 42, guarda "o valor 42 está na posição 0x7ffc".
int x = 42;int *p = &x; /* p aponta para x */#include <stdio.h> int main() { int x = 42; int *p = &x; /* p guarda o endereço de x */ printf("x = %d\n", x); // 42 printf("&x = %p\n", &x); // endereço de x printf("p = %p\n", p); // mesmo endereço printf("*p = %d\n", *p); // 42 (desreferência) /* Alterar x ATRAVÉS do ponteiro */ *p = 100; printf("x agora = %d\n", x); // 100! /* Aritmética de ponteiros */ int arr[] = {10, 20, 30}; int *q = arr; /* arr é o endereço do 1º elemento */ printf("%d %d %d\n", *q, *(q+1), *(q+2)); // 10 20 30 return 0; }
*p quando p == NULL) — causa segmentation fault. Após free(), faz sempre p = NULL para evitar usar um "dangling pointer" (ponteiro para memória já libertada).Pratica com estes desafios progressivos. Cada exercício tem dicas e solução completa — tenta resolver antes de ver!
Escreve um programa que peça o nome e a idade do utilizador e apresente uma mensagem de saudação personalizada.
scanf para ler uma string e um inteiroprintf com múltiplos especificadores%s e o array sem & (já é endereço)char nome[50];2025 - idadefgets(nome, 50, stdin) em vez de scanf#include <stdio.h> int main() { char nome[50]; int idade; printf("Qual é o teu nome? "); scanf("%49s", nome); /* %49s limita a 49 chars */ printf("Qual é a tua idade? "); scanf("%d", &idade); printf("\nOlá, %s!\n", nome); printf("Tens %d anos.\n", idade); printf("Nasceste por volta de %d.\n", 2025 - idade); return 0; }
Lê dois números e um operador (+, -, *, /) e apresenta o resultado. Trata a divisão por zero.
switch/case para selecionar a operaçãofloat para resultados decimaischar op; scanf(" %c", &op); (espaço antes de %c ignora espaços em branco)float ou double para os números para obter resultados decimais#include <stdio.h> int main() { double a, b; char op; printf("Expressão (ex: 3.5 + 2): "); scanf("%lf %c %lf", &a, &op, &b); printf("%.4g %c %.4g = ", a, op, b); switch (op) { case '+': printf("%.6g\n", a + b); break; case '-': printf("%.6g\n", a - b); break; case '*': printf("%.6g\n", a * b); break; case '/': if (b == 0.0) { printf("Erro: divisão por zero!\n"); } else { printf("%.6g\n", a / b); } break; default: printf("Operador desconhecido '%c'\n", op); } return 0; }
Pede um número ao utilizador e apresenta a sua tabuada completa (de 1 a 10) formatada em tabela.
%3d para alinhar números à direita em 3 espaços#include <stdio.h> int main() { int n; printf("Tabuada de que número? "); scanf("%d", &n); printf("\n── Tabuada do %d ──\n", n); for (int i = 1; i <= 10; i++) { printf("%2d × %2d = %3d\n", n, i, n * i); } return 0; }
Cria uma função isPrimo(n) e usa-a para listar todos os primos até um N introduzido pelo utilizador.
#include <math.h> e sqrt(n) — compila com gcc -lm#include <stdio.h> #include <math.h> int isPrimo(int n) { if (n < 2) return 0; if (n == 2) return 1; if (n % 2 == 0) return 0; for (int i = 3; i <= (int)sqrt(n); i += 2) { if (n % i == 0) return 0; } return 1; } int main() { int n, count = 0; printf("Primos até N: "); scanf("%d", &n); printf("\nPrimos até %d:\n", n); for (int i = 2; i <= n; i++) { if (isPrimo(i)) { printf("%4d", i); if (++count % 10 == 0) printf("\n"); } } printf("\n\nTotal: %d primos\n", count); return 0; }
Lê N inteiros do utilizador para um array e ordena-os com o algoritmo bubble sort. Mostra o array antes e depois.
void bubbleSort(int arr[], int n)int temp = a; a = b; b = temp;#include <stdio.h> void imprimir(int arr[], int n) { for (int i = 0; i < n; i++) printf("%d ", arr[i]); printf("\n"); } void bubbleSort(int arr[], int n) { for (int i = 0; i < n - 1; i++) { int trocou = 0; for (int j = 0; j < n - i - 1; j++) { if (arr[j] > arr[j + 1]) { int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; trocou = 1; } } if (!trocou) break; /* otimização */ } } int main() { int n; printf("Quantos números? "); scanf("%d", &n); int arr[n]; printf("Introduz %d inteiros:\n", n); for (int i = 0; i < n; i++) scanf("%d", &arr[i]); printf("Antes: "); imprimir(arr, n); bubbleSort(arr, n); printf("Depois: "); imprimir(arr, n); return 0; }
Cria um array de structs Aluno (nome, número, notas). Calcula a média de cada aluno e lista o melhor e o pior da turma.
typedef struct { char nome[50]; int num; float notas[3]; float media; } Aluno;float calcMedia(float notas[], int n) que recebe o array de notas#include <stdio.h> #include <string.h> #define MAX_ALUNOS 3 #define N_NOTAS 3 typedef struct { char nome[50]; int numero; float notas[N_NOTAS]; float media; } Aluno; float calcMedia(float notas[], int n) { float soma = 0; for (int i = 0; i < n; i++) soma += notas[i]; return soma / n; } int main() { Aluno turma[MAX_ALUNOS] = { {"Ana Silva", 101, {18.0f, 16.5f, 19.0f}}, {"Bruno Costa", 102, {12.0f, 14.0f, 10.5f}}, {"Carla Matos", 103, {15.5f, 17.0f, 16.0f}}, }; int melhor = 0, pior = 0; printf("%-15s %4s %s\n", "Nome", "Num", "Média"); printf("%-15s %4s %s\n", "----", "---", "-----"); for (int i = 0; i < MAX_ALUNOS; i++) { turma[i].media = calcMedia(turma[i].notas, N_NOTAS); printf("%-15s %4d %.1f\n", turma[i].nome, turma[i].numero, turma[i].media); if (turma[i].media > turma[melhor].media) melhor = i; if (turma[i].media < turma[pior].media) pior = i; } printf("\n🥇 Melhor: %s (%.1f)\n", turma[melhor].nome, turma[melhor].media); printf("📉 Pior: %s (%.1f)\n", turma[pior].nome, turma[pior].media); return 0; }
Implementa a tua própria função inverterStr que inverte uma string no próprio lugar usando dois ponteiros.
inicio apontando para o 1º char, fim para o último (antes de '\0')*fim == '\0', depois recua um#include <stdio.h> void inverterStr(char *s) { if (!s || !*s) return; char *inicio = s; char *fim = s; /* avançar fim até ao '\0', depois recuar */ while (*fim) fim++; fim--; while (inicio < fim) { char temp = *inicio; *inicio = *fim; *fim = temp; inicio++; fim--; } } int main() { char s[] = "Linguagem C"; printf("Original: %s\n", s); inverterStr(s); printf("Invertida: %s\n", s); return 0; }
Implementa uma lista ligada com operações: inserir no início, inserir no fim, remover por valor e imprimir.
typedef struct No { int val; struct No *prox; } No; — nota o uso de struct No * dentro da própria structprox para a cabeça atual, actualiza cabeça#include <stdio.h> #include <stdlib.h> typedef struct No { int val; struct No *prox; } No; No* criarNo(int val) { No *n = (No*) malloc(sizeof(No)); n->val = val; n->prox = NULL; return n; } void inserirInicio(No **head, int val) { No *n = criarNo(val); n->prox = *head; *head = n; } void imprimir(No *head) { printf("HEAD"); for (No *cur = head; cur; cur = cur->prox) printf(" → %d", cur->val); printf(" → NULL\n"); } void libertar(No **head) { No *cur = *head; while (cur) { No *prox = cur->prox; free(cur); cur = prox; } *head = NULL; } int main() { No *lista = NULL; inserirInicio(&lista, 30); inserirInicio(&lista, 20); inserirInicio(&lista, 10); imprimir(lista); // HEAD → 10 → 20 → 30 → NULL libertar(&lista); return 0; }
Cria uma mini-agenda que guarda contactos (nome + telefone) num ficheiro .txt. Permite adicionar, listar e pesquisar por nome.
fwrite(&c, sizeof(Contacto), 1, fp) — é mais eficiente que textowhile(fread(&c, sizeof(Contacto), 1, fp) == 1)strstr (pesquisa parcial)#include <stdio.h> #include <string.h> #define FICHEIRO "agenda.dat" typedef struct { char nome[50]; char tel[20]; } Contacto; void adicionar() { Contacto c; printf("Nome: "); scanf("%49s", c.nome); printf("Tel: "); scanf("%19s", c.tel); FILE *fp = fopen(FICHEIRO, "ab"); fwrite(&c, sizeof(Contacto), 1, fp); fclose(fp); printf("✓ Contacto guardado.\n"); } void listar() { Contacto c; FILE *fp = fopen(FICHEIRO, "rb"); if (!fp) { printf("Agenda vazia.\n"); return; } printf("\n%-20s %s\n", "Nome", "Telefone"); printf("%-20s %s\n", "----", "--------"); while (fread(&c, sizeof(Contacto), 1, fp) == 1) printf("%-20s %s\n", c.nome, c.tel); fclose(fp); } int main() { int op; do { printf("\n[1] Adicionar [2] Listar [0] Sair: "); scanf("%d", &op); switch(op) { case 1: adicionar(); break; case 2: listar(); break; } } while (op != 0); return 0; }
Projetos integrados que combinam múltiplos conceitos. Para resolver sem dicas — desafio real.
Conta bancária com struct. Operações: depositar, levantar, transferir, ver extrato. Persistência em ficheiro binário.
Hash table simples com array de strings. Guardar pares palavra/tradução. Pesquisa e remoção eficiente.
Escolhe palavra aleatória de um array. Lê letras, mostra progresso e tentativas. Valida vitória/derrota.
Lê um ficheiro CSV de notas, calcula estatísticas (média, mín, máx, desvio padrão) e gera relatório.
Calculadora em notação polaca reversa com stack (pilha). Implementa a pilha com malloc. Suporta +, -, *, /.
Ler dados de um sensor (DHT11 via GPIO), guardar leituras num ficheiro CSV com timestamp e gerar alertas.
Pratica C diretamente no browser, sem instalar nada. Todos são gratuitos.
Compilador C/C++ online com debugger integrado. Permite executar código passo a passo, inspecionar variáveis e ponteiros em tempo real.
→ onlinegdb.comIDE completo no browser. Cria projetos C, partilha código, colabora em tempo real. Suporta múltiplos ficheiros e bibliotecas externas.
→ replit.comFerramenta avançada que mostra o Assembly gerado pelo teu código C. Essencial para perceber otimizações e o que a CPU realmente executa.
→ godbolt.orgO curso de introdução à ciência da computação mais famoso do mundo. As primeiras semanas são inteiramente em C, com exercícios práticos.
→ cs50.harvard.edu/xMais de 100 exercícios progressivos em C com feedback de mentores. Compila localmente e submete soluções. Sistema de pontuação e progresso.
→ exercism.org/tracks/cFerramenta essencial para detetar memory leaks e erros de memória em programas C. Instala no Linux/macOS: valgrind ./programa
# Instalar GCC (Linux) sudo apt install gcc # Compilar um ficheiro gcc hello.c -o hello # Compilar com avisos e padrão C11 gcc -Wall -Wextra -std=c11 hello.c -o hello # Compilar com math.h gcc programa.c -o programa -lm # Executar ./hello # Detetar memory leaks com Valgrind valgrind --leak-check=full ./programa # Compilar para debugging (GDB) gcc -g programa.c -o programa