paulo1205
(usa Ubuntu)
Enviado em 21/11/2018 - 15:30h
madtheusss escreveu:
Bom , esse exercício é para faculdade, mas estou tendo problemas com alguns erros que não consigo arrumar nem com poder divino.
Cuidado para não blasfemar. Mas se o seu deus não consegue depurar um programa, talvez você tenha de buscar o Deus verdadeiro.
2 - Eu preciso criar um arquivo .txt mostrando as entradas que foi efetuada no código, e mesmo usando uma das duas funções que citei acima (fgets ou scanf) ele cria o arquivo, contudo o arquivo é criado vazio.
Vamos dar uma olhada.
Já tentei pedir ajuda para os professores da faculdade e eles sempre dizem que não sabem mexer no compilador GCC e linux e tals, o que eu acho um absurdo.
É absurdo mesmo. Mas não é atípico. O que tem de professor ruim nas faculdades de informática não é brincadeira.
Que mal lhe pergunte, que compiladores e ambientes de desenvolvimento sua faculdade usa/recomenda?
A seguir o código:
#include <iostream>
#include <string.h>
#include <math.h>
#include <cstdio>
Um tanto inconsistente esse uso de cabeçalhos na forma C++ e na forma em C, não? Por que você não coloca todos eles na forma C++ (omitindo <cmath>, que você não usa, e incluindo <cstdlib>, que você usa por causa de
system () e
exit ())?
Na verdade, acho até que você poderia/deveria usar mais coisas em C++, em lugar de recursos equivalentes do C. <fstream> em lugar de <cstdio>, por exemplo.
#include <algorithm> // Algoritmos padronizado (para usar std::min, abaixo)
#include <fstream>
#include <iomanip> // Manipuladores de I/O (para usar std::flush, abaixo)
#include <iostream>
#include <string>
#include <cctype>
#include <cstring>
using namespace std;
typedef struct reg
A palavra-chave
typedef não serve para nada nesse contexto, pois você não diz qual o nome alternativo do tipo
reg .
Não se esqueça que, em C++, os nomes atribuídos a
struct s,
class es,
union s e
enum s são automaticamente considerados nomes de tipos. Por isso mesmo, eu disse, acima, que faltou um nome alternativo para o tipo
reg , que já havia sido criado como parte da declaração da estrutura.
{
char nome [20];
char tel [20];
char email [20];
Vamos acrescentar um construtor com argumento default, só para opcionalmente zerar o conteúdo de novos objetos, o que é essencial para a segurança de dados a serem gravados em disco (caso contrário, um objeto criado numa área de memória usada anteriormente poderia ficar com parte desses dados, e tais dados irem parar no disco).
reg(bool zeradados=false){
if(zeradados)
memset(this, 0, sizeof *this);
}
};
int tamanho (FILE * );
void cadastrar (FILE *);
void consultar (FILE *);
void gerararqtxt (FILE *);
A não ser que você seja obrigado pela professora de gosto duvidável, troque os ponteiros para
FILE por referências a
std::fstream .
size_t tamanho(fstream &);
void cadastrar(fstream &);
void consultar(fstream &);
void gerararqtxt(fstream &);
int main()
{
int op;
FILE *arq;
if ((arq = fopen("dados.dat" , "rb+")) == NULL) //fopen abrindo o arquivo
{
if ((arq = fopen ("dados.dat", "wb+")) == NULL) // sinal de + é para ler e escrever
{
cout << "Falha ao abrir o arquivo\n";
system("pause");
}
}
Por consistência, troca-se aqui também
FILE por
std::fstream , e já se abre o arquivo logo de cara, no construtor. Eu me valho ainda do fato de que todas as escritas são feitas no final do arquivo, e ligo o
flag que força toda escrita a ocorrer no final (
std::ios_base_app ).
Além disso, não adianta prosseguir com o programa se o arquivo não tiver sido aberto com sucesso.
fstream arq("dados.dat", ios_base::in|ios_base::out|ios_base::binary|ios_base::app);
if(!arq){
cerr << "Falha ao abrir o arquivo. Saindo do programa.\n";
return 1;
}
Eis aqui um problema, na minha opinião.
clear é um comando do mundo UNIX. Se sua professora ou seus colegas executarem esse programa no Windows, em lugar de se limpar a tela, aparecerá uma mensagem de erro. Se você trocar para
CLS , o erro aparecerá no Linux.
Você realmente precisa de apagar a tela? Não pode, simplesmente, pular uma dúzia de espaços em branco e apresentar novamente o menu na parte de baixo da tela?
Usar
system () é sempre uma coisa a ser vista com desconfiança. Se você
realmente precisar de limpar a tela, considere a mensagem 12 do seguinte tópico:
https://www.vivaolinux.com.br/topico/C-C++/Preciso-fazer-um-programa-em-C-para-cadastra-alunos-consu... .
cout << "========== AGENDA ==========";
cout << "\n 1 - Cadastrar\n";
cout << "\n 2 - Consultar\n";
cout << "\n 3 - Gerar arquivo\n";
cout << "\n 4 - Sair\n";
cout << "========== Contatos: %d=\n", tamanho(arq);
cout << "Opção: ";
Em lugar de fazer sete operações de escrita distintas para escrever uma mesma mensagem, considere escrever tudo de uma vez, mas sem deixar o código visualmente, através da concatenação de
strings adjacentes feita automaticamente pelo compilador.
cout <<
"========== AGENDA ==========\n"
"1 - Cadastrar\n"
"2 - Consultar\n"
"3 - Gerar arquivo\n"
"4 - Sair\n"
"========== Contatos: " << tamanho(arq) << "\n"
"Opção: " << flush
;
Um jeito de evitar recorrer a bacalhaus de limpar o
buffer de entrada , tais como
fflush(stdin) (que ainda por cima extrapola o que o padrão determina para
fflush ()) é sempre ler linhas inteiras, e procurar extrair delas os dados desejados.
string line;
if(!getline(cin, line)){
cerr << "\nErro de leitura ao ler opção do terminal. Saindo
do programa.\n";
return 1;
}
size_t nao_numerico;
op=stoi(line, &nao_numerico);
if(nao_numerico<line.length() && !isspace(line[nao_numerico]))
op=0;
Obviamente, você poderia transformar esse tipo de leitura numa função, e usá-la quando precisar ler um inteiro.
switch (op)
{
case 1: // Cadastrar novo contato
cadastrar(arq);
break;
case 2: //Consultnado contato por registro
consultar (arq);
break;
case 3: //Gerando arquivo txt com todos os contatos
gerararqtxt(arq);
break;
case 4: //fechando arquivo
fclose(arq);
Esse fechamento explícito não é necessário quando se usa
std::fstream , pois o destrutor do objeto cuida de fechar o arquivo.
}
}while (op != 4);
}
void cadastrar(FILE *arq)
Ou a nova versão em C++ puro.
void cadastrar(fstream &arq)
Tire essa palavra-chave
class .
reg , por si só, já o nome de um tipo.
Por segurança, tenha o hábito de zerar o conteúdo de todos os dados que você for gravar em disco, a fim de não revelar eventual conteúdo anterior de memória que esteja sendo reutilizada.
reg contato(true); // Força o construtor a zerar qualquer conteúdo anterior da memória ocupada pelo objeto.
Usando sempre leitura segura com
std::getline () e
std::string , podemos apenas ter a seguinte declaração, que vai servir para todos os nossos propósitos.
string line;
fflush(stdin); // fflush para limpar o buffer
Isso morre.
cout << "Cadastrando novo registo: \n";
cout << "\n Numero do registro: \n", tamanho(arq) +1;
Este já é o segundo lugar em que você usa vírgula em lugar de
<< . Não confunda saída formatada em
std::ostream com
printf ().
cout << "Número do registro: " << 1+tamanho(arq) << "\n\n";
Suponho que esse perigoso (ou, no melhor do casos, inútil)
getchar () possa ser removido.
cout << "Nome................: ";
fgets(contato.nome, 15 , stdin);// Aqui era para usar o gets
Há coisa melhor.
if(!getline(cin, line)){
cerr << "\nErro de leitura no terminal. Saindo do programa.\n";
exit(1);
}
strncpy(contato.nome, line.c_str(), min(sizeof contato.nome, line.length()));
Uso semelhante pode ser feito para
contato.tel e
contato.email .
Note que eu permito ocupar todo o tamanho do vetor com caracteres, não necessariamente deixando espaço para o
byte nulo ao final da
string ; só haverá
bytes nulos quando o texto lido for menor do que a largura do campo. Isso evita o desperdício de três bytes por registro com informação inútil, mas vai requerer cuidado, depois, na hora de usar dados lidos do arquivo (na função
geraarqtxt () eu mostro um jeito de usar).
cout << "\nTelefone..........: ";
fgets(contato.tel, 15, stdin); // Aqui tambem
cout <<"\nEmail..............: ";
fgets (contato.email, 20 ,stdin);// Aqui tambem
cout << "\nConfirma <s/n>: ";
cin >> confirma;
Aqui, também, a leitura de linhas inteiras ajuda a evitar deixar caracteres extras no
buffer de entrada.
if(!getline(cin, line)){ /* Aborta, como acima. */ }
if (toupper(confirma) == 'S')
// Note que eu uso declaração dentro de if(), conforme padrão C++17.
if(
size_t non_space{line.find_first_not_of(" \t\n\r\f")};
non_space<line.lenght() && toupper(line[non_space])=='S'
)
{
cout << "\n Gravando ....: \n";
fseek(arq,0,SEEK_END);
Como o arquivo foi aberto com modo
append , o
seek é desnecessário.
fwrite(&contato, sizeof(reg), 1, arq);
A forma de fazer isso com
std::fstream é a seguinte. Lembre-se de checar se a saída foi salva corretamente.
arq.write(reinterpret_cast<char *>(&contato), sizeof contato);
if(!arq){
cerr << "\nErro ao gravar registro: " << strerror(errno) << ". Saindo do programa.\n";
exit(1);
}
}
}
int tamanho(FILE *arq)
{
fseek(arq, 0,SEEK_END);
return ftell(arq) / sizeof(reg);
}
Essa função acaba sendo intrusiva, alterando o ponteiro do arquivo para ler seu tamanho. Seria bom você preservar a posição anterior antes de fazer o deslocamento, e depois voltar a ela antes de retornar (isso, aliás, provavelmente explica sua lista vazia).
size_t tamanho(fstream &arq){
auto old_offset=arq.tellg();
size_t result=arq.seekg(0, ios_base::end).tellg()/sizeof(reg);
arq.seekg(old_offset, ios_base::beg);
return result;
}
void consultar(FILE *arq)
{
reg contato;
int nr;
cout << "\n Consulta pelo Codigo\n";
cout << "\n Informe o Código....: ";
cin >> nr;
if ((nr <= tamanho(arq)) && (nr > 0))
{
fseek(arq, (nr - 1) * sizeof (reg), SEEK_SET);
fread(&contato, sizeof (reg), 1, arq);
cout << "\n Nome............:", contato.nome;
cout << "\n Telefone........:", contato.tel;
cout << "\n ne-mail...........:", contato.email;
}
else
{
cout << "\n Número de registro inválido!";
}
system("pause");
}
Fica como exercício para você a adaptação de
consultar () para uso de
std::fstream . Lembre-se que usar
system () é ruim e não-portável.
void gerararqtxt(FILE *arq)
{
char nomearq [20];
cout << "Digite o nome do arquivo";
cin >> nomearq;
strcat (nomearq,".txt");
FILE *arqtxt = fopen(nomearq, "w");
Veja como fica mais fácil trocando o
array nativo por
std::string (e lembrando de também trocar “
FILE * ” por “
fstream & ”.
string nomearq;
cout << "Digite o nome do arquivo (sem sufixo/extensão): ";
getline(cin, nomearq);
ofstream arqtxt(nomearq+".txt", ios_base::out|ios_base::trunc);
Esse ponto-e-vírgula aí é especialmente nocivo: ele termina o comando
if com um comando nulo, e o bloco que vem depois é executado incondicionalmente.
{
cout << "Não foi possível criar esse arquivo!\n";
system("pause");
return;
}
fprintf(arqtxt, "Nome Telefone email\n");
fprintf(arqtxt, "==============================================\n");
int nr;
reg contato;
for (nr = 0; nr < tamanho(arq); nr++)
Chamar
tamanho () tem um custo (que depois da correção apontada acima, ficou maior ainda). Como o tamanho não deve mudar ao longo do laço de repetição, possivelmente valerá a pena criar uma variável nova antes do laço de repetição, e chamar
tamanho () apenas uma vez, gravando o valor nessa variável (ou constante).
const auto tam_arq=tamanho(arq);
for(size_t nr=0; nr<tam_arq; nr++)
Mas, aliás, você está lendo o arquivo sequencialmente. Então você não precisa de nada disso: basta deslocar o ponteiro de leitura para o início do arquivo e ir lendo registro a registro, independentemente do tamanho.
for(
arq.seekg(0, ios_base::beg);
arq.read(reinterpret_cast<char *>(&contato), sizeof contato);
/* Nada! */
)
{
fseek(arq, nr * sizeof(reg), SEEK_SET);
fread(&contato, sizeof(reg), 1, arq);
Se você optar por ler sequencialmente (que é o lógico), as duas linhas acima somem.
fprintf(arqtxt, "%-20s-%12s%-20s\n", contato.nome, contato.tel, contato.email);
Eu continuo defendendo coerência: I/O em C++ ou I/O em C; a mistura confunde.
Além disso, lembrando que os campos não necessariamente têm
bytes nulos ao final, será necessário impor limites de tamanho na hora de os exibir.
arqtxt.setiosflags(arqtxt.left);
arqtxt
<< setw(20) << string(contato.nome, min(sizeof contato.nome, 20))
<< setw(12) << string(contato.tel, min(sizeof contato.tel, 12))
<< setw(20) << string(contato.email, min(sizeof contato.email, 20))
<< '\n'
;
fprintf(arqtxt, "===========================================================\n");
fclose(arqtxt);
Esse
fprintf () é mole de trocar. Já esse
fclose () não será mais necessário, pois o destrutor do objeto se encarrega de fechar o arquivo. Contudo, note que o
fclose () acima está no lugar errado: ele deveria estar fora do laço de repetição, não dentro dele.
Tendo feito a leitura sequencial e sido atingido o fim do arquivo, o
stream vai ficar marcado com a indicação de fim de arquivo e de que a última operação falhou. Para continuar operando com o arquivo no restante do programa, temos de limpar essas indicações, mas apenas se não tiver ocorrido um erro mais grave.
if(arq.bad()){ // Erro de sistema, mais do que apenas o fim do arquivo.
cerr << "Erro grave de leitura. Saindo do programa.\n";
exit(1);
}
if(arq.eof())
arq.clear();