将字符串分配给字符数组

97
我对以下内容感到有些惊讶。
例子1:
char s[100] = "abcd"; // declare and initialize - WORKS

示例2:

char s[100]; // declare
s = "hello"; // initalize - DOESN'T WORK ('lvalue required' error)

我想知道为什么第二种方法不起作用。它看起来应该是自然的(对于其他数据类型也有效),有人能解释一下背后的逻辑吗?

10个回答

90

在初始化数组时,C允许您使用值来填充数组。所以

char s[100] = "abcd";

基本上相当于

int s[3] = { 1, 2, 3 };

但是它不允许你进行赋值操作,因为s是一个数组而不是自由指针。

s = "abcd" 

将指向abcd的指针值赋给s,但您不能更改s,因为那么就没有任何东西指向该数组。
如果s是可以指向任何内容的指针char*,则可以这样做并且会生效。

如果您想要复制字符串,请使用strcpy


9
好的回答,除非是必须使用,否则不要再使用普通的strcpy。可以使用strncpy或者strlcpy代替。 - dwc
4
“s应该是const char,而不是char。” - aib
1
如果想在初始化后逐个替换字符串字符,则s[0] = 'x'; s[1] = 'y'; s[2] = 'z'; s[3] = 'm';是可行的。 - RBT
2
@dwc,strcpy()没有问题,只要确保你的缓冲区足够长以容纳输入字符串即可。 - 12431234123412341234123
1
@FrancescoBoi:声明char s[100];在堆栈上分配了100个字节。您可以像访问int数组一样访问和修改这些值。行s = "abcd";试图将指向字符串"abcd"的指针赋给s,但这没有意义。你引用的话是说,如果你能够进行那个赋值,原始的100个字节的数组将会丢失。(我认为更清楚的是意识到这个操作没有任何意义。虽然s可能像一个指针一样工作,但它不是一个指针;它是一个在堆栈上的100个字节的数组。) - Nick Matteo
显示剩余3条评论

72
在C语言中,没有所谓的“字符串”(string)。在C语言中,字符串是由一个终止字符 \0 来结束的一维字符数组 char。由于在C语言中不能给数组赋值,因此也不能给字符串赋值。字面量 "hello" 是语法糖,其实际等同于 const char x[] = {'h','e','l','l','o','\0'};
正确的方法是:
char s[100];
strncpy(s, "hello", 100);

或者更好的方式:

#define STRMAX 100
char    s[STRMAX];
size_t  len;
len = strncpy(s, "hello", STRMAX);

2
不是推荐的方法。小心strncpy的怪异行为:https://dev59.com/_HM_5IYBdhLWcg3wt1nD#1258577 - nucleon
我认为这种方法很容易被推荐。 - Volt

14

初始化和赋值是两个不同的操作,它们恰好在这里使用相同的运算符(“=”)。


4
1    char s[100];
2    s = "hello";

在您提供的示例中,s实际上是在第1行初始化的,而不是第2行。尽管此时您没有显式地为其分配一个值,但编译器已经为其分配了一个值。
在第2行,您正在执行赋值操作,您不能像这样将一个字符数组赋值给另一个字符数组。您需要使用strcpy()或某种循环来分配数组的每个元素。

为什么你需要100个字节来表示“hello”?char s[6]不够吗? - mLstudent33
2
@mLstudent33,这是OP给出的示例。 - Matt Davis

3

Sparr's answer进行详细解释:

初始化和赋值是两个不同的操作,但在这里它们都使用相同的运算符(“=”)。

可以这样理解:

想象一下有两个函数,称为InitializeObjectAssignObject。当编译器看到thing = value时,它会查看上下文并调用一个InitializeObject,如果你正在创建一个新的thing。如果不是,则调用AssignObject

通常情况下,InitializeObjectAssignObject的行为通常相同,这没问题。但是在处理char数组(以及其他一些边缘情况)时,它们的行为不同。为什么要这样做?这涉及堆栈与堆等内容,需要另外的文章来详细说明。

PS:顺便说一下,如果您曾经尝试使用C ++,这种思考方式也将有助于您理解复制构造函数和其他类似的东西。


1
请注意,您仍然可以执行以下操作:
s[0] = 'h';
s[1] = 'e';
s[2] = 'l';
s[3] = 'l';
s[4] = 'o';
s[5] = '\0';

