dot(.运算符用于访问结构的成员,而c中的箭头运算符(->用于访问由相关指针引用的结构的成员。
指针本身没有任何可以用点运算符访问的成员(它实际上只是一个描述虚拟内存中某个位置的数字,因此没有任何成员)。因此,如果我们只是定义了点运算符,以便在指针上使用指针时自动取消对指针的引用(编译器在编译时已知的信息)。
那么,为什么语言创建者决定通过添加这个看似不必要的操作符使事情变得更复杂呢?什么是重大设计决策?
- 相关:stackoverflow.com/questions/221346/…-此外,您还可以覆盖->
- 克里斯说一个关于C++的东西当然会有很大的不同。但是既然我们在讨论为什么C是这样设计的,让我们假设我们回到了20世纪70年代——在C++存在之前。
- 我的最佳猜测是,箭头操作符的存在是为了直观地表达"注意它!你在处理一个指针。"
- 乍一看,我觉得这个问题很奇怪。并不是所有的东西都经过精心设计。如果你一生都保持这种风格,你的世界就会充满疑问。得到最多选票的答案确实很有信息性和明确性。但它没有触及你问题的关键点。按照你的提问风格,我可以问太多的问题。例如,关键字"int"是"integer"的缩写;为什么关键字"double"也不更短?
- @君望河这个问题实际上是一个值得关注的问题——为什么.操作符的优先级高于*操作符?如果没有,我们可以有*ptr.member和var.member。
- 这个。and->运算符表示完全不同的操作。前者表示编译时已知的偏移量。后者在运行时取消引用指针,然后应用偏移量。取消对指针的引用会触发未定义的行为(并导致segfault等)。两者都用表达。将隐藏差异,使代码更难读取,更容易出错。
我将把你的问题解释为两个问题:1)为什么->甚至存在;2)为什么.不会自动取消指针的引用。这两个问题的答案都有历史渊源。好的。
为什么->甚至存在?好的。
在C语言的第一个版本(我将其称为"C参考手册"的CRM,1975年5月随第6版Unix提供)中,操作人员->具有非常专有的含义,而不是*和.组合的同义词。好的。
CRM所描述的C语言在许多方面与现代C语言有很大的不同。在CRM结构中,成员实现了字节偏移量的全局概念,可以将其添加到任何地址值,而不受类型限制。即,所有结构成员的所有名称都具有独立的全局含义(因此必须是唯一的)。例如,您可以声明好的。
1 2 3 4
| struct S {
int a;
int b;
}; |
名称a代表偏移量0,而名称b代表偏移量2(假设int类型的大小为2,没有填充)。语言要求翻译单元中所有结构的所有成员都具有唯一的名称或代表相同的偏移值。例如,在同一翻译单元中,您可以另外声明好的。
1 2 3 4
| struct X {
int a;
int x;
}; |
这样就可以了,因为名称a始终代表偏移量0。但是这个附加声明好的。
1 2 3 4
| struct Y {
int b;
int a;
}; |
因为它试图将a重新定义为偏移量2,将b重新定义为偏移量0,所以在形式上是无效的。好的。
这就是->操作符的切入点。由于每个结构成员名称都有自己的自足全局含义,因此语言支持如下表达式好的。
1 2 3
| int i = 5;
i->b = 42; /* Write 42 into `int` at address 7 */
100->a = 0; /* Write 0 into `int` at address 100 */ |
第一次赋值被编译器解释为"取地址5,加上偏移量2,并将42赋给结果地址的int值"。也就是说,上述将把42转让给地址7的int价值。注意,使用->并不关心左侧的表达类型。左侧被解释为右值数字地址(无论是指针还是整数)。好的。
这种欺骗是不可能与*和.的组合。你做不到好的。
因为*i已经是一个无效的表达式。由于*运算符与.独立,因此对其操作数施加了更严格的类型要求。为了解决这个限制,CRM引入了->运算符,它独立于左侧操作数的类型。好的。
正如基思在评论中指出的,->和*和.组合之间的差异,正是CRM在7.1.8中所指的"放宽要求":除了放宽E1为指针类型的要求外,表达式E1?>MOS与(*E1).MOS完全相同。好的。
后来,在K&R C中,许多最初在CRM中描述的功能都被显著地修改了。"结构成员作为全局偏移标识符"的想法被完全删除。->操作符的功能与*和.组合的功能完全相同。好的。
为什么.不能自动取消对指针的引用?好的。
同样,在语言的CRM版本中,.运算符的左操作数必须是左值。这是对该操作数施加的唯一要求(这也使得它不同于上述的->)。注意,CRM不要求.的左操作数具有结构类型。它只要求它是一个左值,任何左值。这意味着在CRM版本的C中,您可以编写这样的代码好的。
1 2 3 4 5
| struct S { int a, b; };
struct T { float x, y, z; };
struct T c;
c.b = 55; |
在这种情况下,编译器会将55写入一个int值,该值位于称为c的连续内存块中的字节偏移量2处,即使类型struct T没有名为b的字段。编译器根本不关心c的实际类型。它只关心c是一个左值:某种可写内存块。好的。
现在注意如果你这样做了好的。
代码被认为是有效的(因为s也是一个左值),编译器只会试图在字节偏移量2处将数据写入指针s本身。不用说,像这样的事情很容易导致记忆溢出,但是语言本身并不关心这些事情。好的。
也就是说,在该语言版本中,对于指针类型重载操作符.的建议不起作用:操作符.在与指针一起使用时(与lvalue指针或任何lvalue一起使用时)已经有了非常具体的含义。毫无疑问,这是非常奇怪的功能。但当时它就在那里。好的。
当然,这种奇怪的功能并不是在C-K&R C的修订版本中引入指针(如您所建议的)的重载.运算符的一个很好的理由,但它还没有完成。也许当时有一些用CRM版本C编写的遗留代码必须得到支持。好的。
(1975 C参考手册的URL可能不稳定。另一份可能有细微差别的副本在这里。)好的。好啊。
- 引用的C参考手册的第7.1.8节说,"除了放宽e1为指针类型的要求外,表达式"e1?>mos""与""(*e1).mos""完全等效。"
- 为什么它不让*i成为某个默认类型(int)的左值?在地址5?那么(*i).b也会以同样的方式工作。
- @利奥:嗯,有些人认为C语言是高级汇编语言。在C历史的那个时期,语言实际上是一个更高层次的汇编程序。
- '因为它试图将a"重定义"为偏移量2,将b"重定义"为偏移量0'——为什么偏移量是2,而不是1?
- @我指的是数据字段从结构开始的字节偏移量。如果int的大小是2个字节,那么int类型的顺序成员将具有偏移量0、2、4、6等。
- 你可能想考虑在维基变得太老之前将其标记出来。通常来说,自动维基是为了防止恶意碰撞,但这里不是这样。
- 你写过Later, in K&R C many features originally described in CRM were significantly reworked. The idea of"struct member as global offset identifier" was completely removed.,但在C语言的发展过程中,丹尼斯M.里奇说:"While it foreshadowed the newer approach to structures, only after it was published did the language support assigning them, passing them to and from functions, and associating the names of members firmly with the structure or union containing them."。你能给我一个更详细的解释吗?
- @安德烈YT在论文的基础上开发了C语言,仿佛在K&R中,结构成员仍然是一个全局偏移标识符。顺便问一下,你有K&R的电子书吗?如果有的话,你能给我一份吗?我的电子邮件是[email protected]。非常感谢你。
- 呵呵。因此,这就解释了为什么Unix中的许多结构(例如,struct stat前缀它们的字段(例如,st_mode前缀)。
- @完美主义M1ng:看起来贝尔实验室网站已经被阿尔卡特朗讯公司接管,原来的页面也不见了。我更新了到另一个站点的链接,尽管我不能说这个站点能保持多久。无论如何,谷歌搜索"里奇C参考手册"通常会找到文件。
- 疯狂地冲向记忆的小径。我正在尝试重新激活一些40年前的C代码,其中作者使用这种方式的结构来实现一种联合。
除了历史原因(好的和已经报告的)之外,还有一个与运算符优先级有关的小问题:点运算符的优先级高于星运算符,因此,如果您的结构包含指向结构的指针,并且包含指向结构的指针…这两个相当:
1 2 3
| (*(*(*a).b).c).d
a->b->c->d |
但第二个更清晰易读。箭头运算符具有最高优先级(与点一样),并从左向右关联。我认为对于指向struct和struct的指针,这比使用点运算符更清楚,因为我们从表达式中知道类型,而不必查看声明,甚至可以在另一个文件中。
- 对于同时包含结构和指向结构的指针的嵌套数据类型,这会使事情变得更加困难,因为您必须考虑为每个子成员访问选择正确的运算符。你可能会得到a.b->c->d或a->b.c->d(我在使用freetype库时遇到了这个问题-我需要一直查找它的源代码)。另外,这也不能解释为什么在处理指针时编译器无法自动取消对指针的引用。
- @比拉斯卡加:嗯,我不认为这比所有的括号更难理解,但也许这只是一个品味问题。不管怎样,没有必要的理由,几乎所有的语言都可以用另一种方式,我只是想说为什么操作者是有用的。并非每件事都是严格必要的,我们甚至可以不需要转换或为之生活,但它们是有用的。
- 为什么投反对票?请留下评论。
- 虽然你所说的事实是正确的,但他们没有以任何方式回答我原来的问题。你解释了A->和*(A)的相等性。注释(已经在其他问题中解释了多次)以及关于语言设计的模糊陈述具有一定的武断性。我觉得你的回答没什么帮助,所以投了反对票。
- @我的观点不是两种不同形式的相等性,而是箭头操作符的一个可能优势,您的问题是为什么要将它添加到语言中。但如果你在寻找一个被证实的历史原因,是的,我的答案不能提供这个。不管怎样,谢谢你回来解释你的决定。
- @Effeffe,the op is saying the language could have easily interpreted a.b.c.das (*(*(*a).b).c).d,rendering the ->useless.因此,作者的版本(a.b.c.d)是平等的(与a->b->c->d相比)。这就是为什么你的答案不回答作战问题。
- @Shahbaz Hmm,yes,probably the only relevant part of my answer is the last one,but it's still a little bit arbitrary.我太晚才明白这个问题。
- @Shahbaz that may be the case for a Java programmer,A/C++programmer will understand EDOCX1&0&and EDOCX1&4).as two very different things:first is a single memory access to a nested sub-object(There is only a single memory object in this case),the second is three memory access,chasing throughCTS这是记忆层的一个不同之处,我相信这是正确的区分这两个案例。
- @Cmaster,as much as I am with you on insulting Java programmers,your argue is not a very good one.例如,在a + b的情况下,如果a和b是intS或floatS,特别是在没有FPU的地方,性能就有差异。应该区分int和float更多?由于这是隐蔽的mov原因,如何取消整合促进?这一点是,a.b可以同时兼任a.b和a->b的工作,以a为基础。这里没有矛盾。
- @Cmaster,and it's not like you are gaining anything from making a distinction.如果你写入了EDOCX1&15,编译器给你一个错误的说法EDOCX1&8,是一个指针,你突然改变了你的思维,因为EDOCX1&17&If you write EDOCX1&15,perhaps writing a macro to avoid passing a pointer to a function or pass the struct by value?复制这个网站码到您的网站上以设置一个投票箱在您的网站上。
- @Shahbaz I didn't mean that as an insult of the Java programmers,they are simply used to a language with fully implicate pointers.我曾经是美洲豹编程员,我也这么想无论如何,我真的认为,我们在C中看到的运算符超载的是最优化的。然而,我意识到,我们所有的人都被数学家所掠夺,数学家们为了美丽的事物而把他们的运动员的负担过重。我也理解他们的动机,因为一组可用符号是有限的。我想,在最后,这只是一个问题,你在画线…
- @Shahbaz你得到了一个安全的位子,当你需要确定你不是任何人。A.B.C.d.is guaranteed to succeed as long as a is fully formed(initiatized).a->b->c->d will segfault if any of a,b,c are nulls.另一方面,请告诉你在哪里做间接记忆存取。
C也很好地避免了任何模棱两可的事情。
当然,点可以被重载来表示这两种情况,但是箭头确保程序员知道他在指针上操作,就像编译器不会让您混合两种不兼容的类型一样。
- 这是简单而正确的答案。c/大部分努力避免超载,因为IMO是关于C的最好事情之一。
- C中的许多事情都是模糊和模糊的。有隐含的转换类型,数学操作员的负荷过重,链接指数是否完全不同取决于你指数一个多维阵列或一个指针阵列,而任何东西都可能是一个宏观隐藏的任何东西(上次命名的公约帮助在那里但C doesn)。