为什么C有区别 – >和。
好吧,这没有什么严重的后果,但它一直在困扰我一段时间:是否有理由区分->
和.
运营商?
当然,现在的规则是这样的.
作用于结构,并且->
作用于指向结构(或联合)的指针。 但是在实践中它是如何工作的 让s
是一个包含元素x
的结构体,让ps
是一个指向相同forms的结构体的指针。
如果你写
s->x
编译器会以这种方式吐出一个警告
你的意思是请重新input并重新编译。
如果你写
ps.x
编译器会以这种方式吐出一个警告
你的意思是ps-> x。 请重新input并重新编译。
由于编译器在编译时知道s
和ps
的types,因此它具有解释正确运算符所需的所有信息。 我怀疑这不像其他警告(如缺less分号),因为正确的修复没有任何含糊之处。
所以这是一个对C1x标准委员会的假设build议(这是永远不会考虑的,因为ISO是保守的):
给定expression式lhs.rhs,如果lhs是一个结构体或者联合体types,那么这个expression式应该指lhs这个名为rhs的元素。 如果lhs是指向struct或者-union的types,那么这应该被解释为(* lhs).rhs。
这肯定能够节省我们所有的时间,并且让人们更容易学习C [而且我已经教导了足够多的C以权威的方式说,学习者觉得这个东西要么令人困惑,要么令人讨厌。]
甚至还有先例,C做了一些类似的事情。 例如,由于实现的原因,函数声明总是强制转换为函数指针,所以f(x,y)
和(*f)(x,y)
都将工作,无论f
被声明为函数还是指针起作用。
所以,我的问题是:这个build议有什么问题? 你能想到在ps.x
和sx
之间会有致命的含糊不清的例子,或者为什么保持强制性的区别是有用的?
那么,如果你真的想把这种function引入到C语言的规范中来,那么为了使它与其他语言“融合”,合乎逻辑的事情就是将“衰减”的概念扩展到指针“来结构types。 你自己用一个函数和一个函数指针做了一个例子。 这样做的原因是因为C中的函数types衰减到所有上下文中的指针types,除了sizeof
和一元&
运算符。 (同样的事情发生在arrays,顺便说一句。)
因此,为了实现类似于你所build议的东西,我们可以引入“struct-to-pointer decay”的概念,它的工作方式与C中的所有其他“衰变”(即array-to – 指针衰减和函数指针衰减)工作:当在expression式中使用typesT
的struct对象时,其types立即衰减,以便键入T*
– 指向结构对象开始的指针 – 除非它是一个操作数sizeof
或unary &
。 一旦这样的衰减规则被引入到结构中,你可以使用->
运算符来访问struct元素,而不pipe你是否有指向结构体的指针或左边的结构本身。 运营商.
在这种情况下将变得完全不必要(除非我错过了某些东西),你总是使用->
和->
。
以上再一次说明,如果是以C语言的精神来实现的话,这个function会是什么样子呢。
但是我会说(同意查尔斯所说的),指向结构的代码和用于结构的代码之间的视觉差异的损失并不是完全可取的。
PS这样一个结构衰减规则的一个明显的负面结果是,除了目前的新手无私地相信“数组只是常数指针”之外,我们会有一群新手无私地相信“结构对象只是常量指针”。 而Chris Torek的数组常见问题将会大约需要1.5-2倍以覆盖结构:)
我不认为你所说的话有什么不对劲。 使用.
为结构指针将工作。
但是,我喜欢这样一个事实,即对结构和结构的指针被区别对待。
它给出了关于操作的一些背景和关于什么可能是昂贵的线索。
考虑一下这个片段,想象它正处于一个相当大的函数中。
sc = 99; f(s); assert(sc == 99);
目前我可以告诉s
是一个结构。 我知道它将被完整地复制给f
。 我也知道那个断言不能开火。
如果使用.
指针指向结构被允许,我不知道任何和断言可能会触发, f
可能会设置sc
(错误 – s->c
)的东西。
另一个缺点是会降低与C ++的兼容性。 C ++允许->
被类重载,以便类可以像“指针”一样。 这很重要.
和->
行为不同。 使用的“新”C代码.
指向结构的指针不会再被C ++代码所接受。
那么显然没有任何含糊之处,或者这个build议是不可能的。 唯一的问题是,如果你看到:
p->x = 3;
你知道p
是一个指针,但如果你允许:
px = 3;
在这种情况下,你实际上并不知道,这可能会造成问题,特别是如果你以后再使用这个指针并使用错误的间接级别。
C编程语言(与其相对的C ++相反)的一个显着特点是成本模型非常明确 。 点与箭头的区别是因为箭头需要额外的内存引用,而且C非常小心地使内存引用的数量从源代码中显而易见。
那么,肯定会遇到这样的情况:
(*item)->elem
(我曾经在一些程序中发生过),如果你写了类似的东西
item.elem
这意味着上述情况,可能会令人困惑的是,elem是一个struct项的元素,还是项指向的结构体的元素,或者是被指向的结构体中的元素是列表中的元素,迭代器项目等,等等。
所以,当使用指向结构指针的指针时,它会使事情变得更清晰一些,&c。
是的,没关系,但这不是C真正需要的
不仅是好的,而且是现代风格。 Java和Go都只是使用而已.
。 由于所有不适合寄存器的东西在某种程度上是一个参考,所以事物和指向事物的指针之间的区分肯定是有点武断的,至less在函数调用之前是这样。
第一个进化步骤是制作解引用运算符后缀,一些dmr曾经暗示他在某个点上是优先的。 帕斯卡这样做,所以它有p^.field
。 唯一的原因就是 ->
运算符,因为必须键入(*p).field
或p[0].field
才是愚蠢的。
所以是的,它会工作。 它甚至会更好,因为它在更高的抽象层次上工作。 人们应该能够做出尽可能多的改变,而不需要下游代码改变,这在某种意义上是更高级语言的全部观点。
我认为使用()
函数调用和[]
数组下标是错误的。 为什么不允许不同的实现导出不同的抽象?
但是没有太多的理由去做这个改变。 C程序员不太可能会因为缺乏语法糖扩展而反抗,这种扩展在expression式中保存了一个字符,反正很难使用,因为如果被普遍采用,它将不会立即被使用。 请记住,当标准委员会stream氓时,他们最终会鼓吹空房间。 他们需要世界编译器开发人员的合作和意愿。
C真正需要的并不是编写不安全代码的方法。 我不介意在C工作,但是项目经理不喜欢让他们的可靠性由他们最坏的人来决定,而C真正需要的是一种安全的方言, 像Cyclone ,或者就像Go一样 。
如果有的话,当前的语法可以让代码的读者知道代码是否与指针或实际对象一起工作。 有些不知道代码的人事先了解它。