Lors du cours n°2, nous avions parlé des types des variables en C, je fais surtout référence au type char qui représente un caractère Le type caractère.

Rappel

Le type char permet de stocker des caractères, et puisqu’ils sont stockés dans l’ordinateur sous forme d’entier, on peut alors aussi utiliser char pour stocker un entier.

Un problème se pose alors…
Une variable de type char ne peut stocker qu’un seul et unique caractère, comment peut-on faire pour stocker des mots entiers ? Voire des phrases entières ?
On cherche un moyen de stocker plusieurs lettre dans un même objet. Justement, on sait déjà le faire, par le biais des tableaux.

Il y a toujours un problème, une chaîne de caractères est plus qu’un tableau, elle doit être manipulable directement, hors ceci n’est possible que si on connait sa taille.

Déclaration

Ici, rien ne change vraiment de déclaration d’un tableau :

char chaine[t]="...";

  • t représente la taille de la chaîne de caractères.
  • "..." la chaîne de caractère que l’on stocke

Exemple :
On souhaite stocker le mot informatique dans une chaine de caractères nommée domaine, on va alors créer un tableau de caractères et stocker le mot.

char domaine[] = "informatique";

Voici ci dessous, une version schématique simple qui vous permet de comprendre comment informatique est stocké.

i 0x1000 n 0x1001 f 0x1002 o 0x1003 r 0x1004 m 0x1005 a 0x1006 t 0x1007 i 0x1008 q 0x1009 u 0x100A e 0x100B \0 0x100C

En fait, lors de la création de la variable domaine qui contient la chaîne de caractère “informatique”, cela va créer un tableau de caractères char contenant chaque lettre du mot et le caractère nul \0 qui permet de marquer la fin de la chaîne de caractères.
Ainsi, dans notre exemple un tableau de 13 éléments est créé, il peut être schématisé grossièrement de la manière suivante :

[ i, n, f, o, r, m, a, t, i, q, u, e, \0]

Chaque caractère est stocké dans un espace mémoire consécutive (s’incrémente de 1 pour chaque caractère). On l’aperçoit sur notre image au dessus :

CaractèreAdresse mémoire
i0x1000
n0x1001
f0x1002
e0x100B
\00x100C

En gros, chaque lettre de type char en C est stocké sur octet, et l’adresse mémoire entre deux caractères est simplement incrémenté de . On utilise \0 comme marque de fin de chaîne car sans lui, le programme ne pourrait pas savoir où s’arrête la chaîne de caractères.

Initialisation

Initialisation explicite

L’initialisation explicite consiste à définir chaque caractère du tableau et éventuellement le caractère nul '\0', obligatoire pour une chaîne de caractères.

Exemple :

char chaine[6] = {'H', 'e', 'l', 'l', 'o', '\0'};

Initialisation implicite

L’initialisation implicite c’est en fait ce qu’on a vu pour la Déclaration, on utilise un littéral de chaîne entre guillemets :

Exemple :

char chaine[] = "Hello";

On peut aussi déclarer un tableau à plus d’éléments que ce qu’on initialise, dans ce cas les éléments non initialisés auront des valeurs dites indéterminées (ou nulles si le tableau est déclaré globalement).

Exemple :

char chaine[20] = "Salut";
AspectInitialisation expliciteInitialisation implicite
DéfinitionChaque caractère + '\0' manuelLittéral de chaîne ("...")
Ajout de '\0'manuelAutomatique
Taille du tableauFixe, définie par l’utilisateurDéduite automatiquement si non précisée
FacilitéMoins pratiquePlus simple et sûre

Manipulation des chaînes de caractères

Accès aux éléments

Grâce à la première partie de ce cours Déclaration, on sait qu’une chaîne de caractères en C représente simplement un tableau de caractères.
Il est alors possible d’accéder aux éléments d’une chaîne de caractères de la même manière que pour des tableaux classiques.
Alors, en considérant une chaîne de caractères chaine d’une taille , si on souhaite accéder au ième élément avec alors on utilisera la forme suivante :

char elementN = chaine[n-1]

Exemple :
En reprenant l’exemple précédant avec le mot informatique, si on souhaite avoir accès à la lettre m alors on exécutera le code suivant :

#include <stdio.h>
 
int main() {
    char domaine[] = "informatique";
    char m = domaine[5];
    printf("6e lettre du domaine : %c", m);
    return 0;
}
6e lettre du domaine : m

Rappel

Une chaîne de caractères est un tableau d’éléments de type char cela implique que pour y accéder on utilise des entiers appelés indices.
Les indices commencent à 0 qui correspond au premier caractère de la chaine jusqu’à pour le dernier caractère de la chaîne. Et, le \0 caractère nul pour marquer la fin de la chaîne est l’élément d’indice .

Ainsi en considérant une chaîne de éléments, voici les indices de manière schématique.

0 1 2 t−2 t−1 \0 t tableau de taille t+1

