0

Estou estudando C e acabei de chegar no conteúdo de arquivos, no qual estou tendo muita dificuldade. São diversas funções para o manejo dos arquivos e eu não sei exatamente se estou me saído bem ou não.

Logo abaixo tem meu código referente a um exercício que fiz, a princípio ele faz o que o enunciado pede, porém, não tenho certeza se está na melhor forma possível. Se puderem me ajudar informando o que pode ter de errado e dando dicas de como posso melhorá-lo.

3-Escreva um programa para converter o conteúdo de um arquivo texto em caracteres maiúsculos. O programa deverá ler do usuário o nome do arquivo a ser convertido e o nome do arquivo a ser salvo.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

int main(){

    FILE *arq1, *arq2;
    char converte[50], NomeArquivo[50];

    /*Leio o nome do 1º arquivo e verifico sua existência logo o abro para leitura*/
    printf("Digite o nome do 1º arquivo(.txt) de onde virão os dados: ");
    scanf("%s", NomeArquivo);
    arq1 = fopen(NomeArquivo, "r");
    if(arq1 == NULL){
        perror("Erro: ");
        exit(1);
    }

    /*Leio o nome do 2º arquivo e verifico sua existência logo o abro para escrita*/
    printf("Digite o nome do 2º arquivo(.txt) aonde serão salvo os dados: ");
    scanf("%s", NomeArquivo);
    arq2 = fopen(NomeArquivo, "w");
    if(arq2 == NULL){
        perror("Erro: ");
        exit(1);   
    }

    //Passo o valor de arq1 para variével converte
    fgets(converte, 50, arq1);
    //Converto o conteúdo que estava em arq1 para maiúscula
    for(int i = 0; i < strlen(converte); i++){
        converte[i] = toupper(converte[i]);
    }
    //Depois de convertida ponho o valor de converte em arq2 (destino)
    fputs(converte, arq2);


    fclose(arq1);
    fclose(arq2);

    return 0;
}
2
  • 1
    seu código funciona bem , mas irá ler apenas 50 caracteres do arquivo de origem, se seu arquivo contiver mais caracteres , use um loop while com a condição EOF , que é o final do arquivo ,
    – devair1010
    Commented 7/10/2023 às 4:59
  • 2
    Sobre a sugestão de verificar EOF em um loop, é uma abordagem que tem alguns problemas, veja aqui, aqui e aqui para entender melhor (e ver tb alternativas de solução - o primeiro link, inclusive, mostra como ler um arquivo inteiro, basta adaptar).
    – hkotsubo
    Commented 10/10/2023 às 11:52

1 Resposta 1

1

Tem muitos problemas com seu programa, e ele nem faz o esperado.

Acompanhe:

  • o programa lê apenas um máximo de 50 caracteres do arquivo de entrada. Mas o enunciado fala em converter um arquivo em outro.
  • o programa usa strlen para achar o tamanho de uma suposta string de entrada, mas não está garantido em lugar algum que a entrada seja uma string. Se testar o programa com um arquivo que tenha apenas 50 'a' esperando ver na saida 50 'A' já imagina o que vai acontecer...
  • scanf é usada para ler o nome dos arquivos. scanf é um scanner, dá pra ver pelo nome. Foi escrita com outro propósito e não para ler nomes do teclado. Se o usuário digitar "Jhonny Rivers.txt" acha que vai funcionar? Não vai.
  • para scanf, um scanner, é absolutamente normal não ler nada. Por isso um int é retornado dizendo quantos items conseguiu ler. Só que o programa não testa e segue em frente.
  • inicialize todas as variáveis e declare uma por linha. É grátis. Especialmente ponteiros não devem ficar sem um valor conhecido.

Prefira:

    FILE* arquivo = fopen( nome, modo);
  • o programa usa fputs() para gravar na saída, uma escolha errada porque vai mudar o arquivo. Essa não é a noção de converter. fputs() acrescenta um '\n' ao final de cada linha. Vai mudar o tamanho e criar linhas onde nem tinha.

