C++: Prática: Banco de Dados Simples

Última atualização: 2026-08-26

Na aula 51, construímos um sistema de agenda.

Agora, vamos construir nosso segundo projeto prático abrangente — um banco de dados simples.

Este projeto usa muitos recursos avançados do C++, sendo um ótimo teste do que você aprendeu.


1. Requisitos do Projeto

(1) 1.1 Requisitos Funcionais

Funcionalidade Descrição
CREATE TABLE Criar uma tabela
INSERT Inserir dados
SELECT Consultar dados
WHERE Filtragem condicional
Index Acelerar consultas
Transaction Garantir ACID

(2) 1.2 Design Simplificado

Como este é um projeto educacional, faremos uma versão simplificada:



2. Design da Estrutura da Tabela

(1) 2.1 Definição de Coluna

▶ Exemplo 2: Exemplo de Código (Dificuldade ⭐)

TEXT 📖 Somente leitura
#include <iostream>
#include <string>
#include <variant>
#include <vector>

// Definição de coluna
struct Column {
	std::string name;
	enum Type { INT, STRING } type;
};

// Tipo de valor (usar variant para armazenar tipos diferentes)
using Value = std::variant<int, std::string>;

// Linha (uma linha de dados)
using Row = std::vector<Value>;

// Tabela
class Table {
private:
	std::string name;
	std::vector<Column> columns;
	std::vector<Row> rows;

public:
	Table(const std::string& name, const std::vector<Column>& columns)
	: name(name), columns(columns) {}
	
	// Inserir uma linha
	void insert(const Row& row) {
		if (row.size() != columns.size()) {
			std::cerr << "Column count mismatch" << std::endl;
			return;
		}
		rows.push_back(row);
	}
	
	// Consultar (simplificado: retornar todas as linhas)
	std::vector<Row> select() {
		return rows;
	}
	
	// Exibir estrutura da tabela
	void describe() const {
		std::cout << "Table name: " << name << std::endl;
		std::cout << "Columns: " << std::endl;
		for (const auto& col : columns) {
			std::cout << "  " << col.name << " (";
			if (col.type == Column::INT) {
				std::cout << "INT";
			} else {
				std::cout << "STRING";
			}
			std::cout << ")" << std::endl;
		}
	}
};

Saída:

TEXT 📖 Somente leitura
Table name:
Columns:
  (
INT
STRING
)


3. Implementação de Consultas

(1) 3.1 Cláusula WHERE

CPP
// Função predicado (para WHERE)
using Predicate = std::function<bool(const Row&)>;

// Consulta com condição
std::vector<Row> selectWhere(Predicate pred) {
	std::vector<Row> result;
	for (const auto& row : rows) {
		if (pred(row)) {
			result.push_back(row);
		}
	}
	return result;
}

// Exemplo: encontrar linhas onde age > 18
auto predicate = [](const Row& row) {
	int age = std::get<int>(row[1]); // Assume que coluna 2 é age
	return age > 18;
};

auto result = table.selectWhere(predicate);


4. Implementação de Índice

(1) 4.1 Índice Simples

TEXT 📖 Somente leitura
#include <map>

// Índice (simplificado: apenas indexar tipos int)
class Index {
private:
	std::map<int, std::vector<int>> index; // Valor -> lista de números de linha

public:
	void build(const std::vector<Row>& rows, int colIndex) {
		index.clear();
		for (int i = 0; i < rows.size(); i++) {
			int key = std::get<int>(rows[i][colIndex]);
			index[key].push_back(i);
		}
	}
	
	std::vector<int> lookup(int key) {
		auto it = index.find(key);
		if (it != index.end()) {
			return it->second;
		}
		return {};
	}
};


5. Suporte a Transações

(1) 5.1 Transação Simplificada

CPP
#include <stack>

class Transaction {
private:
	std::stack<std::vector<Row>> undoStack;

public:
	// Iniciar transação (salvar estado atual)
	void begin(Table& table) {
		undoStack.push(table.rows);
	}
	
	// Commit (limpar pilha de undo)
	void commit() {
		while (!undoStack.empty()) {
			undoStack.pop();
		}
	}
	
	// Rollback (restaurar estado anterior)
	void rollback(Table& table) {
		if (!undoStack.empty()) {
			table.rows = undoStack.top();
			undoStack.pop();
		}
	}
};


6. Exemplo Completo