On voit donc que pour un mot de taille , un tableau de taille t+1 sera créé pour stocker chaque caractère du mot en entier et un emplacement mémoire supplémentaire est ajouté pour stocker le caractère de fin de chaîne \0. C’est pour cela que pour un mot de taille , le tableau qui stocke le mot sera de taille .

Affichage

Mais comment on peut afficher une chaîne de caractères en langage C. On doit vraiment faire comme avec les tableaux classique en parcourant le tableau élément par élément et l’afficher un à un ?

Alors c’est une solution que l’on va regarder, mais ce n’est pas la plus simple ! Il existe plusieurs méthodes pour afficher une chaîne de caractères.
Vous le savez déjà mais pour afficher des informations dans la console on utilise printf et quand on a besoin d’afficher un certain type de donnée on utilise des formats notés %.... Et bien sachez qu’il existe un format réservé aux chaînes de caractères ! Plutôt pratique non ? C’est le format %s.

Exemple :

#include <stdio.h>
 
int main(){
	char chaine[] = "j'écrit un truc pour le kiff";
	
	// Affichage en utilisant le format %s
	printf("%s\n", chaine);
	return 0;
}
j'écrit un truc pour le kiff

C’est la solution la plus simple pour afficher une chaîne de caractère.
On peut aussi faire ce que l’on a vu pendant le cours 6 sur les tableaux, on parcourt chaque élément du tableau grâce à une boucle for puis on affiche chaque caractère un à un.

Warning

Dans ces cas là, on a besoin de la taille du tableau pour pouvoir savoir au bout de combien d’itérations on arrête.

On utilise alors une méthode du package <string.h>, la méthode strlen(chaine) qui permet de donner la taille de la chaîne de caractères sans inclure le marqueur de fin de chaine \0.

Exemple :

#include <stdio.h>
#include <string.h>
 
int main(){
	char chaine[] = "j'écrit un truc pour le kiff";
	// Utilisation de strlen
	int taille = strlen(chaine);
	
	for(int i=0; i<taille; i++){
	    printf("%c", chaine[i]);
	}
	printf("\n");
	return 0;
}

Remarque

On peut aussi utiliser sizeof() que l’on a déjà eu l’occasion de parler pour avoir la taille mémoire totale du tableau, ce qui inclus le marqueur de fin de chaine \0.

#include <stdio.h>
 
int main(){
	char chaine[] = "j'écrit un truc pour le kiff";
	// Utilisation de sizeof
	int taille = sizeof(chaine);
	
	for(int i=0; i<taille; i++){
	    printf("%c", chaine[i]);
	}
	printf("\n");
	return 0;
}
j'écrit un truc pour le kiff·

Le caractère de marqueur de fin de chaîne \0 revient en fait a mettre un dans l’emplacement mémoire qui lui est dédié. Et, comme ce n’est pas un caractère imprimable comme a par exemple, il peut être représenté par différent symbole, ici pour mon compilateur ce sera · le point médian.

Ainsi, la différence entre strlen et sizeof est que l’un d’eux inclus \0 dans la taille de la chaîne, cela peut donc rendre certains programme faux par exemple pour programmer un pendu, si on utilise sizeof, on vas devoir trouver un mot à caractères, alors qu’en vrai, c’est un mot à caractères. Du coup si vous utilisez sizeof il faut penser au .

On peut aussi utiliser une boucle while pour afficher la chaîne de caractères, elle s’arrête alors quand on trouve le marqueur de fin \0.

Exemple :

#include <stdio.h>
 
int main(){
	char chaine[] = "j'écrit un truc pour le kiff";
	
	int i=0;
	while(chaine[i]!='\0'){
	    printf("%c", chaine[i]);
	    i++;
	}
	
	printf("\n");
	return 0;
}
j'écrit un truc pour le kiff

Remarque

On peut utiliser putchar(char) à la place du printf pour afficher un caractère. la fonction est valide avec une boucle while et avec une boucle for aussi.

#include <stdio.h>
 
int main(){
	char chaine[] = "j'écrit un truc pour le kiff";
	
	int i=0;
	while(chaine[i]!='\0'){
	   putchar(chaine[i]);
	    i++;
	}
	
	printf("\n");
	return 0;
}
j'écrit un truc pour le kiff

Warning

Il faut bien faire attention, un tableau de caractères et une chaînes de caractères ne représentent pas la même chose.

char chaine[12] = {'i', 'n', 'f', 'o', 'r', 'm', 'a', ...}

est un tableau de caractères sans marqueur de fin de chaînes \0, on ne peux pas utiliser le format %s pour l’afficher avec printf car dans ce cas, %s attend une chaîne avec \0, ce qui n’est pas le cas ici.

CaractéristiqueTableau de caractèresChaîne de caractères
TerminaisonPas forcément '\0'Toujours '\0'
TailleFixe (déclarée)Fixe ou déduite automatiquement avec littéral
Compatible avec %sNonOui

Lecture

La fonction scanf

Présentation et utilisation

