为什么解引用操作符(*)也被用来声明指针?

64

我不确定这是否是一个合适的编程问题,但这一直困扰着我,我想知道我是否是唯一一个有这种疑惑的人。

在最初学习C++时,我理解了引用的概念,但指针一直让我感到困惑。你问为什么?因为指针的声明方式。

考虑以下代码:

void foo(int* bar)
{
}


int main()
{
    int x = 5;
    int* y = NULL;

    y = &x;
    *y = 15;     
    foo(y);

}
函数foo(int*)接受一个int指针作为参数。由于我已经将y声明为int指针,因此我可以将y传递给foo函数,但当我初学C++时,我将*符号与解引用联系起来,因此我认为需要传递一个解引用的int。我会尝试将*y传递给foo,但显然这是行不通的。
难道为声明指针(或解引用)不能有一个单独的运算符吗?例如:
void test(int@ x)
{
}

11
这个问题无法得到确切的答案,只能进行推测。 - bmargulies
20
可以直接回答;C语言的创造者写了一份文档,详细解释了为什么会这样。 - Crashworks
4
问题可以是出于真正的好奇,对吧?事实上,我认为Crashworks的回答就是这样一个针对我的问题的直接回答。那么为什么只能进行猜测呢? - diggingforfire
3
这并不是基于讨论的,问题有一个明确的答案,我们都已经给出了。 - Stuart Golodetz
2
phooehy案例删除并简化代码到一个简化的main函数可能是有意义的。我从标签中删除了reference,因为这个问题似乎根本不涉及引用。虽然相关问题可能是:“为什么取地址运算符(&)也用于声明引用?”它也以类似的方式进行了重载。 - user166390
显示剩余9条评论
6个回答

92

C语言的发展 中,Dennis Ritchie 这样解释他的理由:

第二个最明显区别于C之前的创新是更完整的类型结构,尤其是在声明语法中的表达... 给定任何类型的对象,应该可以描述一个新的对象,将多个对象收集到数组中,从函数中产生它,或者是指向它的指针... [这]导致了一个与表达式语法相似的名称声明语法。因此,int i,*pi,**ppi; 声明一个整数,一个指向整数的指针,一个指向指向整数的指针。这些声明的语法反映了这样一个观察结果:当在表达式中使用i,*pi和**ppi时,它们都产生一个类型。同样,int f(),*f(),(*f)(); 声明返回整数的函数,返回指向整数的指针的函数,返回整数的函数的指针。 int *api[10],(*pai)[10]; 声明整数指针数组和整数数组的指针。在所有这些情况下,变量的声明类似于使用具有声明头部命名的类型的表达式。语法上的偶然性导致了该语言被认为很复杂。在C中,间接操作符(用*拼写)在语法上是一种一元前缀运算符,就像在BCPL和B中一样。这在简单的表达式中很有效,但在更复杂的情况下,需要使用括号来指示解析。例如,为了区分通过函数返回的值的间接性与调用由指针指定的函数,分别写入*fp() and (*pf)()。在表达式中使用的风格延续到声明中,因此可能声明名称。在更华丽但仍然现实的情况下,事情变得更糟: int *(*pfp)(); 是指向返回整数指针的函数的指针。有两个效果发生。最重要的是,与Pascal等语言相比,C具有相对丰富的描述类型的方法。在像C-Algol 68这样表达的语言中,同样难以理解的对象描述,只是因为对象本身很复杂。第二个效应归功于语法的细节。在C中的声明必须以“从内部向外”风格阅读,许多人发现这种风格难以掌握。 Sethi [Sethi 81]观察到,如果将间接操作符作为后缀运算符而不是前缀运算符,则许多嵌套的声明和表达式将变得更简单,但当时已经太晚改变了。

