Programmation C
Tableaux - Chaînes de caractères
Cours Exercices Correction des exercices

Tableaux Chaînes de caractères

Tableaux

  • Type de données permettant d'agréger en une seule variable un ensemble de valeurs de même type
  • Repérage des valeurs par la donnée de un ou plusieurs indices entiers (tableau de dimension 1 si 1 indice, tableau de dimension 2 si 2 indices...)
  • Possibilité de définir des tableaux de tout type élémentaire
    • Non pointeur
    • Pointeur
  • Deux façons de gérer la taille des tableaux (nombre de valeurs pouvant être stockées et, incidemment, quantité de mémoire allouée pour ce stockage)
    • Allocation statique : taille constante définie au moment de la compilation
    • Allocation dynamique : taille définie en cours d'exécution (voir chapitre Allocation dynamique de la mémoire)
  • Syntaxe en dimension 1 pour une définition avec allocation statique
    nomType nomVariableTableau[VALEUR_TAILLE];
    • Définition d'une variable tableau statique nommée nomVariableTableau, de dimension 1, de taille VALEUR_TAILLE selon cette unique dimension
    • Valeurs du tableau stockées séquentiellement en mémoire en un seul banc : 1ère valeur puis 2ème valeur puis 3ème valeur et ainsi de suite jusqu'à la dernière valeur
  • Contraintes
    • Obligation que VALEUR_TAILLE soit une constante entière (constante littérale, #define ou enum)
      -> Pas de dynamicité sur la taille
    • Impossibilité de changer le type élémentaire, la dimension ou la taille une fois la variable tableau définie
    • Indices comptés à partir de 0 et valables jusqu'à VALEUR_TAILLE-1
    • Pas d'initialisation automatique implicite des valeurs contenues dans les tableaux statiques -> valeurs aléatoires
  • Syntaxe en dimension 2
    nomType nomVariableTableau[VALEUR_TAILLE_1][VALEUR_TAILLE_2];
  • Syntaxe en dimension 3
    nomType nomVariableTableau[VALEUR_TAILLE_1][VALEUR_TAILLE_2][VALEUR_TAILLE_3];
  • Syntaxe en dimension n
    nomType nomVariableTableau[VALEUR_TAILLE_1][VALEUR_TAILLE_2]...[VALEUR_TAILLE_N];
  • Initialisation lors de la définition
    • Possibilité d'indiquer des valeurs d'initialisation lors de la définition d'une variable tableau
    • Syntaxe 1 : Taille de tableau explicite et initialisation avec les valeurs spécifiées entre accolades
      nomType nomVariableTableau[VALEUR_TAILLE] = { v1, v2, v3, ..., vn };
      Initialisation des n premières valeurs du tableau avec les n valeurs entre accolades
      Maintien à la valeur indéterminée des valeurs restantes
      n <= VALEUR_TAILLE sinon non compilable
    • Syntaxe 2 : Taille de tableau définie implicitement avec le nombre de valeurs spécifiées entre accolades
      nomType nomVariableTableau[] = { v1, v2, v3, ..., vn };
      Définition d'un tableau de taille n
      Initialisation du tableau avec les n valeurs entre accolades
  • Connaître la taille d'un tableau
    • Utilisation de l'opérateur sizeof pour calculer le nombre global d'octets du tableau et le nombre d'octets d'un élément du tableau
      Exemple : sizeof(t) pour le nombre global d'octets occupés par le tableau t et sizeof(t[0]) pour le nombre d'octets de l'élement d'indice 0 du tableau
    • Division de l'un par l'autre pour obtenir la taille
  • Important : Repérage explicite possible de tout tableau alloué statiquement au moyen d'une variable de type pointeur sur le type élémentaire du tableau
    • Possibilité de récupérer l'adresse d'un tableau (l'adresse du premier élément du tableau) via l'opérateur & appliqué à la variable tableau qui fournit un pointeur sur le type élémentaire du tableau
      Exemple : pour un tableau t défini par int t[VALEUR_TAILLE];, &t donne l'adresse du tableau sous la forme d'un pointeur sur int. Possibilité d'écrire : int *pt = &t;
    • Possibilité de récupérer l'adresse de n'importe quel élément d'un tableau via l'opérateur & appliqué à l'élément désigné explicitement par son indice qui fournit un pointeur sur le type élémentaire du tableau
      Exemple : pour un tableau t défini par double t[VALEUR_TAILLE];, &t[0] donne l'adresse du double d'indice 0, &t[i] donne l'adresse du double d'indice i. Possibilité d'écrire : double *pt = &t[0];
    • Pour les tableaux de dimension 1, &t toujours égal à &t[0], Pour les tableaux de dimension 2, &t toujours égal à &t[0][0]...
    • Valeurs du tableau placées linéairement dans la zone mémoire pointée par le pointeur même s'il s'agit d'un tableau de dimension supérieure à 1
      -> Stockage linéarisé
      -> Gestion automatique et transparente pour le développeur de l'accès au bon élément en fonction des indices fournis en dimension 1 ou supérieure
  • Exemples
  • #include <stdio.h>

    #define TAILLE_T2 8

    void main(void) {
      int t1[10];
      for (int i = 0; i < 10; i++) {
        printf("%d\n", t1[i]);
      }
      printf("\n");
      t1[0] = 1;
      for (int i = 1; i < 10; i++) {
        t1[i] = i*t1[i-1];
      }
      for (int i = 0; i < 10; i++) {
        printf("%d\n",t1[i]);
      }
      printf("\n");
      double t2[TAILLE_T2];
      for (int i = 0; i < TAILLE_T2; i++) {
        t2[i] = i * 0.5;
      }
      for (int i = 0; i < TAILLE_T2; i++) {
        printf("%lf\n", t2[i]);
      }
      printf("\n");
      printf("%zd %zd\n", sizeof(t1), sizeof(t2));
    }

    Tableaux1.c - Tableaux1.exe
    • Définition de la taille du tableau t1 avec une constante littérale égale à 10
    • Définition de la taille du tableau t2 avec un #define pour une valeur égale à 8
    • Définition du tableau d'int t1
    • Affichage des valeurs contenues dans le tableau t1, pas d'initialisation automatique -> valeurs purement aléatoires
    • Remplissage du tableau t1 avec les valeurs 0! à 9! calculées au moyen de la formule récurrente de factoriel
    • Affichage des valeurs contenues dans le tableau t1
    • Définition du tableau de double t2
    • Remplissage du tableau t2 avec les valeurs 0.0, 0.5, 1.0, 1.5...
    • Affichage des valeurs contenues dans le tableau t2
    • Affichage des nombres d'octets utilisés pour stocker les 10 int du tableau t1 (10*4=40 octets) et les 8 double du tableau t2 (8*8=64 octets)
    90
    0
    1073741824
    -1140847104
    792273360
    32759
    792270745
    32759
    31
    0

    1
    1
    2
    6
    24
    120
    720
    5040
    40320
    36288

    0.000000
    0.500000
    1.000000
    1.500000
    2.000000
    2.500000
    3.000000
    3.500000

    40 64

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

    #define NOMBRE_LIGNES 6
    #define NOMBRE_COLONNES 8

    void main(void) {
      srand(10);
      int tab[NOMBRE_LIGNES][NOMBRE_COLONNES];
      for (int l = 0; l < NOMBRE_LIGNES; l++) {
        for (int c = 0; c < NOMBRE_COLONNES; c++) {
          tab[l][c] = rand() % 10;
        }
      }
      for (int l = 0; l < NOMBRE_LIGNES; l++) {
        for (int c = 0; c < NOMBRE_COLONNES; c++) {
          printf("%3d", tab[l][c]);
        }
        printf("\n");
      }
      printf("\n");
    }

    Tableaux2.c - Tableaux2.exe
    • Définition d'un tableau de dimension 2 de 6x8 double
    • Initialisation de ce tableau avec des valeurs tirées au sort entre 0 et 9
    • Affichage sous forme matricielle de ce tableau
      1 9 2 4 7 6 2 2
      6 9 4 1 3 4 3 2
      1 9 4 0 9 0 3 6
      7 7 0 7 1 3 5 9
      9 4 8 2 1 7 8 1
      9 5 3 3 2 3 9 7

    #include <stdio.h>

    #define TAILLE_T1 12
    #define NOMBRE_LIGNES 6
    #define NOMBRE_COLONNES 8

    void main(void) {
      double t1[TAILLE_T1];
      printf("%zu %zu\n", sizeof(t1), sizeof(t1[0]));
      int TAILLE = sizeof(t1) / sizeof(t1[0]);
      printf("%d\n", TAILLE);
      printf("\n");
      int t2[NOMBRE_LIGNES][NOMBRE_COLONNES];
      printf("%zu %zu %zu\n", sizeof(t2), sizeof(t2[0]), sizeof(t2[0][0]));
      int nl = sizeof(t2) / sizeof(t2[0]);
      int nc = sizeof(t2[0]) / sizeof(t2[0][0]);
      printf("%d %d\n", nl, nc);
    }

    TableauxSizeof.c - TableauxSizeof.exe
    • Définition d'un tableau de double
    • Affichage du nombre total d'octets occupés par les informations du tableau et du nombre d'octets d'un élément du tableau
    • Calcul à partir des informations ci-dessus et affichage du nombre d'éléments du tableau
    • Définition d'un tableau de dimension 2 de d'int
    • Affichage du nombre total d'octets occupés par les informations du tableau, du nombre d'octets occupés par les informations présentes sur une ligne et du nombre d'octets d'un élément du tableau
    • Calcul à partir des informations ci-dessus et affichage du nombre de lignes et du nombre de colonnes du tableau
    96 8
    12

    192 32 4
    6 8
  • Arithmétique des pointeurs et tableaux
    • Possibilité d'utiliser les pointeurs pour parcourir des tableaux en utilisant l'arithmétique des pointeurs
    • Existence d'opérateurs applicables aux variables de type pointeur pour en modifier le contenu
      • Opérateur + : Ajout à la variable pointeur d'un nombre d'octets égal au type sur lequel pointe le pointeur
        Exemple : p = p + 1, où p est une variable de type pointeur sur int et 1 est le nombre entier 1
        -> Incrémentation de l'adresse contenue dans p de 1 * le nombre d’octets du type pointé par p c'est à dire 1 * sizeof(int)
        -> Après ajout, pointage par p de l'int suivant immédiatement en mémoire l'int initialement pointé par p (même s'il ne s'agit pas d'un int !)
      • Opérateur - : Equivalent à l'opérateur + mais pour retrancher au lieu d'ajouter
      • Opérateur ++ : Equivalent à ajouter 1
      • Opérateur -- : Equivalent à retrancher 1
    • Possibilité d'ajouter tout nombre entier n ou de retrancher tout nombre entier n
  • Exemple

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

    #define NOMBRE_VALEURS 6
    #define NOMBRE_LIGNES 4
    #define NOMBRE_COLONNES 3

    void main(void) {
      srand(10);
      double tab1[NOMBRE_VALEURS];
      int tab2[NOMBRE_LIGNES][NOMBRE_COLONNES];
      for (int i = 0; i < NOMBRE_VALEURS; i++) {
        tab1[i] = (rand() % 101) / 100.0;
      }
      for (int i = 0; i < NOMBRE_VALEURS; i++) {
        printf("%p : %6.2lf\n", &tab1[i], tab1[i]);
      }
      printf("\n");
      double* p1 = tab1;
      printf("%p %p\n", tab1, p1);
      for (int i = 0; i < NOMBRE_VALEURS; i++) {
        printf("%p : %6.2lf\n", p1, *p1);
        p1++;
      }
      for (int l = 0; l < NOMBRE_LIGNES; l++) {
        for (int c = 0; c < NOMBRE_COLONNES; c++) {
          tab2[l][c] = rand() % 10;
        }
      }
      printf("\n");
      for (int l = 0; l < NOMBRE_LIGNES; l++) {
        for (int c = 0; c < NOMBRE_COLONNES; c++) {
          printf("%p : %2d    ", &tab2[l][c], tab2[l][c]);
        }
        printf("\n");
      }
      printf("\n");
      int* p2 = &tab2[0][0];
      printf("%p %p\n", tab2, p2);
      for (int i = 0; i < NOMBRE_LIGNES * NOMBRE_COLONNES; i++) {
        printf("%p : %2d\n", p2, *p2);
        p2++;
      }
    }

    TableauxEtPointeurs.c - TableauxEtPointeurs.exe
    • Définition d'un tableau unidimensionnel de double et d'un tableau bidimensionnel de int
    • Initialisation du tableau de double avec des valeurs tirées au sort entre 0.0 et 1.0 avec deux chiffres de précision
    • Affichage de chacune des valeurs du tableau de double précédée de son adresse en mémoire
      -> Contiguïté des valeurs en mémoire (8 octets entre chaque valeur)
    • Définition d'une variable pointeur sur double initialisée avec l'adresse du tableau de double
    • n affichages successifs de la valeur pointée par le pointeur sur double précédée de l'adresse avec entre chaque affichage un décalage de 1 du pointeur (n = nombre de valeurs du tableau de double)
      -> Nouvel affichage des valeurs du tableau de double
    • Initialisation du tableau de int avec des valeurs tirées au sort entre 0 et 9
    • Affichage de chacune des valeurs du tableau de int précédée de son adresse en mémoire
      -> Contiguïté des valeurs en mémoire (4 octets entre chaque valeur)
    • Définition d'une variable pointeur sur int initialisée avec l'adresse du tableau d'int (l'adresse de la valeur d'indice (0,0] du tableau de int
    • n affichages successifs de la valeur pointée par le pointeur sur int précédée de l'adresse avec entre chaque affichage un décalage de 1 du pointeur (n = nombre de valeurs du tableau de int)
      -> Nouvel affichage des valeurs du tableau de int mais en une seule colonne
    0000002D028FFB10 : 0.71
    0000002D028FFB18 : 0.32
    0000002D028FFB20 : 0.40
    0000002D028FFB28 : 0.59
    0000002D028FFB30 : 0.62
    0000002D028FFB38 : 0.15

    0000002D028FFB10 0000002D028FFB10
    0000002D028FFB10 : 0.71
    0000002D028FFB18 : 0.32
    0000002D028FFB20 : 0.40
    0000002D028FFB28 : 0.59
    0000002D028FFB30 : 0.62
    0000002D028FFB38 : 0.15

    0000002D028FFB40 : 2 0000002D028FFB44 : 2 0000002D028FFB48 : 6
    0000002D028FFB4C : 9 0000002D028FFB50 : 4 0000002D028FFB54 : 1
    0000002D028FFB58 : 3 0000002D028FFB5C : 4 0000002D028FFB60 : 3
    0000002D028FFB64 : 2 0000002D028FFB68 : 1 0000002D028FFB6C : 9

    0000002D028FFB40 0000002D028FFB40
    0000002D028FFB40 : 2
    0000002D028FFB44 : 2
    0000002D028FFB48 : 6
    0000002D028FFB4C : 9
    0000002D028FFB50 : 4
    0000002D028FFB54 : 1
    0000002D028FFB58 : 3
    0000002D028FFB5C : 4
    0000002D028FFB60 : 3
    0000002D028FFB64 : 2
    0000002D028FFB68 : 1
    0000002D028FFB6C : 9
  • Tableaux statiques vs fonctions
    • Possibilité de transmettre un tableau de dimension 1 alloué statiquement en paramètre à une fonction
      • Syntaxe d'entête
        nomType1 nomFonction(nomType2[] nomParametre)
      • Autre syntaxe possible : transmission du tableau en tant que variable de type pointeur sur le type élémentaire du tableau selon l'entête
        nomType1 nomFonction(nomType2* nomParametre)
      • Dans les deux cas, réception par la fonction de l'adresse mémoire du tableau
      • Problème : comment connaître la taille du tableau reçu par la fonction ?
        • Impossible avec sizeof car la fonction reçoit un pointeur (une adresse) en entête donc si on utilise sizeof, on obtient la taille du pointeur et non la taille du tableau pointé par le pointeur
        • Solution 1 : avoir la taille en paramètre global -> perte complète de la dynamicité concernant la taille -> pas génial
        • Solution 2 : passer à la fonction la taille en paramètre supplémentaire -> assez lourd
    • Possibilité de transmettre un tableau de dimension supérieure à 1 alloué statiquement en paramètre à une fonction
      • Syntaxe non décrite ici
    • Impossibilité de faire retourner à une fonction un tableau alloué statiquement dans la fonction quelle que soit son type élémentaire, sa dimension ou sa taille
    • Conclusion : tableaux statiques limités fonctionnellement
  • Attention
    • Pas de contrôle en langage C de la validité des indices utilisés sur les tableaux
    • Pas de contrôle en langage C des opérations réalisées en arithmétique des pointeurs
    • Possibilité d'aller lire et écrire en dehors des tableaux
      -> Corruption de la mémoire
      -> dysfonctionnement voire plantage
      -> Comportement erratique

Chaînes de caractères

  • Pas explicitement de type chaîne de caractères en langage C
  • Chaînes de caractères représentées par des tableaux de char contenant les caractères de la chaîne de caractères
  • Fin de la chaîne repéré par le fait que le dernier caractère est suivi du char 0x00 c'est à dire le caractère de code ASCII zéro
    -> Utilisation d'un tableau de n+1 char pour stocker une chaîne de caractères de n caractères
  • Possibilité de déclarer une variable chaîne de caractères explicitement comme une variable pointeur sur char si on l'affecte avec l'adresse d'un tableau de char correctement défini
  • Possibilité d'affecter une variable pointeur sur char avec une constante littérale de type chaîne de caractères mais attention, chaîne non modifiable par écriture directe dans sa zone mémoire
  • Exemples
  • #include <stdio.h>

    void main(void) {
      char* s1 = "ABCD";
      char s2[] = { 'a','b','c','d','e','\0' };
      char s3[50];
      for (int i = 0; i < 10; i++) {
        s3[i] = '0' + i;
      }
      s3[10] = 0x00;
      printf("%s\n", s1);
      printf("%s\n", s2);
      printf("%s\n", s3);
    }

    ChainesDeCaracteres1.c - ChainesDeCaracteres1.exe
    • Définition et initialisation de 3 chaînes de caractères
      • Affectation d'une variable pointeur sur caractère avec l'adresse d'une constante littérale chaîne de caractères (attention : chaîne constante donc non modifiable)
      • Définition d'un tableau de caractères avec taille et initialisation des valeurs avec une liste de caractères donnés entre accolades dont le dernier caractère est le caractère de code ASCII zéro
      • Définition d'un tableau de caractères, remplissage de la partie initiale de ce tableau (rien n'oblige à l'utiliser entièrement) avec des caractères et affectation d'un caractère de valeur zéro en bout
    ABCD
    abcde
    0123456789
  • Manipulation sur les chaînes de caractères
    • Possibilité de développer ses propres fonctions en passant en paramètre de fonction les chaînes à traiter
    • Attention, comme pour tout tableau, pas de retour sur char* d'une fonction sur un tableau défini statiquement localement à la fonction
      Exemple
    • #include <stdio.h>

      int calculerNombreCaracteres(const char* chaine) {
        int cpt = 0;
        while (chaine[cpt] != 0x00) {
          cpt++;
        }
        return cpt;
      }

      void copier(const char* src, char* dst) {
        int i = 0;
        while (src[i] != 0x00) {
          dst[i] = src[i];
          i++;
        }
        dst[i] = 0x00;
      }

      void substituer(char* chaine, char ancien, char nouveau) {
        int i = 0;
        while (chaine[i] != 0x00) {
          if (chaine[i] == ancien) {
            chaine[i] = nouveau;
          }
          i++;
        }
      }

      void copier2(const char* src, char* dst) {
        char* s = (char*)src;
        char* d = dst;
        while (*s != 0x00) {
          *d = *s;
          s++;
          d++;
        }
        *d = 0x00;
      }

      void substituer2(char* chaine, char ancien, char nouveau) {
        char* c = chaine;
        while (*c != 0x00) {
          if (*c == ancien) {
            c[0] = nouveau;
          }
          c++;
        }
      }

      void main(void) {
        char* s = "abcdefabcdef";   // Attention! non modifiable
        char t[20];
        printf("Chaine        : %s\n", s);
        printf("Longeur : %d\n", calculerNombreCaracteres(s));
        copier(s, t);
        printf("Chaine copiee : %s\n", s);
        printf("%s\n", t);
        substituer(t, 'a', 'f');
        printf("Substituion de 'a' par 'f' : %s\n", t);
        substituer(t, 'f', '0');
        printf("Substituion de 'f' par '0' : %s\n", t);
      }

      ChainesDeCaracteresFonctions.c - ChainesDeCaracteresFonctions.exe
      • Trois fonctions de manipulation de chaînes de caractères
        • Compter les caractères
        • Copier une chaîne vers une autre chaîne (2 implantations)
        • Rechercher/remplacer toutes les occurences d'un caractère dans une chaîne (2 implantations)
      • Paramètre formel de fonction pour une chaîne de caractères : toujours un pointeur char* c'est à dire un tableau de caractères
      • Dans toutes les implantations : parcours depuis le début de la chaîne et tant qu'on n'a pas trouvé le caractère de code ASCII égal à 0x00 qui marque la fin de la chaîne
      • Dans toutes les implantations : supposition de la valididité des paramètres (chaînes se finissant par 0x00, tableaux de taille suffisantes...)
      • Programme principal
        • Définition d'une variable chaîne de caractères de type pointeur sur char initialisée avec une chaîne constante littérale (non modifiable)
        • Définition d'une variable chaîne de caractère de type tableau statique de char
        • Affichage de la chaîne constante
        • Copie de la chaîne constante dans le tableau chaîne et affichage du tableau
        • Substitution par des 'f' de tous les 'a' du tableau chaîne puis affichage du tableau
        • Substitution par des '0' de tous les 'f' du tableau chaîne puis affichage du tableau

    • Possibilité d'utiliser les fonctions de la librairie standard string.h si elles correspondent aux besoins
      • Calcul du nombre de caractères d'une chaîne de caractères : strlen
      • Concaténation d'une chaîne de caractères au bout d'une autre chaîne de caractères : strcat
      • Copie d'une chaîne de caractères vers une autre chaîne de caractères : strcpy
      • Recherche d'un caractère dans une chaîne de caractères : strchr
      • Recherche d'une chaîne de caractères dans une chaîne de caractères : strstr
      • Comparaison entre deux chaînes de caractères : strcmp
      • ...
      Exemple
    • #include <stdio.h>
      #include <string.h>

      void main(void) {
        char* s0 = "abcd";
        char* s1 = "ABCD";
        char s2[] = { '0','1','2','3','4','\0' };
        char s3[50];
        printf("%s\n", s0);
        printf("%s\n", s1);
        printf("%s\n", s2);
        printf("\n");
        strcpy(s3, s1);
        printf("%s possede %zu caracteres\n", s3, strlen(s3));
        strcat(s3, s2);
        printf("%s possede %zu caracteres\n", s3, strlen(s3));
        printf("Comparaison de %s et %s : %d\n", s0, s0, strcmp(s0, s0));
        printf("Comparaison de %s et %s : %d\n", s0, s1, strcmp(s0, s1));
        printf("Comparaison de %s et %s : %d\n", s1, s0, strcmp(s1, s0));
        char c = 'z';
        char* pc = strchr(s0, c);
        if (pc != NULL) {
          printf("%c existe dans %s et est en position %d\n", c, s0,(int) (pc-s0) );
        }
        else {
          printf("%c n'existe pas dans %s\n", c, s0);
        }
      }

      ChainesDeCaracteresAPIString.c - ChainesDeCaracteresAPIString.exe
      • Utilisation des fonctions strlen, strcat, strcmp et strchr de la bibliothèque standard string.h
      • Calcul et affichage de la longueur d'une chaîne de caractère
      • Concaténation de deux chaînes de caractères et affichage de la longueur de la chaîne obtenue
      • Comparaison d'une chaîne de caractères avec elle-même et de deux chaînes de caractères différentes
      • Recherche de la position de la première occurence d'un caractère dans une chaîne de caractères et détermination de sa position
      abcd
      ABCD
      01234

      ABCD possede 4 caracteres
      ABCD01234 possede 9 caracteres
      Comparaison de abcd et abcd : 0
      Comparaison de abcd et ABCD : 1
      Comparaison de ABCD et abcd : -1
      z n'existe pas dans abcd
      c existe dans abcd et est en position 2