La fonction scanf permet de lire des saisies de données que se soit des lettres, chiffres, ou alors des chaînes de caractères entières.
Malgré tout, elle possède quelques inconvénient à ne pas négliger pour ne pas être surpris.

Pour bien commencer, étudions le prototype de la fonction scanf. Pour rappel, le prototype d’une fonction consiste juste à donner le type de retour de la fonction, son nom et ses arguments.

int scanf(const char* format, ...);

Oui, le prototype de ma fonction scanf est un peu perturbant, il prend au moins 1 paramètre. Les ... signifient qu’il peut en prendre un nombre variable. On appelle cela une fonction variadique, c’est lorsqu’une fonction peut prendre un nombre variable de paramètres. Comme la fameuse fonction printf qui prend en paramètre au moins une chaîne de caractères puis ensuite ou paramètres supplémentaires correspondant aux données à afficher si il y en a.

Pour en revenir au prototype de scanf,

  • Le paramètre format désigne simplement le type des variables à saisir, par exemple %d pour un entier.
  • La fonction renvoie un entier int, c’est le nombre de variables affectées par la saisie, ça permet en fait de vérifier si la saisie s’est déroulée correctement.
  • En cas d’erreur la fonction retourne EOF. explications plus bas ^EOF

Exemple :
Un exemple assez basique pour l’utilisation de scanf. On demande à l’utilisateur de saisir son âge puis on l’affiche.

#include <stdio.h>
 
int main() {
    int age;
    printf("Entrez votre age : ");
    scanf("%d", &age);
    printf("\nVous avez %d ans.", age);
    return 0;
}
Entrez votre age : 20
Vous avez 20 ans.

Il y a quelques petites choses à voir sur ce code.

  • Dans l’utilisation de scanf pourquoi on met &age pourquoi il y a un & ?
  • Comment on peut savoir si la donnée que l’utilisateur entre est valide ?

Pour comprendre pourquoi il y a un & dans l’utilisation de scanf il suffit de comprendre ce que fait réellement la fonction.
Quand on utilise la fonction scanf c’est pour stocker des saisies utilisateurs dans une variable et l’utiliser plus tard. Ainsi, lorsqu’on utilise scanf (on va prendre notre exemple comme référence) pour afficher l’âge de l’utilisateur, lors de la saisie utilisateur, après validation, la fonction scanf recopie la saisie dans l’adresse mémoire de la variable âge qui doit être spécifiée dans la fonction.
C’est pour cette raison le &, en fait quand on utilise scanf("%d", &age) on souhaite recopier la saisie utilisateur dans l’adresse mémoire créée pour la variable âge. Pour ce faire, on ajoute & avant âge.

Maintenant comment savoir si les données saisies par l’utilisateur sont correctes ?
Regardons ce qu’il se passe si à la place de taper un entier, on tape une chaîne de caractères. On reprend le même code que l’exemple précédant

Entrez votre age : mot
Vous avez 31415 ans.

Alors là c’est étonnant, mais qu’est-ce que c’est ?
En fait on a rentré une chaîne de caractères au lieu d’un entier comme spécifié avec le format %d alors la fonction scanf échoue et ne modifie pas la variable âge. La valeur de cette dernière reste indéfinie l’entier est aléatoire, lors d’une autre exécution, un autre nombre sera affiché c’est pour cette raison que l’on parle de valeur indéfinie.

Il y a déjà quelques paragraphes, j’expliquais que la fonction scanf renvoie un entier, le nombre de variables affectées par la saisie. Et bien voilà comment vérifier la saisie utilisateur. Dans notre exemple on vas vérifier si une variable exactement a été affectée par la saisie si c’est le cas on renvoie l’âge sinon on renvoie un message d’erreur.

Exemple :

#include <stdio.h>
 
int main() {
    int age;
    int result; // Variable pour stockée le résultat de scanf
    printf("Entrez votre age : ");
    result = scanf("%d", &age);
    if(result==1)
        printf("Saisie valide, Vous avez %d ans.", age);
    else 
	    printf("\nErreur de saisie.\n");
    return 0;
}
Entrez votre age : 20
Saisie valide, Vous avez 20 ans.

Entrez votre age : mot
Erreur de saisie.

Aussi, on a dit qu’en cas d’erreur, la fonction scanf renvoie EOF. Cela mérite quelques explications.

Pour clarifier un peu,
la fonction scanf ne renvoie pas toujours EOF End Of File, c’est seulement si une erreur d’entrée/sortie se produit ou alors si on atteint la fin d’un fichier, par exemple Ctrl+Z sur Windows. Notion expliquée lors du cours sur les fichiers. Si la saisie utilisateur ne correspond pas au format attendu, la fonction scanf renverra .

Utilisation avancée

On peut aussi lire plusieurs données en même temps avec la fonction scanf, c’est pour cette raison qu’elle possède un nombre de paramètres variables.

Exemple :
On souhaite lire un mot puis un nombre.

#include <stdio.h>
 
