为什么C++变量声明语法不一致?

14

我声明C++变量时,像这样写:

int a,b,c,d;
或者
string strA,strB,strC,strD;

即首先是类型,然后是逗号分隔的变量名称列表。

然而,我像这样声明指针:

int *a,*b,*c,*d;

以及类似这样的引用:

int &a,&b,&c,&d;

为了保持一致,应该是:

int* a,b,c,d;

int& a,b,c,d;

为什么它不是一致的?


不确定是否是这个原因,但一个指定类型,另一个指定类型的引用方式。 - N_A
可能是在C语言中int* ptr和int *ptr有什么区别?的重复问题。 - Andy Thomas
2
常见误解,我认为是因为教授 C 语言的书籍教导我们在类型和变量之间加上 * 或者 &,而不是放在变量前面。总之,这种用法源自 C++ 的 C 根基。 - Some programmer dude
4
@AndyThomas-Cramer 绝对不是那个问题的副本... - tenfour
1
这并不是不一致的,只是不符合你个人的期望。也许最好的比喻是汽车和船:在汽车中,你向左转动方向盘以向左行驶,在船上,你向左转动舵以向右行驶。两者都没有更多的“一致性”。 - Kerrek SB
@KerrekSB 我不会这么说。不仅被接受的答案加入了证实 OP 的感觉,而且在所有其他地方,其余的类型系统也表明这个逗号声明语法规则是错误的。它只是为了历史原因而保留下来。const 应用于类型,而不是变量。就像在函数签名中,星号应用于返回类型,而不是函数的标识符。 - v.oddou
5个回答

13
我见过的关于为什么像*这样的东西应用于变量而不是类型的最好答案是这样的想法:声明应该跟随使用

基本上,你如何声明一个变量应该看起来类似于你如何使用一个变量。

例如,

int *a, b;
...
*a = 42;
b = 314159;

...使用ab的方式类似于声明ab

甚至C语言的创建者之一Dennis Ritchie也为此行为提供了引用:

  

类比推理导致了名称的声明语法与表达式语法相似,这些名称通常出现在表达式中...在所有这些情况下,变量的声明类似于其类型为声明头部所命名的表达式中的使用。

  • Dennis Ritchie, C语言的发展. 编程语言历史-II版. Thomas J. Bergin, Jr.和Richard G. Gibson, Jr. ACM Press(纽约)和Addison-Wesley(Reading, Mass), 1996; ISBN 0-201-89502-1。

不错的论点。但我仍然认为 Pascal 的方式更好:p: ^Integer; - cyco130
1
@cyco130:我不完全同意“声明模仿使用”,但它(有点)解释了他们当时的想法。 :) - Nate Kohl

12

这是由于C语言的遗产。* 修改符在C中用于变量。因此,由于无法更改第一个而破坏C兼容性,C++设计者通过类比制定了将 & 应用于变量的规则。数组语法也是如此。

int anInt, *aPointerToInt, anArrayOfInt[100];

Bjarne Stroustrup在他的书《C++的设计与演化》中表示,他不满意这一点,但由于需要与C语言兼容,他不得不接受它。他特别对此感到不满意的是:

int *a[10];

从声明中无法确定a是指向一个数组还是一个指向指针的数组(它是一个指向指针的数组,你需要使用方括号来覆盖默认的声明)。

当然,您始终可以使用typedef来使代码变得更简洁。


5
好的,我会尽力进行翻译。以下是需要翻译的内容:+1 for quoting the source (The Design and Evolution of C++)翻译:引用来源(《C++设计与演化》)加1。 - Josh Kelley
1
@JoshKelley:现在我们需要有人解释为什么K&R首先做出了这个决定 :) - cyco130
3
@cyco130:你觉得这个链接怎么样?https://dev59.com/Z2sz5IYBdhLWcg3wVGTJ#7967844 ? - Nate Kohl

