为什么C中的箭头( – >)运算符存在?

点( . )运算符用于访问结构的成员,而C中的箭头运算符( -> )用于访问由所讨论的指针引用的结构的成员。

指针本身没有任何可以用点运算符访问的成员(实际上它只是一个描述虚拟内存中的位置的数字,所以它没有任何成员)。 所以,如果我们只是定义了点运算符,如果指针(编译器在编译时已知的信息)上使用它,就会自动解引用指针。

那么为什么语言创作者决定通过添加这个看似不必要的操作符来使事情更加复杂呢? 什么是大devise决定?

我会把你的问题解释为两个问题:1)为什么->甚至存在,2)为什么. 不会自动解引用指针。 这两个问题的答案都有历史根源。

为什么->甚至存在?

在C语言的第一个版本之一(我将在1975年5月第6版Unix中提到的“ C参考手册 ”中称为CRM),operator ->具有非常独特的含义,而不是与**同义词. 组合

CRM所描述的C语言在很多方面与现代C有很大的不同。 在CRM中,结构体实现了字节偏移的全局概念,可以将其添加到任何没有types限制的地址值中。 即所有结构成员的所有名称都具有独立的全局含义(因此必须是唯一的)。 例如,你可以声明

 struct S { int a; int b; }; 

并且名字a将代表偏移量0,而名字b代表偏移量2(假定inttypes的大小为2并且没有填充)。 所需的语言要求翻译单位中所有结构的所有成员具有唯一的名称或者表示相同的偏移值。 例如,在同一翻译单位,你可以另外申报

 struct X { int a; int x; }; 

那就没问题了,因为名字a会一直代表偏移0.但是这个额外的声明

 struct Y { int b; int a; }; 

将是正式无效的,因为它试图“重新定义” a为偏移量2, b为偏移量0。

这就是->运算符所在的地方。由于每个结构成员名称都有其自足的全局含义,所以支持的语言如

 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值。 请注意,使用->并不关心左侧expression式的types。 左手边被解释为右值数字地址(无论是指针还是整数)。

*和这种欺骗是不可能的. 组合。 你做不到

 (*i).b = 42; 

因为*i已经是一个无效的expression式。 *运营商,因为它是分开的. ,对其操作数施加更严格的types要求。 为了提供解决此限制的能力,CRM引入了->运算符,该运算符与左侧操作数的types无关。

正如Keith在评论中指出的那样, ->* +之间的区别. 组合是CRM在7.1.8中所说的“放宽要求”: 除了放宽指针型E1的要求外, E1−>MOS的expression式恰好等于(*E1).MOS

后来,在K&R C中,最初在CRM中描述的很多function都进行了重新修改。 “结构成员作为全局偏移量标识符”的想法被彻底删除。 ->运算符的function与*和的function完全相同. 组合。

为什么不能. 自动提取指针?

再次,在CRM版本的语言左边的操作数. 运营商被要求是一个左翼 。 这是对该操作数强加的唯一要求(这正是上面解释的->区别)。 请注意,CRM不需要左边的操作数. 有一个结构types。 它只是要求它是一个左值, 任何左值。 这意味着在C的CRM版本中,你可以编写这样的代码

 struct S { int a, b; }; struct T { float x, y, z; }; struct T c; cb = 55; 

在这种情况下,即使typesstruct T没有名为b字段,编译器也会将55写入位于连续内存块c中的byte-offset 2处的int值。 编译器根本不关心c的实际types。 所有它关心的是c是一个左值:某种可写的内存块。

现在请注意,如果你这样做

 S *s; ... sb = 42; 

代码将被认为是有效的(因为s也是一个左值),编译器会简单地尝试在字节偏移量2处将数据写入指针本身 。不用说,这样的事情很容易导致内存溢出,但是这个语言并不关心这些事情。

即在该语言的版本您提出的重载操作符的想法. 对于指针types将不起作用:运算符. 在与指针(左值指针或任何左值)一起使用时已经有了非常具体的含义。 毫无疑问,这是非常奇怪的function。 但当时在那里。

当然,这个奇怪的function并不是引入重载的一个强有力的理由. 在C – K&R C的返工版本中,指针的操作符(正如你所build议的那样),但是还没有完成。 也许在那个时候,C语言的CRM版本中有一些遗留代码需要支持。

(1975 C参考手册的URL可能不稳定,可能有一些细微差别的另一个副本在这里) 。

除了历史(好的和已经报道的)原因之外,运算符优先级还有一点小问题:点运算符的优先级高于星形运算符,所以如果你的结构包含指向包含指向struct的结构的指针…这两个是等价的:

 (*(*(*a).b).c).d a->b->c->d 

但是第二个显然更具可读性。 箭头运算符具有最高的优先级(就像点),同时从左到右。 我认为这比使用点运算符指向结构和结构更清晰,因为我们知道expression式的types,而不必查看声明,甚至可以在另一个文件中。

C也没有做出任何模棱两可的工作。

确定点可以重载意味着两个东西,但箭头确保程序员知道他正在使用一个指针,就像编译器不会让你混合两个不兼容的types。