Sebenta · C/C++ básico (UC00669)
- Introdução
- 1. Por que C/C++
- 2. Setup
- 3. Sintaxe básica de C
- 4. Arrays
- 5. Strings
- 6. Pointers — o coração do C
- 7. Memória dinâmica
- 8. Funções
- 9. Structs
- 10. Ficheiros
- 11. Pré-processador
- 12. Organização em múltiplos ficheiros
- 13. Introdução a C++
- 14. Boas práticas
- 15. Próximos passos
- Glossário
- Recursos
Introdução
C é a linguagem que mudou a computação. Criada em 1972 por Dennis Ritchie nos Bell Labs, deu origem a praticamente tudo o que veio depois: Unix, Linux, Windows, MacOS, todas as outras linguagens "C-like" (C++, Java, JavaScript, C#, Go, Rust, Swift). Aprender C é aprender como o computador realmente funciona — gestão de memória, pointers, estruturas binárias.
C++ estende C com programação orientada a objectos, templates, exceptions, RAII. É a linguagem dos motores de jogos AAA (Unreal Engine), navegadores (Chrome), sistemas operativos modernos, alta-frequência financeira, e muito mais.
Esta sebenta cobre os fundamentos de C com introdução a C++. O foco é dominar pointers, memória dinâmica, structs e organização de código.
1. Por que C/C++
Características-chave de C
- Compilada directamente para código de máquina — velocidade máxima.
- Tipo estático e simples.
- Acesso directo à memória via pointers.
- Sem garbage collector — controlo total e responsabilidade total.
- Portabilidade — corre em quase qualquer hardware.
- Pequena — a especificação cabe em ~150 páginas.
Onde se usa C
- Kernels: Linux, FreeBSD, parte do Windows.
- Embedded: Arduino, ESP32, automóvel, telecom.
- Drivers: hardware.
- Bibliotecas base: libc, OpenSSL, SQLite, FFmpeg.
- Bases de outras linguagens: CPython, V8, Node.
C++ vs C
C++ adiciona: - Classes e herança (OOP). - Templates (genéricos). - Exceptions. - Standard Template Library (STL) com vectors, maps, algorithms. - References (alternativa a pointers em muitos casos). - RAII — gestão de recursos via construtores/destrutores.
Onde se usa C++: - Jogos: Unreal Engine, motores próprios da Naughty Dog, etc. - Sistemas grandes: Chrome, Firefox, Photoshop. - Trading: alta frequência financeira. - 3D/CAD: AutoCAD, Maya, Blender (parte).
Curva de aprendizagem
C é simples mas tem armadilhas (pointers, memory leaks, undefined behavior). C++ é gigante — anos para dominar bem. Esta UC cobre o essencial; vais ficar com base sólida para continuar.
2. Setup
Compilador
Linux/Mac: já tens gcc e g++. Senão:
# Ubuntu/Debian
sudo apt install build-essential
# Mac
xcode-select --install
Windows: instala MSYS2 (msys2.org) ou usa WSL (Windows Subsystem for Linux).
Verifica:
gcc --version
g++ --version
IDE
- VSCode + extensão "C/C++" (Microsoft).
- CLion (JetBrains, pago, excelente).
- Code::Blocks (gratuito, mais simples).
- Vim/Emacs com setup.
Hello World
Cria hello.c:
#include <stdio.h>
int main() {
printf("Olá mundo!\n");
return 0;
}
Compilar e correr:
gcc hello.c -o hello
./hello
Flags úteis
gcc -Wall hello.c -o hello # warnings
gcc -Wall -Wextra hello.c # mais warnings
gcc -std=c11 hello.c # standard C11
gcc -g hello.c -o hello # com debug info
gcc -O2 hello.c -o hello # optimização
3. Sintaxe básica de C
Tipos primitivos
char c = 'A'; // 1 byte (8 bits)
short s = 100; // 2 bytes
int i = 25; // 4 bytes (em sistemas 32/64-bit)
long L = 1234567890L; // 4 ou 8 bytes
long long LL = 9223372036854775807LL; // 8 bytes
unsigned int u = 4000000000U; // só positivos
float f = 0.21f; // 4 bytes
double d = 9.99; // 8 bytes
long double ld = 9.999L; // 16 bytes (em x86)
// Boolean (C99+, em <stdbool.h>)
#include <stdbool.h>
bool ok = true;
sizeof() em runtime:
printf("%zu\n", sizeof(int)); // %zu para size_t
Constantes
#define PI 3.14159 // pré-processador (substituição textual)
const int MAX = 100; // constante (preferida)
printf e scanf
#include <stdio.h>
int main() {
int idade;
char nome[50];
float altura;
printf("Nome: ");
scanf("%49s", nome); // limita a 49 chars (evita overflow)
printf("Idade: ");
scanf("%d", &idade); // & = endereço
printf("Altura: ");
scanf("%f", &altura);
printf("%s tem %d anos e mede %.2fm\n", nome, idade, altura);
return 0;
}
Format specifiers:
- %d int decimal
- %ld long
- %lld long long
- %u unsigned
- %f float / double (em scanf precisa %lf para double)
- %.2f float com 2 casas
- %c char
- %s string
- %p pointer
- %x hex
- %% literal %
Operadores
Como em quase todas as C-likes:
// Aritméticos
+ - * / %
++ --
// Atribuição composta
+= -= *= /= %= &= |= ^= <<= >>=
// Comparação
== != < > <= >=
// Lógicos
&& || !
// Bitwise
& | ^ ~ << >>
// Ternário
int max = (a > b) ? a : b;
Casting
double d = 9.99;
int i = (int) d; // 9 (trunca)
int a = 10, b = 3;
double r = (double) a / b; // 3.333... (sem cast = 3)
Estruturas controlo
Idênticas a outras linguagens C-like (if, else, switch, for, while, do-while). Ver slides para syntax.
4. Arrays
Declaração e acesso
int numeros[5] = {1, 2, 3, 4, 5};
int vazio[10]; // valores indefinidos!
int auto[] = {10, 20, 30}; // tamanho inferido
numeros[0] = 100;
int primeiro = numeros[0];
// Tamanho em compile-time
int n = sizeof(numeros) / sizeof(int);
Sem bounds checking — numeros[100] = 0 compila e corre, mas pode corromper memória.
Multi-dimensional
int matriz[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
int v = matriz[1][2]; // 7
// Iterar
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
printf("%d ", matriz[i][j]);
}
printf("\n");
}
Arrays como parâmetros
// Passar como pointer (não há cópia de array em C)
int soma(int arr[], int tamanho) {
int s = 0;
for (int i = 0; i < tamanho; i++) {
s += arr[i];
}
return s;
}
// Equivalente:
int soma(int *arr, int tamanho) { ... }
Importante: dentro da função, sizeof(arr) retorna o tamanho do pointer, não do array. Tens sempre que passar o tamanho.
5. Strings
char arrays terminadas em \0
char nome[10] = "Ana"; // {'A', 'n', 'a', '\0', ...}
// Acesso char a char
nome[0] = 'B';
// Imprimir
printf("%s\n", nome);
O null terminator (\0) marca o fim. Funções como printf param ao encontrá-lo.
Funções de string (<string.h>)
#include <string.h>
char a[50] = "Ana";
char b[50];
// strlen — comprimento (sem contar \0)
size_t len = strlen(a); // 3
// strcpy — copiar
strcpy(b, a); // b = "Ana"
// strcat — concatenar
strcat(b, " Silva"); // b = "Ana Silva"
// strcmp — comparar
if (strcmp(a, "Ana") == 0) {
printf("Igual!\n");
}
// strncpy / strncat — versões seguras com limite
strncpy(b, a, sizeof(b) - 1);
b[sizeof(b) - 1] = '\0'; // garantir terminação
Input seguro de strings
scanf("%s", ...) é perigoso (pode escrever fora do buffer). Usar fgets:
char linha[100];
if (fgets(linha, sizeof(linha), stdin) != NULL) {
// remover \n no fim
linha[strcspn(linha, "\n")] = '\0';
printf("Leu: %s\n", linha);
}
6. Pointers — o coração do C
Conceito
Variáveis guardam valores; pointers guardam endereços de memória.
int x = 42;
int *p = &x; // p contém o endereço de x
printf("x = %d\n", x); // 42
printf("&x = %p\n", &x); // endereço (ex: 0x7ffe...)
printf("p = %p\n", p); // mesmo endereço
printf("*p = %d\n", *p); // 42 — deref: valor apontado
Modificar através de pointer
int x = 5;
int *p = &x;
*p = 100; // x agora vale 100
printf("%d\n", x); // 100
Pointer NULL
int *p = NULL;
if (p != NULL) {
printf("%d\n", *p);
}
// Deref de NULL = segfault
Pointers e arrays
int arr[5] = {10, 20, 30, 40, 50};
// Nome do array é (quase) pointer ao primeiro elemento
int *p = arr; // = &arr[0]
printf("%d\n", *p); // 10
printf("%d\n", *(p+1)); // 20 (próximo int)
printf("%d\n", p[2]); // 30 (notação array funciona em pointers!)
// Aritmética de pointers
p++; // avança para o próximo int
printf("%d\n", *p); // 20
// Percorrer array via pointer
for (int *q = arr; q < arr + 5; q++) {
printf("%d ", *q);
}
Pointers e funções
// Passar por referência — pode modificar o original
void incrementar(int *n) {
(*n)++;
}
void swap(int *a, int *b) {
int tmp = *a;
*a = *b;
*b = tmp;
}
int main() {
int x = 5;
incrementar(&x);
printf("%d\n", x); // 6
int a = 1, b = 2;
swap(&a, &b);
printf("%d %d\n", a, b); // 2 1
}
Retornar pointers
// ❌ NUNCA retornar pointer a variável local!
int *errado() {
int x = 5;
return &x; // x deixa de existir após return!
}
// ✅ Retornar pointer a memória alocada
int *correcto() {
int *p = malloc(sizeof(int));
*p = 5;
return p;
}
// Quem chamou tem que free()
Pointer para pointer
int x = 42;
int *p = &x;
int **pp = &p;
printf("%d\n", x); // 42
printf("%d\n", *p); // 42
printf("%d\n", **pp); // 42
Usado em arrays de strings (char **argv), funções que modificam pointers, etc.
7. Memória dinâmica
malloc, free
#include <stdlib.h>
int main() {
// Alocar memória para 10 ints
int *arr = (int *) malloc(10 * sizeof(int));
if (arr == NULL) {
fprintf(stderr, "Falha de alocação\n");
return 1;
}
// Usar
for (int i = 0; i < 10; i++) {
arr[i] = i * i;
}
// Libertar
free(arr);
arr = NULL; // evita "use after free"
return 0;
}
calloc — aloca + zera
int *arr = (int *) calloc(10, sizeof(int));
// arr[0]..arr[9] = 0
free(arr);
realloc — redimensionar
int *arr = malloc(5 * sizeof(int));
// ...
arr = (int *) realloc(arr, 10 * sizeof(int));
if (arr == NULL) { /* erro */ }
Stack vs Heap
- Stack: variáveis locais, automáticas. Tamanho fixo, rápida, libertada automaticamente.
- Heap:
malloc/calloc/realloc. Tamanho dinâmico, precisas libertar manualmente.
Memory leaks
void problema() {
int *p = malloc(100);
// esqueço de free
} // p sai de scope, mas memória continua reservada → LEAK
Ferramentas para detectar: Valgrind, AddressSanitizer.
gcc -fsanitize=address programa.c -o programa
./programa # detecta leaks e accesses inválidos
8. Funções
Definição
// Prototype (opcional se definido antes do uso)
int somar(int a, int b);
int somar(int a, int b) {
return a + b;
}
void imprimir(const char *msg) {
printf("%s\n", msg);
}
int main() {
int r = somar(2, 3);
imprimir("Olá");
return 0;
}
const char * = pointer a chars constantes (não modificar a string).
Recursão
int factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
int main() {
printf("%d\n", factorial(5)); // 120
}
Função com número variável de argumentos
#include <stdarg.h>
int soma(int n, ...) {
va_list args;
va_start(args, n);
int total = 0;
for (int i = 0; i < n; i++) {
total += va_arg(args, int);
}
va_end(args);
return total;
}
// soma(3, 10, 20, 30) → 60
9. Structs
Definição
struct Pessoa {
char nome[50];
int idade;
double altura;
};
int main() {
struct Pessoa p;
strcpy(p.nome, "Ana");
p.idade = 25;
p.altura = 1.65;
printf("%s, %d anos\n", p.nome, p.idade);
// Initializer
struct Pessoa p2 = {"Bruno", 30, 1.80};
// Designated initializer (C99+)
struct Pessoa p3 = {.nome = "Carla", .idade = 22};
}
typedef
Para não escrever struct sempre:
typedef struct {
char nome[50];
int idade;
} Pessoa;
// Agora:
Pessoa p = {"Ana", 25};
Pointer para struct
Pessoa p = {"Ana", 25};
Pessoa *ptr = &p;
// Sintaxe 1: deref + access
(*ptr).idade = 26;
// Sintaxe 2 (preferida)
ptr->idade = 26;
Struct como parâmetro
// Por valor (cópia)
void imprimir(Pessoa p) {
printf("%s\n", p.nome);
}
// Por pointer (eficiente, evita cópia)
void imprimir(const Pessoa *p) {
printf("%s\n", p->nome);
}
Arrays de structs
Pessoa pessoas[3] = {
{"Ana", 25, 1.65},
{"Bruno", 30, 1.80},
{"Carla", 22, 1.70}
};
for (int i = 0; i < 3; i++) {
printf("%s, %d\n", pessoas[i].nome, pessoas[i].idade);
}
10. Ficheiros
Ler
#include <stdio.h>
int main() {
FILE *f = fopen("dados.txt", "r");
if (f == NULL) {
perror("Erro ao abrir");
return 1;
}
char linha[256];
while (fgets(linha, sizeof(linha), f) != NULL) {
printf("%s", linha);
}
fclose(f);
return 0;
}
Escrever
FILE *f = fopen("output.txt", "w");
if (f == NULL) {
perror("Erro");
return 1;
}
fprintf(f, "Linha 1\n");
fprintf(f, "Número: %d\n", 42);
for (int i = 0; i < 10; i++) {
fprintf(f, "%d ", i);
}
fclose(f);
Modos
"r"— leitura."w"— escrita (sobrescreve)."a"— append."r+"— leitura + escrita."rb","wb","ab"— binário.
Ficheiros binários
typedef struct {
char nome[50];
int idade;
} Pessoa;
// Escrever
FILE *f = fopen("dados.bin", "wb");
Pessoa p = {"Ana", 25};
fwrite(&p, sizeof(Pessoa), 1, f);
fclose(f);
// Ler
f = fopen("dados.bin", "rb");
Pessoa lida;
fread(&lida, sizeof(Pessoa), 1, f);
fclose(f);
printf("%s, %d\n", lida.nome, lida.idade);
11. Pré-processador
Antes de compilar, o pré-processador processa directivas #:
#include <stdio.h> // inclui ficheiro
#include "meu_header.h" // local
#define PI 3.14159 // substituição textual
#define QUADRADO(x) ((x)*(x)) // macro
#ifdef DEBUG
printf("Debug mode\n");
#endif
#ifndef VERSAO
#define VERSAO 1
#endif
Header guards são essenciais para evitar inclusões duplas:
// utils.h
#ifndef UTILS_H
#define UTILS_H
int somar(int a, int b);
#endif
Ou (mais simples, compilador-específico):
#pragma once
12. Organização em múltiplos ficheiros
utils.h:
#ifndef UTILS_H
#define UTILS_H
int somar(int a, int b);
int maximo(int a, int b);
#endif
utils.c:
#include "utils.h"
int somar(int a, int b) { return a + b; }
int maximo(int a, int b) { return a > b ? a : b; }
main.c:
#include <stdio.h>
#include "utils.h"
int main() {
printf("%d\n", somar(5, 3));
printf("%d\n", maximo(10, 7));
}
Compilar:
gcc main.c utils.c -o programa
Ou separar compilação:
gcc -c main.c # main.o
gcc -c utils.c # utils.o
gcc main.o utils.o -o programa
Makefile básico
CC = gcc
CFLAGS = -Wall -g
programa: main.o utils.o
$(CC) $(CFLAGS) main.o utils.o -o programa
main.o: main.c utils.h
$(CC) $(CFLAGS) -c main.c
utils.o: utils.c utils.h
$(CC) $(CFLAGS) -c utils.c
clean:
rm -f *.o programa
Correr: make.
13. Introdução a C++
Hello world
#include <iostream>
using namespace std;
int main() {
cout << "Olá mundo!" << endl;
return 0;
}
Compilar: g++ hello.cpp -o hello.
Cin/cout
#include <iostream>
#include <string>
using namespace std;
int main() {
string nome;
int idade;
cout << "Nome: ";
cin >> nome;
cout << "Idade: ";
cin >> idade;
cout << "Olá, " << nome << ", " << idade << " anos." << endl;
return 0;
}
<< em cout = "send to". >> em cin = "extract from".
Classes
#include <iostream>
#include <string>
using namespace std;
class Pessoa {
private:
string nome;
int idade;
public:
// Construtor
Pessoa(string n, int i) : nome(n), idade(i) {}
// Método
void apresentar() {
cout << "Sou " << nome << ", " << idade << " anos" << endl;
}
// Getter
string getNome() const { return nome; }
// Setter
void setIdade(int i) { idade = i; }
};
int main() {
Pessoa p("Ana", 25);
p.apresentar();
}
: na construtor = initializer list (mais eficiente que atribuir no corpo).
Herança
class Empregado : public Pessoa {
private:
double salario;
public:
Empregado(string n, int i, double s)
: Pessoa(n, i), salario(s) {}
void apresentar() {
Pessoa::apresentar();
cout << "Salário: " << salario << "€" << endl;
}
};
virtual em método do pai e override no filho para polimorfismo:
class Pessoa {
public:
virtual void apresentar() { ... }
};
class Empregado : public Pessoa {
public:
void apresentar() override { ... }
};
STL — Standard Template Library
vector — array dinâmico
#include <vector>
vector<int> nums = {1, 2, 3};
nums.push_back(4);
nums.pop_back();
nums.size();
for (int n : nums) {
cout << n << " ";
}
string
#include <string>
string s = "Olá";
s += " mundo";
cout << s.length() << endl; // 9
cout << s.substr(0, 3) << endl; // "Olá" (a unicode varia)
map — dictionary
#include <map>
map<string, int> idades;
idades["Ana"] = 25;
idades["Bruno"] = 30;
for (auto& [nome, idade] : idades) {
cout << nome << ": " << idade << endl;
}
Smart pointers (C++11+)
Para evitar new/delete manual:
#include <memory>
unique_ptr<Pessoa> p = make_unique<Pessoa>("Ana", 25);
p->apresentar();
// Auto-deleted quando p sai de scope
unique_ptr para ownership único, shared_ptr para partilhado.
14. Boas práticas
Convenções
- Variáveis/funções:
snake_case(C tradicional) oucamelCase. - Constantes/macros:
UPPER_SNAKE_CASE. - Structs/classes:
PascalCaseousnake_case_t. - Headers: include guards sempre.
Princípios
- Pares malloc/free sempre — count nas tuas reviews.
- Verifica retornos de malloc, fopen, scanf.
- Inicializa variáveis — não confies no lixo na stack.
- Usa
constem parâmetros que não modificas. - Bounds: sempre que indexas um array, pensa nos limites.
size_tpara tamanhos (nãoint).- Flags do compilador:
-Wall -Wextra -Werrorsempre.
Armadilhas comuns
// 1. Comparação de strings
if (s1 == s2) ... // ❌ compara endereços
if (strcmp(s1, s2) == 0) ... // ✅
// 2. Atribuição em vez de comparação
if (x = 5) ... // ❌ atribui (sempre verdadeiro)
if (x == 5) ... // ✅
// 3. Off-by-one
for (int i = 0; i <= 10; i++) arr[i]; // ❌ i=10 fora!
for (int i = 0; i < 10; i++) arr[i]; // ✅
// 4. Buffer overflow
char buf[10];
strcpy(buf, "uma string muito grande"); // ❌ overflow
strncpy(buf, longstring, sizeof(buf) - 1); // ✅
buf[sizeof(buf) - 1] = '\0';
// 5. Use after free
free(p);
*p = 5; // ❌ UB
p = NULL; // ✅ marca-o
15. Próximos passos
- C avançado: bit manipulation, signal handling, sockets, threads (pthread), structs alinhamento.
- C++ moderno: C++17/20/23 — templates, concepts, ranges, coroutines.
- Embedded: Arduino → AVR-C → ARM bare metal.
- Sistemas operativos: ler "The Linux Programming Interface".
- Rust: alternativa moderna a C/C++ com safety por design.
- Go: linguagem "C-like" com GC para concorrência.
Glossário
- Compilador: traduz código fonte → binário.
- Linker: junta múltiplos
.onum executável. - Header (
.h): declarações (prototypes, structs, macros). - Source (
.c): implementação. - Object file (
.o): compilado intermédio. - Static library (
.a): ficheiros.oagregados. - Dynamic library (
.so/.dll): carregada em runtime. - Stack: memória automática (locais).
- Heap: memória dinâmica (malloc).
- Undefined Behavior (UB): compilador pode fazer qualquer coisa.
- Segfault: acesso a memória inválida (NULL, fora de bounds).
Recursos
- "The C Programming Language" (Kernighan & Ritchie) — bíblia.
- "21st Century C" (Ben Klemens) — moderno.
- "Modern C" (Jens Gustedt) — gratuito online.
- "Effective C" (Robert C. Seacord).
- C++ — "A Tour of C++" (Stroustrup) — introdução curta do criador.
- learncpp.com — gratuito, exaustivo.
- godbolt.org — vê assembly gerado.
C é exigente. Vais ter segfaults. Memory leaks. Vais perder horas a entender porque um pointer está corrupto. Mas cada bug que apanhas faz-te um programador melhor — não só em C, mas em qualquer linguagem. Os fundamentos que aprendes aqui são para sempre.