Partage
  • Partager sur Facebook
  • Partager sur Twitter

[RESOLU] Fonction fputc et boucles - problème

Sujet résolu
    13 août 2021 à 17:26:30

    Bonjour à tous, 

    Melchior, je suis le tuto C pendant mes vacances (enfin je ne fais pas que ça :D ) et je tente des trucs. J'en suis au chapitre sur l'écriture dans les fichiers, donc tout ce qui se trouve après, je ne le connais pas encore (aka "excusez moi si je pose une question dont la réponse se trouve dans un chapitre subséquent). Normalement entre le forum et le net, j'arrive à comprendre mes boulettes, mais là je bloque. Du coup je viens demander de l'aide.

    Ce que je cherche à faire :

    - j'ai un petit fichier ("fichier1.txt") dans lequel j'ai 4 fois une suite de caractères ou de chiffres (mes tests, c'était avec 1234567890\n1234567890\n etc ou BCDEF\nBCDEF etc). 4 parce que je suis paresseux, mais j'aimerais que ça marche pour "autant de lignes que je veux", donc j'utilise une boucle.

    - j'aimerais remplacer à chaque ligne le premier caractère par 'A'. Je ne suis pas compliqué, j'imagine que si je comprends comment le faire, avec un fseek, je saurais remplacer à peu près n'importe quelle position avec un caractère (modulo ce que j'ai lu sur le défaut de scanf et la prise en compte de \n).

    Mon idée initiale : 

    1. à chaque ligne, j'enregistre caractère après caractère, et si j'arrive à \n ou à EOF, je sors de la boucle = la boucle a vocation à amener mon curseur à chaque début de ligne (donc plutôt après chaque \n)

    2. Avant chaque boucle, je remplace le premier caractère par 'A' via un fputc()

    Mais ça marche pas... 

    Mon code en l'état : 

    #include <stdio.h>
    #include <stdlib.h>
    #include "main.h"
    #define SIZE_TEXT 100
    
    int main()
    {
        FILE *fichier = NULL; //initialisation d'un pointeur fichier à NULL
        fichier = fopen("fichier1.txt", "r+"); //ouverture du fichier et récupération du pointeur pour contrôle et utilisation
        int caractere = 'Z'; //histoire d'initialiser avec quelque chose que je n'ai pas dans le fichier
    
        if (fichier != NULL) // si ouverture du fichier = OK
        {
    
        while (caractere != EOF) //boucle pour passer de ligne en ligne jusqu'à la fin du fichier
        {
    
        fputc('A', fichier);
    
        do // boucle qui lit chaque caractère, et qui s'arrête après la lecture d'un \n ou d'un EOF
        {
            fseek(fichier, 0, SEEK_CUR);
            caractere = fgetc(fichier); // caractère prend la valeur du caractère lu
    
            if (caractere != EOF) // pout contrôle, j'affiche chaque caractère lu, sauf EOF
            {
            printf("%c", caractere);
            }
    
            if (caractere == EOF) // pour contrôle, j'affiche "EOF" si j'arrive à cette valeur.
                printf("\nEOF");
    
    
        } while (caractere != '\n' && caractere != EOF); // fin de la boucle do de lecture des caractères
    
        }
    
        }
        else //affichage d'erreur à l'ouverture du fichier
        {
            printf("Erreur à l'ouverture du fichier ou pointeur non alimente.");
        }
    
        if (fclose(fichier) == 0) //fermeture du fichier et affichage d'un message de contrôle
        {
            printf("\nFermeture reussie");
        }
        else
        {
            printf("\nFermeture echouee");
        }
        return 0;
    }
    

    Alors : 

    - initialement, je n'avais pas le fseek : 

    -- sans le fputc et sans le fseek, mon programme lit le fichier ligne par ligne... pas de souci

    -- sans le fseek mais avec le fputc : mon programme ajoute bien 'A' en début de fichier, puis il insère des tonnes d'espaces (j'ai l'impression qu'il se coupe parce qu'il boucle, donc il ferme le fichier proprement). Je ne comprends pas d'où viennent ces espaces.

    - j'ai tenté le fseek pour avancer ou reculer d'un caractère, mais alors 

    -- avec un fseek CUR à +1, le programme n'affiche sur la console que les 2 ou 3 e lettre (j'ai un doute)

    -- avec un fseek à -1, le programme boucle en m'affichant A (ça me semble logique)

    -- avec un fseek à 0, je pensais qu'il aurait le même comportement que sans fseek, mais non. Et ça je ne le comprends pas

    Mes questions : 

    - d'où proviennent ces fichus espaces ?

    - pourquoi ça ne boucle pas éternellement avec le fseek alors que j'avais la croyance aveugle qu'il ferait pareil ?

    - A côté de quoi suis-je passé pour arriver à mon résultat (j'avais réussi à faire quelque chose d'intéressant, je crois avec le fseek+1 après le "caractere = fgetc", mais le A était implanté en seconde position des lignes...) ?

    - pourquoi la vie est si cruelle ?

    -
    Edité par Melchiorpdb 15 août 2021 à 14:08:27

    • Partager sur Facebook
    • Partager sur Twitter
      13 août 2021 à 18:21:12

      Si j'ai un conseil à te donner, lis le fichier d'entrée et génères un fichier de sortie. C'est beaucoup plus propre.
      Si ton fichier  était très gros, cela te prendrait beaucoup de temps.
      Quand tu auras vu l'utilisation de malloc, tu pourras lire le fichier d'un seul coup.
      fseek et ftell t'aideront à connaître la grandeur du fichier.
      • Partager sur Facebook
      • Partager sur Twitter

      Le Tout est souvent plus grand que la somme de ses parties.

        13 août 2021 à 21:50:24

        Salut Pierrot, 

        c'est sympa de me répondre !

        Je suis sûr qu'il y a des manières plus élégantes de faire, et je les utiliserai probablement en avançant dans le cours. Là, j'essaie de comprendre ce qui ne fonctionne pas, même si c'est un peu théorique... tu ne vois pas ?

        • Partager sur Facebook
        • Partager sur Twitter
          13 août 2021 à 23:17:56

          -

          -
          Edité par edgarjacobs 13 août 2021 à 23:18:24

          • Partager sur Facebook
          • Partager sur Twitter

          On écrit "j'ai tort", pas "tord" qui est le verbe "tordre" à la 3ème personne de l'indicatif présent

            14 août 2021 à 2:01:20

            Je constate la même chose que toi, il y a 1024 caractères blancs par ligne en incluant la fin de ligne.
            La première ligne contient bien un 'a' au début.
            Je n'ai pas d'explication bien que le 1024 devrait me dire quelque chose.
            C'est la première fois que je vois ce genre de code.
            En plus, c'est compliqué de tester car le programme détruit le fichier "fichier1.txt" à chaque exécution.
            Je te suggère ce que j'ai dit précédemment, c'est à dire d'écrire sur un nouveau fichier.
            Quand ton test fonctionnera, tu pourras regarder les fonctions rename et remove pour remplacer l'ancien fichier par le nouveau.

            Cela m'a agacé de ne pas comprendre. J'ai poussé mes tests plus loin ...

            Je suppose que tu es sur Windows comme moi.

            Sur Windows, la fin de ligne est codée sur 2 caractères, le return ('\r') et le newline ('\n').

            J'ai utilisé des outils personnels et surtout la fonction ftell() qui m'ont aidé à comprendre.

            J'ai ajouté un autre fseek() après le while de fin de boucle (ligne 34?).

            Le programme fonctionne maintenant.

            Je suppose que d'effacer le newline dérange les fonctions de lecture et écriture de Windows.

            -
            Edité par PierrotLeFou 14 août 2021 à 4:14:05

            • Partager sur Facebook
            • Partager sur Twitter

            Le Tout est souvent plus grand que la somme de ses parties.

              14 août 2021 à 7:46:08

              Bonjour,

              Il y a beaucoup de choses à dire et il faut bien commencer par quelque chose. Donc pour commencer : il faut toujours bien indenter son code, toujours. Ton code bien indenté ressemble à :

              #include <stdio.h>
              #include <stdlib.h>
              #include "main.h"
              #define SIZE_TEXT 100
              
              int main()
              {
                  FILE *fichier = NULL; //initialisation d'un pointeur fichier à NULL
                  fichier = fopen("fichier1.txt", "r+"); //ouverture du fichier et récupération du pointeur pour contrôle et utilisation
                  int caractere = 'Z'; //histoire d'initialiser avec quelque chose que je n'ai pas dans le fichier
              
                  if (fichier != NULL) { // si ouverture du fichier = OK
              
                      while (caractere != EOF) { //boucle pour passer de ligne en ligne jusqu'à la fin du fichier
              
                          fputc('A', fichier);
              
                          do { // boucle qui lit chaque caractère, et qui s'arrête après la lecture d'un \n ou d'un EOF
                              fseek(fichier, 0, SEEK_CUR);
                              caractere = fgetc(fichier); // caractère prend la valeur du caractère lu
              
                              if (caractere != EOF) { // pout contrôle, j'affiche chaque caractère lu, sauf EOF
                                  printf("%c", caractere);
                              }
              
                              if (caractere == EOF) { // pour contrôle, j'affiche "EOF" si j'arrive à cette valeur.
                                  printf("\nEOF");
                              }
              
              
                          } while (caractere != '\n' && caractere != EOF); // fin de la boucle do de lecture des caractères
              
                      }
              
                  } else { //affichage d'erreur à l'ouverture du fichier
                      printf("Erreur à l'ouverture du fichier ou pointeur non alimente.");
                  }
              
                  if (fclose(fichier) == 0) { //fermeture du fichier et affichage d'un message de contrôle
                      printf("\nFermeture reussie");
                  } else {
                      printf("\nFermeture echouee");
                  }
                  return 0;
              }
              

              Je te rassure, j'ai utilisé un outil pour le faire. Ton IDE doit pouvoir le faire ou sinon regarde du côté de astyle par exemple.

              Ensuite, c'est toujours bien de lire la doc. Comme je suppose que tu es sous windows on trouve sur la page fseek :

              La doc microsoft a écrit:

               For streams opened in text mode, fseek and _fseeki64 have limited use, because carriage return-line feed translations can cause fseek and _fseeki64 to produce unexpected results. The only fseek and _fseeki64 operations guaranteed to work on streams opened in text mode are:

              • Seeking with an offset of 0 relative to any of the origin values.
              • Seeking from the beginning of the file with an offset value returned from a call to ftell when using fseek or _ftelli64 when using _fseeki64.

              Donc si tu ouvres ton fichier en mode texte … mais tu ne spécifies rien, ni b ni t dans ton fopen (et c'est important pour windows, pas pour le reste du monde). 

              Là à nouveau, si on consulte la doc de fopen (windows)  :

              La doc microsoft a écrit:

              If t or b is not given in mode, the default translation mode is defined by the global variable _fmode. If t or b is prefixed to the argument, the function fails and returns NULL.

              Donc : il faut vraiment spécifier le mode d'ouverture de ton fichier, car sous windows il y a une différence et que le mode par défaut n'est pas clairement défini.Et il ne faut pas utiliser fseek et compagnie comme si dans le cas d'une ouverture en mode texte, ou alors comme indiqué dans la doc.

              Pour info, il y a eu historiquement des choix différents pour indiquer dans un fichier le «saut à la ligne». Windows a choisi de l'indiquer avec deux caractères que l';on nomme CR et LF, Mac a choisi uniquement CR (sur les vieux OS) et LF sur MacOs, Unix a toujours (pour ce que je sais) utilisé LF. CR signifie cariage return (retour chariot, oui ça date des machines à écrire mécanique) et LF line feed (retour à la ligne).
              Le souci avec CRLF c'est qu'il s'agit de deux octets (pour simplifier) et qu'en C on note ça avec un char, '\n' en l'occurrence. Du coup quand on ouvre un fichier en mode texte, le double octet CRLF est traduit en un seul char … alors qu'en mode binaire c'en est bien 2. Les autres systèmes n'ont pas ce souci et texte ou binaire c'est la même chose.

              Maintenant si on regarde la structure de ton programme de loin :

              int main()
              {
                  FILE *fichier = NULL; //initialisation d'un pointeur fichier à NULL
                  fichier = fopen("fichier1.txt", "r+"); //ouverture     
                  ....
                  if (fichier != NULL) { // si ouverture du fichier = OK
                      ....
                  } else { //affichage d'erreur à l'ouverture du fichier
                      ....
                  }
              
                  if (fclose(fichier) == 0) { //fermeture du fichier et affichage d'un message de contrôle
                      ....
                  } else {
                      ....
                  }
                  return 0;
              }
              

              Tu n'as besoin de fermer le fichier uniquement si tu as réussi à l'ouvrir.
              Regarde aussi la doc de errno pour déterminer l'erreur exacte avec des fonctions comme perror.


              La portion de code :

                             if (caractere != EOF) { // pout contrôle, j'affiche chaque caractère lu, sauf EOF
                                  printf("%c", caractere);
                              }
              
                              if (caractere == EOF) { // pour contrôle, j'affiche "EOF" si j'arrive à cette valeur.
                                  printf("\nEOF");
                              }
              
              

              c'est typiquement un if/else … 

              Ah oui … les commentaires qui décrivent le code ne servent strictement à rien du tout, que dalle, nada, nothing, nichts … rien. Un commentaire ne doit être utilisé que pour expliquer et non décrire. Et a priori, si tu as besoin d'expliquer ton code c'est que le code est souvent (mais pas toujours) mal écrit. Un code se doit d'être lisible. Les commentaires doivent se limiter à un strict minimum et sont à éviter dans la mesure du possible.

              Tu pourrais éventuellement regarder aussi du côté de la fonction ungetc …


              Pour info, pour ce genre de tâche il existe d'autres méthodes plus avancées qui seront plus efficaces. Mais bon pour un entraînement c'est cool.

              • Partager sur Facebook
              • Partager sur Twitter
                14 août 2021 à 8:08:15

                @White Crow:
                Merci pour astyle, je faisais mon indentation avec un petit programme Python bien imparfait.
                J'ai connu l'époque des téléscripteurs où le retour de chariot n'était pas qu'une métaphore.
                Le retour était assez bruyant ...
                Le line feed était pour faire avancer le papier. On "feed"ait la mitrailleuse qui imprimait ...
                Pour le problème du PO, je ne me suis pas douté tout de suite de ce que c'était.
                Une chance que le ftell() est correct.
                En général, je fais le test d'ouverture et vérification à l'envers:
                si open ne marche pas:
                 affiche erreur
                sinon
                 bravo, je peux continuer
                J'aime bien utiliser perror() et errno.h pour mes erreurs.
                • Partager sur Facebook
                • Partager sur Twitter

                Le Tout est souvent plus grand que la somme de ses parties.

                  14 août 2021 à 15:24:52

                  Il y aussi le fait qu'après une écriture sur un fichier ouvert en mode '+', un fflush() ou un repositionnement est nécessaire avant une opération de lecture, de même qu'après une lecture pour faire une écriture:

                  For files open for update (those which include a "+" sign), on which both input and output operations are allowed, the stream shall be flushed (fflush) or repositioned (fseek, fsetpos, rewind) before a reading operation that follows a writing operation. The stream shall be repositioned (fseek, fsetpos, rewind) before a writing operation that follows a reading operation (whenever that operation did not reach the end-of-file).

                  Ce qui pourrait expliquer ceci

                  -- sans le fseek mais avec le fputc : mon programme ajoute bien 'A' en début de fichier, puis il insère des tonnes d'espaces

                  -
                  Edité par edgarjacobs 14 août 2021 à 18:10:56

                  • Partager sur Facebook
                  • Partager sur Twitter

                  On écrit "j'ai tort", pas "tord" qui est le verbe "tordre" à la 3ème personne de l'indicatif présent

                    14 août 2021 à 17:45:52

                    Merci edgarjacobs, je n'avais pas trouvé ce texte.
                    Intuitivement, je me doutais qu'il fallait faire quelque chose entre la lecture et l'écriture, et vice versa.
                    J'ai un petit programme de dump de fichier et ce que je voyais étaient des 0 et non des espaces.
                    Les éditeurs ont de la difficulté avec de très longues lignes remplies de 0 ou autre chose.
                    Je pense que le fichier n'avait qu'une seule ligne de 4096 caractères.
                    Ce qui correspond peut-être à la longueur d'un bloc sur un SSD (?)
                    • Partager sur Facebook
                    • Partager sur Twitter

                    Le Tout est souvent plus grand que la somme de ses parties.

                      14 août 2021 à 19:25:25

                      PierrotLeFou a écrit:

                      Je pense que le fichier n'avait qu'une seule ligne de 4096 caractères.
                      Ce qui correspond peut-être à la longueur d'un bloc sur un SSD (?)


                      Formaté en NTFS, sous windows, un cluster fait par défaut 4Ko (ssd, hdd)
                      • Partager sur Facebook
                      • Partager sur Twitter

                      On écrit "j'ai tort", pas "tord" qui est le verbe "tordre" à la 3ème personne de l'indicatif présent

                        15 août 2021 à 0:30:42

                        Hello, 

                        merci pour ces explications. Je ne connaissais effectivement pas la doc (j'ose croire que c'est logique à ce stade du cours), mais vos commentaires me permettent de mieux comprendre ce que je fais (et surtout ce que je dois faire). 

                        Outre les questions d'indentement (sic), de commentaires (je les avais ajouté surtout pour poster ici, mais je pense que mon réflexe même pour moi serait plutôt de décrire que d'expliquer effectivement) et de if else, je comprends que le souci vient à la fois :

                        - d'un comportement que je ne prends pas en compte quand j'utilise une ouverture en +;

                        - d'un ensemble de caractères liés à mon utilisation de windows qui compliquent la lecture.

                        Du coup je viens de refaire un mini test (il rejoint d'ailleurs ce que Pierrot disait au début, de plutôt aller écrire dans un nouveau fichier) : 

                        int main()
                        {
                            FILE *fichier = NULL;
                            fichier = fopen("fichier1.txt", "wb");
                            int caractere = 'Z';
                        
                            if (fichier != NULL) {
                        
                                fprintf(fichier, "1234567890\n");
                                fprintf(fichier, "1234567890\n");
                                fprintf(fichier, "1234567890\n");
                                fprintf(fichier, "1234567890");
                                fseek(fichier, 0, SEEK_SET);
                        
                                while (caractere != EOF) {
                                    fputc('A', fichier);
                                    do {
                                        caractere = fgetc(fichier);
                                        if (caractere != EOF) {
                                            printf("%c", caractere);
                                        } else {
                                            printf("\nEOF");
                                        }
                        
                                    } while (caractere != '\n' && caractere != EOF);
                        
                                }
                        
                            } else {
                                printf("Erreur à l'ouverture du fichier ou pointeur non alimente.");
                            }
                        
                            if (fclose(fichier) == 0) {
                                printf("\nFermeture reussie");
                            } else {
                                printf("\nFermeture echouee");
                            }
                        
                            return 0;
                        }

                        Je retrouve bien un comportement normal pour la transformation des lignes après leur écriture via le fprintf, et je garde les erreurs de lecture (je n'ai pas regardé ça ce soir). J'imagine que je pourrai les corriger avec des fseek plus intelligents ou alors en faisant simplement une boucle de lecture après l'ensemble des opérations. 

                        Pierrot, tu avais gardé l'exemple de code que tu avais réalisé (je vois que tu es autant agacé de ne pas comprendre que moi :D ). 

                        Merci à tous une nouvelle fois, je trouve ça très sympa d'avoir des réponses aussi détaillées.

                        -
                        Edité par Melchiorpdb 15 août 2021 à 0:31:47

                        • Partager sur Facebook
                        • Partager sur Twitter
                          15 août 2021 à 1:42:12

                          Oui, j'ai gardé le code en question. Je suis encore un peu agacé ...
                          Pour ceux qui connaissent, j'ai Cygwin64 sur mon ordi.
                          Je me suis fait un petit script en bash qui utilise sed pour enlever les CR. Je fais un cat -vn du fichier et je ne vois plus les CR
                          Mais quand je repasse le programme en ouvrant avec "rb+" sans le seek() à la fin de la boucle, ça ne marche pas.
                          Si je le remet, ça fonctionne à nouveau.
                          • Partager sur Facebook
                          • Partager sur Twitter

                          Le Tout est souvent plus grand que la somme de ses parties.

                            15 août 2021 à 11:05:32

                            PierrotLeFou a écrit:

                            Je suis encore un peu agacé ...

                            Sorry ! :D
                            • Partager sur Facebook
                            • Partager sur Twitter
                              15 août 2021 à 12:03:02

                              Norme a écrit:

                              7.21.5.3 The fopen function

                              When a file is opened with update mode ('+' as the second or third character in the above list of mode argument values), both input and output may be performed on the associated stream. However, output shall not be directly followed by input without an intervening call to the fflush function or to a file positioning function (fseek, fsetpos, or rewind), and input shall not be directly followed by output without an intervening call to a file positioning function, unless the input peration encounters endof-file. Opening (or creating) a text file with update mode may instead open (or create) a binary stream in some implementations.

                              Donc :

                              #include <stdio.h>
                              
                              int main(void)
                              {
                                  FILE *fichier = NULL;
                                  fichier = fopen("fichier1.txt", "r+");
                                  int caractere = 'Z';
                              
                                  fprintf(fichier, "1234567890\n");
                                  fprintf(fichier, "1234567890\n");
                                  fprintf(fichier, "1234567890\n");
                                  fprintf(fichier, "1234567890");
                                  rewind(fichier);
                              
                                  while (caractere != EOF)
                                  {
                                      fputc('A', fichier);
                                      fflush(fichier);
                                      do
                                      {
                                          caractere = fgetc(fichier);
                                          if (caractere != EOF)
                                          {
                                              printf("%c", caractere);
                                          }
                                          else
                                          {
                                              printf("\nEOF");
                                          }
                                      }
                                      while (caractere != '\n' && caractere != EOF);
                                      fseek(fichier, 0L, SEEK_CUR);
                                  }
                                  return 0;
                              }



                              -
                              Edité par rouIoude 15 août 2021 à 14:03:11

                              • Partager sur Facebook
                              • Partager sur Twitter
                              ...
                                15 août 2021 à 14:06:42

                                Hello Rouloude, 

                                merci beaucoup, je comprends bcp mieux la source de l'erreur. Et effectivement, avec les fseek après chaque input, on parvient bien à lire l'ensemble du fichier. On peut même revenir à l'objectif initial que je m'étais fixé avec la lecture d'un fichier et sa modif. Petite adaptation dans ton code pour lire chaque ligne depuis le début du coup : 

                                #include <stdio.h>
                                
                                int main()
                                {
                                    FILE *fichier = NULL;
                                    fichier = fopen("fichier1.txt", "r+");
                                    int caractere = 'Z';
                                
                                    rewind(fichier);
                                
                                    while (caractere != EOF)
                                    {
                                        fputc('A', fichier);
                                        fseek(fichier, -1, SEEK_CUR);
                                        do
                                        {
                                            caractere = fgetc(fichier);
                                            fseek(fichier, 0, SEEK_CUR);
                                            if (caractere != EOF)
                                            {
                                                printf("%c", caractere);
                                            }
                                            else
                                            {
                                                printf("\nEOF");
                                            }
                                        }
                                        while (caractere != '\n' && caractere != EOF);
                                    }
                                    return 0;
                                }
                                

                                Merci à tous pour votre aide précieuse !

                                • Partager sur Facebook
                                • Partager sur Twitter

                                [RESOLU] Fonction fputc et boucles - problème

                                × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                                × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                                • Editeur
                                • Markdown