Manipulação de Arquivos e Operadores em C++: Sistema de Gerenciamento de Dados

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;
}

Publicado em 7-3 03:43