int main() {
    char prenom[100];
    int age;
    
    printf("Entrez votre prénom et votre âge : ");
    scanf("%s%d", prenom, &age);
    printf("Salut %s, tu as %d ans", prenom, age);
    return 0;
}
Entrez votre prénom et votre âge : Paul 45
Salut Paul, tu as 45 ans

Limites

Lecture limitée

Exemple :
On souhaite que l’utilisateur entre son nom et son prénom pour l’afficher.

#include <stdio.h>
 
int main() {
    char identite[100];
    printf("Entrez votre prénom et nom : ");
    scanf("%s", identite);
    printf("\nSalut tu t'appelles : %s", identite);
    return 0;
}
Entrez votre prénom et nom : Paul Durant
Salut tu t'appelles : Paul

Bon je pense que tu as quelques questions à poser :

  • Pourquoi dans l’utilisation de scanf, cette fois on met pas le & de l’adresse mémoire devant identité ?
  • Pourquoi le résultat de la saisie est simplement Paul et pas Paul Durant comme tapé au dessus ?

On y vient, un peu de patience !
En fait, dans notre cas on souhaite passer une chaîne de caractères à notre fonction scanf, et on a expliqué qu’en fin de compte c’est un tableau de char. Et en langage C, lorsque l’on passe un tableau en paramètre d’une fonction, ce dernier est automatiquement converti en pointeur vers le premier élément de ce dernier.
Pour simplifier, quand on passe un tableau en paramètre de fonction c’est la même chose que si on passait l’adresse mémoire du premier élément du tableau. La notion de pointeur arrive… D’où le fait qu’ici on a pas besoin de &.

Un second problème se dresse devant nous, on a tapé Paul Durant et le programme a affiché Paul uniquement. Que s’est-il passé pour Durant ?
La fonction scanf s’arrête de lire une saisie utilisateur si elle tombe sur un espace, une tabulation ou i on appui sur entrée. Alors le second mot, ici Durant n’est donc pas récupéré.
Alors il n’est pas désintégré, au contraire même il est toujours en mémoire dans ce qu’on appelle un buffer,c e qui signifie que la prochaine fois que l’on appellera la fonction scanf, elle lira toute seule Durant qui est “resté en plan” dans la mémoire.
Observons à l’aide d’un exemple

Exemple :
On reprend le même que précédemment et on utilise 2 fois scanf

#include <stdio.h>
 
int main() {
    char identite[100];
    printf("Entrez votre prénom et nom : ");
    scanf("%s", identite);
    printf("\nSalut tu t'appelle : %s", identite);
 
	// Seconde utilisation
    printf("\nEntrez votre nouveau prénom et nom : ");
    scanf("%s", &identite);
    printf("\nSalut maintenant tu est : %s\n", identite);
    return 0;
}
Entrez votre prénom et nom : Paul Durant
Salut tu t'appelle : Paul

Entrez votre nouveau prénom et nom : 
Salut maintenant tu est : Durant

On remarque bien que lors de la seconde utilisation de scanf, l’utilisateur ne saisit rien, c’est la fonction elle même qui va lire le mot laissé dans la mémoire puis l’affiche.

Dépassement de mémoire

Le dépassement de mémoire est un vrai cauchemar pour tout développeur, pour comprendre ce concept plus que fondamental, nous allons l’expliquer avec un exemple et des schémas visuels semblables aux précédents.

Création d’une variable domaine d’une taille définie, ici .

char domaine[6];

Cela signifie alors que l’on peut stocker un prénom de lettres, car le dernier emplacement mémoire de la chaîne est réservé au marqueur de fin de chaîne \0 à ne pas oublier !.

0 1 2 3 4 \0 5 tableau de taille 6

Considérons maintenant le code suivant avec l’exécution associée.

#include <stdio.h>
 
int main() {
    char domaine[6];
    
    printf("Entrez votre domaine d'étude : ");
    scanf("%s", domaine);
    printf("Domaine : %s", domaine);
    return 0;
}
Entrez votre domaine d'étude : informatique
Domaine : informatique

À première vue, rien ne semble s’être produit. Malheureusement si, on vient de faire ce qu’on appelle un buffer overflow ou dépassement de mémoire en français.
Ce que nous avions fais c’était créer un tableau pouvant stocker des mots allant jusqu’à 5 lettres. Or ici, on lit le mot informatique qui contient lettre.
De manière visuelle, on obtient ceci :

i 0 n 1 f 2 o 3 r 4 m 5 a 6 t 7 i 8 q 9 u 10 e 11 \0 12 tableau de taille 6 taille réelle du tableau 13

En gros le tableau de base est trop petit pour stocker le mot informatique ainsi on le voit en rouge, le dépassement de mémoire.
Cela peut être dangereux car si il n’est pas contrôlé, l’utilisateur peut écrire ce qu’il veut dans la mémoire. Il peut surtout insérer du code en mémoire et faire en sorte qu’il soit exécuté par le programme. C’est l’attaque par buffer overflow, une attaque pirate très difficile à faire.
Pour plus d’informations sur le dépassement et ses dangers : https://fr.wikipedia.org/wiki/D%C3%A9passement_de_tampon