使用strncpy()更加美观和简单,尽管我相信strncpy()在内部确实是这样做的。 - Chris Lutz
当然可以。但这是最接近's =“hello”;'的方法了。实际上,这应该在C中实现,因为您可以分配结构体。 - aib
我的意思是,在结构体中进行成员逐个复制的赋值操作是有意义的,但在数组中却没有意义。 - aib

1

我知道这个问题已经有答案了,但我想分享一下我在C/C++ Facebook群里回答一个非常类似问题的答案。


数组没有赋值运算符函数。 这意味着您不能简单地将一个字符数组分配给字符串字面值。为什么?因为数组本身没有任何赋值运算符。(*它是一个无法更改的常量指针。)

数组只是一块连续分配的内存区域,数组的名称实际上是数组的第一个元素的指针。(引自https://www.quora.com/Can-we-copy-an-array-using-an-assignment-operator)

要将字符串字面值(例如"Hello world""abcd")复制到您的字符数组中,必须手动将字符串字面值的所有字符元素复制到数组中。 char s[100]; 这将初始化一个长度为100的空数组。
现在,要将您的字符串字面值复制到此数组中,请使用strcpy strcpy(s, "abcd"); 这将从字符串字面值"abcd"复制内容并将其复制到s [100]数组中。
这是一个关于编程的很好的例子:

Here's a great example of what it's doing:

int i = 0; //start at 0
do {
    s[i] = ("Hello World")[i]; //assign s[i] to the string literal index i
} while(s[i++]); //continue the loop until the last char is null

你显然应该使用strcpy而不是这个自定义字符串文字复制器,但它是一个很好的例子,可以解释strcpy的基本工作原理。
希望这可以帮到你!

0

我对此感到烦恼... 如果能这样做,那真的很合乎逻辑:

char string[10] = "hello";  

应该给出相同的结果:

char string[10] = {0}; 
string = "hello";        // doesn't work!

但我猜它就是不那么工作。无论如何,这是我的解决方法:

char string[10] = {0}; 
sprintf(string,"hello");   

这几乎一样好,因为它很短。

奇怪的是,对我有用的另一件事(虽然可能有点离题)是当你声明结构体数组时,可以使用双引号进行初始化,像这样:

struct myStruct {
   char name[NAX_NAME_LEN];
};

struct myStruct name[DATA_ARRAY_SIZE];

name[1] = (struct myStruct ) { "Hello" };

顺便提一下,这种初始化方法被称为“复合字面量”,我很想知道有没有人能解释为什么可以使用双引号而不是字符串 =“hello”的方式……

顺便说一下,如果你有很多字符串,这种方法非常棒,因为它允许你编写像这样的代码:

#define DATA_ARRAY_SIZE 4
enum names{ BOB, GEORGE, FRANK, SARAH};
struct myStruct {
       char name[NAX_NAME_LEN];
};
struct myStruct name[DATA_ARRAY_SIZE];

name[BOB   ] = (struct myStruct ) { "Bob" };
name[GEORGE] = (struct myStruct ) { "George" };
name[FRANK ] = (struct myStruct ) { "Frank" };
name[SARAH ] = (struct myStruct ) { "Sarah" };

或者,如果你要为某个应用程序进行多语言支持:

#define NUM_LANGUAGES 4
enum languages{ ENGLISH , FRENCH  , SWEDISH , ITALIAN };
struct myStruct {
       char intro[NAX_NAME_LEN];

};
struct myStruct name[DATA_ARRAY_SIZE];

intro[ENGLISH ] = (struct myStruct ) { "Hello" };
intro[FRENCH  ] = (struct myStruct ) { "Bonjour" };
intro[SWEDISH ] = (struct myStruct ) { "Hej" };
intro[ITALIAN ] = (struct myStruct ) { "Ciao" };

-1
你可以使用这个:
yylval.sval=strdup("VHDL + Volcal trance...");

在编程中,yylval是char*类型。strdup函数可以完成该任务。


使用<string.h>中的strdup函数就可以完成这个任务 :) - Yekatandilburg
strdup函数会复制一个字符串并返回指向该副本的指针。对于字符数组而言,使用该函数相当于没有进行任何操作,回到了最初的状态。 - JoshKisb

-3

我会使用的是

char *s = "abcd";

2
大多数人不会这样做,因为它会冒着未定义的行为风险。上述代码仅适用于“const char*”。 - Toby Speight

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