Ir para o conteúdo
  • Revista PROGRAMAR: Já está disponível a edição #59 da revista programar. Faz já o download aqui!

PsySc0rpi0n

Verificar o final de um ficheiro de texto

Mensagens Recomendadas

PsySc0rpi0n    8
PsySc0rpi0n

Boas...

Tenho o seguinte code que neste momento é suposto ainda só contar as linhas de um ficheiro de texto. Mas não estou a ver como verificar se já cheguei ao fim do ficheiro já que o EOF é um inteiro e o pfile é um FILE* e o compilador queixa-se que estou a fazer uma comparação entre dois valores de tipos diferentes!

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

#define BUFFER 128

typedef struct mtable {
 char modelName [bUFFER];
 unsigned short int sizes [bUFFER];
 char measName [bUFFER];
 float measures [bUFFER];
}mtable;

FILE* open_file (char* fname){
 FILE* pfile = NULL;

 if ((pfile = fopen(fname, "r")) == NULL){
   fprintf(stderr, "File Error!");
   exit (-1);
 }
  return pfile;
}

int main(int argc, char** argv){
 FILE* pfile = NULL;
 unsigned short int line_count = 0;
 char tmp_char;

   if (argc != 2){
     printf ("Filename error!\n");
     exit (-1);
   } else
     pfile = open_file (argv [1]);

   fseek (pfile, 0, SEEK_CUR);

   while (pfile != EOF){
     fread (&tmp_char, sizeof (char), 1, pfile);
     if (tmp_char == '\n')
       line_count++;
   }

   fclose (pfile);
   return 0;
}
 

Partilhar esta mensagem


Ligação para a mensagem
Partilhar noutros sites
pwseo    223
pwseo

PsySc0rpi0n,

Directamente da manpage de fread:

RETURN VALUE
      On  success,  fread()  and  fwrite() return the number of items read or
      written.  This number equals the number of bytes transferred only  when
      size  is 1.  If an error occurs, or the end of the file is reached, the
      return value is a short item count (or zero).

      fread() does not distinguish between end-of-file and error, and callers
      must use feof(3) and ferror(3) to determine which occurred.

EDIT: Provavelmente já tinhas percebido, pela altura que escrevi o post, mas fica aqui na mesma a resposta.

Editado por pwseo

Partilhar esta mensagem


Ligação para a mensagem
Partilhar noutros sites
PsySc0rpi0n    8
PsySc0rpi0n

while (!feof (pfile)){
 fread (&tmp_char, sizeof (char), 1, pfile);
 if (tmp_char == '\n')
   line_count++;
}

Ainda assim, isto dá uma linha a mais, por isso subtraio 1 à variável line_count após sair do loop while!

Editado por PsySc0rpi0n

Partilhar esta mensagem


Ligação para a mensagem
Partilhar noutros sites
HappyHippyHippo    1153
HappyHippyHippo

O problema de uso do (f)eof e que so te retorna verdadeiro apos houver erro na leitura da stream.

O que estou a dizer e que mesmo que o stream esteja vazio e/ ou no final, e necessário efectuar uma leitura para que a flag seja marcada.

O problema no teu codigo e que quer o fread suceda ou falhe, estas a actualizar o contador, isto porque caso falhe, o valor de tmp_char nao e alterado contendo o valor lido anteriormente (que vou supor que seja o terminador de linha devido a referires que o resultado e apresentado incrementando de 1).

A solucao de decrementar está incorrecta porque se o ultimo caracter do stream nao e o terminador de linha, estaras a contar n-1 linhas do ficheiro.

O que deverias fazer, e como ja deverias saber ao ler o que ja escrevi, e validar a leitura(fread) e so incrementar se o fread suceder


IRC : sim, é algo que ainda existe >> #p@p

Partilhar esta mensagem


Ligação para a mensagem
Partilhar noutros sites
pwseo    223
pwseo

Foi precisamente pelos motivos que o HappyHippyHippo enumerou que te pedi para colocares o código (mas ele adiantou-se a responder)