7
这是一个很好的解释,我希望我们在课堂上能够真正地被告知这些内容,这本来会帮助我更早地理解指针。 - diggingforfire
1
这非常有帮助!我一直对函数指针的奇怪语法感到困惑。了解背景使得阅读和编写它们变得更加容易。喜欢这个网站 <3 - pezcode
3
抱歉,我无法编辑它。写作外语时总会发生这种事情。 - diggingforfire
2
这个引语的哪个部分解释了为什么前缀*被选择而不是其他替代方案? - David Heffernan
1
@David 或者你可以阅读链接文档的其余部分,其中描述了BCPL的根源。 - Crashworks
显示剩余7条评论

17

如果你这样写,原因就更清晰了:

int x, *y;

也就是说,x 和 *y 都是 int 类型。因此 y 是一个 int 指针。


实际上,如果您扩展它,这并不更清晰。我不想引发争论,但我想指出这种语法注定会导致争议。 - Lightness Races in Orbit
1
我同意在实践中不应该使用这种语法 - 它只是为了说明问题(这也是David所说的)。 - Stuart Golodetz
确实,我没有任何反对你或你的回答(除了“更清晰”这一部分)。 - Lightness Races in Orbit
没关系,我不会生气的 :) 只是想澄清一下。 - Stuart Golodetz
3
可能不是世界上最清晰的答案,但由于在iPad上打字,因此比平时短。我试图表达的观点是,如果您将与变量名组合在一起,那么这种语法的来源就变得更加清晰明了——也就是说,通过说y是int,您暗示y本身是int*。由于所给出的参考资料已经证实了这个答案(而且这不是一个猜测),我不确定敌意来自何处。无论如何,我们同意各自保留自己的看法。 - Stuart Golodetz
显示剩余2条评论

12

这是一个比C++还要早的语言决定,因为C++继承了C语言的特性。据我所知,这个决定的动机是使声明和使用等价,也就是说,给定一个声明 int *p;,表达式 *p 的类型与 int 相同,就像用 int i; 声明后,表达式 i 的类型是 int 一样。


10
因为委员会以及在C++标准化之前几十年开发C++的人决定*应该保留其原始的三个意思:
- 指针类型 - 解引用运算符 - 乘法
你提出了*(以及类似的&)的多重含义很令人困惑,这是正确的。我多年来一直认为它们是新手理解语言时的重要障碍。
为什么不为C++选择另一个符号?
“向后兼容”是根本原因......最好在新上下文中重用现有符号,而不是通过将先前不是运算符的符号转换为新的含义来破坏C程序。
为什么不为C选择另一个符号?
无法确定,但可以提出并已经提出了几个论点。最重要的是:
当标识符出现在与声明符相同形式的表达式中时,它会产生指定类型的对象。{K&R,p216}
这也是为什么C程序员倾向于将他们的星号右对齐而不是左对齐的原因,即:
int *ptr1; // roughly C-style
int* ptr2; // roughly C++-style

虽然两种变量在这两种语言的程序中都存在,但它们的使用方式有所不同。

3
我认为委员会没有做出任何决定;很可能是丹尼斯·里奇(Dennis Ritchie)在创造C语言时做出的决定。 - Brian Neal
委员会负责我们今天所知道的语言。公平的说,它始于DR,但那不是我们现在所知道的“C”。而且,比其他任何事情都更重要的是,C++委员会对C++语言做出了有意识的决定——Ritchie没有这样做。 - Lightness Races in Orbit
8
在委员会成立之前,Ritchie 发明了那种语法。Stroustrup 不打算不使用它而破坏与 C 的向后兼容性。所有这些决策都是在 C 和 C++ 开发被置于标准机构之前做出的。 - Brian Neal
3
好的,但是我们现在所知道的语言是由那些委员会管理的,而那些委员会目前负责做出决定。除非你想要将第一个原始汤中的蛋白质归功于他们。 - Lightness Races in Orbit

6

哈哈,我感同身受,我也遇到了完全相同的问题。

我曾认为指针应该声明为&int,因为指针是某个东西的地址,这很有道理。

过了一段时间,我自己想了想,C语言中的每种类型都可以反向阅读,比如

int * const x

可以读作

x const * int

一个常量x,当被解引用(用*表示)时,它的类型是int。所以必须是指针才能解引用


6

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