Seu programa só deve mudar as letras, de minúsculas para maiúsculas. Um arquivo de 100 letras vai virar um arquivo de 100 letras. Se tiver alguma letra. Se não tiver nenhuma seu arquivo de saída tem que ser igualzinho ao de entrada. Essa é a noção de converter.

  • pode até chamar perror para mostrar uma mensagem de erro mais significativa, mas chamar exit() em main tem pouco sentido, porque main é `main e retornar dela vai terminar o programa de qualquer maneira.

  • se deu erro ao abrir o segundo arquivo pode ser preciosismo mas deveria fechar o primeiro antes de encerrar.

  • não use esses comentários /* */ isso dá muito erro e é um porre pra acertar. Levou uns anos para colocarem // em C mas foi logo nos anos 80 então faz tempo.

  • não use acentos em comentários: podem não sair na tela nem serem impressos em computadores que não o seu.

  • ao declarar algo em C se associa um nome a um tipo. Por exemplo em FILE *arq1, *arq2 seu compilador vai prontamente te dizer que esá declarando arq1 como FILE* e arq2 como FILE*. E é claro que se arq é FILE* então *arq é FILE. Isso é C. Mas não é a declaração. Sua religião pode recomendar declarar int a, *b, c; mas a declaração é int a; int* b; int c;, o que seu compilador pode confirmar. Prefira tipo nome;

Sobre a conversão

Entenda que pode concluir seu programa assim como está tentando, lendo um número máximo de letras por vez e gravando na saída depois de converter.

Mas o problema com essa idéia é que de fato vai chamar uma função toupper()para testar cada letra em um loop. Para um milhão de letras um milhão de chamadas de função, divididas em grupos pelo loop externo. Tudo para fazer a mesma coisa. Todo a vai virar A por exemplo. É como testar sempre se é uma minúscula para depois converter.

Em geral nesses casos se usa uma tabela, e não se testa nada.

A idéia é que cada byte da saída passa pelo programa e sai do outro igualzinho, ou alterado se for uma letra minúscula.

considere uma tabela

Veja esse trecho

    int   idx[256];
    for (int i = 0; i < 256; i += 1) idx[i] = i;
    for (int i = 'a'; i <= 'z'; i += 1)
        idx[i] = i - ('a' - 'A');

ou mesmo

    int idx[256];
    for (int i = 0; i < 'a'; i += 1) idx[i] = i;
    for (int i = 'a'; i <= 'z'; i += 1)
        idx[i] = i - ('a' - 'A');
    for (int i = 'z' + 1; i < 256; i += 1) idx[i] = i;

Então essa tabela é o programa: se for minúscula converte, se não for apenas copia. E os computadores são extremamente rápidos em fazer isso.

Então com os arquivos abertos seu programa poderia ser de uma linha só:

    while ( (ch=fgetc(in)) >= 0) putc(idx[ch], out);

Onde ch é int porque fgetc e fputc operam com int.

Exemplo

O programa abaixo faz a conversão. E permite que o usuário digite os nomes dos arquivos já na linha de comando se preferir. Você vai preferir, por exemplo, porque ao rodar o programa já se sabe que vai passar os nomes dos dois arquivos então é um porre ficar esperando dois prompts e digitar dois nomes e teclar dois ENTER a toa...

SOpt> ./p.exe a.txt b.txt
    De "a.txt" para "b.txt"
SOpt>

Mas se não digitar, ok, espera os chatos prompts e tal

SOpt> ./p
Entrada: a.txt
  Saida: b.txt
    De "a.txt" para "b.txt"
SOpt>

Eis o código

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char** argv)
{
    char  entrada[256] = {0};
    char* p            = NULL;
    char  saida[256]   = {0};

    int idx[256];
    for (int i = 0; i < 'a'; i += 1) idx[i] = i;
    for (int i = 'a'; i <= 'z'; i += 1)
        idx[i] = i - ('a' - 'A');
    for (int i = 'z' + 1; i < 256; i += 1) idx[i] = i;

    if (argc > 2)
    {
        strcpy(entrada, argv[1]);
        strcpy(saida, argv[2]);
    }
    else
    {
        printf("Entrada: ");
        p = fgets(entrada, sizeof(entrada), stdin);
        if (p == NULL) return -1;  // nao leu nada
        printf("  Saida: ");
        p = fgets(saida, sizeof(saida), stdin);
        if (p == NULL) return -2;  // nao leu nada
    }
    // fgets preserva o \n se couber no vetor
    if (entrada[strlen(entrada) - 1] == '\n')
        entrada[strlen(entrada) - 1] = 0;
    if (saida[strlen(saida) - 1] == '\n')
        saida[strlen(saida) - 1] = 0;
    fprintf(
        stderr, "    De \"%s\" para \"%s\"", entrada,
        saida);
    // abre os arquivos
    FILE* in = fopen(entrada, "r");
    if (in == NULL) return -3;
    FILE* out = fopen(saida, "w");
    if (out == NULL)
    {
        fclose(in);
        return 3;
    }
    // converte
    int ch = 0;
    while ((ch = fgetc(in)) >= 0) putc(idx[ch], out);
    fclose(in);
    fclose(out);
    return 0;
}

a tabela é sempre a mesma

Só que essa tabela idx é

    int idx[256];
    for (int i = 0; i < 'a'; i += 1) idx[i] = i;
    for (int i = 'a'; i <= 'z'; i += 1)
        idx[i] = i - ('a' - 'A');
    for (int i = 'z' + 1; i < 256; i += 1) idx[i] = i;

Isso roda então para montar a tabela: toda vez que o programa é executado vai gerar exatamente a mesma coisa. Claro que não é esperto, são apenas 256 valores que pode ter em cada byte do arquivo. idx vai ficar sempre igualzinho, assim:

const int idx[256] =
{
  0,   1,   2,   3,   4,   5,   6,   7,   8,   9,  10,  11,  12,  13,  14,  15, 
 16,  17,  18,  19,  20,  21,  22,  23,  24,  25,  26,  27,  28,  29,  30,  31, 
 32,  33,  34,  35,  36,  37,  38,  39,  40,  41,  42,  43,  44,  45,  46,  47, 
 48,  49,  50,  51,  52,  53,  54,  55,  56,  57,  58,  59,  60,  61,  62,  63, 
 64,  65,  66,  67,  68,  69,  70,  71,  72,  73,  74,  75,  76,  77,  78,  79, 
 80,  81,  82,  83,  84,  85,  86,  87,  88,  89,  90,  91,  92,  93,  94,  95, 
 96, 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 
'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 123, 124, 125, 126, 127, 
128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 
144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 
160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 
176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 
192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 
208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 
224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 
240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255
};

Então é claro que isso já podia estar no código.

Meio chatinho de digitar, mas podia ser gerado, certo? Claro.

Esse programa

#include "stdio.h"
int main(int argc, char** argv)
{
    FILE* saida = stdout;
    int   disco = 0;  // gerou arquivo?
    if (argc > 1)
    {
        saida = fopen(argv[1], "w");
        disco = 1;
    };
    int out = 1;
    fprintf(saida, "\nconst int idx[256] =\n{\n\n");
    for (int i = 0; i < 'a'; i += 1)
    {   // nao muda nada
        fprintf(saida, "%3d, ", i);
        if (out % 16 == 0) fprintf(saida, "\n");
        out += 1;
    };
    for (int i = 'a'; i <= 'z'; i += 1)
    {
        fprintf(saida, "'%c', ", i - ('a' - 'A'));
        if (out % 16 == 0) fprintf(saida, "\n");
        out += 1;
    };
    // agora o intervalo entre '9'e 'A', 58..64
    for (int i = 'z' + 1 ; i < 255; i += 1)
    {
        fprintf(saida, "%3d, ", i);
        if (out % 16 == 0) fprintf(saida, "\n");
        out += 1;
    };
    fprintf(saida, "255\n\n}; // ARFNeto '23\n");
    if (disco)
    {
        fprintf(
            stderr, "\nGerado trecho de codigo em '%s'\n",
            argv[1]);
    };
    fclose(saida);
    return 0;
};  // main()

faz isso, a vida toda. E dá pra escolher o nome do arquivo ou ver na saída padrão mesmo...

exemplo com a tabela estática

#include <stdio.h>
#include <string.h>
#include "tabela.h"

int main(int argc, char** argv)
{
    char  entrada[256] = {0};
    char* p            = NULL;
    char  saida[256]   = {0};

    if (argc > 2)
    {
        strcpy(entrada, argv[1]);
        strcpy(saida, argv[2]);
    }
    else
    {
        printf("Entrada: ");
        p = fgets(entrada, sizeof(entrada), stdin);
        if (p == NULL) return -1;  // nao leu nada
        printf("  Saida: ");
        p = fgets(saida, sizeof(saida), stdin);
        if (p == NULL) return -2;  // nao leu nada
    }
    // fgets preserva o \n se couber no vetor
    if (entrada[strlen(entrada)-1] == '\n')
        entrada[strlen(entrada)-1] = 0;
    if (saida[strlen(saida)-1] == '\n')
        saida[strlen(saida)-1] = 0;
    fprintf(stderr, "    De \"%s\" para \"%s\"", entrada, saida);
    // abre os arquivos
    FILE* in = fopen(entrada, "r");
    if (in == NULL) return -3;
    FILE* out = fopen(saida, "w");
    if (out == NULL)
    {
        fclose(in);
        return 3;
    }
    // converte
    int ch = 0;
    while ( (ch=fgetc(in)) >= 0) putc(idx[ch], out);
    fclose(in);
    fclose(out);
    return 0;
}

E tabela.h pode ser gerado pelo programa anterior, apenas usando

    ./gen tabela.h
    // ou
   ./gen > tabela.h

Considere

3
  • Agradeço a explicação, será de grande valia, achei bem interessante esse modo mais complexo e "profissional" de resolver o exercício. Gostaria de lembrá-lo que sou iniciante e dizer que o que pode ser obvio a você, pode não ser para mim. Vou tentar aplicar suas dicas. Muito obrigado! Commented 11/10/2023 às 16:49
  • 2
    Espero que te ajude. Não entendi a observação sobre o que pode ser óbvio pra mim ou para você. Se tem alguma dúvida em particular pergunte: posso editar a resposta. Foi para tirar dúvidas que escrevi o exemplo e o programa acessório
    – arfneto
    Commented 11/10/2023 às 21:15
  • int ch = 0; while ((ch = fgetc(in)) >= 0) putc(idx[ch], out); esse é o programa todo. Apenas pega os valores da tabela. de in para out
    – arfneto
    Commented 1/01 às 15:11

Você deve fazer log-in para responder a esta pergunta.

Esta não é a resposta que você está procurando? Pesquise outras perguntas com a tag .