如何在C语言中创建一个字符串数组?

340

我正在尝试在C语言中创建字符串数组。如果我使用以下代码:

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

gcc 给我报错 "warning: assignment from incompatible pointer type"。请问正确的做法是什么?

编辑:我很好奇为什么这会引起编译警告,因为如果我执行 printf(a[1]);,它会正确地打印出"hmm"。


14
只是为了记录,char (*a[2])[14] 是一个包含两个指向包含14个字符的数组的指针数组。 - avakar
5
我认为这是指有十四个指向两个字符数组的指针 xD。 - fortran
124
我读过的用于解密C类型的最有用的建议是:“从名称开始,向右读取,如果必须则向左读取”:char (*a[2])[14] - 从a开始,向右移动:“两个元素的数组”,向左移动:“指向”的,括号结束了,所以继续向右读取:“长度为14的数组”,向左读取:“char”... 把它们组合起来,我们可以得出“a是一个指向两个长度为14的数组的指针数组,这些数组中每个元素都是char类型”。 - Mark K Cowan
5
那个提示最终使我决定将指针写成char *str而不是char* str。我来自Delphi/Pascal背景,习惯于后一种方式,直到我遇到了更复杂的类型。前一种方式仍然看起来很丑陋,但可以使类型标注更加一致(在我看来)。 - Mark K Cowan
15个回答

295

如果您不想更改字符串,那么可以简单地执行以下操作

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

如果您按此方式操作,您将分配一个包含两个指向const char的指针的数组。然后,这些指针将被设置为静态字符串"blah""hmm"的地址。

如果您确实想要更改实际的字符串内容,则必须执行类似以下的操作:

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

这将分配两个连续的每个包含14个 char 的数组,然后将静态字符串的内容复制到它们中。


243

C语言中有几种创建字符串数组的方法。如果所有字符串的长度都相同(或者至少具有相同的最大长度),您可以简单地声明一个char类型的二维数组,并根据需要进行赋值:

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");

你也可以添加一个初始化器列表:

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

假设在初始化程序中,字符串大小和数量与您的数组维数匹配。在这种情况下,将每个字符串文字(它本身是一个以零结尾的char数组)的内容复制到分配给strs的内存中。此方法的问题是可能存在内部碎片; 如果您有99个字符串长度为5个字符或更少,但有1个字符串长度为20个字符,那么这99个字符串将至少有15个未使用的字符;这是一种浪费空间的方式。

相反,您可以使用一个指向char的1-d数组来存储而非char的2-d数组:

char *strs[NUMBER_OF_STRINGS];

请注意,在这种情况下,你仅仅分配了指向字符串的指针的内存空间;字符串本身的内存空间必须在其他地方分配(可以是静态数组或使用malloc()calloc()函数)。你可以像之前的示例一样使用初始化列表:

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

你并没有复制字符串常量的内容,而是简单地存储了指向它们的指针。请注意,字符串常量可能是不可写的;你可以像这样重新分配指针:

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

但是您可能无法更改字符串的内容;即

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

可能会被禁止。

您可以使用 malloc() 动态为每个字符串分配缓冲区,并将其复制到该缓冲区中:

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

顺便说一句,

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

声明a为一个指向14元素字符数组的2个元素指针的数组。


3
@Slater:是的,如果它是malloc调用的结果。 - John Bode
1
为什么我们只能在声明为2D数组的字符串数组上使用strcpy?为什么标准赋值会失败? - Andrew S
5
基本上,这是由于C语言对数组表达式的处理方式造成的结果;在大多数情况下,类型为T [N]的表达式将被转换为类型为T *的表达式,并且表达式的值是第一个元素的地址。因此,如果您编写了str = "foo",您将尝试将字符串"foo"的第一个字符的地址分配给数组str,但这是行不通的。有关详细信息,请参见this answer - John Bode
@JohnBode,您能否添加一点小调整吗?char *strs[NUMBER_OF_STRINGS] = {0}; 这可以通过将strs初始化为NULL来帮助预防未来的问题。很多人在搜索C语言中的字符串数组时会阅读这篇文章。 - cokedude
1
为了更清晰,char strs[NUMBER_OF_STRINGS][STRING_LENGTH+1] = {"foo", "bar", "bletch", ...}; 可以改为 char strs[NUMBER_OF_STRINGS][MAX_STRING_LENGTH+1] = {"foo", "bar", "bletch", ...}; - 71GA

120

哎呀!常量字符串:

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

如果我没记错的话。
哦,而且你想要使用strcpy来进行赋值,而不是使用=运算符。strcpy_s更安全,但它既不在C89标准中,也不在C99标准中。
char arr[MAX_NUMBER_STRINGS][MAX_STRING_SIZE]; 
strcpy(arr[0], "blah");

更新:Thomas表示strlcpy是正确的方式。

[不可移植]


