Comment créer un tableau de chaînes en C?


263

J'essaie de créer un tableau de chaînes en C. Si j'utilise ce code:

char (*a[2])[14];
a[0]="blah";
a[1]="hmm";

gcc me donne "avertissement: affectation d'un type de pointeur incompatible". Quelle est la bonne façon de procéder?

edit: Je suis curieux de savoir pourquoi cela devrait donner un avertissement au compilateur car si je le fais printf(a[1]);, il affiche correctement "hmm".

c  arrays  string 

12
Juste pour mémoire, char (*a[2])[14]est un tableau de deux pointeurs vers un tableau de 14 caractères.
avakar

4
Je pensais que c'était quatorze pointeurs vers des tableaux de deux caractères xD
fortran

74
Le conseil le plus utile que j'ai jamais lu pour déchiffrer les types C: "Commencez par le nom, lisez à droite quand vous le pouvez, à gauche quand vous devez": char (*a[2])[14]- commencez à a, déplacez à droite: "tableau de deux", déplacez à gauche: "pointeur vers", crochet complet donc lisez à droite: "tableau de forteen", lisez à gauche: "char" ... Mettez-le ensemble et nous avons "un est un tableau de deux pointeurs vers des tableaux de forteen chars"
Mark K Cowan

4
@dotancohen: Cette astuce est ce qui m'a finalement convaincu d'écrire des pointeurs char *strplutôt que char* str. Issu d'un parcours Delphi / Pascal, j'étais très habitué à cette dernière voie jusqu'à ce que je tombe sur des types plus complexes. L'ancienne méthode me semble toujours laide, mais rend la notation de type plus cohérente (IMO).
Mark K Cowan

@MarkKCowan, incroyable! Merci! :)
Dr.Essen

Réponses:


232

Si vous ne voulez pas changer les chaînes, vous pouvez simplement faire

const char *a[2];
a[0] = "blah";
a[1] = "hmm";

Lorsque vous le faites comme ceci, vous allouerez un tableau de deux pointeurs à const char. Ces pointeurs seront ensuite définis sur les adresses des chaînes statiques "blah"et "hmm".

Si vous voulez pouvoir changer le contenu réel de la chaîne, vous devez faire quelque chose comme

char a[2][14];
strcpy(a[0], "blah");
strcpy(a[1], "hmm");

Cela allouera deux tableaux consécutifs de 14 chars chacun, après quoi le contenu des chaînes statiques y sera copié.


185

Il y a plusieurs façons de créer un tableau de chaînes en C. Si toutes les chaînes vont être de la même longueur (ou au moins avoir la même longueur maximale), vous déclarez simplement un tableau 2D de caractères et attribuez-le si nécessaire:

char strs[NUMBER_OF_STRINGS][STRING_LENGTH+1];
...
strcpy(strs[0], aString); // where aString is either an array or pointer to char
strcpy(strs[1], "foo");

Vous pouvez également ajouter une liste d'initialiseurs:

char strs[NUMBER_OF_STRINGS][STRING_LENGTH+1] = {"foo", "bar", "bletch", ...};

Cela suppose que la taille et le nombre de chaînes dans l'initialiseur correspondent aux dimensions de votre tableau. Dans ce cas, le contenu de chaque chaîne littérale (qui est elle-même un tableau de caractères terminé par zéro) est copié dans la mémoire allouée aux chaînes. Le problème avec cette approche est la possibilité de fragmentation interne; si vous avez 99 chaînes de 5 caractères ou moins, mais 1 chaîne de 20 caractères, 99 chaînes auront au moins 15 caractères inutilisés; c'est une perte d'espace.

Au lieu d'utiliser un tableau de caractères 2D, vous pouvez stocker un tableau de pointeurs 1D vers char:

char *strs[NUMBER_OF_STRINGS];

Notez que dans ce cas, vous n'avez alloué que de la mémoire pour contenir les pointeurs vers les chaînes; la mémoire des chaînes elles-mêmes doit être allouée ailleurs (soit sous forme de tableaux statiques, soit en utilisant malloc()ou calloc()). Vous pouvez utiliser la liste d'initialisation comme dans l'exemple précédent:

char *strs[NUMBER_OF_STRINGS] = {"foo", "bar", "bletch", ...};

Au lieu de copier le contenu des constantes de chaîne, vous stockez simplement les pointeurs vers elles. Notez que les constantes de chaîne peuvent ne pas être accessibles en écriture; vous pouvez réaffecter le pointeur, comme ceci:

strs[i] = "bar";
strs[i] = "foo"; 

