Do C ao C++ moderno — OOP, STL, templates, smart pointers e tudo o que precisas para escrever código eficiente e elegante.
// hello.cpp — C++ moderno com classes #include <iostream> #include <string> class Saudacao { std::string nome; public: Saudacao(std::string n) : nome(n) {} void dizer() const { std::cout << "Olá, " << nome << "!\n"; } }; int main() { Saudacao s("Mundo"); s.dizer(); return 0; }
Antes de escrever código, é fundamental compreender porque C++ continua a ser uma das linguagens mais poderosas e versáteis da computação moderna.
O C++ é uma extensão da linguagem C criada por Bjarne Stroustrup nos laboratórios Bell a partir de 1979, tornando-se oficial em 1985. Nasceu como "C com Classes" e evoluiu para integrar múltiplos paradigmas: programação procedural, orientada a objetos, genérica e funcional.
Hoje, o C++ está no núcleo dos browsers (Chrome, Firefox), motores de jogos (Unreal Engine), bases de dados (MySQL, MongoDB), sistemas embebidos, motores de IA (TensorFlow, PyTorch) e toda a infraestrutura de alto desempenho. C++ oferece o controlo do C com a expressividade de uma linguagem moderna.
/* Sem classes — struct + funções soltas */ typedef struct { char nome[50]; int idade; } Pessoa; void cumprimentar(Pessoa* p) { printf("Olá, %s!\n", p->nome); }
// Classes encapsulam dados e comportamento class Pessoa { std::string nome; int idade; public: Pessoa(std::string n, int i); void cumprimentar() const; };
Segue esta ordem para uma progressão lógica — cada módulo constrói sobre o anterior. Clica num módulo para ver os tópicos e um exemplo de código.
#include <iostream> #include <string> using namespace std; // Função sobrecarregada (overloading) void imprimir(int n) { cout << "int: " << n << endl; } void imprimir(string s) { cout << "string: " << s << endl; } void imprimir(double d) { cout << "double: " << d << endl; } int main() { string nome; cout << "O teu nome: "; cin >> nome; imprimir(nome); // chama versão string imprimir(42); // chama versão int imprimir(3.14); // chama versão double return 0; }
class Retangulo { double largura, altura; // private por defeito public: // Construtor com lista de inicialização Retangulo(double l, double a) : largura(l), altura(a) {} double area() const { return largura * altura; } double perimetro() const { return 2 * (largura + altura); } // Setters com validação void setLargura(double l) { if (l > 0) largura = l; } };
class Forma { // classe abstrata public: virtual double area() const = 0; // método puro virtual ~Forma() = default; void info() const { cout << "Área: " << area() << "\n"; } }; class Circulo : public Forma { double raio; public: Circulo(double r) : raio(r) {} double area() const override { return 3.14159 * raio * raio; } };
class Vetor2D { public: double x, y; Vetor2D(double x, double y) : x(x), y(y) {} // v1 + v2 Vetor2D operator+(const Vetor2D& o) const { return {x + o.x, y + o.y}; } // cout << v friend ostream& operator<<(ostream& os, const Vetor2D& v) { return os << "(" << v.x << ", " << v.y << ")"; } };
// Uma função que funciona para qualquer tipo template <typename T> T maximo(T a, T b) { return a > b ? a : b; } // Classe genérica — par de valores template <typename A, typename B> class Par { public: A primeiro; B segundo; Par(A a, B b) : primeiro(a), segundo(b) {} }; int main() { cout << maximo(3, 7); // int: 7 cout << maximo(3.5, 2.1); // double: 3.5 Par<string, int> p("Alice", 20); }
#include <vector> #include <algorithm> #include <map> vector<int> nums = {5, 2, 8, 1, 9, 3}; // Ordenar e iterar sort(nums.begin(), nums.end()); for (auto n : nums) cout << n << " "; // → 1 2 3 5 8 9 // Mapa: palavra → frequência map<string, int> freq; freq["hello"]++; freq["world"]++; freq["hello"]++; cout << freq["hello"]; // 2
#include <memory> // unique_ptr — libertado automaticamente auto up = make_unique<Retangulo>(4.0, 3.0); cout << up->area(); // 12 — sem new/delete! // shared_ptr — contagem de referências auto sp1 = make_shared<Circulo>(5.0); auto sp2 = sp1; // ambos apontam para o mesmo cout << sp1.use_count(); // 2 referências // Libertado quando ambos saem de scope
class ErroValor : public std::exception { string msg; public: ErroValor(string m) : msg(m) {} const char* what() const noexcept override { return msg.c_str(); } }; double dividir(double a, double b) { if (b == 0) throw ErroValor("Divisão por zero"); return a / b; } try { cout << dividir(10, 0); } catch (const ErroValor& e) { cerr << "Erro: " << e.what() << "\n"; }
// Lambda — função anónima auto quadrado = [](int x) { return x * x; }; cout << quadrado(5); // 25 // Lambda com STL vector<int> v = {1,2,3,4,5}; transform(v.begin(), v.end(), v.begin(), [](int x) { return x * x; }); // v = {1, 4, 9, 16, 25} // Structured bindings (C++17) map<string, int> notas = {{"Ana", 18}, {"Bob", 15}}; for (auto& [nome, nota] : notas) cout << nome << ": " << nota << "\n";
#include <fstream> #include <sstream> // Escrever ficheiro ofstream out("dados.txt"); out << "Ana 18\nBob 15\n"; out.close(); // Ler linha a linha e parsear ifstream in("dados.txt"); string linha; while (getline(in, linha)) { istringstream ss(linha); string nome; int nota; ss >> nome >> nota; cout << nome << " tem " << nota << "\n"; }
Os padrões mais usados no dia a dia de qualquer programador C++.
#include <iostream> #include <string> using namespace std; int main() { // Tipos básicos int i = 42; double d = 3.14; bool b = true; string s = "Olá C++"; // Entrada e saída cout << s << "\n"; string nome; cin >> nome; getline(cin, nome); // lê linha inteira // auto — dedução de tipo auto x = 3.14; // x é double auto y = 42; // y é int // Referências int a = 10; int& r = a; // r é alias de a r = 20; // a agora vale 20 // Iniciação uniforme (C++11) int v{5}; // equivalente a int v = 5; double w{3.14}; return 0; }
| Manipulador | Efeito | Exemplo |
|---|---|---|
setw(n) | Largura mínima | setw(8) << 42 → 42 |
setprecision(n) | Casas decimais | setprecision(2) << 3.14159 → 3.14 |
fixed | Notação decimal | fixed << 3.14 → 3.140000 |
left / right | Alinhamento | left << setw(8) << "hi" |
boolalpha | true/false em texto | boolalpha << true → true |
hex / oct | Base numérica | hex << 255 → ff |
#include <iomanip>. Muitos (como fixed e boolalpha) são persistentes — aplicam-se a todos os outputs seguintes.
class ContaBancaria { string titular; double saldo; public: // Construtor com lista de inicialização ContaBancaria(string t, double s = 0.0) : titular(t), saldo(s) {} // Destrutor ~ContaBancaria() { cout << titular << ": conta encerrada\n"; } // Método const — não modifica o objeto double getSaldo() const { return saldo; } bool levantar(double v) { if (v > saldo) return false; saldo -= v; return true; } void depositar(double v) { saldo += v; } // Sobrecarga de << como friend friend ostream& operator<<(ostream& os, const ContaBancaria& c) { return os << c.titular << ": €" << c.saldo; } };
int main() { ContaBancaria c1("Ana", 500.0); ContaBancaria c2("Bob"); // saldo = 0 c1.depositar(100.0); if (!c1.levantar(800.0)) cout << "Saldo insuficiente!\n"; cout << c1 << "\n"; // Ana: €600 cout << c2 << "\n"; // Bob: €0 }
#include <vector> #include <map> #include <set> #include <algorithm> // ── vector ── vector<int> v = {3, 1, 4, 1, 5}; v.push_back(9); v.pop_back(); cout << v.size() << "\n"; // 5 sort(v.begin(), v.end()); // ── map (ordenado por chave) ── map<string, int> idades; idades["Ana"] = 20; idades["Bob"] = 18; for (auto& [nome, idade] : idades) cout << nome << ": " << idade << "\n"; // ── set (sem duplicados) ── set<int> s = {1, 2, 2, 3, 3}; cout << s.size(); // 3 — únicos // ── Algoritmos ── auto it = find(v.begin(), v.end(), 4); int cnt = count(v.begin(), v.end(), 1); // 2 auto mx = *max_element(v.begin(), v.end());
| Contêiner | Acesso | Inserção | Uso típico |
|---|---|---|---|
vector<T> | O(1) | O(1) fim | Array dinâmico |
list<T> | O(n) | O(1) | Inserções no meio |
deque<T> | O(1) | O(1) ambos | Fila dupla |
map<K,V> | O(log n) | O(log n) | Dicionário ordenado |
unordered_map<K,V> | O(1) amort. | O(1) amort. | Dicionário rápido |
set<T> | O(log n) | O(log n) | Conjunto ordenado |
stack<T> | topo | O(1) | LIFO |
queue<T> | frente | O(1) | FIFO |
class Animal { protected: string nome; public: Animal(string n) : nome(n) {} virtual void falar() const = 0; // puro virtual ~Animal() = default; string getNome() const { return nome; } }; class Cao : public Animal { public: Cao(string n) : Animal(n) {} void falar() const override { cout << nome << ": Au-au!\n"; } }; class Gato : public Animal { public: Gato(string n) : Animal(n) {} void falar() const override { cout << nome << ": Miau!\n"; } }; int main() { // Polimorfismo via ponteiro base vector<Animal*> animais; animais.push_back(new Cao("Rex")); animais.push_back(new Gato("Whiskers")); for (auto* a : animais) { a->falar(); // chama método correto! delete a; } }
delete ptr_base o destrutor da classe derivada não é chamado — memory leak garantido.
vector<unique_ptr<Animal>> a vector<Animal*> — elimina o new/delete manual e garante destruição correta.
#include <memory> // unique_ptr — uma única dono auto up = make_unique<Cao>("Rex"); up->falar(); // Rex: Au-au! // Destruído automaticamente ao sair de scope // Transferir propriedade auto up2 = std::move(up); // up fica nulo // shared_ptr — vários donos auto sp1 = make_shared<Gato>("Whiskers"); auto sp2 = sp1; // ref count = 2 cout << sp1.use_count(); // 2 sp1.reset(); // ref count = 1 // Destruído quando sp2 sair de scope // Polimorfismo com smart pointers vector<unique_ptr<Animal>> animais; animais.push_back(make_unique<Cao>("Buddy")); animais.push_back(make_unique<Gato>("Luna")); for (auto& a : animais) a->falar(); // Sem delete! Tudo gerido automaticamente.
| Tipo | Uso | Transferível |
|---|---|---|
unique_ptr | Propriedade exclusiva. Usa por defeito. | Sim (std::move) |
shared_ptr | Partilhado entre vários donos. | Sim (cópia) |
weak_ptr | Referência sem propriedade — evita ciclos. | Não é dono |
#include <functional> #include <algorithm> // Sintaxe: [capture](params) -> retorno { corpo } auto soma = [](int a, int b) { return a + b; }; cout << soma(3, 4); // 7 // Captura por valor int base = 10; auto somar_base = [base](int x) { return x + base; }; // Captura por referência int cont = 0; auto incrementar = [&cont]() { cont++; }; incrementar(); incrementar(); cout << cont; // 2 // Com STL — ordenar por comprimento vector<string> palavras = {"banana", "kiwi", "maçã"}; sort(palavras.begin(), palavras.end(), [](const string& a, const string& b) { return a.size() < b.size(); }); // kiwi, maçã, banana // std::function — guardar lambdas function<int(int)> fib = [&fib](int n) -> int { return n <= 1 ? n : fib(n-1) + fib(n-2); };
| Sintaxe | Significado |
|---|---|
[] | Não captura nada |
[=] | Captura tudo por valor |
[&] | Captura tudo por referência |
[x] | Captura x por valor |
[&x] | Captura x por referência |
[=, &x] | Tudo por valor, exceto x por ref. |
[this] | Captura o ponteiro this |
Pratica com estes desafios progressivos. Cada exercício tem dicas e solução completa — tenta resolver antes de ver!
Saudacao com um construtor que recebe um nome e um método dizer() que imprime uma mensagem personalizada.const que não modifica o objetoprivate e o método dizer() public.Saudacao(string n) : nome(n) {}const pois não altera o estado do objeto.#include <iostream> #include <string> using namespace std; class Saudacao { string nome; public: Saudacao(string n) : nome(n) {} void dizer() const { cout << "Olá, " << nome << "! Bem-vindo ao C++.\n"; } }; int main() { Saudacao s1("Ana"); Saudacao s2("Bob"); s1.dizer(); s2.dizer(); return 0; }
Calculadora com métodos somar, subtrair, multiplicar e dividir, com validação de divisão por zero via exceção.throwtry/catchstd::invalid_argumentconst pois não modificam estado.<stdexcept> para aceder a std::invalid_argument.try { cout << c.dividir(5, 0); } catch(...) { ... }#include <iostream> #include <stdexcept> using namespace std; class Calculadora { public: double somar(double a, double b) const { return a + b; } double subtrair(double a, double b) const { return a - b; } double multiplicar(double a, double b) const { return a * b; } double dividir(double a, double b) const { if (b == 0.0) throw invalid_argument("Divisão por zero!"); return a / b; } }; int main() { Calculadora c; cout << c.somar(3, 4) << "\n"; // 7 cout << c.multiplicar(5, 6) << "\n"; // 30 try { cout << c.dividir(10, 0) << "\n"; } catch (const invalid_argument& e) { cerr << "Erro: " << e.what() << "\n"; } return 0; }
std::vector<double> para armazenar notas. Calcula a média, o máximo e o mínimo. Lista as notas acima da média.*max_element(v.begin(), v.end()) para o máximo.for(auto n : notas) soma += n; e divide por notas.size().if (n > media) no loop.#include <iostream> #include <vector> #include <algorithm> #include <numeric> using namespace std; int main() { vector<double> notas = {15.5, 18.0, 12.0, 9.5, 17.0, 14.0}; double soma = accumulate(notas.begin(), notas.end(), 0.0); double media = soma / notas.size(); double mx = *max_element(notas.begin(), notas.end()); double mn = *min_element(notas.begin(), notas.end()); cout << "Média: " << media << "\n"; cout << "Máximo: " << mx << "\n"; cout << "Mínimo: " << mn << "\n"; cout << "Acima da média: "; for (double n : notas) if (n > media) cout << n << " "; cout << "\n"; return 0; }
Forma com método puro area(). Implementa Circulo, Retangulo e Triangulo. Usa um vector de ponteiros base para polimorfismo.virtual double area() const = 0; na classe base para a tornar abstrata.vector<unique_ptr<Forma>> com make_unique<Circulo>(raio).virtual ~Forma() = default;#include <iostream> #include <vector> #include <memory> #include <cmath> using namespace std; class Forma { public: virtual double area() const = 0; virtual string tipo() const = 0; virtual ~Forma() = default; void info() const { cout << tipo() << ": área = " << area() << "\n"; } }; class Circulo : public Forma { double r; public: Circulo(double r) : r(r) {} double area() const override { return M_PI * r * r; } string tipo() const override { return "Círculo"; } }; class Retangulo : public Forma { double l, a; public: Retangulo(double l, double a) : l(l), a(a) {} double area() const override { return l * a; } string tipo() const override { return "Retângulo"; } }; int main() { vector<unique_ptr<Forma>> formas; formas.push_back(make_unique<Circulo>(5.0)); formas.push_back(make_unique<Retangulo>(4.0, 3.0)); for (auto& f : formas) f->info(); return 0; }
std::map<string,int> para contar a frequência de cada palavra. Apresenta as palavras por ordem de frequência (descendente).istringstream ss(texto); string palavra; while(ss >> palavra) para dividir.vector<pair<string,int>> e ordena com lambda: sort(..., [](auto& a, auto& b){ return a.second > b.second; })#include <iostream> #include <sstream> #include <map> #include <vector> #include <algorithm> using namespace std; int main() { string texto = "o rato roeu a roupa do rei de roma o rato"; map<string, int> freq; istringstream ss(texto); string palavra; while (ss >> palavra) freq[palavra]++; // Ordenar por frequência vector<pair<string,int>> v(freq.begin(), freq.end()); sort(v.begin(), v.end(), [](auto& a, auto& b){ return a.second > b.second; }); for (auto& [p, c] : v) cout << p << ": " << c << "\n"; return 0; }
Vetor3D com sobrecarga de +, -, produto escalar (*), norma e operador << para output.a.x*b.x + a.y*b.y + a.z*b.z. Retorna double.sqrt(x*x + y*y + z*z). Inclui <cmath>.operator<< deve ser friend para aceder a membros privados.#include <iostream> #include <cmath> using namespace std; class Vetor3D { public: double x, y, z; Vetor3D(double x, double y, double z) : x(x), y(y), z(z) {} Vetor3D operator+(const Vetor3D& o) const { return {x+o.x, y+o.y, z+o.z}; } Vetor3D operator-(const Vetor3D& o) const { return {x-o.x, y-o.y, z-o.z}; } // Produto escalar double operator*(const Vetor3D& o) const { return x*o.x + y*o.y + z*o.z; } double norma() const { return sqrt(x*x + y*y + z*z); } friend ostream& operator<<(ostream& os, const Vetor3D& v) { return os << "(" << v.x << ", " << v.y << ", " << v.z << ")"; } }; int main() { Vetor3D a(1,2,3), b(4,5,6); cout << "a + b = " << (a+b) << "\n"; // (5, 7, 9) cout << "a · b = " << (a*b) << "\n"; // 32 cout << "|a| = " << a.norma() << "\n"; // 3.742 }
Stack<T> com operações push, pop, peek, isEmpty e size. Usa internamente um vector<T>.template <typename T> class Stack { vector<T> dados; ... }pop() e peek() devem lançar runtime_error se a stack estiver vazia.Stack<int>, Stack<string>, Stack<double>.#include <iostream> #include <vector> #include <stdexcept> using namespace std; template <typename T> class Stack { vector<T> dados; public: void push(const T& v) { dados.push_back(v); } T pop() { if (isEmpty()) throw runtime_error("Stack vazia"); T v = dados.back(); dados.pop_back(); return v; } const T& peek() const { if (isEmpty()) throw runtime_error("Stack vazia"); return dados.back(); } bool isEmpty() const { return dados.empty(); } size_t size() const { return dados.size(); } }; int main() { Stack<int> s; s.push(1); s.push(2); s.push(3); cout << s.pop() << "\n"; // 3 cout << s.peek() << "\n"; // 2 cout << s.size() << "\n"; // 2 return 0; }
unique_ptr para gestão automática de memória. Operações: inserir no início, imprimir e contar elementos.struct No { int val; unique_ptr<No> prox; }; — o destrutor automático liberta recursivamente.novo->prox = std::move(head), depois head = std::move(novo).#include <iostream> #include <memory> using namespace std; struct No { int val; unique_ptr<No> prox; No(int v) : val(v) {} }; class Lista { unique_ptr<No> head; public: void inserir(int v) { auto novo = make_unique<No>(v); novo->prox = std::move(head); head = std::move(novo); } void imprimir() const { const No* p = head.get(); while (p) { cout << p->val << " → "; p = p->prox.get(); } cout << "null\n"; } }; int main() { Lista l; l.inserir(3); l.inserir(2); l.inserir(1); l.imprimir(); // 1 → 2 → 3 → null // Sem delete! unique_ptr gere tudo. }
pipeline que aplica sequencialmente uma lista de transformações (vector<function<int(int)>>) a um valor. Demonstra composição funcional.int pipeline(int v, vector<function<int(int)>> fns)for (auto& f : fns) v = f(v); e retorna o resultado.#include <iostream> #include <vector> #include <functional> using namespace std; int pipeline(int v, vector<function<int(int)>> fns) { for (auto& f : fns) v = f(v); return v; } int main() { auto resultado = pipeline(3, { [](int x) { return x * x; }, // 3 → 9 [](int x) { return x + 1; }, // 9 → 10 [](int x) { return x * 2; }, // 10 → 20 [](int x) { return x - 5; } // 20 → 15 }); cout << resultado << "\n"; // 15 return 0; }
Matriz<T,R,C> com operações de acesso, soma, transposição e impressão formatada. Usa arrays internos de tamanho fixo (sem heap).T dados[R][C]; como membro privado para armazenamento estático sem heap.operator()(int r, int c) é mais idiomático que operator[][] para matrizes em C++.Matriz<T,C,R> — nota a inversão dos parâmetros de template!#include <iostream> #include <iomanip> using namespace std; template <typename T, int R, int C> class Matriz { T dados[R][C]{}; public: T& operator()(int r, int c) { return dados[r][c]; } T operator()(int r, int c) const { return dados[r][c]; } Matriz<T,R,C> operator+(const Matriz<T,R,C>& o) const { Matriz<T,R,C> res; for (int i=0; i<R; i++) for (int j=0; j<C; j++) res(i,j) = dados[i][j] + o(i,j); return res; } Matriz<T,C,R> transposta() const { Matriz<T,C,R> res; for (int i=0; i<R; i++) for (int j=0; j<C; j++) res(j,i) = dados[i][j]; return res; } void imprimir() const { for (int i=0; i<R; i++) { for (int j=0; j<C; j++) cout << setw(4) << dados[i][j]; cout << "\n"; } } };
Projetos maiores que combinam múltiplos conceitos. Ideais para o portfólio.
Conta, ContaPoupanca e ContaCorrente com herança. Implementa transferências, extrato e juros. Usa shared_ptr para gerir contas partilhadas entre clientes.Livro, Utilizador e Biblioteca. Requisitos: pesquisa por título/autor, empréstimos com prazo, persistência em ficheiro CSV.Carta e Tabuleiro, registo de pontuação, modo a 2 jogadores. Usa unique_ptr para as cartas.Matrix genérica. Operações: soma, produto, determinante, inversa, eliminação de Gauss. Interface de linha de comandos interativa.unordered_map. Algoritmos: BFS, DFS, caminho mais curto (Dijkstra). Leitura do grafo a partir de ficheiro.EventBus genérico usando templates e function. Subscritores, publicadores, filtros por tipo. Aplica ao design de jogos.Ferramentas, referências e comunidades para continuar a aprender.
g++ com sudo apt install g++ (Linux/WSL) ou usa o MSVC no Visual Studio (Windows). Compila sempre com g++ -std=c++17 -Wall -Wextra ficheiro.cpp -o programa para ativar todos os avisos.