Partilhar esta mensagem


Ligação para a mensagem
Partilhar noutros sites
PsySc0rpi0n    8
PsySc0rpi0n

Tentei assim mas está a contar mal na mesma.

while (!feof (pfile)){
   fread (&tmp_char, sizeof (char), 1, pfile);
   if (ferror (pfile)){
     perror ("Read Error");
     break;
   }else if (tmp_char == '\n')
     line_count++;
 }

Edited:

Alterei para

while (!feof (pfile)){
   if (!fread (&tmp_char, sizeof (char), 1, pfile)){
     break;
   }else if (tmp_char == '\n')
     line_count++;
}

E parece que já está a dar! Mas pelos vistos a condição do while é redundante pelo que vi na net! Se colocar um '1' no while, o resultado é o mesmo!

Editado por PsySc0rpi0n

Partilhar esta mensagem


Ligação para a mensagem
Partilhar noutros sites
pwseo    223
pwseo

PsySc0rpi0n,

Não compreendeste o que o HappyHippyHippo tentou dizer-te. Vamos experimentar de outra forma.

Primeiro, atenta no que diz a manpage de feof:

DESCRIPTION
      ...
      The function feof() tests the  end-of-file  indicator  for  the  stream
      pointed  to by stream, returning nonzero if it is set.  The end-of-file
      indicator can be cleared only by the function clearerr().
      ...

Portanto, a feof só tem o resultado esperado se e apenas se o indicador de end-of-file tiver sido definido.

O teu problema é o seguinte: esse indicador só é definido depois de tentares ler além do final do ficheiro.

E agora, que implicação tem isto no teu código?

Simples. Depois de leres o último char do ficheiro, a feof vai continuar a devolver zero, e por isso vais ter mais uma iteração no ciclo. Nessa nova iteração, o teu código faz o seguinte:

  1. usa a fread para ler um char, mas isto não acontece por termos agora ultrapassado o final do ficheiro; tmp_char mantém-se inalterado agora o indicador de end-of-file é definido
  2. uma vez que o valor de tmp_char continua a ser '\t', o contador de linhas é incrementado

Só após esta iteração desnecessária é que feof vai finalmente devolver o valor pretendido.

Seguem-se exemplos de tudo isto, mas simplificados para perceberes.

#include <stdio.h>

int main(void) {
   char c = 0;
   int  n = 0;

   while (!feof(stdin)) {
       fread(&c, 1, 1, stdin);
       n += 1;
   }

   printf("Lidos %d chars.\n", n);

   return 0;
}

E agora um exemplo de uma sessão de execução deste programa:

~/.../tmp $ echo -n "abc" | ./psy
Lidos 4 chars.

Como podes ver, foi contado um char a mais.

O que tu precisas de fazer é simples: tens que utilizar o valor de retorno da fread, que te indica o número de blocos lidos (que no teu caso são blocos de 1 char). Quando a fread devolver zero vais utilizar feof para determinar se o motivo da leitura de zero blocos foi ter chegado ao fim do ficheiro.

Exemplo:

#include <stdio.h>

int main(void) {
   char c = 0;
   int  n = 0;

   while (fread(&c, 1, 1, stdin) > 0) {
       /* n só é incrementado SE fread() devolver um valor positivo */
       n += 1;
   }

   if (feof(stdin)) {
       /* Só imprimimos esta mensagem se o ficheiro tiver sido lido na totalidade */
       printf("Lidos %d chars.\n", n);
   }

   return 0;
}

~/.../tmp $ echo -n "abc" | ./psy-fixed
Lidos 3 chars.

Relativamente ao teu último post, agora: o primeiro excerto de código sofre do problema que acabei de descrever, pelo simples facto de ignorares o valor devolvido pela fread. O segundo excerto já funciona precisamente porque agora verificas o valor de fread e quebras o ciclo quando este for zero, impedindo assim a iteração adicional. Logicamente, e como tu reparaste também, esse while não precisa de invocar feof.