Mais vous ne pourrez peut-être pas modifier le contenu de la chaîne; c'est à dire,

strs[i] = "bar";
strcpy(strs[i], "foo");

peut ne pas être autorisé.

Vous pouvez utiliser malloc()pour allouer dynamiquement le tampon pour chaque chaîne et copier dans ce tampon:

strs[i] = malloc(strlen("foo") + 1);
strcpy(strs[i], "foo");

BTW,

char (*a[2])[14];

Déclare a comme un tableau de 2 éléments de pointeurs vers des tableaux de 14 éléments de char.


3
@Slater: oui, si c'est le résultat d'un mallocappel.
John Bode

Merci pour cette réponse très détaillée. Cela m'a vraiment aidé.
cokedude

1
Pourquoi ne pouvons-nous utiliser strcpy que sur des tableaux String déclarés comme tableau 2D. Pourquoi l'affectation standard échoue-t-elle?
Andrew S

4
@AndrewS: La réponse complète ne rentre pas dans un commentaire, mais en gros c'est un artefact de la façon dont C traite les expressions du tableau; dans la plupart des cas, une expression de type T [N]est convertie en une expression de type T *et la valeur de l'expression est l'adresse du premier élément. Donc, si vous écriviez str = "foo", vous essayez d'attribuer l'adresse du premier caractère de "foo"au tableau str, ce qui ne fonctionne pas. Voir cette réponse pour plus de détails.
John Bode

@JohnBode pourriez-vous s'il vous plaît ajouter le petit ajustement? char *strs[NUMBER_OF_STRINGS] = {0}; Cela permet d'éviter de futurs problèmes en initialisant strsà NULL. Beaucoup de gens lisent ce post lorsque Google recherche sur un tableau de chaînes en C.
cokedude

94

Ack! Chaînes constantes:

const char *strings[] = {"one","two","three"};

Si je me souviens bien.

Oh, et vous voulez utiliser strcpy pour l'affectation, pas l'opérateur =. strcpy_s est plus sûr, mais ce n'est ni dans les normes C89 ni dans les normes C99.

char arr[MAX_NUMBER_STRINGS][MAX_STRING_SIZE]; 
strcpy(arr[0], "blah");

Mise à jour: Thomas dit que strlcpyc'est la voie à suivre.


Est-ce C99? Je ne pense pas que ce soit possible dans ANSI C.
Noldorin

6
C'est possible en C89 et C99. Peu importe que ce soit avec ou sans const, bien que le premier soit préféré.
avakar

1
Eh bien, const est nouveau, et vous deviez auparavant spécifier la taille du tableau externe (3 dans ce cas), mais sinon c'est parfaitement acceptable K&R C.J'ai un vieux livre C protégé par copyright 1984 qui a une section montrant comment fais ça. Ils appellent cela un "tableau en lambeaux". Bien sûr, il n'avait pas d '"opérateurs", et strcpy_s est un nouveau sur moi.
TED

6
strcpy_s est une fonction Microsoft. Il devrait probablement être évité car il n'est pas dans la norme C.
Cromulent

5
strcpy_s et d'autres "fonctions sûres" sont normalisés ISO / IEC TR 24731 (c'est une norme publiée ISO et en tant que telle n'est pas disponible en ligne gratuitement; le projet le plus récent est open-std.org/jtc1/sc22/wg14/www /docs/n1225.pdf )
Pavel Minaev

14

Voici quelques-unes de vos options:

char a1[][14] = { "blah", "hmm" };
char* a2[] = { "blah", "hmm" };
char (*a3[])[] = { &"blah", &"hmm" };  // only since you brought up the syntax -

printf(a1[0]); // prints blah
printf(a2[0]); // prints blah
printf(*a3[0]); // prints blah

L'avantage a2est que vous pouvez ensuite effectuer les opérations suivantes avec des littéraux de chaîne

a2[0] = "hmm";
a2[1] = "blah";

Et pour a3vous, procédez comme suit:

a3[0] = &"hmm";
a3[1] = &"blah";

Car a1vous devrez utiliser strcpy()(mieux encore strncpy()) même lorsque vous affectez des littéraux de chaîne. La raison en est que a2, et a3sont des tableaux de pointeurs et vous pouvez faire en sorte que leurs éléments (c'est-à-dire des pointeurs) pointent vers n'importe quel stockage, alors qu'il a1s'agit d'un tableau de «tableau de caractères» et donc chaque élément est un tableau qui «possède» son propre stockage ( ce qui signifie qu'il est détruit lorsqu'il sort du champ d'application) - vous ne pouvez copier que des éléments dans son stockage.

