“char[]”是一个合适的类型吗?

7

昨天,我惊讶地发现一些代码似乎将char[]视为一种类型:

typedef std::unique_ptr<char[]> CharPtr;

以前,我可能会写出这样的内容:

typedef std::unique_ptr<char*, CharDeleter> CharPtr;
// Custom definition of CharDeleter omitted

经过一些研究,我发现char[]语法是可行的,因为std::unique_ptr提供了一个模板特化来处理数组(例如它会自动调用delete[]来删除数组而不需要自定义删除器)

但是在C++中,char[]实际上是什么意思呢?

我见过这样的语法:

const char a[] = "Constant string"; // Example 1

char *p = new char[5]; // Example 2

bool foo(char param[10]); // Example 3

以下是我对这些示例的解释:

示例1:在栈上分配了一个静态数组,因为编译时已知字符串的真实大小,所以空索引是有效的(例如,编译器在幕后基本上处理了长度)

示例2:动态分配了5个连续字符,第一个字符存储在p中存储的地址中。

示例3:定义了一个函数,该函数将大小为10的数组作为参数。(在幕后,编译器将数组视为指针)--例如,以下内容是错误的:

void foo(char test[5]) {}
void foo(char * test) {}

因为函数签名对编译器来说存在歧义。

我觉得我理解数组/指针的差异和相似之处。我的困惑可能源于我在构建/阅读C++模板方面缺乏经验。

我知道模板特化基本上允许根据模板类型参数使用定制的模板(基于特定模板)。char[]只是可用于模板特化(调用特定特化)的语法吗?

另外,像char[]这样的数组“类型”的正式名称是什么?


1
最好不要使用C风格的数组,这很少是一个好选择。 - stefan
你似乎在这里问了几个不同的问题。如果你坚持使用 char[] 的话会更好。 - Lightness Races in Orbit
感谢您关注char[]问题 - 这确实是我想要回答的核心。 - CRN
3个回答

4
在C++中,char[]实际上是一个数组类型。在声明中,如果标识符的类型是“派生声明符类型列表T”的形式,那么标识符的类型就是一个数组类型。数组元素类型不能是引用类型、void类型、函数类型或抽象类类型。如果常量表达式存在,则它必须是整型常量表达式且其值必须大于零。常量表达式指定了数组的元素数量。如果常量表达式的值为N,则数组有N个元素,编号从0到N-1,标识符的类型是“派生声明符类型列表array of N T”。数组类型对象包含一组连续分配的非空的T类型的子对象。如果省略常量表达式,则标识符的类型是“派生声明符类型列表array of unknown bound of T”,这是一个不完整的对象类型。需要注意的是,“派生声明符类型列表array of N T”类型与“派生声明符类型列表array of unknown bound of T”类型是不同的类型。关于例子1,尽管在[C++11: 8.5.5]中这一点令人惊讶地不清楚,但带有初始化程序的char[]是一个特殊情况,不适用于上述文本:实际上a是一个const char[16]。因此,“编译器在幕后基本上为我们处理了长度”。

示例3定义了一个以大小为10的数组作为参数的函数。(在幕后,编译器把数组当作指针来处理)

几乎正确。实际上这个转换并没有任何“幕后”的东西:它已经被写在文档中了。而且明确且标准化。

所以:

-- e.g. it is an error to have:

void foo(char test[5]) {}
void foo(char * test) {}

because the function signatures are ambiguous to the compiler.

事实上,这并不是因为“模棱两可”,而是因为您字面上定义了同一个函数两次。

我觉得有人应该提到,如果你真的想在C++中保留数组长度,你可以这样做:void foo(char (&test)[5]) {} - emsr
@emsr:我认为这不相关。这个问题已经过于宽泛了,不需要成为关于使用数组的每一种方式的论文! - Lightness Races in Orbit
1
太棒了,这正是我想要找的。谢谢你的回答,Lightness Races in Orbit。 - CRN
1
@emsr,我也很感激你的评论,因为我之前不知道C++中还有这种数组语法——又是"&"的另一种用法 :) - CRN
@CRN:这只是它的“引用类型”的意思。 - Lightness Races in Orbit

3

char[]是一种类型,但是你不能创建它的实例。它是一种不完整的对象类型,有点像struct foo;

这意味着模板可以将char[]作为一种类型来使用,但是它们不能创建char[]类型的变量,但是它们可以与该类型进行交互。

现在,从C继承了一堆数组的“神奇”行为。作为函数参数参数,char[]会变成char*char[33]也是如此!)

作为局部变量,char x[]="foo";char y[]={'a','b','c'};会变成一个固定大小的数组。在这里,char[]的意思是“自动调整数组大小”。

在某种程度上,这些都是参数类型和变量声明中的怪癖,而不是类型的怪癖。您声明的类型看起来与您声明的类型并不相同。

还有一堆涉及类型衰减的奇怪行为——像char x[3];这样的char[3]类型的变量会在转瞬间衰减为char*。这与自动调整数组一样,基本上是从C遗留下来的。

所有这些都在标准中明确描述,但是因为它们与大多数“常规”类型有很大不同,所以它们就像魔术一样。

毕竟,任何足够晦涩的标准特性都无法区分是否为魔法。


2

是的,char[]表示复合类型“未知大小的char数组”。它是一个不完整的类型,但可以稍后完成:

extern char a[];    // "a" has incomplete type at point of declaration

char a[10];         // Now "a" has complete type.

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