为啥解引用运算符 (*) 也用于声明指针?
Posted
技术标签:
【中文标题】为啥解引用运算符 (*) 也用于声明指针?【英文标题】:Why is the dereference operator (*) also used to declare a pointer?为什么解引用运算符 (*) 也用于声明指针? 【发布时间】:2012-01-30 21:55:29 【问题描述】:我不确定这是否是一个正确的编程问题,但它一直困扰着我,我想知道是否只有我一个人。
最初学习 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)
【问题讨论】:
这个问题无法回答,只能推测。 @bmargulies 可以直接回答; C 的创建者写了一份文档,解释了为什么会这样。 一个问题可以是真正的好奇心,对吧?事实上,我发现 Crashworks 的答案就是这样,直接回答了我的问题。那么为什么只能推测呢? @ildjarn:这不是基于讨论的,这个问题有一个明确的答案,我们都给出了答案。 去掉phooehy
的情况并将代码简化为简化的main
可能是有意义的。我从标签中删除了reference
,因为问题似乎根本与参考无关。尽管 related 问题可能是:“为什么地址运算符 (&) 也用于声明引用?”它也以类似的方式重载。
【参考方案1】:
在The Development of the C Language 中,Dennis Ritchie 如此解释了他的推理:
第二个最能清楚地区分 C 与它的创新 前辈是这种更完整的类型结构,尤其是它的 声明语法中的表达式...给定任何对象 类型,应该可以描述一个收集的新对象 几个到一个数组中,从一个函数中产生它,或者是一个指向 它.... [这] 导致了 名称的声明语法反映了表达式语法的声明语法 名称通常出现在其中。 因此,
int i, *pi, **ppi;
声明一个整数,一个指向整数的指针,一个 指向整数的指针。这些声明的语法 反映i, *pi, and **ppi
都产生int
类型的观察结果 在表达式中使用时。同样,
int f(), *f(), (*f)();
声明 一个函数返回一个整数,一个函数返回一个指针 整数,指向返回整数的函数的指针。int *api[10], (*pai)[10];
声明一个指向整数的指针数组,以及一个指向 一个整数数组。在所有这些情况下,声明 变量类似于它在类型为 1 的表达式中的用法 在声明的开头命名。
语法上的意外导致了感知的复杂性 语言。间接运算符,在 C 中拼写为 *,在语法上是 一元前缀运算符,就像在 BCPL 和 B 中一样。这在 简单的表达式,但在更复杂的情况下,括号是 需要指导解析。例如,要区分 通过调用一个函数返回的值间接 由指针指定的函数,一个写
*fp() and (*pf)()
分别。表达式中使用的风格一直延续到 声明,因此可以声明名称
int *fp(); int (*pf)();
在更华丽但仍然现实的情况下, 事情变得更糟:
int *(*pfp)();
是一个指向函数的指针 返回一个指向整数的指针。有两种影响发生。 最重要的是,C有一套比较丰富的描述方式 类型(例如,与 Pascal 相比)。语言中的声明为 富有表现力的 C——例如,Algol 68——描述同样难以理解的对象 理解,仅仅是因为对象本身很复杂。一种 第二个效果归功于语法的细节。 C 中的声明必须是 以许多人难以掌握的“由内而外”的方式阅读。 Sethi [Sethi 81] 观察到许多嵌套的 如果间接,声明和表达式会变得更简单 运算符已被视为后缀运算符而不是前缀,但是 到那时改变已经太迟了。
【讨论】:
这是一个很好的解释,我希望我们真的在课堂上被告知这些事情,它会帮助我更快地理解指针。 @Tomalak Geret'kal:太糟糕了,我现在不能编辑它。好吧,用外语写作时确实会发生这些事情。 @diggingforfire 你可能还喜欢我用指针推理的“方格纸和铅笔”技术(例如:***.com/questions/7062853/…)。我一般认为,首先学习机器实际使用指针做什么会使学习 C 抽象变得更容易。而不是尝试先学习抽象,然后再学习具体。 这句话的哪一部分解释了为什么选择前缀 * 而不是替代品? @DavidHeffernan - BCPL 中的“就像在 BCPL 和 B 中一样”所有一元运算符都是前缀【参考方案2】:这样写,原因就更清楚了:
int x, *y;
也就是说,x 和 *y 都是整数。因此 y 是一个 int *。
【讨论】:
其实这个扩展一下就不清楚了。我不想开始争论,但我想指出,这种语法必然会导致一个。 我同意你不应该在实践中使用这种语法——这只是为了说明这一点(这与大卫所说的相同)。 确实,对您或您的回答没有任何不利之处(也许“更清晰”的部分除外) 没有冒犯 :) 只是想澄清一下。 可能不是世界上最清晰的答案,但在 iPad 上打字比平时短。我试图说明的一点是,如果你将 * 与变量名分组,那么这个语法的来源就会变得更清楚 - 即通过说 * y 是一个 int 的想法,你暗示 y 本身是一个 int * .由于给出的参考资料在实践中证实了这个答案(这不是猜测),我不确定敌意来自哪里。无论如何,让我们同意不同意。【参考方案3】:这是一个早于 C++ 的语言决定,因为 C++ 从 C 继承了它。我曾经听说其动机是声明和使用是等价的,即给定声明 int *p;
表达式 *p
是int
类型,就像int i;
表达式i
是int
类型一样。
【讨论】:
【参考方案4】:因为委员会以及那些在 C++ 标准化之前的几十年中开发 C++ 的人,决定 *
应该保留其最初的三个含义:
您认为*
(以及类似地,&
)的多重含义令人困惑,这是对的。多年来,我一直认为它们是语言新手理解的重大障碍。
为什么不为 C++ 选择另一个符号?
向后兼容性是根本原因...最好在新的上下文中重用现有符号,而不是通过将以前的非运算符转换为新的含义来破坏 C 程序。
为什么不为 C 选择另一个符号?
不可能确切地知道,但有几个论点可以——并且已经——提出。最重要的想法是:
当 [an] 标识符出现在与声明符形式相同的表达式中时,它产生一个指定类型的对象。 K&R, p216
这也是 C 程序员倾向于[需要引用]更喜欢将星号右对齐而不是左对齐的原因,即:
int *ptr1; // roughly C-style
int* ptr2; // roughly C++-style
尽管在两种语言的程序中都可以找到这两种变体,但各不相同。
【讨论】:
我认为委员会没有做出任何决定;可能是 Dennis Ritchie 创建 C 的时候。 委员会负责我们今天所知道的语言。公平点,它从 DR 开始,但这不是我们现在所知道的“C”。而且,最重要的是,C++ 委员会有意识地决定了 C++ 语言——Ritchie 没有。 在委员会成立之前,Ritchie 发明了这种语法。 Stroustrup 不会因为不使用它而破坏与 C 的向后兼容性。所有这些决定都是在 C 和 C++ 的开发被纳入标准机构之前做出的。 @BrianNeal:很好,但是我们所知道的语言是由这些委员会管理的,而这些委员会——目前——负责该决定。除非您想将原始汤中的第一种蛋白质归功于此。【参考方案5】:Expert C Programming: Deep C Secrets 的第 65 页包括以下内容:C 哲学认为对象的声明应该看起来像它的用途。
The C Programming Language, 2nd edition (aka K&R) 的第 216 页包括:一个声明符被解读为一个断言,当它的标识符出现在与声明符相同形式的表达式中时,它会产生一个指定类型的对象。
我更喜欢 van der Linden 所说的方式。
【讨论】:
【参考方案6】:哈哈,我感受到了你的痛苦,我也有同样的问题。
我认为应该将指针声明为&int
,因为指针是某物的地址是有意义的。
一段时间后,我自己想,C 中的每种类型都必须向后阅读,比如
int * const a
适合我
a constant something, when dereferenced equals an int
。
必须取消引用的东西必须是指针。
【讨论】:
以上是关于为啥解引用运算符 (*) 也用于声明指针?的主要内容,如果未能解决你的问题,请参考以下文章