De facto, o teu ciclo deveria ser assim:

/* já agora, sizeof(char) é SEMPRE 1 */
while (fread(&tmp_char, 1, 1, pfile)) {
   if (tmp_char == '\n')
       line_count++;
}

/* Se necessário, podes depois utilizar feof() e ferror() para distingir
* diferentes motivos de término do input, mas fora do ciclo while.
*/

E pronto, espero que tenhas compreendido. E já agora, uma vez que estás a ler um char de cada vez, provavelmente seria mais simples fazê-lo com fgetc:

while ((c = fgetc(pfile)) != EOF) {
   if ('\n' == (char)c)
       line_count++;
}

Outra solução seria ler grandes blocos de chars de cada vez com fread, mas isso para já iria complicar-te a vida.

Vai dizendo coisas.

Partilhar esta mensagem


Ligação para a mensagem
Partilhar noutros sites
PsySc0rpi0n    8
PsySc0rpi0n

Eu tinha percebido o que estava a acontecer... O que não percebi há bocado e acho que continuo a não perceber ao certo é o uso de funções como ferror, feof e outras do género!

Partilhar esta mensagem


Ligação para a mensagem
Partilhar noutros sites
pwseo    223
pwseo

Eu disse num dos fragmentos de código: usas feof e ferror somente após fread ter devolvido zero. Ora vê novamente (deixei um comentário no código).

  • Voto 1

Partilhar esta mensagem


Ligação para a mensagem
Partilhar noutros sites
PsySc0rpi0n    8
PsySc0rpi0n

Mas usava como?

Assim:

while (fread(&tmp_char, 1, 1, pfile))
 if (tmp_char == '\n')
   line_count++;
if (feof(pfile))
 printf ("End Of File reached!\n");
if (ferror(pfile)) 
 printf ("Error reading\n");

Partilhar esta mensagem


Ligação para a mensagem
Partilhar noutros sites
pwseo    223
pwseo

Exacto. Atenção, nem sempre é boa ideia omitir as chavetas, pois pode levar a confusão :)

Volto a salientar: se vais ler 1 char de cada vez, fica mais limpo se utilizares fgetc.

Editado por pwseo

Partilhar esta mensagem


Ligação para a mensagem
Partilhar noutros sites
PsySc0rpi0n    8
PsySc0rpi0n

Ok, vou então mudar para o fgetc e colocar os parêntesis...

Outra coisa, para contar as linhas, tinha outro método qe não a leitura caractér a caractér? Por exemplo, se lesse palavra a palavra, conseguia detectar o '\n'?

E também porque é que na tua versão do fgetc, fizeste o cast da variável 'c'? Ela já não era um char?

Editado por PsySc0rpi0n

Partilhar esta mensagem


Ligação para a mensagem
Partilhar noutros sites
HappyHippyHippo    1153
HappyHippyHippo

Outra coisa, para contar as linhas, tinha outro método qe não a leitura caractér a caractér? Por exemplo, se lesse palavra a palavra, conseguia detectar o '\n'?

e como achas que consegues ler palavra a palavra ?

E também porque é que na tua versão do fgetc, fizeste o cast da variável 'c'? Ela já não era um char?

não

o valor de retorno da função fgetc é um inteiro, isto porque a função necessita de retornar o valor EOF que é um inteiro e não um char


IRC : sim, é algo que ainda existe >> #p@p

Partilhar esta mensagem


Ligação para a mensagem
Partilhar noutros sites
PsySc0rpi0n    8
PsySc0rpi0n

e como achas que consegues ler palavra a palavra ?

Havia uma função que lia até encontrar um espaço ou até o tamanho por nós definido. Não me recordo se era o fscanf ou sscanf. Não me recordo.

não

o valor de retorno da função fgetc é um inteiro, isto porque a função necessita de retornar o valor EOF que é um inteiro e não um char

Então como é que ele usa o valor de retorno para verificar se coincide com o '\n'? Como é que alguma vez vai coincidir com o' \n'?

