为啥解引用运算符 (*) 也用于声明指针?

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; 表达式 *pint 类型,就像int i; 表达式iint 类型一样。

【讨论】:

【参考方案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。 必须取消引用的东西必须是指针。

【讨论】:

以上是关于为啥解引用运算符 (*) 也用于声明指针?的主要内容,如果未能解决你的问题,请参考以下文章

解引用指针并存储对指向数据的引用,然后获取引用地址并对其执行指针运算。 C++

通过指针、强制转换和取消引用加载向量?

C++原生指针,引用与智能指针

c语言解引用和取地址操作符?

运行时类型识别

为啥移动赋值运算符应该返回对 *this 的引用 [重复]