Les chaînes de caractères du langage C
===
###### tags: `algo` `bourg` `c++` `iut` `cm`
Le cours en deux vidéos:
{%youtube c_cdBUwPsZ4 %}
{%youtube Wy3xtNGAaVQ %}
En C++, il existe deux façons de traiter les chaînes de caractères (ce qui permet d'utiliser des mots ou des phrases). La plus simple consiste à passer par le type objet ``string``, que nous découvrirons au semestre prochain. La seconde consiste à utiliser les chaînes de caractères du langage C, c'est-à-dire comme des tableaux de caractères. Cette solution restant commune dans de nombreux programmes C++, vous ne pouvez décemment pas ignorer son fonctionnement (ne serait-ce que pour passer des arguments en paramètre de la fonction ``main``, où les chaînes du langage C sont obligatoires).
### Représentation des chaînes
Une chaîne de caractères de type C se représente par un tableau de caractères à une dimension, terminée par un caractère spécial de code ASCII nul, le ``'\0'``. Cela signifie qu'une chaîne de caractères de longeur $n$ aura besoin d'un tableau de taille au moins $(n+1)$ pour être représentée.
Par exemple, si je veux stocker le mot "toto" dans une chaîne de caractères, j'aurai besoin d'un tableau de $5$ caractères qui ressemblera à ça:
| 't' | 'o' | 't' | 'o' | '\0' |
| --- | --- | --- | --- | ---- |
### Déclaration-intialisation
Une déclaration de chaîne de caractères se fait donc naturellement comme ça:
````code=C++
char ch[5];
````
En général, on déclare des tableaux de caractères avec une taille suffisamment grande pour accueillir les chaînes que l'on va utiliser dans le programme. C'est là tout l'intérêt du marqueur de fin de chaîne ``'\0'``: si vous déclarez un tableau de longueur $100$, vous pouvez le remplir avec n'importe quelle chaine de longueur inférieure à $100$. Même s'il y a des caractères inutilisés en fin de tableau, le marqueur de fin de chaîne permet de repérer quelles sont les cases du tableau qui ont du sens, et les autres seront ignorées.
Pour initialiser une chaîne de caractères, il existe plusieurs stratégies. Imaginons que je souhaite initialiser ma variable ``chaine`` déclarée ci-dessous avec le mot "bonjour". Je peux faire:
````code=C++
char chaine[100];
chaine[0]='b';
chaine[1]='o';
...
chaine[7]='r';
chaine[8]='\0';
````
Wow. Vous imaginez si je dois initialiser ma chaine avec une phrase de 200 caractères ?

Si j'ai un ou deux caractères dans ma chaine, pourquoi pas utiliser cette solution. Mais sinon, c'est clairement une stratégie lourde qui n'est pas à privilégier.
Une autre stratégie serait de passer par une déclaration-intialisation avec des accolades, comme pour les tableaux:
````code=C++
char chaine[100]={'b','o','n','j','o','u','r','\0'};
````
Là encore, même si c'est un peu plus compact, cela reste une technique peu recommandée car il faut utiliser des apostrophes autour de chaque lettre.
Une troisième stratégie consiste à passer par une déclaration-intialisation avec des guillemets, comme ceci:
````code=C++
char chaine[100]="bonjour";
````
En termes de légéreté, on est bien mieux.

Cette méthode est clairement la plus adaptée et celle que vous devez retenir. Contrairement aux deux méthodes précédentes, elle a en plus le mérite d'ajouter automatiquement le symbole ``'\0'`` à la fin du tableau. Par ailleurs, si vous voulez que la taille du tableau corresponde exactement à la longueur de la chaîne, vous n'êtes pas obligés de donner la taille dans les crochets: ``char chaine[]="bonjour";``. Dans cet exemple, C++ calcule automatiquement la taille du tableau (ici ce sera $8$, le marqueur de fin de chaîne étant compté).
:::danger
Si la dernière stratégie est à privilégier, il est important de noter qu'elle n'est valable que dans le cadre d'une déclaration-initialisation. Cela signifie que si vous voulez remplacer "bonjour" par "toto" dans la chaîne plus loin dans votre code, l'instruction ``chaine="toto";`` sera rejetée par le compilateur. La seule des techniques ci-dessus qui serait valide serait une recopie case à case, soit la première. Cela n'a rien de surprenant: dans les chapitres précédents, nous avons déjà vu que pour recopier un tableau dans un autre, seule une copie case à case est valide. Cependant, nous verrons plus tard dans le chapitre qu'il existe des fonctions dédiées aux chaines de caractères qui permettent de recopier une chaine dans une autre sans avoir à écrire explicitement une recopie case à case.
Pour résumer, les affectations de type ``tableau1=tableau2`` ou encore ``tableau1="bonjour";`` (sur une ligne différente d'une déclaration), on dira que c'est LE MAL. A bannir tout de suite de vos codes.

:::
### <a id="parcours-chaine"></a>Parcours d'une chaine
On parcours une chaine de caractères comme un tableau, càd avec une boucle. Mais contrairement aux tableaux, on s'arrête souvent avant la fin puisqu'on ne lit les cases que jusqu'au caractère de fin `'\0'`.
Par conséquent, si vous souhaitez parcourir les cases d'une chaine de caractère une à une, la syntaxe la plus naturelle consiste à utiliser une boucle ``while`` avec le marqueur de fin de chaine comme condition d'arrêt. Par exemple:
````code=C++
char chaine[]="abracadabra";
int i;
while (chaine[i]!='\0'){
cout << chaine[i];
i++;
}
````
### Affichage et lecture d'une chaîne
Pour afficher une chaine de caractères à la console, on pourrait utiliser une boucle comme celle ci-dessus. Cependant, il est important de noter que l'opérateur d'affichage ``<<`` permet d'afficher les chaines de caractères. Le tableau est lu automatiquement jusqu'à trouver un marqueur de fin de chaîne ``'\0'`` (qui lui n'est pas affiché). C'est une fonctionnalité très pratique puisqu'elle évite d'écrire les cases une à une (comme dans l'exemple précédent).
Exemple:
````code=C++
char chaine[]="abracadabra";
cout << chaine << endl; //fait la même chose que le code ci-dessus
````
:::info
Les chaines de caractères ayant un statut à part pour le compilateur, c'est pour cette raison que l'opérateur ``<<`` affiche les cases du tableau comme espéré. Nous vous rappelons que si vous souhaitez afficher les cases de n'importe quel autre type de tableau (par exemple un tableau d'entiers, de réels...), l'opérateur ``<<`` ne fonctionnera pas. Il vous faudra parcourir le tableau pour l'afficher case à case.
:::
De même, on peut lire une chaine depuis la console grâce à l'opérateur ``>>``. Il faut évidemment que la chaîne tapée par l'utilisateur soit de taille inférieure à celle du tableau censé l'accueillir (avec le ``'\0'`` compris). Par exemple:
````code=C++
char ch[10];
cin >> ch ;
````
Un tel code permet de lire des chaines au clavier de longueur au plus $9$. Pour rappel, souvenez-vous que ``cin`` s'arrête dès qu'il rencontre un caractère séparateur (espace, fin de ligne, tabulation...). Cela signifie que l'on ne pourra pas lire des phrases avec cette technique.
Si l'on veut lire des phrases (avec donc des espaces par exemple), il faut utiliser la fonction ``getline`` qui est orientée objet. La syntaxe est un peu étonnante (vous comprendrez au semestre 2), mais en attendant, voici comment appeler cette fonction:
````code=C++
char ch[10];
cin.getline(ch,10);//2 paramètres: la chaine et le nombre de caractères maxi à lire
````
### Les fonctions de manipulation des chaînes de caractères
A l'intérieur de la librairie ``cstring``, il existe plusieurs fonctions fréquemment utilisées par les développeurs pour simplifier leur travail avec les chaines de caractères. Dans ce qui suit, nous allons vous en décrire les principales.
#### Longueur d'une chaîne
La fonction ``int strlen(char chaine[])`` retourne la longueur d'une chaine de caractère. Notez que le symbole ``'\0'`` n'est pas pris en compte. Par exemple, l'expression ``strlen("toto")`` vaudra $4$.
:::warning
Evitez d'utiliser cette fonction pour parcourir votre chaine de caractères, avec un code comme celui-ci:
````code=C++
for (int i=0;i<strlen(ch);i++){
...
}
````
Clairement, l'appel à la fonction strlen dans le for est à bannir parce que vous le faites à chaque tour de boucle. C'est beaucoup trop lourd ! :elephant: :elephant: :elephant:
Au pire, appelez la fonction avant la boucle et stockez la taille dans une variable. La meilleure solution pour parcourir une chaine reste celle décrite [ci-dessus](#parcours-chaine).
:::
#### Concaténation de deux chaînes
La fonction ``void strcat(char ch1[], char ch2[])`` permet de concaténer (càd coller) la chaine ``ch2`` à la suite de la chaine ``ch1``. Pour que l'opération soit réalisée sans erreur, il est important que la chaîne ``ch1`` ait une taille suffisamment large pour accueillir la concaténation. Par exemple, le code ci-dessous produirait une erreur à l'exécution:
````code=C++
char ch1[]="bonjour";
char ch2[]="madame";
strcat(ch1,ch2);
````
En effet, la taille de ``ch1`` n'est que de 8, et il faudrait qu'elle soit de taille au moins 14 pour accueillir le mot "madame" à la suite de "bonjour".
Sur cet exemple, la solution est de déclarer la première chaine avec une taille plus grande (quelque chose plus grand que 14):
````code=C++
char ch1[50]="bonjour";
char ch2[]="madame";
strcat(ch1,ch2);
````
#### Comparaison de chaînes
La fonction ``int strcmp(char ch1[], char ch2[])`` permet de comparer deux chaines de caractères selon l'ordre lexicographique (ordre du dictionnaire). Cette fonction retourne un entier:
* nul si les deux chaines sont identiques.
* positif si ``ch1>ch2``, autrement dit si ``ch1`` arrive après ``ch2`` dans le dictionnaire.
* négatif si ``ch1<ch2``, càd que ``ch1`` arrive avant ``ch2`` dans le dictionnaire.
Par exemple, l'expression ``strcmp("bonjour","toto")`` est négative, alors que ``strcmp("toto72","toto4")`` est positive.
#### Copie de chaîne
La fonction ``void strcpy(char destination[], char source[])`` permet de copier la chaine ``source`` dans la chaine ``destination``. Comme pour la fonction ``strcat``, il est nécessaire que la taille de la chaine ``destination`` soit aussi grande que celle de ``source``. Il est à noter que cette fonction est très utilisée pour modifier des chaines.
Exemple:
````code=C++
char ch[50]="bonjour";
cout << ch ; //affiche bonjour
strcpy(ch,"au revoir");
cout << ch; //affiche au revoir
````
:::warning
Rappelez-vous, sur l'exemple ci-dessus, écrire ``ch="au revoir"`` serait LE MAL. La stratégie de la copie case à case serait possible (du genre ``ch[0]='a'; ch[1]='u' ...`` ) mais l'appel à la fonction ``strcpy`` est beaucoup plus pratique parce qu'elle fait exactement ce travail là.
:::
:::info
Attention, dans les nouvelles versions de C++, ``strcpy`` est souvent remplacée par une version sécurisée, dont l'entête est:
``
void strcpy_s(char destination[],int taille, char source[])
``
Vous serez obligé d'utiliser cette version en TP. Pour le deuxième paramètre (taille), il vous suffira de mettre la taille de la chaine destination. Exemple sur le code ci-dessus:
````code=C++
char ch[50]="bonjour";
cout << ch ; //affiche bonjour
strcpy_s(ch,50,"au revoir");
cout << ch; //affiche au revoir
````
:::
#### Conversion de chaînes
Il est possible de convertir une chaine de caractères vers un entier ou un réel (à condition que ces chaines contiennent des valeurs numériques). Pour cela, on utilise les fonctions ``atoi`` (pour les entiers) et ``atof`` (pour les réels).
Exemple:
````C++
char ch[]="317";
int i;
i=atoi(ch);
cout << i << endl ; //Affiche 317
````