Cela nous amène également à l'inconvénient d'utiliser a2et a3- puisqu'ils pointent vers un stockage statique (où les littéraux de chaîne sont stockés) dont le contenu ne peut pas être modifié de manière fiable (à savoir un comportement non défini), si vous souhaitez affecter des littéraux non-chaîne au les éléments de a2ou a3- vous devrez d'abord allouer dynamiquement suffisamment de mémoire, puis leurs éléments pointer vers cette mémoire, puis copier les caractères dedans - et ensuite vous devez vous assurer de désallouer la mémoire lorsque vous avez terminé.

Bah - C ++ me manque déjà;)

ps Faites-moi savoir si vous avez besoin d'exemples.


J'avais besoin de tableaux de chaînes pour un projet Arduino. À la fin, j'ai utilisé le style a2. J'ai d'abord essayé le style a1 définissant mon tableau de chaînes comme char a1 [] [2] = {"F3", "G3" ... etc. } car il était destiné à stocker des chaînes longues de 2 caractères. Cela a donné une sortie inattendue car j'ai oublié que le terminateur nul signifierait que chaque chaîne devrait avoir une taille d'au moins 3 pour stocker les 2 caractères. En utilisant le style a2, je n'avais pas besoin de spécifier la longueur de la chaîne, et elle pouvait également s'adapter à différentes longueurs de chaîne, j'ai donc décidé de m'en tenir à cela :-)
Jeromy Adofo

char (* a3 []) [] = {& "blah", & "hmm"}; => ne fonctionne pas dans g ++ Apple LLVM version 9.1.0, mais il fonctionne dans gcc
1234

12

Ou vous pouvez déclarer un type de structure, qui contient un tableau de caractères (1 chaîne), les créer un tableau des structures et donc un tableau multi-éléments

typedef struct name
{
   char name[100]; // 100 character array
}name;

main()
{
   name yourString[10]; // 10 strings
   printf("Enter something\n:);
   scanf("%s",yourString[0].name);
   scanf("%s",yourString[1].name);
   // maybe put a for loop and a few print ststements to simplify code
   // this is just for example 
 }

L'un des avantages de cette méthode par rapport à toute autre méthode est qu'elle vous permet de numériser directement dans la chaîne sans avoir à l'utiliser strcpy;


10

Dans ANSI C:

char* strings[3];
strings[0] = "foo";
strings[1] = "bar";
strings[2] = "baz";

8
@Zifre: Je suis totalement en désaccord. Il fait partie du type - un "pointeur char" dans ce cas. Que diriez-vous de toute façon ... ça fait partie du nom de la variable? J'ai vu beaucoup de programmeurs compétents utiliser ce style.
Noldorin

14
Pour ceux qui lisent ceci, je voudrais souligner que Bjarne Stroustrup met le * par le type ...
MirroredFate

1
@MirroredFate: correct. En effet, c'est une pratique recommandée en C ++ d'après ce que je sais. Sémantiquement, cela n'a aucun sens pour moi de le mettre par l'identifiant, en raison de la façon dont il est utilisé. : /
Noldorin

16
@Noldorin char* foo, bar;quel est le type de bar?
mASOUD

10
C a été développé par Dennis Ritchie en 1972 et en 1988, lui et Brian Kernighan ont publié la deuxième édition de K&R - The C Programming Language, un livre que beaucoup considèrent comme la norme de facto pour C.Ils mettent le * par l'identifiant.
Marius Lian

10

Si les chaînes sont statiques, vous feriez mieux de:

const char *my_array[] = {"eenie","meenie","miney"};

Bien qu'il ne fasse pas partie de l'ANSI C de base, il est probable que votre environnement prend en charge la syntaxe. Ces chaînes sont immuables (en lecture seule) et, par conséquent, dans de nombreux environnements, utilisent moins de surcharge que la construction dynamique d'un tableau de chaînes.

Par exemple, dans les petits projets de micro-contrôleur, cette syntaxe utilise la mémoire programme plutôt que (généralement) une mémoire RAM plus précieuse. AVR-C est un exemple d'environnement prenant en charge cette syntaxe, mais la plupart des autres également.


10

Si vous ne voulez pas garder une trace du nombre de chaînes dans le tableau et que vous souhaitez les parcourir, ajoutez simplement une chaîne NULL à la fin:

char *strings[]={ "one", "two", "three", NULL };

int i=0;
while(strings[i]) {
  printf("%s\n", strings[i]);
  //do something
  i++;
};

Je crois que cela n'est valable qu'en C ++. En C, NULL n'est pas garanti d'être nul, donc la boucle peut ne pas se casser quand elle le devrait. Corrigez-moi si je me trompe.
Palec

2
Aucune idée :) Vous pouvez comparer avec NULL dans l'instruction while si vous le souhaitez.
Sergey

