Recherche…


Introduction

En C, une chaîne n'est pas un type intrinsèque. Une chaîne de caractères est la convention pour avoir un tableau de caractères à une dimension qui se termine par un caractère nul, par un '\0' .

Cela signifie qu'une chaîne de caractères avec un contenu de "abc" aura quatre caractères 'a' , 'b' , 'c' et '\0' .

Voir l'exemple de base de l'introduction aux chaînes .

Syntaxe

  • char str1 [] = "Bonjour, monde!"; / * Modifiable * /
  • char str2 [14] = "Bonjour tout le monde!"; / * Modifiable * /
  • char * str3 = "Bonjour tout le monde!"; /* Non modifiable*/

Calculez la longueur: strlen ()

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

int main(int argc, char **argv) 
{
    /* Exit if no second argument is found. */
    if (argc != 2) 
    {
        puts("Argument missing.");
        return EXIT_FAILURE;
    }

    size_t len = strlen(argv[1]);
    printf("The length of the second argument is %zu.\n", len);

    return EXIT_SUCCESS;
}

Ce programme calcule la longueur de son deuxième argument d'entrée et stocke le résultat dans len . Il imprime ensuite cette longueur au terminal. Par exemple, lorsqu'il est exécuté avec les paramètres program_name "Hello, world!" , le programme va sortir The length of the second argument is 13. car la chaine Hello, world! est composé de 13 caractères.

strlen compte tous les octets du début de la chaîne jusqu'à, mais sans inclure, le caractère NUL de terminaison, '\0' . En tant que tel, il ne peut être utilisé que lorsque la chaîne est garantie pour être terminée par NUL.

Gardez également à l'esprit que si la chaîne contient des caractères Unicode, strlen ne vous indiquera pas le nombre de caractères de la chaîne (certains caractères pouvant compter plusieurs octets). Dans de tels cas, vous devez compter vous-même les caractères ( c. -à-d. Les unités de code). Considérez la sortie de l'exemple suivant:

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

int main(void) 
{
    char asciiString[50] = "Hello world!";
    char utf8String[50] = "Γειά σου Κόσμε!"; /* "Hello World!" in Greek */

    printf("asciiString has %zu bytes in the array\n", sizeof(asciiString));
    printf("utf8String has %zu bytes in the array\n", sizeof(utf8String));
    printf("\"%s\" is %zu bytes\n", asciiString, strlen(asciiString));
    printf("\"%s\" is %zu bytes\n", utf8String, strlen(utf8String));
}

Sortie:

asciiString has 50 bytes in the array
utf8String has 50 bytes in the array
"Hello world!" is 12 bytes
"Γειά σου Κόσμε!" is 27 bytes

Copie et concaténation: strcpy (), strcat ()

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

int main(void)
{
  /* Always ensure that your string is large enough to contain the characters
   * and a terminating NUL character ('\0')!
   */
  char mystring[10];

  /* Copy "foo" into `mystring`, until a NUL character is encountered. */
  strcpy(mystring, "foo");
  printf("%s\n", mystring);

  /* At this point, we used 4 chars of `mystring`, the 3 characters of "foo",
   * and the NUL terminating byte.
   */

  /* Append "bar" to `mystring`. */
  strcat(mystring, "bar");
  printf("%s\n", mystring);

  /* We now use 7 characters of `mystring`: "foo" requires 3, "bar" requires 3
   * and there is a terminating NUL character ('\0') at the end.
   */

  /* Copy "bar" into `mystring`, overwriting the former contents. */
  strcpy(mystring, "bar");
  printf("%s\n", mystring);

  return 0;
}

Les sorties:

foo
foobar
bar

Si vous ajoutez à, ou à partir ou copiez à partir d'une chaîne existante, assurez-vous qu'il est terminé NUL!

Les littéraux de chaîne (par exemple "foo" ) seront toujours terminés par le compilateur.

Comparaison: strcmp (), strncmp (), strcasecmp (), strncasecmp ()

Les fonctions strcase* ne sont pas la norme C, mais une extension POSIX.

La fonction strcmp compare lexicographiquement deux tableaux de caractères à terminaison nulle. Les fonctions renvoient une valeur négative si le premier argument apparaît avant le second dans l'ordre lexicographique, zéro s'il est égal ou positif si le premier argument apparaît après le second dans l'ordre lexicographique.

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

void compare(char const *lhs, char const *rhs)
{
    int result = strcmp(lhs, rhs); // compute comparison once
    if (result < 0) {
        printf("%s comes before %s\n", lhs, rhs);
    } else if (result == 0) {
        printf("%s equals %s\n", lhs, rhs);
    } else { // last case: result > 0
        printf("%s comes after %s\n", lhs, rhs);
    }
}