La fonction gets

La fonction gets permet de lire une ligne entière de l’entrée standard jusqu’au retour à la ligne \n et de stocker cette dernière dans un tableau fourni.

Exemple :

#include <stdio.h>
 
int main() {
    char line[100]; 
 
    printf("Entrez une chaîne de caractères : ");
    gets(line); 
    printf("Vous avez entré : %s\n", line); // Affichage
 
    return 0;
}
Entrez une chaîne de caractères : une chaîne simple
Vous avez entré : Une chaîne simple

Inutile de passer encore énormément de temps sur gets, elle pose les mêmes problèmes que scanf : le dépassement de mémoire.

Remarque

Depuis la norme C11, gets a été supprimée de la norme C.

La fonction fgets

La fonction fgets permet de récupérer une chaîne de caractères à partir d’un flux (le plus souvent, stdin).

Prototype de la fonction

char *fgets(char *var, int t, FILE *stream);
  • var la variable qui va permettre de stocker la chaîne de caractères lue.
  • t la taille de la chaîne à stocker.
  • stream le flux sur lequel on récupère la chaîne (stdin, fichier, …)

Il faut savoir que la fonction fgets arrête la lecture lorsqu’elle rencontre \n, la fin du flux, ou alors après t-1 caractères lus (\0 le dernier caractère).

Warning

La fonction fgets inclus \n dans la chaîne lue si celle ci est rencontrée avant la limite.

Exemple :

#include <stdio.h>
 
int main() {
    char buffer[100];
 
    printf("Entrez une chaîne de caractères : ");
    fgets(buffer, sizeof(buffer), stdin); // Lecture sécurisée de la chaîne
    printf("Vous avez entré : %s\n", buffer);
 
    return 0;
}
Entrez une chaine de caractères : Apprendre la prog C
Vous avez entré : Apprendre la prog C
Critèrescanfgetsfgets
Usage principalLire un mot (jusqu’au premier espace)Lire une ligne complèteLire une ligne complète ou partie
Limitation de lectureNe lit qu’un mot, pas les espacesAucun contrôle sur la tailleLimité à n-1 caractères
SécuritéPeu sûr : peut dépasser le buffer si l’utilisateur tape tropTrès dangereux : risque de débordement mémoireSûr si n correct
Gestion du \nLe \n reste dans le bufferSupprime le \nautomatiquementConserve le \n si présent (à retirer manuellement si nécessaire)
RemarquesUtiliser avec spécificateur de longueur : %99s pour un buffer de 100Obsolète et déconseillé, supprimé depuis C11Recommandé pour la lecture de chaînes. Supprimer le \n et vider le flux si la ligne est trop longue

Fonctions de manipulation

Copier

En programmation C, on peut avoir besoin de copier une chaîne de caractère et il existe 2 méthodes possibles.

Parcourir et recopier

Ce qui semble le plus intuitif pour commencer c’est de parcourir la chaîne a copier et copier chaque élément un à un dans une seconde chaîne.

Exemple :

#include <stdio.h>
 
int main() {
    char chaine[] = "Bonjour le monde";
    char copie[100];
    
    int i = 0;
    while(chaine[i]!='\0') {
        copie[i]=chaine[i];
        i++;
    }
    chaine[i]='\0';
    printf("Chaine : %s \n|| Copie : %s", chaine, copie);
    return 0;
}
Chaine : Bonjour le monde 
|| Copie : Bonjour le monde

Warning

Ne pas oublier d’ajouter le \0 ) à la fin de la chaine.

La fonction strcpy

La fonction strcpy de la bibliothèque <string.h> permet de copier une chaîne origine (\0 inclus) dans une chaîne dest.

char* strcpy(char *dest, const char *origine)
  • elle renvoie un pointeur vers la chaîne copiée.
#include <stdio.h>
#include <string.h>
 
int main() {
    char chaine[] = "Bonjour le monde";
    char copie[100];
    
    strcpy(copie, chaine);
    printf("Chaine : %s \n|| Copie : %s", chaine, copie);
    return 0;
}
Chaine : Bonjour le monde 
|| Copie : Bonjour le monde

Concaténer

Définition

On considère deux chaînes de caractères ch1 et ch2 le terme concaténer revient à assembler les deux chaînes l’une après l’autre.

Fonction strcat

char *strcat(char *destination, const char *origine);
  • On ajoute origine à la fin de destination
  • La fonction renvoie un pointeur vers le premier caractère de destination

Il faut faire attention, la fonction strcat ne vérifie pas si la taille du tableau destination est assez grande pour ajouter origine à la fin, pensez à trouver une alternative !. Buffer overflow !

#include <stdio.h>
#include <string.h>
 