7
原因是:在C和C++中,&*实际上适用于变量而不是类型。
因此问题在于,如果您想声明4个指向整数的指针,您应该写:
int *a, *b, *c, *d;

写下int* a, b;的意思是

int *a;
int b;

因此,很多人更喜欢写int *a;而不是int* a;,这只是代码风格的问题。
当然,int*是一种类型,但是C语法明确规定,在变量声明中*&将应用于变量。
这是因为在C语言中数组的声明方式也是这样的:
int a[10], b;

std::cout << typeid(a).name() << std::endl; // it will print int[10]
std::cout << typeid(b).name() << std::endl; // it will print int

在其他语言(如C#)中,使用的语法是int[] array;

另一件事是指向函数的语法:

// Now we declare a variable that is a pointer to function
int (*pt2Function)(char, char) = NULL;

我们正在将 * 应用到变量名中。

在我看来,虽然我们中有90%的人喜欢不同的方式,但对变量应用 *& 更符合语言的其他部分。

同样地,在处理数组时也是如此:

// Now we declare a variable that is a pointer to an array of 10 integers.
int (*arrayptr)[10];

1
他似乎知道那个。他的问题是为什么会这样? - Benjamin Lindley
1
如果你真的想要一致性,就需要使用typedef。例如:typedef int* intptr; intptr a,b,c,d; - Sodved
不完全正确,否则你就不能执行 typedef int* intptr; intptr a, b, c, d; - tenfour
这是另一个问题,Tenfour,区别只在于语法,而不在实际含义上。当然,int*是一种类型。 - Salvatore Previti

5

C语言有一个声明规则:声明与使用相似。这意味着变量的声明看起来像变量的使用方式。例如:

int i;

使用这个变量类似于i,并且该表达式的结果为int类型。
int *i;

使用这个变量看起来像是*i,这个表达式的结果是一个int。这也意味着一个表达式i的结果是一个指向int的指针。
int i(void);

使用这个变量看起来像是 i(),并且会得到一个 int。这也意味着 i 是一个不带参数并返回一个 int 的函数。
int (*i[5])();

使用这个变量的方式类似于(*i[x])(),结果为int。这也意味着*i[x]是返回int的函数,i[x]是返回int的函数指针,i是一个返回int的函数指针数组。
int a, *b, c(void), (*d[5])(void);

声明了几个表达式,它们都具有int类型,但变量本身并不全是int

N.B. 在函数和数组的声明中,子声明并不与变量的使用直接相似。即int i [5];并不意味着在使用i时需要将“5”放在方括号内,而int i(int);也不意味着您调用函数时需要说i(int);。此外,结构类型的变量声明与使用不匹配,因为您必须为每个成员变量声明表达式。

与声明变量的语法相关的是typedef的语法。要声明具有特定类型的typedef,您可以使用与声明所需类型的变量相同的语法,但在声明前面加上typedef,变量名成为typedef名称。

typedef int (*array_of_function_pointers[5])(void);

C++增加了引用,模板和成员指针等诸多内容。它试图在某种程度上遵循旧的C约定,但这种约定对于C++添加的许多东西并不真正有效,因此您会开始出现不一致性。您只需要学习其特点,并将其归结为C和C++之间不完美的融合。


2

C++继承了C语言的语法。K&R书籍(§5.1)中提到该表达式

int *ip, **ipp;

这里的意图是希望使用mnemonic(即你读到“*ip**ipp具有类型int”时,你可以记忆它),他们想要模仿使用语法。

我认为对人类来说这相当难以理解且反直觉,并且与typedef不一致,例如:

typedef int* intptr;
intptr a, b; // equivalent to: int *a, *b

但实际上,这并不是C++作者的决定。让这种语言与C兼容意味着使用这种语法。


我不明白为什么这比其他任何typedef更不一致。 - Flexo
好的答案。不仅这与typedef不一致,而且C++类型系统中的每个其他地方都不同意变量所属的限定符。特别是函数签名:type+qualif functionid args - v.oddou

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