int main(void)
{
    compare("BBB", "BBB");
    compare("BBB", "CCCCC");
    compare("BBB", "AAAAAA");
    return 0;
}

Les sorties:

BBB equals BBB
BBB comes before CCCCC
BBB comes after AAAAAA

En tant que strcmp , la fonction strcasecmp compare également ses arguments lexicographiquement après avoir traduit chaque caractère en son correspondant en minuscule:

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

void compare(char const *lhs, char const *rhs)
{
    int result = strcasecmp(lhs, rhs); // compute case-insensitive comparison once
    if (result < 0) {
        printf("%s comes before %s\n", lhs, rhs);
    } else if (result == 0) {
        printf("%s equals %s\n", lhs, rhs);
    } else { // last case: result > 0
        printf("%s comes after %s\n", lhs, rhs);
    }
}

int main(void)
{
    compare("BBB", "bBB");
    compare("BBB", "ccCCC");
    compare("BBB", "aaaaaa");
    return 0;
}

Les sorties:

BBB equals bBB
BBB comes before ccCCC
BBB comes after aaaaaa

strncmp et strncasecmp comparent au plus n caractères:

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

void compare(char const *lhs, char const *rhs, int n)
{
    int result = strncmp(lhs, rhs, n); // compute comparison once
    if (result < 0) {
        printf("%s comes before %s\n", lhs, rhs);
    } else if (result == 0) {
        printf("%s equals %s\n", lhs, rhs);
    } else { // last case: result > 0
        printf("%s comes after %s\n", lhs, rhs);
    }
}

int main(void)
{
    compare("BBB", "Bb", 1);
    compare("BBB", "Bb", 2);
    compare("BBB", "Bb", 3);
    return 0;
}

Les sorties:

BBB equals Bb
BBB comes before Bb
BBB comes before Bb

Tokenisation: strtok (), strtok_r () et strtok_s ()

La fonction strtok décompose une chaîne en chaînes plus petites, ou jetons, en utilisant un ensemble de délimiteurs.

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

int main(void)
{
    int toknum = 0;
    char src[] = "Hello,, world!";
    const char delimiters[] = ", !";
    char *token = strtok(src, delimiters);
    while (token != NULL)
    {
        printf("%d: [%s]\n", ++toknum, token);
        token = strtok(NULL, delimiters);
    }
    /* source is now "Hello\0, world\0\0" */
}

Sortie:

1: [Hello]
2: [world]

La chaîne de délimiteurs peut contenir un ou plusieurs délimiteurs et différentes chaînes de délimiteurs peuvent être utilisées avec chaque appel à strtok .

Les appels à strtok pour continuer à numériser la même chaîne source ne doivent pas transmettre la chaîne source à nouveau, mais plutôt passer NULL comme premier argument. Si la même chaîne source est transmise, le premier jeton sera ré-unifié. C'est-à-dire qu'avec les mêmes délimiteurs, strtok renverrait simplement le premier jeton.

Notez que comme strtok n'alloue pas de nouvelle mémoire pour les jetons, il modifie la chaîne source . C'est-à-dire que dans l'exemple ci-dessus, la chaîne src sera manipulée pour produire les jetons référencés par le pointeur renvoyé par les appels à strtok . Cela signifie que la chaîne source ne peut pas être const (elle ne peut donc pas être un littéral de chaîne). Cela signifie également que l'identité de l'octet de délimitation est perdue (c'est-à-dire que dans l'exemple, les "," et "!" Sont effectivement supprimés de la chaîne source et que vous ne pouvez pas déterminer le caractère de délimitation correspondant).

Notez également que plusieurs délimiteurs consécutifs dans la chaîne source sont traités comme un seul; dans l'exemple, la deuxième virgule est ignorée.

strtok n'est ni thread-safe ni ré-entrant car il utilise un tampon statique lors de l'analyse. Cela signifie que si une fonction appelle strtok , aucune fonction qu’elle appelle lorsqu’elle utilise strtok ne peut également utiliser strtok , et elle ne peut être appelée par aucune fonction utilisant strtok .

Un exemple qui démontre les problèmes causés par le fait que strtok n'est pas rentrant est le suivant:

char src[] = "1.2,3.5,4.2";
char *first = strtok(src, ","); 