int main() {
    char ch[50] = "Bonjour, ";
    char ajout[] = "monde !";
 
    strcat(ch, ajout);  // ajoute ajout à la fin de ch
    printf("%s\n", ch);
 
    return 0;
}
Bonjour, monde !

On peut réfléchir à une solution pour concaténer les chaînes de caractères sans avoir de dépassement de mémoire. La solution suivante vient d’une réflexion personnelle ce n’est pas forcément la meilleure chose à faire…

Si on considère et deux chaînes de tailles et respectivement (sans avoir de tableau trop grand) donnée par la fonction strlen(). Alors La taille du tableau est donné par en comptant le caractère nul.

Exemple :

#include <stdio.h>
#include <string.h>
 
int main() {
    char ch[] = "Bonjour ";
    char ajout[] = "tout le monde !";
 
    int taille_ch = strlen(ch);
    int taille_ajout = strlen(ajout);
    int taille_totale = taille_ch + taille_ajout + 1; // +1 pour le caractère nul
 
    char resultat[taille_totale];
    strcpy(resultat, ch);     // copie ch dans resultat
    strcat(resultat, ajout);  // ajoute ajout à la fin de resultat
 
    printf("%s\n", resultat);
 
    return 0;
}
Bonjour tout le monde !

Voici l’algorithme de la solution proposée pour éviter les dépassements mémoire.

concaténation
	Définition et initialisation implicite
	Récupérer les tailles respectives 
	Calcul taille totale

	Définition d'un tableau de résultat de taille_totale
	On copie origine dans resultat
	On concatène ajout dans resultat

	On affiche résultat
fin

Bon ok, je sais c’est un peu plus long mais bon là pas de dépassement mémoire.

Fonction strncat

En langage C, la fonction strncat (définie dans <string.h>) permet de concaténer (ajouter) une chaîne de caractères à la fin d’une autre, en limitant le nombre de caractères copiés.

char *strncat(char *destination, const char *origine, size_t n);
  • destination : la chaîne de destination (doit être assez grande pour contenir le résultat).
  • origine : la chaîne à ajouter.
  • n : le nombre maximum de caractères à copier depuis origine. Elle renvoie aussi un pointeur vers destination.

Warning

  • Il faut que le tableau destination soit assez grand pour contenir la chaîne finale + \0 (le caractère de fin de chaîne).
  • strncat ajoute toujours \0 à la fin.

Exemple :

#include <stdio.h>
#include <string.h>
 
int main() {
    char dest[20] = "Bonjour";
    char src[] = "_le monde !";
 
    // On ajoute seulement les 5 premiers caractères de src
    strncat(dest, src, 5);
 
    printf("Résultat : %s\n", dest)
    return 0;
}
Bonjour_le m

On peut illustrer la concaténation de l’exemple précédant de la manière suivante :

AVANT dest[20] = "Bonjour" B0 o1 n2 j3 o4 u5 r6 \07 8 9 10 11 12 13 14 15 16 17 18 19 src[] = "_le monde !" _ l e m o n d e ! \0 APRÈS dest[20] = "Bonjour_le m" B0 o1 n2 j3 o4 u5 r6 _7 l8 e9 10 m11 \012 13 14 15 16 17 18 19 src[] _ l e m o n d e ! \0 5 caractères copiés depuis src vers dest (indices 7 à 11)

Comparer

En C, on ne peut pas comparer des chaînes avec les opérateurs == ou != comme en Python ou Java, car une chaîne est représentée par un tableau de caractères (donc une adresse mémoire). Pour comparer le contenu, on utilise la fonction strcmp (ou ses variantes) de <string.h>.

L’objet de cette partie, est de présenter le prototype des fonctions de comparaison, les détailler brièvement et montrer un exemple simple.

Fonction strcmp

int strcmp(const char *ch1, const char *ch2);
  • Renvoie un entier selon le résultat de la comparaison.
  • Prend en paramètres de chaînes de caractères.
Valeur de retourSiginification
0Les deux chaînes sont identiques
<0ch1 plus petit que ch2
>0ch1 plus grand que ch2

Exemple :

#include <stdio.h>
#include <string.h>
 
int main() {
    char str1[] = "Bonjour";
    char str2[] = "Bonjour";
    char str3[] = "Bonsoir";
 
    if (strcmp(str1, str2) == 0) {
        printf("str1 et str2 sont identiques\n");
    } else {
        printf("str1 et str2 sont différents\n");
    }
 
    if (strcmp(str1, str3) == 0) {
        printf("str1 et str3 sont identiques\n");
    } else {
        printf("str1 et str3 sont différents\n");
    }
 
    return 0;
}
str1 et str2 sont identiques
str1 et str3 sont différents 

La fonction strncmp

Elle permet en fait de comparer les premiers caractères.

int strncmp(const char *ch1, const char *ch2, size_t n );
  • Elle renvoie un entier résultant de la comparaison
  • Elle prend en argument les deux chaînes ch1 et ch2 puis n le nombre de caractères à comparer.

