Comment supprimer les caractères non chiffre en langage C

Résolu
salammans Messages postés 1 Statut Membre -  
mamiemando Messages postés 33228 Date d'inscription   Statut Modérateur Dernière intervention   -
Bonjour,

Je suis débutante en programmation. J'essaie de supprimer les caractères qui ne sont pas des chiffres dans une chaîne (sans utiliser des fonctions déjà existantes).

Le corrigé de mon exercice me montre la solution (voir capture d'écran), mais je ne la comprends pas. Quelqu'un peut m'expliquer?

void supprime_non_chiffres(char* chaine)
{
 int i, j;

 i = 0;
 j = 0;
 while (chaine[i] != '\0')
 {
  if (est_un_chiffre(chaine[i]))
  {
   chaine[j] = chaine[i];
   j++;
  }
  i++;
 }
 chaine[j] = '\0';
}



Merci !

2 réponses

  1. [Dal] Messages postés 6122 Date d'inscription   Statut Contributeur Dernière intervention   1 108
     
    Salut salammans,

    Qu'est ce que tu ne comprends pas ?

    Dans ce que tu envoies, la fonction
    est_un_chiffre()
    n'est pas implémentée, cependant je suppose qu'elle fonctionne de façon similaire à la fonction standard
    isdigit()
    .

    La fonction
    supprime_non_chiffres()
    parcourt la chaîne avec deux curseurs :
    • un qui avance sur tous les caractères de la chaîne à la recherche de chiffres
    • l'autre qui avance de un à un seulement lorsqu'un chiffre est trouvé à mesure que les char existants sont écrasés


    Lorsque toute la chaîne est parcourue, la chaîne est terminée par le caractère terminateur de chaîne
    '\0'
    .

    Cette fonction
    supprime_non_chiffres()
    ne peut être appelée qu'avec une chaîne sous la forme de tableau de
    char
    ou avec un pointeur sur
    char
    pointant sur de la mémoire allouée avec
    malloc
    , mais pas avec une constante littérale, puisque la chaîne passée est modifiée sur place.

    Dal
    0
  2. mamiemando Messages postés 33228 Date d'inscription   Statut Modérateur Dernière intervention   7 944
     
    Bonjour,

    Je me permets de compléter la réponse de Dal

    Rappels préliminaires sur les chaînes de caractères et les pointeurs en C
    • chaine
      est un pointeur, c'est à dire l'adresse d'un bloc mémoire.
    • plus précisément,
      chaine
      est de type
      char *
      ce qui signifie que ce bloc mémoire peut être vu comme une tableau dont chaque case fait la taille d'un
      char
      (ça tombe bien, c'est ce qu'on veut pour une chaîne !)
    • comme le pointeur est typé (ici les cases sont vues comme des
      char
      ), alors les opérateurs
      chaine[i]
      ,
      *chaine
      , et
      chaine++
      sont bien définis (ce ne serait pas le cas pour un adresse "générique", qui se note
      void *
      ). Pour ton exercice, seul
      chaine[i]
      nous intéresse. Du point de vue du C, il retourne le
      i
      -ème bloc de taille
      char
      à partir de l'adresse stockée dans la variable
      chaine
      . De notre point de vue d'être humain, cela revient à récupérer le
      i
      -ème caractère de
      chaine
      .
    • cependant, rien dans le type
      char *
      n'indique combien de caractère fait la chaîne. Si on commence à lire caractère par caractère ce bloc, toute la question est donc "quand s'arrêter ?". Pour répondre à ce problème, en C/C++ (et en fait dans tous les langages), en mémoire, une chaîne contient un caractère d'arrêt supplémentaire, par convention
      '\0'
      .
    • cela signifie que la boucle suivante permet d'afficher un par un les caractère de
      s
      :


    #include <stdio.h>
    
    int main(){
        const char * s = "Bonjour 123 Au revoir";
        int i;
        while(s[i] != '\0') {
            printf("%c", s[i]);
            i++;
        }
        return 0;
    }
    • si on veut ignorer les caractères non numériques, on pourrait utiliser comme le dit Dal
      isdigit
      :


    #include <stdio.h>
    #include <ctype.h>
    
    int main(){
        const char * s = "Bonjour";
        int i;
        for(i = 0; s[i] != '\0'; i++) {
            if (isdigit(s[i])) {
                printf("%c", s[i]);
            }   
        }
        return 0;
    }
    • Note enfin que quand on écrit
      s = "Bonjour"
      , cela signifie implicitement que le bloc
      "Bonjour\0"
      est créé de manière statique (dans l'exécutable). Le
      '\0'
      est implicitement pris en compte.


    Que doit faire ton programme ?

    Si celle-ci contient des caractères numériques, la chaîne transformée sera plus courte, et c'est pourquoi il faudra :
    • "supprimer" les caractères non-numériques ;
    • "décaler" de n caractères vers la gauche tous les caractères numériques s'ils sont précédés de n caractères non-numériques ;
    • "décaler" de n caractères vers la gauche le caractère d'arrêt
      \0
      .


    Que fait ton corrigé ?

    Plutôt que de maintenir une variable
    n
    , ton programme modifie en place la chaîne de caractère. La "tête de lecture" (à la position
    i
    ) est toujours en avance par rapport à la "tête d'écriture" (à la position
    j
    ), donc c'est effectivement une bonne manière de faire.

    De plus, comme la chaîne à transformer est déjà allouée en mémoire, il n'est pas utile d'allouer un bloc mémoire pour la chaîne transformée. Nous allons maintenant ajouter quelques
    printf
    dans le programme pour voir ce qui se passe. Cependant, comme le montre la section suivante, il va y avoir un peu de travail préalable.

    Vu que tu n'as pas forcément encore fait le cours sur les allocations mémoires, et même si je prends le soin de tout expliquer, tu peux l'ignorer dans un premier temps si tu as mal à la tête et aller directement à la section "exécution du programme" :-)

    Allocations dynamiques,
    const char *
    , et programme minimal

    Cependant il faut savoir que si tu déclare ta chaîne
    const char * s = "Bonjour 123 Au revoir";
    tu ne pourras pas la modifier (à cause du
    const
    ). On pourrait se dire, pas de problème je vire le
    const
    . Sauf que c'est une mauvaise idée. En réalité, cette chaîne n'est pas modifiable (elle est stockée dans l'exécutable, pas en mémoire), et donc si tu tentes de la modifier, ton programme plantera à cause d'une erreur de segmentation. Dit autrement, si une chaîne est initialisée en dur dans un programme écrit en C, ce devrait toujours être un
    const char *
    (chaîne non modifiable).

    C'est pourquoi dans le code ci-dessous, je prends le soin d'allouer dynamiquement une copie de
    chaine
    avec s. Afin de bien séparer ton problème initial et cet aspect là, je sépare maintenant le code en deux : le
    main
    s'occupe de cette allocation, et la fonction
    transform
    est une réécriture de ton corrigé :

    #include <stdio.h>
    #include <ctype.h>
    #include <string.h>
    #include <stdlib.h>
    
    void transform(char * s) {
        int i, j = 0;
        for(i = 0; s[i] != '\0'; i++) {
            printf("i = %3d j = %3d s = %s\n", i, j, s);
            if (isdigit(s[i])) {
                s[j] = s[i];
                j++;
            }
        }
        s[j] = '\0';
    }
    
    
    int main(){
        const char * chaine = "Bonjour 123Au456 revoir";
        char * s = (char *) malloc(strlen(chaine) + 1);
        if (s == NULL) {
            fprintf(stderr, "Pas assez de mémoire!");
            return 1;
        }   
        strcpy(s, chaine);
        transform(s);
        printf("s = %s\n", s);
        free(s);
        return 0;
    }


    Tu noteras que l'allocation mémoire est faite avec
    malloc
    (qui retourne une adresse générique de type
    void *
    ), donc c'est pour ça qu'on la convertit (cast) en
    char *
    avec l'opérateur unaire de cast
    (char *)
    . Ce bloc doit pouvoir contenir autant de caractère que
    chaine
    , donc la longueur de chaine (qu'on obtient avec la fonction standard
    strlen
    ) + 1 (pour le
    '\0'
    ).

    Ensuite, si tu lis la documentation de
    malloc
    , tu verras que cette fonction retourne
    NULL
    . Donc ici, on prend le soin de contrôler que l'allocation a fonctionné, et sinon, on écrit un message d'erreur dans la sortie d'erreur standard, puis on quitte le programme avec un code d'exécution non nul pour signifier que le programme a planté.

    Si l'allocation a marché, on recopie
    chaine
    dans
    s
    à l'aide de
    strcpy
    , puis on fait la transformation. Ça ne fera pas d'erreur de segmentation car on n'opère pas sur un
    const char *
    .

    Qui dit
    malloc
    (si celui-ci a fonctionné) dit
    free
    . Donc, quand on n'a plus besoin du bloc mémoire
    s
    , on le désalloue avec la fonction
    free
    . La librairie C sait exactement quel est la taille de ce bloc puisqu'elle en a gardé trace quand tu as fait le
    malloc
    et c'est pourquoi
    free
    ne prend en paramètre que l'adresse du bloc à désallouer.

    Exécution du programme

    On obtient ceci :

    (mando@silk) (~) $ gcc -Wall -g toto.c && ./a.out 
    i = 0 j = 0 s = Bonjour 123Au456 revoir
    i = 1 j = 0 s = Bonjour 123Au456 revoir
    i = 2 j = 0 s = Bonjour 123Au456 revoir
    i = 3 j = 0 s = Bonjour 123Au456 revoir
    i = 4 j = 0 s = Bonjour 123Au456 revoir
    i = 5 j = 0 s = Bonjour 123Au456 revoir
    i = 6 j = 0 s = Bonjour 123Au456 revoir
    i = 7 j = 0 s = Bonjour 123Au456 revoir
    i = 8 j = 0 s = Bonjour 123Au456 revoir
    i = 9 j = 1 s = 1onjour 123Au456 revoir
    i = 10 j = 2 s = 12njour 123Au456 revoir
    i = 11 j = 3 s = 123jour 123Au456 revoir
    i = 12 j = 3 s = 123jour 123Au456 revoir
    i = 13 j = 3 s = 123jour 123Au456 revoir
    i = 14 j = 4 s = 1234our 123Au456 revoir
    i = 15 j = 5 s = 12345ur 123Au456 revoir
    i = 16 j = 6 s = 123456r 123Au456 revoir
    i = 17 j = 6 s = 123456r 123Au456 revoir
    i = 18 j = 6 s = 123456r 123Au456 revoir
    i = 19 j = 6 s = 123456r 123Au456 revoir
    i = 20 j = 6 s = 123456r 123Au456 revoir
    i = 21 j = 6 s = 123456r 123Au456 revoir
    i = 22 j = 6 s = 123456r 123Au456 revoir
    s = 123456


    Si tu as besoin de plus de clarifications, n'hésite pas.

    Bonne chance
    0