6
这个在C89和C99都是可能的。无论是否带有const,都不会有影响,尽管前者更受欢迎。 - avakar
1
const是新的,以前你必须指定外部数组的大小(在这种情况下为3),但除此之外,这是完全可以接受的K&R C。我有一本1984年版权的旧C书,其中有一节展示如何做到这一点。他们称其为“不规则数组”。当然,它没有“运算符”,而strcpy_s对我来说是新的。 - T.E.D.
7
strcpy_s是一种Microsoft函数。由于它不符合标准C,建议避免使用。 - Cromulent
5
strcpy_s和其他“安全函数”已标准化为ISO/IEC TR 24731(这是一个ISO发布的标准,因此不能免费在线获取;最新的草案可以在http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1225.pdf找到)。 - Pavel Minaev
1
实际上,复制以零结尾的 C 字符串的正确方法是使用 strlcpy,除非您百分之百确定不需要它。 - Thomas Tempelmann
显示剩余11条评论

20

以下是您的一些选择:

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

a2 的优势在于您可以对字符串字面量执行以下操作

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

对于a3,您可以执行以下操作:

a3[0] = &"hmm";
a3[1] = &"blah";
对于变量a1,即使在赋值字符串常量时,你也必须使用strcpy()(最好是strncpy())。原因是a2a3是指针数组,你可以使它们的元素(即指针)指向任何存储空间,而a1是一个“字符数组的数组”,所以每个元素都是一个拥有自己存储空间的数组(这意味着当它超出范围时会被销毁),你只能将内容复制到它的存储空间中。
这也带来了使用a2a3的缺点 - 因为它们指向静态存储区域(即字符串常量存储的位置),其内容不能可靠地更改(即未定义的行为),如果你想要将非字符串常量分配给a2a3的元素,则必须首先动态分配足够的内存,然后将它们的元素指向该内存,并将字符复制到其中-完成后,你必须确保释放内存。 p.s. 如果需要示例,请告诉我。

我需要在Arduino项目中使用字符串数组。最终我使用了a2风格。一开始我尝试了a1风格,将我的字符串数组定义为char a1[][2] = { "F3", "G3" ...etc. },因为它旨在存储长度为2的字符串。这导致输出结果出乎意料,因为我忘记了空终止符会使每个字符串的大小至少为3才能存储2个字符。使用a2风格,我不需要指定字符串的长度,它也可以容纳不同长度的字符串,所以我决定坚持使用它 :-) - Jeromy Adofo
char (*a3[])[] = { &"blah", &"hmm" }; => 在 g++ Apple LLVM version 9.1.0 中无法工作,但在 gcc 中可以。 - 1234

15

如果您不想跟踪数组中字符串的数量并希望对它们进行迭代,只需在末尾添加NULL字符串:

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

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

我相信这只在C++中有效。在C中,NULL不能保证为零,因此循环可能不会在应该停止时中断。如果我错了,请纠正我。 - Palec
3
如果你愿意,可以在while语句中将其与NULL进行比较。 - Sergey

14

或者你可以声明一个结构体类型,其中包含一个字符数组(1个字符串),然后创建一个结构体数组,从而创建一个多元素数组。

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 
 }

相对于其他任何方法,这种方法的优点之一是它允许您直接扫描到字符串中而无需使用 strcpy


12

如果这些字符串是静态的,最好使用:

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

虽然不是ANSI C的基本部分,但很可能您的环境支持这种语法。 这些字符串是不可变的(只读),因此在许多环境中,它们使用的开销比动态构建字符串数组要少。

例如,在小型微控制器项目中,这种语法使用程序存储器而不是(通常更宝贵的)RAM存储器。 AVR-C是支持此语法的示例环境,但大多数其他环境也是如此。


11

在 ANSI C 中:

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

8
@Zifre: 我完全不同意。这在类型上非常重要- 在这种情况下是一个“char指针”。你会怎么说呢?它是变量名的一部分吗?我见过许多有能力的程序员使用这种风格。 - Noldorin
14
为了任何阅读此内容的其他人,我想指出Bjarne Stroustrup将*放在类型旁边... - MirroredFate
1
@MirroredFate:正确。的确,根据我的了解,在C++中这是推荐的做法。从语义上讲,将其放置在标识符旁对我来说没有意义,因为它的使用方式如此。 :/ - Noldorin
16
@Noldorin char* foo, bar; 中的 bar 是什么类型? - mASOUD
10
C语言是由Dennis Ritchie于1972年开发的,1988年他与Brian Kernighan共同出版了《C程序设计语言》第二版,很多人认为这本书是C语言事实上的标准。他们在标识符前添加了*号。 - Marius Lian
显示剩余7条评论

9
字符串字面量是const char *类型。
你使用括号的方式有点奇怪。你可能想表达的是:
const char *a[2] = {"blah", "hmm"};

这个代码声明了一个包含两个指向常量字符的指针数组,并将它们初始化为指向两个硬编码的字符串常量。


3

您的代码正在创建一个函数指针数组。尝试使用以下方法:

char* a[size];

或者

char a[size1][size2];

相反,查看wikibooks以了解数组指针


1
佩服你不同的方法...像你这样的人让堆栈溢出... - Vishal Kumar Sahu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接