Exemple :

#include <stdio.h>
#include <string.h>
 
int main() {
    char str1[] = "Bonjour";
    char str2[] = "Bonbon";
 
    if (strncmp(str1, str2, 3) == 0) {
        printf("Les 3 premiers caractères sont identiques\n");
    } else {
        printf("Les 3 premiers caractères sont différents\n");
    }
 
    return 0;
}
Les 3 premiers caractères sont identiques.

Comparaison manuelle

Comme toutes les manipulations vues sur les chaînes, on peut aussi n’utiliser aucune des fonctions vues et faire notre propre fonction de comparaison en utilisant une boucle.

Exemple :

#include <stdio.h>
 
int main() {
    char str1[] = "Bonjour";
    char str2[] = "Bonjour";
    int i = 0, identique = 1;
 
    while (str1[i] != '\0' || str2[i] != '\0') {
        if (str1[i] != str2[i]) {
            identique = 0;
            break;
        }
        i++;
    }
 
    if (identique) {
        printf("Les chaînes sont identiques\n");
    } else {
        printf("Les chaînes sont différentes\n");
    }
 
    return 0;
}
Les chaînes sont identiques

On compare les deux chaînes en les parcourant selon un indice commun i pour accéder à l’élément à la position i de chaque chaîne. On boucle tant qu’au moins une des chaînes n’est pas terminée, c’est à dire tant que l’on ne rencontre pas le marqueur de fin de chaîne \0.
Si les éléments courant des chaînes sont différent on met identique à et on stoppe la boucle. En gros ça veut dire que les chaînes ne sont pas identiques. Dans le cas échéant, on incrémente l’indice et on boucle une autre fois.

Rechercher

En C, pour rechercher un caractère ou une sous-chaîne dans une chaîne, on utilise principalement les fonctions de <string.h> ou on peut le faire manuellement.

La fonction strchr

Le fonction strchr de la bibliothèque <string.h> permet de localiser la première occurrence d’un caractère dans une chaîne de caractères.

char *strchr(char * chaine, int car);
  • Elle prend en paramètres :
    • Une chaîne de caractères chaine, c’est là ou on effectue la recherche
    • Un entier qui correspond au caractère recherché pour rappel char est aussi un entier, le code ASCII du caractère
  • Elle renvoie un pointeur vers l’occurrence du caractère. Le cas échéant, la fonction renverra NULL.

Remarque

Si car = \0 alors la fonction renverra un pointeur vers la fin de la chaîne.

Exemple :
On récupère la première occurrence de la lettre o et on affiche la sous-chaîne à partir de ce dernier.

#include <stdio.h>
#include <string.h>
 
int main() {
    const char *texte = "Bonjour le monde";
    char *ptr; // Le pointeur vers l'occurrence
 
    ptr = strchr(texte, 'o');
 
    if (ptr != NULL) {
        printf("Caractère trouvé : '%c'\n", *ptr);
        printf("Sous-chaîne à partir du 'o' : \"%s\"\n", ptr);
    } else {
        printf("Caractère non trouvé\n");
    }
 
    return 0;
}
Caractère trouvé : 'o'
Sous-chaîne à partir du 'o' : "onjour le monde"


Jusque là c’est assez simple, on a pris un exemple dans lequel la lettre o était présente dans la chaîne. Et puis même si ce dernier n’était pas présent, c’est le bloc else qui aurait pris le relai et affiché que le caractère n’a pas été trouvé.

Mais maintenant, on considère la chaîne informatique et on cherche l’occurrence de la lettre b, sans savoir si cette dernière est présente dans le mot. On va alors rechercher avec strchr sans vérifier si le pointeur existe.
L’objectif est de comprendre ce qu’il va se passer pour ne pas le reproduire.

#include <stdio.h>
#include <string.h>
 
int main() {
    char *chaine = "informatique";
    char *ptr;
 
    // Recherche du premier 'z'
    ptr = strchr(chaine, 'z');
 
        printf("Caractère trouvé : '%c'\n", *ptr);
        printf("Sous-chaîne à partir du 'o' : \"%s\"\n", ptr);
 
    return 0;
}
Segmentation fault

Qu-est-ce que ça veut dire ? Comment cela s’est-il produit ?

On cherche la lettre z dans le chaîne informatique naturellement, cette dernière n’apparaît pas dans le mot alors lors de l’utilisation de la fonction strchr le pointeur ptr sera égal à NULL.
Ensuite sans vérifier l’existence du pointeur vous souhaitez afficher le caractère trouvé. Puisqu’ici ptr=NULL vous essayez d’accéder à l’adresse 0x0 ce qui est interdit en C, l’erreur segmentation fault erreur d’exécution se produit.

C’est pour cette raison qu’il faut vérifier l’existence du pointeur avant d’exploiter les résultats puisque s’il n’existent pas, ça risque de faire planter votre code dans la majeure partie des cas.

  • SOLUTION