do 
{
    char *part;
    /* Nested calls to strtok do not work as desired */
    printf("[%s]\n", first);
    part = strtok(first, ".");
    while (part != NULL)
    {
        printf(" [%s]\n", part);
        part = strtok(NULL, ".");
    }
} while ((first = strtok(NULL, ",")) != NULL);

Sortie:

[1.2]
 [1]
 [2]

L'opération attendue est que la boucle do while while externe doit créer trois jetons constitués de chaque chaîne de nombres décimaux ( "1.2" , "3.5" , "4.2" ), pour chacun desquels les appels strtok de la boucle interne doivent être séparés. chaînes de caractères numériques ( "1" , "2" , "3" , "5" , "4" , "2" ).

Cependant, comme strtok n'est pas ré-entrant, cela ne se produit pas. Au lieu de cela, le premier strtok crée correctement le jeton "1.2 \ 0" et la boucle interne crée correctement les jetons "1" et "2" . Mais alors le strtok dans la boucle externe est à la fin de la chaîne utilisée par la boucle interne et renvoie immédiatement NULL. Les deuxième et troisième sous-chaînes du tableau src ne sont pas analysées du tout.

C11

Les bibliothèques C standard ne contiennent pas de version thread-safe ou ré-entrante, mais d'autres le sont, comme POSIX ' strtok_r . Notez que sur strtok_s , l'équivalent strtok , strtok_s est strtok_s pour les threads.

C11

C11 possède une partie facultative, l'Annexe K, qui offre une version thread-safe et ré-entrante nommée strtok_s . Vous pouvez tester la fonctionnalité avec __STDC_LIB_EXT1__ . Cette partie facultative n'est pas largement prise en charge.

La fonction strtok_s diffère de la fonction POSIX strtok_r de stocker en dehors de la chaîne en cours de jeton et en vérifiant les contraintes d'exécution. Sur les programmes correctement écrits, les strtok_s et strtok_r se comportent de la même manière.

L'utilisation de strtok_s avec l'exemple donne maintenant la réponse correcte, comme ceci:

/* you have to announce that you want to use Annex K */ 
#define __STDC_WANT_LIB_EXT1__ 1
#include <string.h>

#ifndef __STDC_LIB_EXT1__
# error "we need strtok_s from Annex K"
#endif

char src[] = "1.2,3.5,4.2";  
char *next = NULL;
char *first = strtok_s(src, ",", &next);

do 
{
    char *part;
    char *posn;

    printf("[%s]\n", first);
    part = strtok_s(first, ".", &posn);
    while (part != NULL)
    {
        printf(" [%s]\n", part);
        part = strtok_s(NULL, ".", &posn);
    }
} 
while ((first = strtok_s(NULL, ",", &next)) != NULL);

Et le résultat sera:

[1.2]
 [1]
 [2]
[3.5]
 [3]
 [5]
[4.2]
 [4]
 [2]

Rechercher la première / dernière occurrence d'un caractère spécifique: strchr (), strrchr ()

Les fonctions strchr et strrchr trouvent un caractère dans une chaîne, c'est-à-dire dans un tableau de caractères terminé par NUL. strchr renvoie un pointeur sur la première occurrence et strrchr sur la dernière.

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

int main(void)
{
    char toSearchFor = 'A';

    /* Exit if no second argument is found. */
    if (argc != 2)
    {
        printf("Argument missing.\n");
        return EXIT_FAILURE;
    }

    {
        char *firstOcc = strchr(argv[1], toSearchFor);
        if (firstOcc != NULL) 
        {
            printf("First position of %c in %s is %td.\n", 
              toSearchFor, argv[1], firstOcc-argv[1]); /* A pointer difference's result 
                                     is a signed integer and uses the length modifier 't'. */
        }
        else
        {
            printf("%c is not in %s.\n", toSearchFor, argv[1]);
        }
    }

    {
        char *lastOcc = strrchr(argv[1], toSearchFor);
        if (lastOcc != NULL)
        {
            printf("Last position of %c in %s is %td.\n",
              toSearchFor, argv[1], lastOcc-argv[1]);
        }
    }

    return EXIT_SUCCESS;
}

Sorties (après avoir généré un exécutable nommé pos ):

$ ./pos AAAAAAA
First position of A in AAAAAAA is 0.
Last position of A in AAAAAAA is 6.
$ ./pos BAbbbbbAccccAAAAzzz
First position of A in BAbbbbbAccccAAAAzzz is 1.
Last position of A in BAbbbbbAccccAAAAzzz is 15.
$  ./pos qwerty             
A is not in qwerty.

Une utilisation courante de strrchr consiste à extraire un nom de fichier d'un chemin. Par exemple, pour extraire myfile.txt de C:\Users\eak\myfile.txt :