Editado por PsySc0rpi0n

Partilhar esta mensagem


Ligação para a mensagem
Partilhar noutros sites
HappyHippyHippo    1153
HappyHippyHippo

Havia uma função que lia até encontrar um espaço ou até o tamanho por nós definido. Não me recordo se era o fscanf ou sscanf. Não me recordo.

com o scanf consegues ler uma palavra, mas isso não quer dizer que seja mais fácil de perceber o código, um exemplo seria:

while (scanf("$*[^\n]") >= 0)
 nlinhas++;

mas este código tem um problema de ainda assim teres de remover/ler o caracter terminador de linha, e como tal poderias implementar a segunite solução:

while (scanf("$*[^\n]\n") >= 0)
 nlinhas++;

mas mesmo assim, aqui terias o problema de que se a última linha não terminar com o '\n', voltas a contar n-1 linhas do ficheiro

Então como é que ele usa o valor de retorno para verificar se coincide com o '\n'? Como é que alguma vez vai coincidir com o' \n'?

não percebo esta pergunta

olhaste bem para o código do pwseo ?


IRC : sim, é algo que ainda existe >> #p@p

Partilhar esta mensagem


Ligação para a mensagem
Partilhar noutros sites
PsySc0rpi0n    8
PsySc0rpi0n

Ok, então ler char a char será a solução mais simples e viável para já, certo?

Quanto ao código do pwseo, o que eu percebo é que o If dentro do while compara o '\n' com o valor de retorno do fgetc e se o resultado for verdadeiro, então foi porque encontrou o '\n',e faz isto enquanto o fgetc não sinalizar a variável 'c' com um EOF. Percebi mal?

Partilhar esta mensagem


Ligação para a mensagem
Partilhar noutros sites
PsySc0rpi0n    8
PsySc0rpi0n

Ok, acho que já percebi o code do pwseo relativamente ao fgetc. O fgetc retorna um unsigned char convertido num int para verificar se o ponteiro para o ficheiro está sinalizado com EOF. Portanto se convertermos o que o fgetc retorna de volta para um unsigned char, conseguimos verificar se é um '\n'.

Partilhar esta mensagem


Ligação para a mensagem
Partilhar noutros sites
HappyHippyHippo    1153
HappyHippyHippo

Ok, acho que já percebi o code do pwseo relativamente ao fgetc. O fgetc retorna um unsigned char convertido num int para verificar se o ponteiro para o ficheiro está sinalizado com EOF. Portanto se convertermos o que o fgetc retorna de volta para um unsigned char, conseguimos verificar se é um '\n'.

estás quase certo, o único erro é que a função não retorna um unsigned char convertido para inteiro, simplesmente retorna um inteiro.

diz, qual a diferença entre um unsigned char de valor 97 e um inteiro de valor 97 ?


IRC : sim, é algo que ainda existe >> #p@p

Partilhar esta mensagem


Ligação para a mensagem
Partilhar noutros sites
HappyHippyHippo    1153
HappyHippyHippo

O char 97 é o ASCII equivalente que é ou o 'a' ou o 'A' e o inteiro 97 é mesmo o 97.

é ao 'a', mas como podes ver, o valor numérico é o mesmo.

no entanto o valor EOF é avaliado como um valor negativo representado num inteiro, e como tal, deve ser sempre avaliado como um inteiro.

e é essa a razão que o fgetc retorna un inteiro.


IRC : sim, é algo que ainda existe >> #p@p

Partilhar esta mensagem


Ligação para a mensagem
Partilhar noutros sites

Crie uma conta ou ligue-se para comentar

Só membros podem comentar

Criar nova conta

Registe para ter uma conta na nossa comunidade. É fácil!

Registar nova conta

Entra

Já tem conta? Inicie sessão aqui.

Entrar Agora

×

Aviso Sobre Cookies

Ao usar este site você aceita os nossos Termos de Uso e Política de Privacidade. Este site usa cookies para disponibilizar funcionalidades personalizadas. Para mais informações visite esta página.