▶ Exemplo 1: Usando o Banco de Dados Simples (Dificuldade ⭐⭐⭐)

TEXT 📖 Somente leitura
int main() {
	// Criar tabela
	Table t("users", {{"name", Column::STRING}, {"age", Column::INT}});
	
	// Inserir dados
	t.insert({"Alice", 25});
	t.insert({"Bob", 30});
	t.insert({"Charlie", 20});
	
	// Consultar todos
	auto rows = t.select();
	std::cout << "All users:" << std::endl;
	for (const auto& row : rows) {
		std::cout << std::get<std::string>(row[0]) << ", "
			<< std::get<int>(row[1]) << std::endl;
	}
	
	return 0;
}

Saída:

TEXT 📖 Somente leitura
All users:
, 


7. Direções de Extensão

(1) 7.1 Extensões de Funcionalidade

Funcionalidade Dificuldade Descrição
Persistência ⭐⭐ Salvar em arquivo
JOIN ⭐⭐⭐⭐ Junções multi-tabela
SQL Parser ⭐⭐⭐⭐⭐ Analisar declarações SQL
Controle de Concorrência ⭐⭐⭐⭐ Segurança de threads


❓ Exercícios

(1) Exercício Básico (Dificuldade ⭐⭐)

Adicione um recurso DELETE ao banco de dados.

(2) Exercício Intermediário (Dificuldade ⭐⭐⭐)

Adicione um recurso UPDATE ao banco de dados.

(3) Exercício Desafio (Dificuldade ⭐⭐⭐⭐)

Implemente um SQL parser simples que suporte SELECT * FROM table WHERE age > 18.


▶ Exemplo 3: Definição de estrutura de tabela de banco de dados (Dificuldade ⭐)

CPP
#include <iostream>
#include <string>
#include <vector>
#include <variant>

// Tipo de valor (suporta múltiplos tipos de dados)
using Value = std::variant<int, std::string, double>;

// Uma linha de dados
struct Row {
    int id;
    Value name;
    Value age;
    Value score;

    void display() const {
        std::cout << "ID: " << id << std::endl;
        std::cout << "Name: " << std::get<std::string>(name) << std::endl;
        std::cout << "Age: " << std::get<int>(age) << std::endl;
        std::cout << "Score: " << std::get<double>(score) << std::endl;
    }
};

int main() {
    Row r = {1, std::string("Alice"), 20, 95.5};
    r.display();
    return 0;
}
▶ Experimente

Saída:

TEXT 📖 Somente leitura
ID: 
Name: 
Age: 
Score: 

❓ Perguntas Frequentes

P: Quão rápida é a busca em map? R: map é implementado com uma árvore rubro-negra internamente, então busca, inserção e exclusão são todas O(log n). Para 1 milhão de registros, o pior caso requer apenas cerca de 20 comparações. P: Por que usar CSV para armazenamento em arquivo? R: O formato CSV é simples, legível por humanos e pode ser aberto diretamente no Excel. JSON suporta estruturas aninhadas mas é um pouco mais complexo de analisar. Para produção, SQLite é recomendado. P: Como prevenir injeção de SQL? R: Esta é uma implementação simples. Em ambientes de produção reais, sempre use consultas parametrizadas ou prepared statements. Nunca concatene diretamente a entrada do usuário em strings SQL.

📖 Resumo

Ponto de Conhecimento Aplicação
variant Armazenar valores de tipos diferentes
vector<Row> Armazenar dados de tabela
map Implementar índices
Objetos de função Implementar predicados WHERE

📝 Exercícios

  1. Básico (Dificuldade ⭐): Execute o programa de banco de dados, crie uma tabela "students" (campos: matrícula, nome, idade, nota), insira 3 registros e consulte todos os registros.

  2. Intermediário (Dificuldade ⭐⭐): Adicione um recurso de "consulta ordenada" — suporte SELECT * FROM table ORDER BY age. Dica: use std::sort + expressão lambda para ordenar por um campo especificado.

  3. Desafio (Dificuldade ⭐⭐⭐): Implemente "junção multi-tabela" — crie duas tabelas (students, grades), suporte SELECT students.name, grades.score FROM students JOIN grades ON students.id = grades.student_id. Você precisa projetar a lógica de JOIN e a mesclagem de resultados.


Próxima aula: Resumo do Curso e Roteiro Avançado (#53)

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%