9

Les littéraux de chaîne sont const char *s.

Et votre utilisation de parenthèses est étrange. Vous voulez probablement dire

const char *a[2] = {"blah", "hmm"};

qui déclare un tableau de deux pointeurs sur des caractères constants et les initialise pour pointer sur deux constantes de chaîne codées en dur.


3

Votre code crée un tableau de pointeurs de fonction. Essayer

char* a[size];

ou

char a[size1][size2];

au lieu.

Voir les wikibooks des tableaux et des pointeurs


1
chapeau pour votre approche différente ... Des gens comme vous font empiler la pile pour déborder ...
Sahu V Kumar

1

bonjour vous pouvez essayer ce soufflet:

 char arr[nb_of_string][max_string_length]; 
 strcpy(arr[0], "word");

un bel exemple d'utilisation, tableau de chaînes en c si vous le souhaitez

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


int main(int argc, char *argv[]){

int i, j, k;

// to set you array
//const arr[nb_of_string][max_string_length]
char array[3][100];

char temp[100];
char word[100];

for (i = 0; i < 3; i++){
    printf("type word %d : ",i+1);
    scanf("%s", word);
    strcpy(array[i], word);
}

for (k=0; k<3-1; k++){
    for (i=0; i<3-1; i++)
    {
        for (j=0; j<strlen(array[i]); j++)
        {
            // if a letter ascii code is bigger we swap values
            if (array[i][j] > array[i+1][j])
            {
                strcpy(temp, array[i+1]);
                strcpy(array[i+1], array[i]);
                strcpy(array[i], temp);

                j = 999;
            }

            // if a letter ascii code is smaller we stop
            if (array[i][j] < array[i+1][j])
            {
                    j = 999;
            }

        }
    }
}

for (i=0; i<3; i++)
{
    printf("%s\n",array[i]);
}

return 0;
}

0
char name[10][10]
int i,j,n;//here "n" is number of enteries
printf("\nEnter size of array = ");
scanf("%d",&n);
for(i=0;i<n;i++)
{
    for(j=0;j<1;j++)
    {
        printf("\nEnter name = ");
        scanf("%s",&name[i]);
    }
}
//printing the data
for(i=0;i<n;i++)
{
    for(j=0;j<1;j++)
    {
        printf("%d\t|\t%s\t|\t%s",rollno[i][j],name[i],sex[i]);
    }
    printf("\n");
}

Ici, essayez ceci !!!


1
pouvez-vous expliquer pourquoi avez-vous besoin de la boucle for avec la variable j, c'est-à-dire pour (j = 0; j <1; j ++)?
SouvikMaji

0

Il me manquait en quelque sorte un tableau de chaînes plus dynamique, où la quantité de chaînes pouvait varier en fonction de la sélection au moment de l'exécution, mais sinon les chaînes devraient être corrigées.

J'ai fini par coder un extrait de code comme ceci:

#define INIT_STRING_ARRAY(...)          \
    {                                   \
        char* args[] = __VA_ARGS__;     \
        ev = args;                      \
        count = _countof(args);         \
    }

void InitEnumIfAny(String& key, CMFCPropertyGridProperty* item)
{
    USES_CONVERSION;
    char** ev = nullptr;
    int count = 0;

    if( key.Compare("horizontal_alignment") )
        INIT_STRING_ARRAY( { "top", "bottom" } )

    if (key.Compare("boolean"))
        INIT_STRING_ARRAY( { "yes", "no" } )

    if( ev == nullptr )
        return;

    for( int i = 0; i < count; i++)
        item->AddOption(A2T(ev[i]));

    item->AllowEdit(FALSE);
}

char** evrécupère le pointeur sur les chaînes du tableau, et count récupère la quantité de chaînes à l'aide de la _countoffonction. (Similaire à sizeof(arr) / sizeof(arr[0])).

Et il y a une conversion Ansi en Unicode supplémentaire en utilisant la A2Tmacro, mais cela peut être facultatif pour votre cas.


-6

Un bon moyen est de définir vous-même une chaîne.

#include <stdio.h>
typedef char string[]
int main() {
    string test = "string";
    return 0;
}

C'est vraiment aussi simple que cela.


4
Il vous manque un ;, et comment cela crée-t-il un tableau de chaînes ?
keyser
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.