Alguém pode me ajudar com esse código ?

1. Alguém pode me ajudar com esse código ?

Matheus Diniz
madtheusss

(usa Manjaro Linux)

Enviado em 18/11/2018 - 12:21h

Bom , esse exercício é para faculdade, mas estou tendo problemas com alguns erros que não consigo arrumar nem com poder divino.
1 - Minha professora usa windows e la eles usam a função gets_s, aqui eu tentei usar a funçção gets mas não foi com muito sucesso, substituí por fgets ou cin (scanf) ,segundo ela disso que isso não é o ideal.
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.
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.
A seguir o código:

#include <iostream>
#include <string.h>
#include <math.h>
#include <cstdio>

using namespace std;

typedef struct reg
{
char nome [20];
char tel [20];
char email [20];

};

int tamanho (FILE * );
void cadastrar (FILE *);
void consultar (FILE *);
void gerararqtxt (FILE *);

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

do
{
system ("clear");
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: ";
cin >> op;
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);
}
}while (op != 4);
}

void cadastrar(FILE *arq)
{
class reg contato;
char confirma;

fflush(stdin); // fflush para limpar o buffer
cout << "Cadastrando novo registo: \n";
cout << "\n Numero do registro: \n", tamanho(arq) +1;
getchar();
cout << "Nome................: ";
fgets(contato.nome, 15 , stdin);// Aqui era para usar o gets
cout << "\nTelefone..........: ";
fgets(contato.tel, 15, stdin); // Aqui tambem
cout <<"\nEmail..............: ";
fgets (contato.email, 20 ,stdin);// Aqui tambem
cout << "\nConfirma <s/n>: ";
cin >> confirma;

if (toupper(confirma) == 'S')
{
cout << "\n Gravando ....: \n";
fseek(arq,0,SEEK_END);
fwrite(&contato, sizeof(reg), 1, arq);
}
}
int tamanho(FILE *arq)
{
fseek(arq, 0,SEEK_END);
return ftell(arq) / sizeof(reg);
}

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

void gerararqtxt(FILE *arq)
{
char nomearq [20];
cout << "Digite o nome do arquivo";
cin >> nomearq;
strcat (nomearq,".txt");
FILE *arqtxt = fopen(nomearq, "w");
if (!arqtxt);
{
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++)
{
fseek(arq, nr * sizeof(reg), SEEK_SET);
fread(&contato, sizeof(reg), 1, arq);
fprintf(arqtxt, "%-20s-%12s%-20s\n", contato.nome, contato.tel, contato.email);
fprintf(arqtxt, "===========================================================\n");
fclose(arqtxt);
}
}



  


2. Re: Alguém pode me ajudar com esse código ?

Paulo
paulo1205

(usa Ubuntu)

Enviado em 19/11/2018 - 16:49h

madtheusss escreveu:

Bom , esse exercício é para faculdade, mas estou tendo problemas com alguns erros que não consigo arrumar nem com poder divino.
1 - Minha professora usa windows e la eles usam a função gets_s, aqui eu tentei usar a funçção gets mas não foi com muito sucesso, substituí por fgets ou cin (scanf) ,segundo ela disso que isso não é o ideal.


Mas como assim?! Vocês estão usando C++, então não deveriam usar nem gets() nem tampouco gets_s() ou fgets() em cima de arrays ou ponteiros de caracteres, mas sim std::getline() em dados do tipo std::string. Se o fizerem, não fará diferença estar no Windows, no LInux ou em qualquer outro sistema.

Mesmo assim, fico curioso de saber por que sua professora diria que usar fgets() “não é o ideal”. Por acaso ela ignora que gets_s() é uma função que não é definida no padrão propriamente, mas sim num anexo (o infame Annex K) explicitamente designado como opcional ao padrão do C11?

Em todo caso, você pode criar sua própria versão stub de gets_s(), usando as funções tradicionais, que são realmente parte do padrão da biblioteca do C.

#ifdef linux
int gets_s(char *dest, size_t dest_size){
if(!dest || dest_size==0)
return (errno=EINVAL);
errno=0;
if(!fgets(dest, dest_size, stdin)){
dest[0]='\0';
return errno;
}
size_t len=strlen(dest);
if(len==dest_size-1 && dest[len-1]!='\n'){
int discarded;
do
discarded=getchar();
while(discarded!='\n' && discarded!=EOF);
}
else if(dest[len-1]=='\n')
dest[len-1]='\0';
return 0;
}
#endif


Preciso sair agora. Mais tarde complemento a resposta.


3. Re: Alguém pode me ajudar com esse código ?

Paulo
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 structs, classes, unions e enums 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;
}



do
{
system ("clear");


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
;


        cin >>  op; 


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) 


{
class reg contato;


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. 


    char confirma; 


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


    getchar(); 


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


    if (!arqtxt); 


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();


} 







Patrocínio

Site hospedado pelo provedor RedeHost.
Linux banner

Destaques

Artigos

Dicas

Tópicos

Top 10 do mês

Scripts