mercredi 23 octobre 2019

Texte et Arduino (2): les tableaux de caractères du langage C

Dans mon billet précédent, j'ai exploré une première façon de traiter du texte avec Arduino, en utilisant la classe String. Tel que promis, voyons maintenant la façon traditionnelle de procéder en langage C: les tableaux de caractères à terminaison nulle.

Avant de plonger, je récapitule rapidement les avantages et inconvénients des deux façons de procéder:
  • classe String (billet précédent): Plus simple pour les débutants, mais engendre une fragmentation de la mémoire qui pourrait rendre le programme instable.
  • tableaux de caractère: Un peu plus compliqué à utiliser par les débutants , mais pas de problème de fragmentation de mémoire, et applicable à tout ce qui se programme en langage C (pas seulement l'Arduino).

Sketch de démonstration

Pour commencer, voici un sketch qui illustre plusieurs des fonctions énumérées dans la suite de cet article. L'utilisateur écrit un message dans le moniteur série, et le sketch effectue diverses opérations afin d'analyser ce message.

Attention: Pour que le programme fonctionne correctement, le moniteur série doit être réglé à "Nouvelle Ligne".

-
-

Voici ce qu'affiche le moniteur série pour le message "Arduino est l'ami des makers":



Création d'un tableau de caractère

Une chaîne de caractère en C est un tableau (array) qui contient des éléments de type char. Dans le sketch de démonstration ci-dessus, 3 tableaux de caractères sont créés aux lignes 8, 9 et 10.

Par exemple, je peux créer un tableau de caractères intitulé "monTexte" de la façon suivante:

char monTexte[7];

Puisqu'il s'agit d'un tableau comportant 7 éléments, il pourra contenir un texte ayant une taille maximale de...6 caractères. Pourquoi seulement 6 plutôt que 7? Parce qu'en langage C, une chaîne de caractères se termine toujours par le caractère "NULL", qui indique la fin de la chaîne.

Lors de l'exécution du programme, la variable monTexte pourra donc contenir le mot "banane" (6 lettres, plus le caractère NULL). Elle pourrait aussi contenir un mot plus court, comme "pomme" (5 lettres, plus le caractère NULL).

Mais il faut faire très attention de ne pas tenter d'y faire entrer le mot "rutabaga" (8 lettres, plus le caractère NULL): si vous le faites, les caractères excédentaires seront quand même inscrits en mémoire, mais à un endroit qui n'a pas été réservé pour le contenu de la variable monTexte! Le comportement de votre programme sera alors totalement imprévisible.

Notez qu'il est également possible laisser au compilateur le soin de calculer lui-même le nombre d'éléments requis dans le tableau, si vous lui affectez une valeur dès sa création:

char monTexte[]="banane";

Puisqu'on n'a pas spécifié le nombre d'éléments, le compilateur va créer un tableau de 7 caractères (les 6 lettres du mot "banane", et un caractère nul à la fin).  Mais ici encore: pas question, pendant l'exécution du programme, de remplacer le mot "banane" par un mot plus long!

Lors de la création de votre tableau de caractère, vous devez donc veiller à choisir une taille suffisante pour contenir le texte que vous voudrez bien y placer (1 élément de plus que le nombre maximal de lettres). Dans la suite du programme, vous devrez vous assurer de ne jamais dépasser le maximum établi au départ.

N.B.: Dans la suite de cet article, les hyperliens mènent vers la documentation complète de chaque fonction.

Affectation d'une valeur à la chaîne de caractères

Une fois votre tableau créé, vous pouvez remplacer le texte qu'il contient grâce à la fonction strcpy() (voir, par exemple, la ligne 29 du sketch de démonstration). Attention: strcpy() ne vous offre aucune protection contre le risque d'affecter à votre tableau une chaîne de caractères plus longue que ce qu'il est capable de contenir.

Pour éviter de dépasser par mégarde la taille limite de votre tableau, il peut être plus prudent d'utiliser la fonction strncpy() (ligne 42 du sketch de démonstration) car elle permet de spécifier le nombre maximal de caractères qui seront placés dans le tableau.

Concaténation de deux chaînes de caractères

Pour concaténer (fusionner) deux chaînes de caractère, on utilise strcat() (ligne 32). On peut aussi utiliser strncat() (ligne 37) pour limiter le nombre de caractères qui seront ajoutés à la chaîne cible.

Comparaison de deux chaînes de caractères

Pour vérifier si deux chaînes sont identiques ou non, on utilise strcmp() (ligne 59). Il y a aussi strncmp() (ligne 69), pour comparer le début de deux chaînes, en spécifiant le nombre de caractères à considérer lors de la comparaison.

Connaître le nombre de caractères d'une chaîne

La fonction strlen() (ligne 48) retourne le nombre de caractères de la chaîne, situés avant le caractère nul.

Analyse caractère par caractère

On peut accéder à un caractère de la chaîne grâce à l'opérateur []. Par exemple, à la ligne 55 du sketch de démonstration, "texteRecu[3]" retourne le 4e caractère de la chaîne "texteRecu" (puisque le premier caractère porte le numéro 0).

Pour trouver la première occurrence d'un caractère dans une chaîne on peut utiliser strchr() (ligne 79), qui retourne un pointeur vers le caractère trouvé. Si vous cherchez la position de ce caractère à l'intérieur la chaîne, il s'agit de faire une soustraction de deux pointeurs (voir la ligne 84).

strrchr() (ligne 91) est similaire à strchr(), sauf que la recherche débute à la fin de la chaîne (dernière occurrence du caractère). Ici encore, la position de ce caractère sera obtenue grâce à une soustraction de pointeurs (ligne 96).

memchr() est similaire à strchr(), sauf qu'on peut restreindre la recherche aux premiers caractères de la chaîne. Voir la ligne 102 du sketch de démonstration.

À la ligne 114, j'ai utilisé strpbrk() afin de trouver la première occurrence de n'importe quel des caractères fournis (dans ce cas précis: toutes les voyelles de l'alphabet). J'ai ensuite trouvé la position de ce caractère en faisant la soustraction des deux pointeurs (ligne 119). Mais cette même position peut également être trouvée au moyen de strcspn() (ligne 143), qui retourne le nombre de caractères consécutifs au début d'un string qui ne figurent pas dans la liste fournie. strspn() (ligne 139) fait le contraire: elle retourne le nombre de caractères consécutifs au début d'un string qui figurent dans la liste fournie.

Recherche d'une chaîne à l'intérieur d'une chaîne

À la ligne 126 du sketch de démonstration, j'utilise strstr() afin de vérifier si la chaîne "ami" se trouve à l'intérieur du message reçu. Puisque la fonction retourne un pointeur vers le début de cette sous-chaîne, j'effectue ensuite une soustraction de pointeurs (ligne 131) pour connaître la position de cette "sous-chaîne", si elle a effectivement été trouvée.

Un peu n'importe quoi...

À la ligne 147, j'utilise la fonction memset() afin de remplacer les 3 premiers caractères d'une chaîne par le même caractère (dans ce cas précis: un astérisque).

À la ligne 153, grâce à strrev(), j'écris le message à l'envers, même si la pertinence de cette fonction m'échappe totalement.

Conversion d'un nombre en chaîne de caractères

Il est souvent utile de convertir un nombre en chaîne de caractères (pour afficher une mesure issue d'un capteur, par exemple).

Malgré son étrange syntaxe, la fonction sprintf() (ligne 159) est toute désignée pour intégrer un nombre entier à l'intérieur d'une chaîne de caractères. À la ligne 163, j'utilise itoa() pour parvenir au même résultat (itoa() n'est pas une fonction C standard, mais elle est supportée par Arduino)­.

En général, sprintf() permet aussi de convertir des valeurs décimales (float) au moyen d'une syntaxe du genre "sprintf(monText,"Valeur mesuree: %f volts",nombre)". Sur un Arduino AVR, toutefois, ça ne fonctionne pas, et il faut plutôt utiliser dtostrf() (ligne 170).

Conversion d'une chaîne de caractères en nombre

À la ligne 176, j'ai utilisé atoi() afin de transformer une chaîne de caractère en entier (variable de type int). Finalement, à la ligne 180, atof() m'a permis de transformer une chaîne de caractère en valeur décimale (variable de type float).

Yves Pelletier   (TwitterFacebook)

4 commentaires:

  1. sprintf(monText,"Valeur mesuree: %f volts",nombre)
    => avec %f, ça devrait fonctionner ;-)

    RépondreSupprimer
  2. J’ai un tableau à deux dimensions de type char tab[100][100], je veux émettre et recevoir les données vers un serial pc en utilisant la méthode de uart de communication avec les tableaux svp j’ai besoin d’aide concernant le code sur atmel studio

    RépondreSupprimer
  3. Bonjour, j'ai du modifier la ligne 102 pourquoi?
    resultat = (char*) memchr(texteRecu, 'p', 5);

    merci

    RépondreSupprimer
  4. encore moi, j'ai du modifier aussi ligne 59, passer par une chaine de char intermedière.

    // Comparaison de 2 chaînes (strcmp)
    strcpy(reponse,"$D?5F*");
    if (!strcmp(texteRecu, reponse))

    une histoire de '\0' en fin du message de droite qui n'y est pas.
    cordialement
    .

    RépondreSupprimer