char和char[1]之间的区别

58

C++中使用char和char[1]有什么区别(如果有的话)。

例子:

struct SomeStruct
{
   char x;
   char y[1];
};

对于unsigned char,是否遵循相同的原因?


19
这个问题的错误答案数量令人震惊。+1因为你问了一个显然许多人误解的问题。 - Billy ONeal
2
我看到这个问题,几乎可以听到数百个键盘疯狂地敲打声,争先恐后地回答。 - Anthony
2
@Billy,@Duracell:不幸的是,大多数C和C++程序员并不真正理解数组。一方面,这有点令人惊讶,考虑到它们是多么基础;另一方面,数组衰减和下标运算的工作方式使得初学者很容易认为数组和指针是同一种东西。 - James McNellis
3个回答

39
主要区别在于你使用的语法来访问你的单个字符。
"访问"指的是使用语言中的各种运算符对其进行操作,其中大部分或全部针对char与char数组应用时会产生不同的效果。这使得x和y听起来几乎完全不同,事实上它们都由一个字符组成,但该字符以非常不同的方式表示。
实现可能导致其他差异,例如根据您使用的类型对结构进行不同的对齐和填充。但我怀疑这一点。
运算符的差异示例是char可被赋值,而数组则不能:
SomeStruct a;
a.x = 'a';
a.y[0] = 'a';
SomeStruct b;
b.x = a.x; // OK
b.y = a.y; // not OK
b.y[0] = a.y[0]; // OK

但是,y不可分配的事实并不能阻止SomeStruct的可分配性:

b = a; // OK

这一点与类型无关,无论是 char 还是其他类型。在内存中,一个此类型的对象和一个大小为1的数组基本上是相同的。

顺便说一下,在某些情况下,选择使用 charchar[1] 会产生很大的区别,有时会让人们误以为数组实际上是指针。虽然不是你的例子,但作为函数参数:

void foo(char c);     // a function which takes a char as a parameter
void bar(char c[1]);  // a function which takes a char* as a parameter
void baz(char c[12]); // also a function which takes a char* as a parameter

barbaz中提供的声明中的数字在C++语言中完全被忽略。显然,某个人曾经认为它对程序员是有用的,作为文档的形式,表明函数baz正在期望其指针参数指向一个包含12个char元素的数组的第一个元素。

barbaz中,c从来没有数组类型 - 它看起来像是数组类型,但实际上不是,它只是一种带有与char *c相同含义的花哨的特殊情况语法。这就是为什么我在“使用”上加了引号 - 你根本没有真正使用char[1],它只是看起来像是这样的。


9
+1,我已经厌倦了所有回答都说 y 是一个指针。 - casablanca
4
好的,明了的回答。如果作者添加构造函数进行初始化(如果他要添加一个),这将是另一件不同的事情,但遗憾的是,在当前的C++中,除了对其进行值初始化之外,它不适用于数组。 - Johannes Schaub - litb
3
主要区别在于类型系统所分配的类型x的类型为char,而y的类型为char[1]。我知道这听起来像是在说显而易见的事情,但这真的是主要区别。例如,您无法将y传递给期望char类型参数的函数。但是当传递给sizeof运算符时,xy最终都将返回相同的值,并且两者在内存中表示方式可能完全相同。此外,y可以降级为指针,这意味着您可以将其传递给类似于strcpy的函数,但是您不能对x做到这一点。 - Charles Salvia
1
我相信最终@Charles有一个非常好的观点。类型不同,最终许多操作将表现不同。例如,!a.x!a.y都可以工作,但意义完全不同。而cout << a.x可能会产生预期的结果,但cout << a.y只会产生无意义的结果。 - Johannes Schaub - litb
2
@J-16:在您的机器上,char指针大小是多少?a.y的大小是多少?char* p; char a[1]; assert(sizeof(p) == sizeof(a));将会失败;这完全与类型有关,而不是与左值或右值有关。 - Roger Pate
显示剩余15条评论

11
如果你在生产代码中看到过构造体的最后一个成员是char y[1],那么很可能你遇到了结构体hack的实例。
这个短数组是真正的可变长度数组的代替品(请记住,在c99之前,c标准中没有这样的东西)。程序员总是会在堆上分配这样的结构体,并确保分配的大小足够使用他想要使用的实际数组大小。

Steve:除非那段C++代码需要与C代码交互。例如,C++代码使用Win32API。 - Billy ONeal
@Billy:如果黑客出现在由某个C API定义的结构体中,那没问题。我想这个黑客仍然是“在C++代码”中的,因为只要你在C++程序中#include它,头文件就会变成C ++,所以可以将其视为我的规则例外。 - Steve Jessop
4
@Billy:我认为@Steve的建议在这种情况下仍然适用,但原因不同。每当你需要使用Win32 API时,都要小心... ;) - jalf
@jalf:也许我应该只说C API。POSIX肯定有一些这样的API。 - Billy ONeal
@Billy:比如说 struct dirent - Steve Jessop

3
除了Steve强调的使用中的符号差异外,char [1]可以传递给例如template <int N> void f(char(&a)[N]),其中char x = '\0'; f(&x);不匹配。可靠地捕获数组参数的大小非常方便和令人放心。
它还可能意味着不同的事情:实际长度可能更长(如dmckee所解释的),或者内容在逻辑上是一个ASCIIZ字符串(在这种情况下为空),或者是字符数组(在这种情况下有一个元素)。如果结构体是几个相关结构体之一(例如数学向量,其中数组大小是模板参数,或者编码某些I/O操作所需的内存布局),那么与其他字段相似度较高的地方可能会建议优先选择单个字符数组,从而使支持代码更简单/更通用。

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