Este experimento explora técnicas fundamentais de programação em C++, incluindo sobrecarga de operadores, manipulação de fluxos de arquivos e tratamento de exceções. O objetivo é implementar um sistema completo para gerenciar dados de participantes de competições.
Primeira Tarefa: Implementação de Estruturas e Funções utilitárias
Definição da estrutura de dados para competidores:
competidor.hpp
#pragma once
#include <iomanip>
#include <iostream>
#include <string>
struct Competidor {
long codigo; // Identificador único
std::string nome; // Nome do participante
std::string curso; // Curso/Programa de estudo
int resolucoes; // Quantidade de problemas resolvidos
int penalidade; // Tempo total de penalização
};
// Sobrecarga do operador de inserção
inline std::ostream& operator<<(std::ostream& saida, const Competidor& c) {
saida << std::left;
saida << std::setw(15) << c.codigo
<< std::setw(15) << c.nome
<< std::setw(15) << c.curso
<< std::setw(10) << c.resolucoes
<< std::setw(10) << c.penalidade;
return saida;
}
// Sobrecarga do operador de extração
inline std::istream& operator>>(std::istream& entrada, Competidor& c) {
entrada >> c.codigo >> c.nome >> c.curso >> c.resolucoes >> c.penalidade;
return entrada;
}
Módulo de funções auxiliares para manipulação de dados:
helpers.hpp
#pragma once
#include <fstream>
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>
#include "competidor.hpp"
// Critério de ordenação: resoluções(decrescente), penalidade(crescente)
inline bool criterio_ordenacao(const Competidor& a, const Competidor& b) {
if(a.resolucoes != b.resolucoes)
return a.resolucoes > b.resolucoes;
return a.penalidade < b.penalidade;
}
// Escrita genérica para qualquer stream de saída
inline void gravar(ostream& os, const vector<Competidor>& dados) {
for (const auto& item : dados)
os << item << '\n';
}
// Exibição em tela
inline void exibir(const vector<Competidor>& dados) {
gravar(cout, dados);
}
// Persistência em arquivo
inline void salvar(const string& nome_arquivo, const vector<Competidor>& dados) {
ofstream arquivo_saida(nome_arquivo);
if (!arquivo_saida)
throw runtime_error("não foi possível abrir " + nome_arquivo);
gravar(arquivo_saida, dados);
}
// Carregamento de arquivo (ignora linha de cabeçalho)
inline vector<Competidor> carregar(const string& nome_arquivo) {
ifstream arquivo_entrada(nome_arquivo);
if (!arquivo_entrada)
throw runtime_error("não foi possível abrir " + nome_arquivo);
string linha;
getline(arquivo_entrada, linha); // Ignora cabeçalho
vector<Competidor> lista;
Competidor temp;
int sequencial;
while (arquivo_entrada >> sequencial >> temp)
lista.push_back(temp);
return lista;
}
Programa principal para ordenação de competidores:
main_ordenar.cpp
#include <algorithm>
#include <iostream>
#include <stdexcept>
#include <vector>
#include "competidor.hpp"
#include "helpers.hpp"
const string ARQUIVO_ENTRADA = "./dados.txt";
const string ARQUIVO_SAIDA = "./resultado.txt";
void executar() {
vector<Competidor> competidores;
try {
competidores = carregar(ARQUIVO_ENTRADA);
sort(competidores.begin(), competidores.end(), criterio_ordenacao);
exibir(competidores);
salvar(ARQUIVO_SAIDA, competidores);
} catch (const exception& erro) {
cerr << erro.what() << '\n';
return;
}
}
int main() {
executar();
return 0;
}
Análise Técnica - Questões de Estudo:
Pergunta 1: Polimorfismo de streams
(1) Por que a função write() aceita qualquer tipo de stream? std::cout e std::ofstream são derivados de std::ostream. Em C++, um objeto derivado pode ser implicitamente convertido para referência da classe base, permitindo que a mesma função opere com diferentes tipos de stream.
(2) É necessário criar versões overload da função para cada tipo? Não, uma única função que aceita std::ostream& é suficiente para tratar ambos os casos.
Pergunta 2: Tratamento de erros
(1) O que acontece quando a abertura do arquivo falha? Uma exceção do tipo runtime_error é lançada pelo construtor de ifstream/ofstraem.
(2) Como o programa responde a erros? O bloco try-catch intercepta a exceção, exibe a mensagem descritiva através do método what() e encerra a execução da função através de return.
Pergunta 3: Diferenças entre abordagens de E/S As implementações com stream de arquivo e stdout possuem equivalência funcional, pois compartilham a mesma interface pública e produzem resultados idênticos.
Pergunta 4: Validação de dados O carregamento atual pode falhar com dados mal formatados, pois a função não possui validação nem mecanismo para ignorar linhas problemáticas.
Segunda Tarefa: Sistema Orientado a Objetos
Definição da classe Estudante:
estudante.hpp
#pragma once
#include <iostream>
#include <string>
class Estudante {
public:
Estudante() = default;
~Estudante() = default;
const string get_curso() const;
int get_nota() const;
friend ostream& operator<<(ostream& os, const Estudante& e);
friend istream& operator>>(istream& is, Estudante& e);
private:
int codigo;
string nome;
string curso;
int nota; // Escala 0-100
};
Implementação dos métodos da classe:
estudante.cpp
#include "estudante.hpp"
#include <stdexcept>
const string Estudante::get_curso() const {
return curso;
}
int Estudante::get_nota() const {
return nota;
}
// Operador de saída com formatação tabulada
ostream& operator<<(ostream& os, const Estudante& e) {
os << e.codigo << "\t" << e.nome << "\t" << e.curso << "\t" << e.nota;
return os;
}
// Operador de entrada com verificações
istream& operator>>(istream& is, Estudante& e) {
if (!is) return is;
is >> e.codigo;
if (!is) return is;
is >> e.nome;
if (!is) return is;
is >> e.curso;
if (!is) return is;
is >> e.nota;
// Validação de nota
if (e.nota < 0 || e.nota > 100) {
cerr << "[alerta] Código " << e.codigo << " com nota inválida: " << e.nota << "\n";
e.nota = 0;
}
return is;
}
Classe gerenciadora de estudantes:
gerenciador.hpp
#pragma once
#include <string>
#include <vector>
#include "estudante.hpp"
class Gerenciador {
public:
void carregar(const string& arquivo); // Carrega dados de arquivo
void ordenar(); // Ordena por curso e nota
void exibir() const; // Exibe em tela
void salvar(const string& arquivo) const; // Persiste em arquivo
private:
void escrever(ostream& os) const; // Escrita genérica
private:
vector<Estudante> estudantes;
};
Implementação dos métodos de gerenciamento:
gerenciador.cpp
#include "gerenciador.hpp"
#include "estudante.hpp"
#include <fstream>
#include <stdexcept>
#include <algorithm>
#include <iostream>
#include <sstream>
#include <limits>
void Gerenciador::carregar(const string& arquivo) {
ifstream fluxo(arquivo);
if (!fluxo.is_open()) {
throw runtime_error("falha ao abrir arquivo: " + arquivo);
}
estudantes.clear();
Estudante temp;
string linha;
int numero_linha = 0;
while (getline(fluxo, linha)) {
numero_linha++;
istringstream stream_linha(linha);
try {
stream_linha >> temp;
estudantes.push_back(temp);
} catch (const invalid_argument& e) {
cerr << "[aviso] linha " << numero_linha << " " << e.what() << ", ignorada\n";
}
}
fluxo.close();
}
void Gerenciador::ordenar() {
sort(estudantes.begin(), estudantes.end(), [](const Estudante& a, const Estudante& b) {
if (a.get_curso() != b.get_curso()) {
return a.get_curso() < b.get_curso();
}
return a.get_nota() > b.get_nota();
});
}
void Gerenciador::exibir() const {
escrever(cout);
}
void Gerenciador::salvar(const string& arquivo) const {
ofstream fluxo(arquivo);
if (!fluxo.is_open()) {
throw runtime_error("falha ao abrir arquivo: " + arquivo);
}
escrever(fluxo);
fluxo.close();
}
void Gerenciador::escrever(ostream& os) const {
for (const auto& alu : estudantes) {
os << alu << "\n";
}
}
Programa com interface interativa:
main_sistema.cpp
#include <iostream>
#include <limits>
#include <string>
#include "gerenciador.hpp"
const string ARQUIVO_DADOS = "./dados.txt";
const string ARQUIVO_RESULTADO = "./resultado.txt";
void mostrar_menu() {
cout << "\n**********Sistema de Gerenciamento**********\n"
"1. Carregar arquivo\n"
"2. Ordenar dados\n"
"3. Exibir em tela\n"
"4. Salvar em arquivo\n"
"5. Encerrar\n"
"Selecione uma opção: ";
}
void iniciar() {
Gerenciador gerenciador;
while(true) {
mostrar_menu();
int opcao;
cin >> opcao;
try {
switch (opcao) {
case 1: gerenciador.carregar(ARQUIVO_DADOS);
cout << "Carregamento concluído\n"; break;
case 2: gerenciador.ordenar();
cout << "Ordenação concluída\n"; break;
case 3: gerenciador.exibir();
cout << "Exibição concluída\n"; break;
case 4: gerenciador.salvar(ARQUIVO_RESULTADO);
cout << "Arquivo salvo com sucesso\n"; break;
case 5: return;
default: cout << "Opção inválida\n";
}
}
catch (const exception& e) {
cout << "Erro: " << e.what() << '\n';
}
}
}
int main() {
iniciar();
return 0;
}