char *getFileName(const char *path)
{
    char *pend;

    if ((pend = strrchr(path, '\')) != NULL)
        return pend + 1;

    return NULL;
}

Itération sur les caractères d'une chaîne

Si nous connaissons la longueur de la chaîne, nous pouvons utiliser une boucle for pour parcourir ses caractères:

char * string = "hello world"; /* This 11 chars long, excluding the 0-terminator. */
size_t i = 0;
for (; i < 11; i++) {
    printf("%c\n", string[i]);    /* Print each character of the string. */
}

Alternativement, nous pouvons utiliser la fonction standard strlen() pour obtenir la longueur d'une chaîne si nous ne savons pas quelle est la chaîne:

size_t length = strlen(string);
size_t i = 0; 
for (; i < length; i++) {
    printf("%c\n", string[i]);    /* Print each character of the string. */
}

Enfin, nous pouvons tirer parti du fait que les chaînes de caractères de C sont garanties sans aucune terminaison (ce que nous avons déjà fait lors du passage à strlen() dans l'exemple précédent ;-)). Nous pouvons parcourir le tableau quelle que soit sa taille et cesser d’itérer une fois que nous avons atteint un caractère nul:

size_t i = 0;
while (string[i] != '\0') {       /* Stop looping when we reach the null-character. */
    printf("%c\n", string[i]);    /* Print each character of the string. */
    i++;
}

Introduction de base aux chaînes

En C, une chaîne est une suite de caractères terminée par un caractère nul ('\ 0').

Nous pouvons créer des chaînes à l'aide de chaînes littérales , qui sont des séquences de caractères entourées de guillemets doubles. par exemple, prenez la chaîne littérale "hello world" . Les littéraux de chaîne sont automatiquement annulés.

Nous pouvons créer des chaînes en utilisant plusieurs méthodes. Par exemple, nous pouvons déclarer un caractère char * et l'initialiser pour désigner le premier caractère d'une chaîne:

char * string = "hello world";

Lors de l'initialisation d'un caractère char * à une chaîne constante comme ci-dessus, la chaîne elle-même est généralement allouée en lecture seule; string est un pointeur sur le premier élément du tableau, qui est le caractère 'h' .

Comme le littéral de chaîne est alloué en mémoire morte, il est non modifiable 1 . Toute tentative de modification entraînera un comportement indéfini , il est donc préférable d'ajouter const pour obtenir une erreur de compilation comme celle-ci

char const * string = "hello world";

Il a un effet similaire à 2

char const string_arr[] = "hello world";

Pour créer une chaîne modifiable, vous pouvez déclarer un tableau de caractères et initialiser son contenu à l'aide d'un littéral de chaîne, comme ceci:

char modifiable_string[] = "hello world";

Ceci est équivalent à ce qui suit:

char modifiable_string[] = {'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '\0'};

Comme la deuxième version utilise un initialiseur avec accolades, la chaîne n'est pas automatiquement terminée par un caractère null à moins qu'un caractère '\0' soit explicitement inclus dans le tableau de caractères, généralement comme son dernier élément.


1 Non modifiable implique que les caractères dans le littéral de chaîne ne peuvent pas être modifiés, mais rappelez-vous que la string pointeur peut être modifiée (peut pointer ailleurs ou peut être incrémentée ou décrémentée).

2 Les deux chaînes ont un effet similaire dans le sens où les caractères des deux chaînes ne peuvent pas être modifiés. Il convient de noter que string est un pointeur sur char et qu'il s'agit d'une valeur l modifiable, de sorte qu'il peut être incrémenté ou pointer vers un autre emplacement alors que le tableau string_arr est une valeur l non modifiable, il ne peut pas être modifié.

Création de tableaux de chaînes

Un tableau de chaînes peut signifier plusieurs choses:

  1. Un tableau dont les éléments sont des char * s
  2. Un tableau dont les éléments sont des réseaux de char s

Nous pouvons créer un tableau de pointeurs de caractères comme suit:

char * string_array[] = {
    "foo",
    "bar",
    "baz"
};

Rappelez-vous: lorsque nous affectons des littéraux de chaîne à char * , les chaînes elles-mêmes sont allouées en mémoire morte. Cependant, le tableau string_array est alloué en lecture / écriture. Cela signifie que nous pouvons modifier les pointeurs dans le tableau, mais nous ne pouvons pas modifier les chaînes vers lesquelles ils pointent.

Dans C, le paramètre principal argv (le tableau des arguments de ligne de commande transmis lors de l'exécution du programme) est un tableau de caractères char * : char * argv[] .

Nous pouvons également créer des tableaux de tableaux de caractères. Comme les chaînes sont des tableaux de caractères, un tableau de chaînes est simplement un tableau dont les éléments sont des tableaux de caractères:

char modifiable_string_array_literals[][4] = {
    "foo",
    "bar",
    "baz"
};

Ceci est équivalent à:

char modifiable_string_array[][4] = {
    {'f', 'o', 'o', '\0'},
    {'b', 'a', 'r', '\0'},
    {'b', 'a', 'z', '\0'}
};

Notez que nous spécifions 4 comme taille de la deuxième dimension du tableau; chacune des chaînes de notre tableau est en réalité de 4 octets, car nous devons inclure le caractère de terminaison nulle.

strstr

/* finds the next instance of needle in haystack 
   zbpos: the zero-based position to begin searching from
   haystack: the string to search in
   needle: the string that must be found
   returns the next match of `needle` in `haystack`, or -1 if not found
*/
int findnext(int zbpos, const char *haystack, const char *needle)
{
    char *p; 

    if (((p = strstr(haystack + zbpos, needle)) != NULL)
        return p - haystack;

    return -1;
}

strstr recherche l'argument haystack (first) de la chaîne pointée par needle . Si trouvé, strstr renvoie l'adresse de l'occurrence. S'il n'a pas pu trouver d' needle , il retourne NULL. Nous utilisons zbpos afin de ne pas retrouver la même aiguille encore et encore. Pour sauter la première instance, nous ajoutons un décalage de zbpos . Un clone de Notepad pourrait appeler findnext comme ceci, afin de mettre en œuvre son dialogue "Find Next":

/*
    Called when the user clicks "Find Next"
    doc: The text of the document to search
    findwhat: The string to find
*/
void onfindnext(const char *doc, const char *findwhat)
{
    static int i;

    if ((i = findnext(i, doc, findwhat)) != -1)
        /* select the text starting from i and ending at i + strlen(findwhat) */
    else
        /* display a message box saying "end of search" */
}

Littéraux de chaîne

Les chaînes littérales représentent zéro terminal, statique durée des tableaux de char . Comme ils ont une durée de stockage statique, un littéral de chaîne ou un pointeur vers le même tableau sous-jacent peut être utilisé de différentes manières, ce que ne permet pas un pointeur vers un tableau automatique. Par exemple, le retour d'un littéral de chaîne à partir d'une fonction a un comportement bien défini:

const char *get_hello() {
    return "Hello, World!";  /* safe */
}

Pour des raisons historiques, les éléments du tableau correspondant à un littéral de chaîne ne sont pas formellement const . Néanmoins, toute tentative de les modifier a un comportement indéfini . En règle générale, un programme qui tente de modifier le tableau correspondant à un littéral de chaîne se bloque ou présente un dysfonctionnement.

char *foo = "hello";
foo[0] = 'y';  /* Undefined behavior - BAD! */

Lorsqu'un pointeur pointe vers une chaîne littérale - ou où il peut parfois faire - il est conseillé de déclarer référent ce pointeur const pour éviter d' engager un tel comportement non défini accidentellement.

const char *foo = "hello";
/* GOOD: can't modify the string pointed to by foo */

D'un autre côté, un pointeur vers ou dans le tableau sous-jacent d'un littéral de chaîne n'est pas en soi intrinsèquement spécial; sa valeur peut être librement modifiée pour indiquer autre chose:

char *foo = "hello";
foo = "World!"; /* OK - we're just changing what foo points to */

En outre, bien que initialisations pour char tableaux peuvent avoir la même forme que les littéraux de chaîne, l' utilisation d'un tel initialiseur ne confère pas les caractéristiques d'une chaîne littérale sur le tableau initialisé. L'initialiseur désigne simplement la longueur et le contenu initial du tableau. En particulier, les éléments sont modifiables s'ils ne sont pas explicitement déclarés const :

char foo[] = "hello";
foo[0] = 'y';  /* OK! */

Remettre une chaîne à zéro

Vous pouvez appeler memset pour mettre à zéro une chaîne (ou tout autre bloc de mémoire).

str est la chaîne à mettre à zéro et n est le nombre d'octets de la chaîne.

#include <stdlib.h> /* For EXIT_SUCCESS */
#include <stdio.h>
#include <string.h>


int main(void)
{
  char str[42] = "fortytwo";
  size_t n = sizeof str; /* Take the size not the length. */

  printf("'%s'\n", str);

  memset(str, '\0', n);

  printf("'%s'\n", str);

  return EXIT_SUCCESS;
}

Impressions:

'fortytwo'
''

Un autre exemple:

#include <stdlib.h> /* For EXIT_SUCCESS */
#include <stdio.h>
#include <string.h>


#define FORTY_STR "forty"
#define TWO_STR "two"

int main(void)
{
  char str[42] = FORTY_STR TWO_STR;
  size_t n = sizeof str; /* Take the size not the length. */
  char * point_to_two = strstr(str, TWO_STR);

  printf("'%s'\n", str);

  memset(point_to_two, '\0', n);

  printf("'%s'\n", str);

  memset(str, '\0', n);

  printf("'%s'\n", str);

  return EXIT_SUCCESS;
}

Impressions:

'fortytwo'
'forty'
''

strspn et strcspn

Etant donné une chaîne, strspn calcule la longueur de la sous-chaîne initiale (span) constituée uniquement d'une liste de caractères spécifique. strcspn est similaire, sauf qu'il calcule la longueur de la sous-chaîne initiale composée de tous les caractères sauf ceux listés:

/*
  Provided a string of "tokens" delimited by "separators", print the tokens along
  with the token separators that get skipped.
*/
#include <stdio.h>
#include <string.h>

int main(void)
{
    const char sepchars[] = ",.;!?";
    char foo[] = ";ball call,.fall gall hall!?.,";
    char *s;
    int n;

    for (s = foo; *s != 0; /*empty*/) {
        /* Get the number of token separator characters. */
        n = (int)strspn(s, sepchars);

        if (n > 0)
            printf("skipping separators: << %.*s >> (length=%d)\n", n, s, n);

        /* Actually skip the separators now. */
        s += n;

        /* Get the number of token (non-separator) characters. */
        n = (int)strcspn(s, sepchars);

        if (n > 0)
            printf("token found: << %.*s >> (length=%d)\n", n, s, n);

        /* Skip the token now. */
        s += n;
    }

    printf("== token list exhausted ==\n");

    return 0;
}

Les fonctions analogues utilisant des chaînes de caractères larges sont wcsspn et wcscspn ; ils sont utilisés de la même manière.

Copier des chaînes

Les affectations de pointeur ne copient pas les chaînes

Vous pouvez utiliser le = opérateur pour copier des entiers, mais vous ne pouvez pas utiliser le = opérateur de copier des chaînes en C. Les chaînes en C sont représentés sous forme de tableaux de caractères avec un nul caractère de fin, donc en utilisant le = opérateur enregistre uniquement l'adresse ( pointeur) d'une chaîne.

#include <stdio.h>

int main(void) {
    int a = 10, b;
    char c[] = "abc", *d;

    b = a; /* Integer is copied */
    a = 20; /* Modifying a leaves b unchanged - b is a 'deep copy' of a */
    printf("%d %d\n", a, b); /* "20 10" will be printed */

    d = c; 
    /* Only copies the address of the string - 
    there is still only one string stored in memory */
    
    c[1] = 'x';
    /* Modifies the original string - d[1] = 'x' will do exactly the same thing */

    printf("%s %s\n", c, d); /* "axc axc" will be printed */

    return 0;
}

L'exemple ci-dessus a été compilé car nous avons utilisé char *d plutôt que char d[3] . L'utilisation de ce dernier provoquerait une erreur de compilation. Vous ne pouvez pas affecter de tableaux en C.

#include <stdio.h>

int main(void) {
    char a[] = "abc";
    char b[8];

    b = a; /* compile error */
    printf("%s\n", b);

    return 0;
}

Copie de chaînes à l'aide de fonctions standard

strcpy()

Pour copier des chaînes, la fonction strcpy() est disponible dans string.h . Un espace suffisant doit être attribué à la destination avant la copie.

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

int main(void) {
    char a[] = "abc";
    char b[8];

    strcpy(b, a); /* think "b special equals a" */
    printf("%s\n", b); /* "abc" will be printed */

    return 0;
}
C99

snprintf()

Pour éviter le dépassement de la mémoire tampon, snprintf() peut être utilisé. Ce n’est pas la meilleure solution en termes de performances car elle doit analyser la chaîne de template, mais c’est la seule fonction de limitation de tampon pour la copie de chaînes facilement disponibles dans la bibliothèque standard, qui peut être utilisée sans étapes supplémentaires.

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

int main(void) {
    char a[] = "012345678901234567890";
    char b[8];

#if 0
    strcpy(b, a); /* causes buffer overrun (undefined behavior), so do not execute this here! */
#endif

    snprintf(b, sizeof(b), "%s", a); /* does not cause buffer overrun */
    printf("%s\n", b); /* "0123456" will be printed */

    return 0;
}

strncat()

Une deuxième option, avec de meilleures performances, consiste à utiliser strncat() (une version de vérification de dépassement de tampon de strcat() ) - elle prend un troisième argument indiquant le nombre maximal d’octets à copier:

char dest[32];

dest[0] = '\0';
strncat(dest, source, sizeof(dest) - 1);
    /* copies up to the first (sizeof(dest) - 1) elements of source into dest,
    then puts a \0 on the end of dest */

Notez que cette formulation utilise sizeof(dest) - 1 ; Ceci est crucial car strncat() ajoute toujours un octet nul (bon), mais ne le compte pas dans la taille de la chaîne (cause de confusion et écrasement du tampon).

Notez également que l'alternative - concaténer après une chaîne non vide - est encore plus compliquée. Considérer:

char dst[24] = "Clownfish: ";
char src[] = "Marvin and Nemo";
size_t len = strlen(dst);

strncat(dst, src, sizeof(dst) - len - 1);
printf("%zu: [%s]\n", strlen(dst), dst);

La sortie est la suivante:

23: [Clownfish: Marvin and N]

Notez, cependant, que la taille spécifiée comme la longueur n'était pas la taille du tableau de destination, mais la quantité d'espace qui lui restait, sans compter l'octet null du terminal. Cela peut causer de gros problèmes de réécriture. C'est aussi un peu de gaspillage; Pour spécifier correctement l'argument de longueur, vous connaissez la longueur des données dans la destination. Vous pouvez donc spécifier l'adresse de l'octet nul à la fin du contenu existant, en évitant que strncat() :

    strcpy(dst, "Clownfish: ");
    assert(len < sizeof(dst) - 1);
    strncat(dst + len, src, sizeof(dst) - len - 1);
    printf("%zu: [%s]\n", strlen(dst), dst);

Cela produit le même résultat que précédemment, mais strncat() n'a pas besoin de strncat() le contenu existant de dst avant de commencer à copier.

strncpy()

La dernière option est la fonction strncpy() . Bien que vous puissiez penser que cela devrait venir en premier, c’est une fonction plutôt trompeuse qui comporte deux pièges principaux:

  1. Si la copie via strncpy() frappe la limite du tampon, un caractère nul strncpy() ne sera pas écrit.
  2. strncpy() remplit toujours complètement la destination, avec des octets nuls si nécessaire.

(Cette implémentation bizarre est historique et était initialement destinée à gérer les noms de fichiers UNIX )

La seule façon correcte de l'utiliser est d'assurer manuellement la null-terminaison:

strncpy(b, a, sizeof(b)); /* the third parameter is destination buffer size */
b[sizeof(b)/sizeof(*b) - 1] = '\0'; /* terminate the string */
printf("%s\n", b); /* "0123456" will be printed */

Même alors, si vous avez un gros tampon, il devient très inefficace d'utiliser strncpy() raison du remplissage null supplémentaire.

Convertir les chaînes en nombre: atoi (), atof () (dangereux, ne les utilisez pas)

Avertissement: Les fonctions atoi , atol , atoll et atof sont intrinsèquement dangereuses, car: Si la valeur du résultat ne peut pas être représentée, le comportement est indéfini. (7.20.1p1)

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

int main(int argc, char** argv)
{
    int val;
    if (argc < 2)
    {
        printf("Usage: %s <integer>\n", argv[0]);
        return 0;
    }

    val = atoi(argv[1]);

    printf("String value = %s, Int value = %d\n", argv[1], val);

    return 0;
}

Lorsque la chaîne à convertir est un entier décimal valide compris dans la plage, la fonction fonctionne:

$ ./atoi 100
String value = 100, Int value = 100
$ ./atoi 200
String value = 200, Int value = 200

Pour les chaînes qui commencent par un nombre suivi de quelque chose d'autre, seul le nombre initial est analysé:

$ ./atoi 0x200
0
$ ./atoi 0123x300
123

Dans tous les autres cas, le comportement est indéfini:

$ ./atoi hello
Formatting the hard disk...

En raison des ambiguïtés ci-dessus et de ce comportement indéfini, la famille de fonctions atoi ne devrait jamais être utilisée.

  • Pour convertir en long int , utilisez strtol() au lieu de atol() .
  • Pour convertir en double , utilisez strtod() au lieu de atof() .
C99
  • Pour convertir en long long int , utilisez strtoll() au lieu de atoll() .

données formatées en chaîne lecture / écriture

Écrire des données formatées en chaîne

int sprintf ( char * str, const char * format, ... );

Utilisez la fonction sprintf pour écrire des données flottantes dans une chaîne.

#include <stdio.h>
int main ()
{
  char buffer [50];
  double PI = 3.1415926;
  sprintf (buffer, "PI = %.7f", PI);
  printf ("%s\n",buffer);
  return 0;
}

Lire des données formatées à partir d'une chaîne

int sscanf ( const char * s, const char * format, ...);

Utilisez la fonction sscanf pour analyser les données formatées.

#include <stdio.h>
int main ()
{
  char sentence []="date : 06-06-2012";
  char str [50];
  int year;
  int month;
  int day;
  sscanf (sentence,"%s : %2d-%2d-%4d", str, &day, &month, &year);
  printf ("%s -> %02d-%02d-%4d\n",str, day, month, year);
  return 0;
}

Convertir en toute sécurité des chaînes en nombre: fonctions strtoX

C99

Depuis C99, la bibliothèque C possède un ensemble de fonctions de conversion sécurisées qui interprètent une chaîne sous forme de nombre. Leurs noms sont de la forme strtoX , où X est l'un de l , ul , d , etc. pour déterminer le type de cible de la conversion

double strtod(char const* p, char** endptr);
long double strtold(char const* p, char** endptr);

Ils vérifient qu'une conversion a un débordement ou un débordement:

double ret = strtod(argv[1], 0); /* attempt conversion */

/* check the conversion result. */
if ((ret == HUGE_VAL || ret == -HUGE_VAL) && errno == ERANGE) 
    return;  /* numeric overflow in in string */
else if (ret == HUGE_VAL && errno == ERANGE) 
    return; /* numeric underflow in in string */

/* At this point we know that everything went fine so ret may be used */

Si la chaîne ne contient aucun numéro, cette utilisation de strtod renvoie 0.0 .

Si cela n'est pas satisfaisant, le paramètre supplémentaire endptr peut être utilisé. C'est un pointeur sur le pointeur qui sera dirigé vers la fin du nombre détecté dans la chaîne. S'il est défini sur 0 , comme ci-dessus ou NULL , il est simplement ignoré.

Ce paramètre endptr indique s'il y a eu une conversion réussie et, le cas échéant, où le numéro s'est terminé:

char *check = 0;
double ret = strtod(argv[1], &check); /* attempt conversion */

/* check the conversion result. */
if (argv[1] == check) 
    return; /* No number was detected in string */
else if ((ret == HUGE_VAL || ret == -HUGE_VAL) && errno == ERANGE) 
    return; /* numeric overflow in in string */
else if (ret == HUGE_VAL && errno == ERANGE) 
    return; /* numeric underflow in in string */

/* At this point we know that everything went fine so ret may be used */

Il existe des fonctions analogues à convertir en types entiers plus larges:

long strtol(char const* p, char** endptr, int nbase);
long long strtoll(char const* p, char** endptr, int nbase);
unsigned long strtoul(char const* p, char** endptr, int nbase);
unsigned long long strtoull(char const* p, char** endptr, int nbase);

Ces fonctions ont un troisième paramètre nbase la base numérique dans laquelle le numéro est écrit.

long a = strtol("101",   0, 2 ); /* a = 5L */
long b = strtol("101",   0, 8 ); /* b = 65L */
long c = strtol("101",   0, 10); /* c = 101L */
long d = strtol("101",   0, 16); /* d = 257L */
long e = strtol("101",   0, 0 ); /* e = 101L */
long f = strtol("0101",  0, 0 ); /* f = 65L */
long g = strtol("0x101", 0, 0 ); /* g = 257L */

La valeur spéciale 0 pour nbase signifie que la chaîne est interprétée de la même manière que les littéraux numériques sont interprétés dans un programme C: un préfixe 0x correspond à une représentation hexadécimale, sinon un 0 est octal et tous les autres nombres sont décimaux.

Ainsi, le moyen le plus pratique d’interpréter un argument de ligne de commande comme un nombre serait

int main(int argc, char* argv[] {
    if (argc < 1)
        return EXIT_FAILURE; /* No number given. */

    /* use strtoull because size_t may be wide */
    size_t mySize = strtoull(argv[1], 0, 0);

    /* then check conversion results. */

     ...

    return EXIT_SUCCESS;
}

Cela signifie que le programme peut être appelé avec un paramètre en octal, décimal ou hexadécimal.



Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow