Licence CC BY-SA

TP : l'en-tête <string.h>

Les quatre derniers cours ayant été riches en nouveautés, posons-nous un instant pour mettre en pratique tout cela.

Préparation

Dans le chapitre précédent, nous vous avons dit qu’une chaîne de caractères se manipulait comme un tableau, à savoir en parcourant ses éléments un à un. Cependant, si cela s’arrêtait là, cela serait assez gênant. En effet, la moindre opération sur une chaîne nécessiterait d’accéder à ses différents éléments, que ce soit une modification, une copie, une comparaison, etc. Heureusement pour nous, la bibliothèque standard fournit une suite de fonctions nous permettant de réaliser plusieurs opérations de base sur des chaînes de caractères. Ces fonctions sont déclarées dans l’en-tête <string.h>.

L’objectif de ce TP sera de réaliser une version pour chacune des fonctions de cet en-tête qui vont vous êtes présentées. :)

strlen
size_t strlen(char *chaine);

La fonction strlen() vous permet de connaître la taille d’une chaîne fournie en argument. Celle-ci retourne une valeur de type size_t. Notez bien que la longueur retournée ne comprend pas le caractère nul. L’exemple ci-dessous affiche la taille de la chaîne « Bonjour ».

#include <stdio.h>
#include <string.h>


int main(void)
{
    printf("Longueur : %zu\n", strlen("Bonjour"));
    return 0;
}
Résultat
Longueur : 7
strcpy
char *strcpy(char *destination, char *source);

La fonction strcpy() copie le contenu de la chaîne source dans la chaîne destination, caractère nul compris. La fonction retourne l’adresse de destination. L’exemple ci-dessous copie la chaîne « Au revoir » dans la chaîne chaine.

#include <stdio.h>
#include <string.h>


int main(void)
{
    char chaine[25] = "Bonjour\n";

    strcpy(chaine, "Au revoir");
    printf("%s\n", chaine);
    return 0;
}
Résultat
Au revoir

La fonction strcpy() n’effectue aucune vérification. Vous devez donc vous assurer que la chaîne de destination dispose de suffisamment d’espace pour accueillir la chaîne qui doit être copiée (caractère nul compris !).

strcat
char *strcat(char *destination, char *source);

La fonction strcat() ajoute le contenu de la chaîne source à celui de la chaîne destination, caractère nul compris. La fonction retourne l’adresse de destination. L’exemple ci-dessous ajoute la chaîne « tout le monde » au contenu de la chaîne chaine.

#include <stdio.h>
#include <string.h>


int main(void)
{
    char chaine[25] = "Bonjour";

    strcat(chaine, " tout le monde");
    printf("%s\n", chaine);
    return 0;
}
Résultat
Bonjour tout le monde

Comme strcpy(), la fonction strcat() n’effectue aucune vérification. Vous devez donc vous assurer que la chaîne de destination dispose de suffisamment d’espace pour accueillir la chaîne qui doit être ajoutée (caractère nul compris !).

strcmp
int strcmp(char *chaine1, char *chaine2);

La fonction strcmp() compare deux chaînes de caractères. Cette fonction retourne :

  • une valeur positive si la première chaîne est « plus grande » que la seconde ;
  • zéro si elles sont égales ;
  • une valeur négative si la seconde chaîne est « plus grande » que la première.

Ne vous inquiétez pas au sujet des valeurs positives et négatives, nous y reviendrons en temps voulu lorsque nous aborderons les notions de jeux de caractères et d’encodages dans la troisième partie du cours. En attendant, effectuez simplement une comparaison entre les deux caractères qui diffèrent.

#include <stdio.h>
#include <string.h>


int main(void)
{
    char chaine1[] = "Bonjour";
    char chaine2[] = "Au revoir";

    if (strcmp(chaine1, chaine2) == 0)
        printf("Les deux chaînes sont identiques\n");
    else
        printf("Les deux chaînes sont différentes\n");

    return 0;
}
Résultat
Les deux chaînes sont différentes
strchr
char *strchr(char *chaine, int ch);

La fonction strchr() recherche la présence du caractère ch dans la chaîne chaine. Si celui-ci est rencontré, la fonction retourne l’adresse de la première occurrence de celui-ci au sein de la chaîne. Dans le cas contraire, la fonction renvoie un pointeur nul.

#include <stddef.h>
#include <stdio.h>
#include <string.h>


int main(void)
{
    char chaine[] = "Bonjour";
    char *p = strchr(chaine, 'o');

    if (p != NULL)
        printf("La chaîne `%s' contient la lettre %c\n", chaine, *p);

    return 0;
}
Résultat
La chaîne `Bonjour' contient la lettre o
strpbrk
char *strpbrk(char *chaine, char *liste);

La fonction strpbrk() recherche la présence d’un des caractères de la chaîne liste dans la chaîne chaine. Si un de ceux-ci est rencontré, la fonction retourne l’adresse de la première occurrence au sein de la chaîne. Dans le cas contraire, la fonction renvoie un pointeur nul.

#include <stddef.h>
#include <stdio.h>
#include <string.h>


int main(void)
{
    char chaine[] = "Bonjour";
    char *p = strpbrk(chaine, "aeiouy");

    if (p != NULL)
        printf("La première voyelle de la chaîne `%s' est : %c\n", chaine, *p);

    return 0;
}
Résultat
La première voyelle de la chaîne `Bonjour' est : o
strstr
char *strstr(char *chaine1, char *chaine2);

La fonction strstr() recherche la présence de la chaîne chaine2 dans la chaîne chaine1. Si celle-ci est rencontrée, la fonction retourne l’adresse de la première occurrence de celle-ci au sein de la chaîne. Dans le cas contraire, la fonction renvoie un pointeur nul.

#include <stddef.h>
#include <stdio.h>
#include <string.h>


int main(void)
{
    char chaine[] = "Bonjour";
    char *p = strstr(chaine, "jour");

    if (p != NULL)
        printf("La chaîne `%s' contient la chaîne `%s'\n", chaine, p);

    return 0;
}
Résultat
La chaîne `Bonjour' contient la chaîne `jour'

Cela étant dit, à vous de jouer. ;)

Par convention, nous commencerons le nom de nos fonctions par la lettre « x » afin d’éviter des collisions avec ceux de l’en-tête <string.h>.

Correction

Bien, l’heure de la correction est venue. :pirate:

strlen
size_t xstrlen(char *chaine)
{
    size_t i;

    for (i = 0; chaine[i] != '\0'; ++i)
        ;

    return i;
}
strcpy
char *xstrcpy(char *destination, char *source)
{
    size_t i;

    for (i = 0; source[i] != '\0'; ++i)
        destination[i] = source[i];

    destination[i] = '\0'; // N'oubliez pas le caractère nul final !
    return destination;
}
strcat
char *xstrcat(char *destination, char *source)
{
	size_t i = 0, j = 0;

	while (destination[i] != '\0')
		++i;

	while (source[j] != '\0') {
		destination[i] = source[j];
		++i;
		++j;
	}

	destination[i] = '\0'; // N'oubliez pas le caractère nul final !
	return destination;
}
strcmp
int xstrcmp(char *chaine1, char *chaine2)
{
    while (*chaine1 == *chaine2)
    {
        if (*chaine1 == '\0')
            return 0;

        ++chaine1;
        ++chaine2;
    }

    return (*chaine1 < *chaine2) ? -1 : 1;
}
strchr
char *xstrchr(char *chaine, int ch)
{
    while (*chaine != '\0')
        if (*chaine == ch)
            return chaine;
        else
            ++chaine;

    return NULL;
}
strpbrk
char *xstrpbrk(char *chaine, char *liste)
{
    while (*chaine != '\0')
    {
        char *p = liste;

        while (*p != '\0')
        {
            if (*chaine == *p)
                return chaine;

            ++p;
        }

        ++chaine;
    }

    return NULL;
}
strstr
char *xstrstr(char *chaine1, char *chaine2)
{
    while (*chaine1 != '\0')
    {
        char *s = chaine1;
        char *t = chaine2;

        while (*s != '\0' && *t != '\0')
        {
            if (*s != *t)
                break;
      
            ++s;
            ++t;
        }

        if (*t == '\0')
            return chaine1;

        ++chaine1;
    }

    return NULL;
}

Pour aller plus loin : strtok

Pour les plus aventureux d’entre-vous, nous vous proposons de réaliser en plus la mise en œuvre de la fonction strtok() qui est un peu plus complexe que ses congénères. :)

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

La fonction strtok() est un peu particulière : elle divise la chaîne chaine en une suite de sous-chaînes délimitée par un des caractères présents dans la chaîne liste. En fait, cette dernière remplace les caractères de la chaîne liste par des caractères nuls dans la chaîne chaine (elle modifie donc la chaîne chaine !) et renvoie les différentes sous-chaînes ainsi créées au fur et à mesure de ses appels.

Lors des appels subséquents, la chaîne chaine doit être un pointeur nul afin de signaler à strtok() que nous souhaitons la sous-chaîne suivante. S’il n’y a plus de sous-chaîne ou qu’aucun caractère de la chaîne liste n’est trouvé, celle-ci retourne un pointeur nul.

L’exemple ci-dessous tente de scinder la chaîne « un, deux, trois » en trois sous-chaînes.

#include <stddef.h>
#include <stdio.h>
#include <string.h>


int main(void)
{
    char chaine[] = "un, deux, trois";
    char *p = strtok(chaine, ", ");

    for (size_t i = 0; p != NULL; ++i)
    {
        printf("%zu : %s\n", i, p);
        p = strtok(NULL, ", ");
    }

    return 0;
}
Résultat
0 : un
1 : deux
2 : trois

La fonction strtok() étant un peu plus complexe que les autres, voici une petite marche à suivre pour réaliser cette fonction.

  • regarder si la chaîne fournie (ou la sous-chaîne suivante) contient, à son début, des caractères présents dans la seconde chaîne. Si oui, ceux-ci doivent être passés ;
  • si la fin de la (sous-)chaîne est atteinte, retourner un pointeur nul ;
  • parcourir la chaîne fournie (ou la sous-chaîne courante) jusqu’à rencontrer un des caractères composant la seconde chaîne. Si un caractère est rencontré, le remplacer par un caractère nul, conserver la position actuelle dans la chaîne et retourner la sous-chaîne ainsi créée (sans les éventuels caractères passés au début) ;
  • si la fin de la (sous-)chaîne est atteinte, retournez-la (sous-)chaîne (sans les éventuels caractères passés au début).

Vous aurez besoin d’une variable de classe de stockage statique pour réaliser cette fonction. Également, l’instruction goto pourra vous être utile.

Correction
char *xstrtok(char *chaine, char *liste)
{
    static char *dernier;
    char *base = (chaine != NULL) ? chaine : dernier;

    if (base == NULL)
        return NULL;

separateur_au_debut:
    for (char *sep = liste; *sep != '\0'; ++sep)
        if (*base == *sep)
        {
            ++base;
            goto separateur_au_debut;
        }

    if (*base == '\0')
    {
        dernier = NULL;
        return NULL;
    }

    for (char *s = base; *s != '\0'; ++s)
        for (char *sep = liste; *sep != '\0'; ++sep)
            if (*s == *sep)
            {
                *s = '\0';
                dernier = s + 1;
                return base;
            }

    dernier = NULL;
    return base;
}

Dans le chapitre suivant, nous aborderons le mécanisme de l’allocation dynamique de mémoire.