paulo1205
(usa Ubuntu)
Enviado em 14/02/2018 - 08:17h
Eu dei uma examinada por alto no seu programa.
Você cometeu alguns erros que são comuns com iniciantes. Se você for iniciante e ainda não tiver sido devidamente orientado, não tem muito do que se envergonhar.
A primeira coisa que salta ao olhos é a forma como você chama as funções especializadas de dentro de
main () e depois volta delas para
main (). Parece que você tentou ser bem explícito, mas o que você fez está errado, e é uma fonte de possível estouro do tamanho máximo da pilha de execução. Assim sendo, esse é um erro que você tem de corrigir, e aprender a fazer do jeito correto para não mais incorrer nele.
Quando uma função termina (i.e. chega ao final ou executa um comando
return ), o programa automaticamente retorna o fluxo de execução para logo depois do ponto em que a função foi chamada. Assim sendo, em vez de ter algo na forma abaixo (semelhante ao que você fez),
int main(){
// Código repetido a cada ciclo (ex.: imprimir menu de opções, ler opção etc.).
switch(opt){
case 1: func1(); break;
case 2: func2(); break;
// ...
}
// Código após decidir sair do programa.
}
void func1(){
// Faz o que tem de fazer.
main(); // Chama main() de novo, desde seu começo.
}
void func2(){
// Faz o que tem de fazer.
main(); // Chama main() de novo, desde seu começo.
} , o certo seria ter o seguinte:
int main(){
// Código de preparação (executado só uma vez).
do {
// Código repetido a cada ciclo (ex.: imprimir menu de opções, ler opção etc.).
switch(opt){
case 1: func1(); break;
case 2: func2(); break;
// ...
}
} while(condicao_de_repeticao);
// Código após decidir sair do programa.
}
void func1(){
// Faz o que tem de fazer.
// Não chama main(): apenas retorna.
}
void func2(){
// Faz o que tem de fazer.
// Não chama main(): apenas retorna.
}
Outro problema com seu programa é o uso aparentemente inconsistente de arquivos.
A forma mais comum de trabalhar com algo semelhante a uma base de dados é abrir o arquivo uma única vez, no início do programa, com um modo que permita tanto leitura como escrita, e ir fazendo as consultas, inclusões e modificações à medida em que elas forem necessárias. Além disso, os registros costumam ter tamanhos fixos, de modo que você consiga posicionar operações de leitura individuais através de uma conta simples (nº do regsitro desejado vezes o tamanho de cada registro) e que não precise reescrever todo o banco de dados quando tiver de alterar um único registro.
O inconveniente de arquivos com registros de tamanho fixo é que eles dão um pouco mais de trabalho de manipular: em vez de usar
std::strings para os campos de texto do registro, você teria de usar arrays de caracteres tradicionais.
Mas trabalhar com texto sequencial tem inconvenientes, a meu ver, maiores. Um deles é que o arquivo não é, como talvez possa parecer, um simples conjunto de linhas de texto, mas sim um conjunto de bytes em sequência. Se um arquivo tem dez linhas e você quiser modificar apenas o conteúdo da quinta linha, a única forma disso acontecer de modo seguro é garantindo que o novo conteúdo da linha tem exatamente o mesmo número de caracteres que a anterior: se o novo tamanho for diferente, você pode acabar ficando com uma quantidade diferente de linhas (se o conteúdo novo for menor), interferindo com o conteúdo das linhas seguintes (se for maior) ou mesmo corromper o arquivo (especialmente no Windows, que indica o fim de linha com dois bytes, em vez de com apenas um).
Se você quiser continuar trabalhando com múltiplas aberturas e arquivo texto sequencial, eu sugiro que você abra o arquivo para gravação com a opção
ios::trunc atmbém na hora de regravar o arquivo (e não apenas ao apagar seu conteúdo), a fim de não deixar “restos” no arquivo caso um registro encolha de tamanho.
Ainda mais um problema é a repetição de código muito parecido. Tal repetição fica muito evidente na sua função
AlterarNotas (). Facilitaria corrigir esse problema se você trocasse a forma da estrutura de dados de
struct Aluno{
std::string matricula, nomeAluno, resultadoFinal;
float nota_1, nota_2, nota_3, nota_4, media;
}; para
struct Aluno{
std::string matricula, nome /* nomeAluno é redundante, já que a struct já se chama Aluno */, resultadoFinal;
float notas[4], media;
}; . Com isso, sua função de alterar notas poderia ser bem reduzida (mostrando apenas um trecho e sem verificação de erros):
// ...
int n;
cout << "Qual nota deseja alterar (1 a 4)? ";
cin >> n;
// Como os arrays em C têm índices que começam em zero, diminuo 1 do que o usuário tiver digitado.
cout << "Digite a nota do " << (n--) << "º bimestre: ";
cin >> tabelaDeNotas[id].notas[n];
// ...
const auto &aluno=tabelaDeNotas[id];
arquivo << aluno.matricula << ' ' << aluno.nome;
for(const auto & nota: aluno.notas)
arquivo << ' ' << nota;
arquivo << '\n';
Isso ficaria melhor ainda se você criasse funções que fizessem a entrada e a saída do registro, e usasse essas funções para manipular o registro inteiro, em vez de trabalhar com cada campo em todas as operações de E/S posteriores.
ostream operator<<(ostream &os, const Aluno &al){
os << al.matricula << ' ' << al.nome;
for(const auto & nota: al.notas)
os << ' ' << nota;
return os << '\n';
}
istream operator>>(istream &is, Aluno &al){
is >> al.matricula >> al.nome;
for(auto & nota: al.notas)
is >> al.nota;
is.ignore(); // Ignora marca de linha de linha.
return is;
}
// ...
void ObterDados(...){
// ...
istream arquivo("caminho_do_arquivo.dat");
while(arquivo >> tabelaDeNotas[n])
n++;
// O bloco de três linhas acima é agora plenamente suficiente para ler o arquivo todo
// (desde, é claro, que ele tenha menos registros do que o tamanho máximo do array).
}
void GravarDados(...){
// ...
ostream arquivo("caminho_do_arquivo.dat", ios::trunc);
while(n<total_registros && (arquivo << tabelaDeNotas[n]))
n++;
// ...
}