为什么我们不能将新字符串赋值给字符数组,但可以赋值给指针?

4
我试图将一个字符串重新赋值给预初始化的数组a[],但是我只得到了一个错误。
main()
{
    char a[] = "Sunstroke";
    char *b = "Coldwave";

    a = "Coldwave";
    b = "Sunstroke";
    printf("\n %s %s",a,b);
}

[错误]: 将类型为'char *'的值分配给类型为'char [10]'的变量时,类型不兼容。我尝试过搜索但未找到原因。我也尝试通过重新声明来重新分配它,例如:
char a[] = "Sunstroke";

但它没有起作用...

但是对于指针,像上面的程序那样是可行的。


5
为什么我们能吃苹果,却不能爱情?为什么我们可以在水中游泳,却不能在熔化的硼中游泳?因为它们是不同的东西……指针可以被赋值,而数组则不能。 - Kerrek SB
因为数组名称无法修改。再次强调,数组与指针不同。 - Yu Hao
我想反过来问为什么你想这样做——你希望引发什么行为,与 char *b = "Coldwave"; 获得的行为不同? - Brian Cain
使用 std::string,你的问题就迎刃而解了。 - chris
2
你是在问C还是C++?在这两种语言中,数组和指针可能具有相同的行为,但操作字符串的习惯用法却非常不同。 - Mike Seymour
4个回答

7
为了理解这里发生的事情,有两个语言规则很重要:
  • 数组不可赋值。
  • 数组可以转换为指向其第一个元素的指针。
此外,理解像"中暑"一样的字符串字面量也很重要。它是一个静态的常量字符数组,大于等于一个带终止符的字符串所需的所有字符。因此,在这种情况下,它是一个包含九个字符和一个以零值结尾的终止符的const char [10]数组。由于是静态的,该数组在程序的生命周期内存储在内存中。
char a[] = "Sunstroke";

这创建了一个本地数组,并通过从字符串文字中复制字符来初始化它。
char *b = "Coldwave";

这将创建一个指针,并将其初始化为指向字面量本身。请注意,这是危险的:字面量是const的,但指针不是,因此您可以编写试图修改字面量的代码,从而导致未定义的行为。这种转换已被弃用(在C++中肯定如此,我不确定C),因此编译器应该会给出警告。你已经启用了所有可用的编译器警告,对吗?

a = "Coldwave";

这里尝试重新分配数组,但失败了,因为数组不能被赋值。它们之所以不能被赋值,并没有特别好的理由;这只是语言演变的方式。

b = "Sunstroke";

这会重新分配指针以指向不同的文字。这没问题(除了上面提到的缺少const)。

如果您需要操作字符串,则:

  • 在 C 中,您需要仔细创建足够大的数组,并使用 <string.h> 库函数(或自己编写的代码)来操作这些数组中的字符;
  • 在 C++ 中,使用 std::string 类来为您处理内存管理、赋值等。

4

类似"Coldwave"这样的硬编码字符串实际上是char[](字符数组)类型。但是修改它们是未定义行为(参考C99:6.4.5.6)。但是请注意,下面的b仍然是一个char*(字符指针):

char *b = "Coldwave";

给定了一个char[]。这是可以的。但是它与这个不同:

char a[] = "Coldwave";

这是 char[] 的一个 初始化 。变量只能在声明时进行一次初始化,而初始化是唯一能够通过赋值来填充数组或其他复合类型(例如结构体)的情况。但是你不能这样做:

char c[] = a;

因为在赋值语句的右侧使用时,数组变量充当指向其所表示数组的指针,这就是为什么char *b = a有效的原因。
因此,您无法对上述变量执行此操作的原因如下:
a = b;
// or
a = "Sunstroke";

这是因为将一个char*赋值给char[]是不可行的,你只能反其道而行之。


我只是想问一下为什么代码 a = "coldwave"; 会出错... 如果我故意重新声明它,比如 char a[] = "coldwave";,那么它又会报错说变量已经被重新声明了。 - r_goyal
4
因为第一个是“赋值语句”,而你不能将任何东西赋值给一个数组——你只能把东西复制到里面。除非在声明数组时,这个例外是“初始化”。你只能在声明变量时初始化一次。在C中,你不能重新声明标识符。一旦a在特定作用域内存在,即使它是相同类型的,你也不能声明另一个a - CodeClown42
1
硬编码的字符串字面值,例如"Coldwave"被视为const char*类型,实际上它是char数组类型。 - newacct
2
补充@newacct的观点,需要注意的是,根据上下文,数组会退化为“类型的指针”,它们在这些情况下并不是“左值”。仅仅说数组与指针不同可能会让人感到困惑,因为在许多情况下,它们将明显表现得像指针,例如作为函数参数传递等。 - Shafik Yaghmour

3
在C语言中,如果我们查看c99 draft standard第6.5.16节“赋值运算符”第2段,其中写道:赋值运算符应该有一个可修改的左值作为其左操作数。另外,在第6.3.2.1节“Lvalues, arrays, and function designators”第1段中写道:可修改的左值是指不具有数组类型的左值[...]。因此,由于数组不是可修改的左值,所以不能对它们进行赋值操作。至于初始化,第6.7.8节“Initialization”第14段中写道:字符类型的数组可以通过字符字符串字面量来初始化[...]。
C++草案标准中,相关章节为4.2 数组转指针转换1段,它说:

类型为“N个T的数组”或“未知边界的T的数组”的左值或右值可以转换为类型为“指向T的指针”的纯右值。结果是数组的第一个元素的指针。

prvalue是纯右值,根据第5.17赋值和复合赋值运算符的第1段所述:

[...]所有都需要可修改的左值作为其左操作数[...]


1
让我简化这个程序:
char a[] = "Sunstroke";
char *b = a;

假设a的地址为100,在内存中,它看起来像这样(仅说明指针的大小和字节序可能有所不同):
[S] [u] [n] [s] [t] [r] [o] [k] [e] [\0]         ...       [0] [0] [0] [100]
100 101 102 103 104 105 106 107 108 109                           200
 ^                                                                 ^
 |                                                                 |
 a                                                                 b

只要数组的生命周期存在,a 将始终在同一位置,您无法修改它。
另一方面,b 是一个包含数组地址的指针,您可以修改 b 的值以指向其他位置。

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