if(*ptr){
	// Code a exécuter si occurrence trouvée
} else {
	// Code a exécuté si occurence absente
}

La fonction strstr

Elle permet cette fois de rechercher une sous-chaîne au lieu d’un simple caractère (avec strchr).

char *strstr(char *ch, char *motif);

Elle permet de rechercher motif dans la chaîne ch.
Elle retourne :

  • Un pointeur vers la première occurrence de motif si elle existe
  • NULL le cas échéant

Remarque

ptr = strstr(chaine, "");

Renverra en fait la chaîne de caractère chaine.

Exemple :

#include <stdio.h>
#include <string.h>
 
int main() {
    char *chaine = "informatique";
    char *ptr;
 
    // Recherche d'une sous-chaîne qui n'existe pas
    ptr = strstr(chaine, "zzz");
    if(ptr){
        printf("Motif trouvé : \"%s\"\n", ptr);
    }else{
        printf("Motif absent ici....");
    }
 
    return 0;
}
Motif absent ici....

Astuce

Les deux écritures suivantes en C sont strictement équivalentes :

if(ptr) === if(ptr != NULL)

Découper

La fonction strtok permet de découper une chaîne de caractères en morceaux selon des séparateurs.

char *strtok(char *chaine, char *sep);

On découpe la chaîne chaine en fonction du séparateur sep.

Le fonctionnement de cette fonction est un peu plus complexe à comprendre mais penchons nous quand même dessus, histoire de savoir ce qu’il se passe.

Fonctionnement de strtok(s, " ") avec s = "le mon de" ⚠️ strtok MODIFIE la chaîne : chaque séparateur trouvé est remplacé par \0 État initial (buffer s en mémoire) l0 e1 2 m3 o4 n5 6 d7 e8 \09 1) Premier appel : strtok(s, " ") l0 e1 \02 m3 o4 n5 6 d7 e8 \09 "le" 2) Deuxième appel : strtok(NULL, " ") l0 e1 \02 m3 o4 n5 \06 d7 e8 \09 "mon" 3) Troisième appel : strtok(NULL, " ") l0 e1 \02 m3 o4 n5 \06 d7 e8 \09 "de" 4) Quatrième appel : strtok(NULL, " ") → NULL NULL (plus de jetons) Caractère normal Séparateur → '\0'

On considère une chaîne de caractères s et un délimiteur d tels que :

char* s[] = "le mon de";
char* d[] = " ";

Pour suivre l’évolution du token t tel que :

char* token = strtok(s, d);

Définition

Un token (anglais) ou jeton (français) est une sous-chaîne extraite d’une chaîne plus grande, séparée par des délimiteurs.

  • Premier appel strtok(s, d) La fonction strtok commence au début de le mon de. Elle va alors lire successivement les lettres l puis e qui ne correspondent pas au délimiteur.
    Elle trouve ensuite un espace, qu’elle va donc remplacer par \0.
    La variable token pointe alors vers la chaîne le, et la chaîne initiale devient donc :
"le\0mon de"
  • Deuxième appel : strtok(NULL, d) La fonction strtok reprend la lecture après \0, elle continue de lire les lettres m, o, n pour tomber à nouveau sur un délimiteur qu’elle remplace aussi par \0.
    Résultat, token pointe désormais vers mon et la chaîne initiale est encore modifiée :
"le\0mon\0de"
  • Troisième appel : strtok(NULL, d) La fonction reprend la lecture après le \0 qu’elle vient de mettre à la place du délimiteur. Elle rencontre alors les caractères d et e, puis arrive à la fin de la chaîne en rencontrant le marqueur de fin de chaîne.
    Résultat token pointe vers de et la chaîne n’est pas modifiée, strtok ne va pas modifier \0 par \0 ce serait inutile.

  • Quatrième appel : strtok(NULL, d) Plus de token, la fonction retourne NULL puisqu’elle a finit de lire la chaîne.

Sensibilité à la casse

Ah, la sensibilité à la casse en C est un point important : par défaut, les comparaisons de chaînes (strcmp, strtok, etc.) distinguent les majuscules et minuscules.

strcmp("Chat", "chat") // renvoie != 0, donc elles sont considérées différentes

Pour ignorer la casse, il y a plusieurs techniques selon ce que tu veux faire mais nous allons voir la plus simple.

  • Convertir les chaînes en minuscules ou majuscules Avec tolower ou toupper sur chaque caractère.

Exemple :

#include <stdio.h>
#include <ctype.h>
#include <string.h>
 
void to_lower(char *s) {
    for(int i = 0; s[i]; i++) {
        s[i] = tolower((char) s[i]);
    }
}
 
int main() {
    char s1[] = "Chat";
    char s2[] = "chat";
 
    to_lower(s1);
    to_lower(s2);
 
    if(strcmp(s1, s2) == 0) {
        printf("Égal (insensible à la casse)\n");
    }
}

Certaines des écritures sont encore un peu floues pour vous, c’est normal, nous y reviendrons plus tard…

Suivant