V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
vcfghtyjc
V2EX  ›  C++

《C++ Primer》关于 reference 和 pointer 部分看的人“生气”

  •  
  •   vcfghtyjc · 2022-03-15 12:13:14 +08:00 · 7262 次点击
    这是一个创建于 980 天前的主题,其中的信息可能已经有所发展或是发生改变。

    对 C++一直是一知半解。最近正式的重读《 C++ Primer 》,不知道为什么看到 reference 和 pointer (章节:2.3 )莫名的生气。

    不理解当初设计 C++的人多喜欢“&” 和 “*” 这两个符号。为什么不同功能要用同样的符号啊?!"&" 放在定义处代表引用,放到定义过的变量前代表取地址。键盘上那么多符号,就不能换个符号吗?当初大学 C++学的不好这个设计要背一半的锅。😂

    64 条回复    2022-03-25 14:09:25 +08:00
    Mohanson
        1
    Mohanson  
       2022-03-15 12:16:50 +08:00   ❤️ 13
    学 Rust 吧, 只要键盘上能打出的符号在 Rust 里都被用作 token 了.
    yzbythesea
        2
    yzbythesea  
       2022-03-15 12:17:29 +08:00
    & 是这个变量的地址,* 是这个指针地址里的内容。不太明白“代表引用”的含义,& 在定义处也是取址吧。
    learningman
        3
    learningman  
       2022-03-15 12:17:50 +08:00
    那我就想传个指针,是不是传成指针的引用了多一次操作?
    有聪明的啊,Go 加不加*一个语义,去吧(
    smallthing
        4
    smallthing  
       2022-03-15 12:20:54 +08:00 via iPhone   ❤️ 10
    笑死了,根本就是不懂
    codehz
        5
    codehz  
       2022-03-15 12:24:26 +08:00 via Android
    (问就是 c 遗产(
    因为*已经用来表示指针和解引用了,&已经用来表示取地址了,你还能用别的什么符号来表示引用吗
    l00t
        6
    l00t  
       2022-03-15 12:28:33 +08:00   ❤️ 1
    这是延续 C 的风格吧, * 本身不也有好几个意思吗? 声明和定义的时候表示指针,实际用的时候表示指针指向。这都是小事了。

    extern ,static 那一堆才看着烦。
    darklights
        7
    darklights  
       2022-03-15 12:30:25 +08:00 via Android
    相信我,就算换了个符合,大学 C++该学不好的还是学不好。
    statumer
        8
    statumer  
       2022-03-15 12:31:54 +08:00   ❤️ 15
    等你学深了,就知道 C++ 的引用符号还只是小事
    我看楼主应该还没被 glvalue, prvalue, xvalue, lvalue, 右值引用, 引用的 value category 折磨过
    dobelee
        9
    dobelee  
       2022-03-15 12:34:35 +08:00 via iPhone
    golang 解君愁。
    vcfghtyjc
        10
    vcfghtyjc  
    OP
       2022-03-15 12:37:42 +08:00
    @statumer 刚看到里面提到 l-value, r-value 。看了两眼就先搁置了。。。
    ChaosesIb
        11
    ChaosesIb  
       2022-03-15 12:38:55 +08:00 via Android
    这只能说是理解不同,引用参数起的作用类似于传参时自动加&,那么把&加到参数声明处代表自动取址也就很自然了。语言对于引入新符号 /关键字总是保守的,历史包袱很重的 C++就更是这样了。
    hhjuteman
        12
    hhjuteman  
       2022-03-15 12:42:19 +08:00   ❤️ 4
    就这你就不行了啦?

    来一段模版类型推导折叠规则:
    T& & => 什么类型?
    T& && => 什么类型?
    T&& & => 什么类型?
    T&& && => 什么类型?


    你要是连 reference 的符号都觉得绕,劝你早日放弃 c++。
    yulon
        13
    yulon  
       2022-03-15 12:42:57 +08:00
    你要明白 C 和 C++ 是两个语言,设计引用的时候没有什么特别方便的语法,只能硬性规定声明里的 & 是引用。

    C 的声明满足 [左边结果类型,右边表达式] ,右边的所有符号其实是操作符不是修饰符。

    C++ 是怎么方便怎么来了,声明里的 & 确实算是修饰符了,还有 using type ,你用 typedef 多重嵌套的函数指针会想死的。
    Huelse
        14
    Huelse  
       2022-03-15 12:45:19 +08:00
    个人认为 C++中左右值和转移语义最重要且实用
    Kasumi20
        15
    Kasumi20  
       2022-03-15 12:46:00 +08:00
    不都是指针?引用让程序员减少了一步解引用而已
    zvl0reqglvd
        16
    zvl0reqglvd  
       2022-03-15 12:46:14 +08:00
    初学者还是不要找虐了。。。。
    vcfghtyjc
        17
    vcfghtyjc  
    OP
       2022-03-15 12:51:55 +08:00
    @yulon 感觉这个解释让我比较容易理解。你这里的的左边是这个意思吗?

    int [左边] variable_name [右边]

    所以当 "&" 在左边时是用来表述类型,当 "&" 在右边时表示操作符(取地址)。
    littlewing
        18
    littlewing  
       2022-03-15 12:56:21 +08:00
    @vcfghtyjc 智能指针、右值引用、移动语义、完美转发,这都是现代 C++ 的重要特性,不学这些等于没学现在 C++
    littlewing
        19
    littlewing  
       2022-03-15 12:58:05 +08:00   ❤️ 1
    C++ Primer 那本书不用看得太细,很多地方都太细节了

    另外,你要多写写就懂了,光看是不行的

    推荐 《 Effective Modern C++》这本书
    vcfghtyjc
        20
    vcfghtyjc  
    OP
       2022-03-15 12:59:18 +08:00
    @littlewing 多谢指出。等我慢慢看。
    fgwmlhdkkkw
        21
    fgwmlhdkkkw  
       2022-03-15 13:02:37 +08:00
    @Mohanson 哈哈哈哈哈哈
    dongcidaci
        22
    dongcidaci  
       2022-03-15 13:03:58 +08:00 via Android
    冷静一下,其实你会发现也没有那么难理解
    jasonkayzk
        23
    jasonkayzk  
       2022-03-15 13:23:01 +08:00   ❤️ 1
    杰哥:我看你是完全不懂喔~( doge
    freeup
        24
    freeup  
       2022-03-15 13:31:51 +08:00
    最近我也在看 C++这本书 对于这块感觉还好 还是满能接受的 估计是因为之前 C#中使用简单的指针操作也是这两个符号吧
    crackhopper
        25
    crackhopper  
       2022-03-15 13:39:08 +08:00
    python 的**在行参和实参,行为还不一样呢。。当然还是 C++更变态点,建议汇编水平高了,结合编译的汇编来看,帮忙加深理解。

    我个人觉得 C++难以理解的有 memory_order...反正看 cpp reference 只会看晕,还涉及缓存可见、缓存一致。模板我学得也比较差,大概皮毛吧,主要不写库没空搞,也就掌握到 reference collapse 和一些实例化流程方面了解一点。abi 也不够了解,我个人觉得 abi 问题可以用汇编解决,但也没去深入研究过。链接机制虽然知道,但没有在源码层面有了解。elf 格式了解的也比较皮毛。我觉得基本 c/c++还是要配合汇编、操作系统、CPU 、GPU 等等一起学,加深理解。如果用不上这些,也没必要用 c/c++,其他语言更方便。
    crackhopper
        26
    crackhopper  
       2022-03-15 13:46:40 +08:00   ❤️ 1
    当然,如果 C++比较熟练,其实写起来也挺快的。一般自己都会积累常用的轮子和自己造的轮子。适用于特定领域。
    按照 linus 的说法是,C++很可能不同人掌握的深入不一样,会有大量的沟通成本。因为 c++一些行为是隐式的,所以他更推崇 c 。有一定道理,但 c 写起来会麻烦点,需要把轮子重新造一遍。可能 c++最大的问题就是如何控制参与项目的人所掌握的深度,和开发中所使用的深度。(毕竟:C++ makes it harder to shoot yourself, but when you do it blows your whole leg off )
    如果只是应用层级的开发,不涉及硬件,不设计高性能计算,换语言更好吧,毕竟即使不同人掌握的深度深浅不一也不会造成太大的灾难。
    dongfang
        27
    dongfang  
       2022-03-15 13:47:39 +08:00   ❤️ 2
    你说的引用符号是不会出现混淆的。
    因为是声明的时候用,int& 是引用。
    指针是这样子,int*。
    完全不会混淆。
    上面都是声明的时候,作为声明的类型。
    &作为一目运算符,是取地址。
    &作为两目运算符,是按位与。
    *作为一目运算符,是对指针取值。
    *做为两目运算符,是乘法。
    也不会混淆,绝大多数情况下,语义是明确的。
    可能出现混淆的时候,要加括号,比如
    a*(*ptr)。这样别人也很容易看出,第一个*是乘法,第二个是对指针取值。
    C++里面&和*的多重的语义,我个人是感觉没问题的。
    反倒是,像 golang 里面的 switch 的多重语义放在一起,我感觉不是很好。
    另外,如果你觉得引用类型声明的时候,不用&这个符号,你想用键盘上的哪个呢?
    int$这样子,还是 int@这样子?感觉都挺丑的,还不如 int&。
    SingeeKing
        28
    SingeeKing  
       2022-03-15 13:51:25 +08:00
    @Mohanson #1 perl 不服 [手动狗头]
    newmlp
        29
    newmlp  
       2022-03-15 13:52:48 +08:00
    引用其实就是指针,不过就是个语法上有区别
    dongfang
        30
    dongfang  
       2022-03-15 14:03:40 +08:00
    再多说一点,*和&混用,一般也不会混淆。
    比如,
    int* & a = ptr;
    这里的 a 是对指针类型 int*的引用。
    int& * a; 这个是无法编译的。
    不能定义一个指向引用的指针。
    dongfang
        31
    dongfang  
       2022-03-15 14:13:41 +08:00
    还有一点,从语义上,引用和指针没有任何关系。
    引用是引用,指针是指针。
    编译器在底层可以通过指针去实现引用这个功能,但是这并不是标准的规定。
    从语义上,引用是取一个变量的别名,指针是内存中的地址。
    所以,引用可以在内存中没有地址,一种很简单的思路,
    就比如 int& a = b;
    直接用#define a b 来实现。
    也不是不可以。只不过这没法实现引用的其他功能。
    dongfang
        32
    dongfang  
       2022-03-15 14:19:37 +08:00
    如果你在语义上,无法区分引用和指针。
    那后面的左值引用、右值引用,移动语义就搞不明白了。
    有了这些东西能避免很多时候的拷贝开销。
    用指针也可以实现这些优化,但是太繁琐了,不够现代
    Tianqi
        33
    Tianqi  
       2022-03-15 16:07:20 +08:00
    C++为了兼容 C 语言和向后兼容做了很多妥协
    wffnone
        34
    wffnone  
       2022-03-15 16:17:21 +08:00   ❤️ 1
    你生气发帖的时候,别人已经学下一节了。
    fpure
        35
    fpure  
       2022-03-15 16:33:41 +08:00
    C 语言的变量声明的规则类似模式匹配,C++的引用显然违背了这个规则
    exonuclease
        36
    exonuclease  
       2022-03-15 17:41:43 +08:00
    cpp 控制的东西太细节了所以符号看起来乱七八糟的 不过至少同样的功能 c#的语法好看一点
    public struct Point3D
    {
    private static Point3D origin = new Point3D(0,0,0);
    public static ref readonly Point3D Origin => ref origin; // static const Point3D& Origin = origin;
    }
    cybird
        37
    cybird  
       2022-03-15 17:45:02 +08:00
    ?????
    指针是指针、引用是引用、右值是右值、左值是左值、表达式是表达式。
    GeruzoniAnsasu
        38
    GeruzoniAnsasu  
       2022-03-15 17:46:41 +08:00
    @hhjuteman 我 tm 直接按着编译器的头让它吐个报错来告诉我(恼
    exonuclease
        39
    exonuclease  
       2022-03-15 17:54:15 +08:00
    @exonuclease 好久不写 cpp 好像写错了 这里应该只有声明 static const Point3D& Origin
    leirenbb
        40
    leirenbb  
       2022-03-15 19:26:52 +08:00 via iPhone
    那你还能说 c++不如都搞成指针 再加上 gc 之类的。不要玩什么栈变量呢🤣
    xfriday
        41
    xfriday  
       2022-03-15 23:12:13 +08:00
    “ C++ 根本解决不了 C 语言的问题,它只会让事情变得更糟。这真是一门很烂的语言。” -- Linus
    FrankHB
        42
    FrankHB  
       2022-03-15 23:31:49 +08:00
    @statumer 你这算啥,好歹算是理论上扯得通的。
    看着 ISO/IEC 14882 扯什么 polymorphic class 就管 inclusion polymorphism 而对 parametric polymorphism (template) 和 ad-hoc polymorphism (coercion, overloading) 睁眼瞎就来气。
    今天还刚婊过 structural bindings 不算 variables (放置 IEC 2382 的定义)就是设计者菜(搞得一开始都没法 capture ),除了能偷懒不改 decltype 的规则以外一点“好处”都没有。
    当然这些比起 golang spec 这种指 object 叫 variable 的玩意儿来讲,血压上升效果还不那么明显。
    FrankHB
        43
    FrankHB  
       2022-03-15 23:34:31 +08:00
    FrankHB
        44
    FrankHB  
       2022-03-16 00:49:59 +08:00   ❤️ 3
    @vcfghtyjc 算了正经回一下 OP 吧。
    引用虽然没什么大不了的,但是左值右值这种 1960 年代的经典基础常识理解不了,建议回炉,否则以后基本只能混所谓 GC 语言的饭了。
    cf. https://en.wikipedia.org/wiki/Fundamental_Concepts_in_Programming_Languages
    很少有单独语言特性的重要性超过这种基本设计。一个少数例外是 1930 年代的 lambda 。

    一开始叫左右是因为 CPL 里的原始设计就是按文法性质分的。之后 BCPL 和 B 里就把意图解释明白了:
    https://www.bell-labs.com/usr/dmr/www/bcpl.pdf
    https://www.thinkage.ca/gcos/expl/b/manu/manu.html#Section6_2
    C 也一样,C++只是多加了几种。
    当然这个设计就结果上其实也不咋地,当年还有合并引用和左值的: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/1992/N0130.pdf ;很大原因是因为 C 的左值没算到类型系统里。
    FrankHB
        45
    FrankHB  
       2022-03-16 00:58:11 +08:00   ❤️ 2
    @yulon 建议回炉重修 C 的声明语法。
    C++的&、C++/CLI 的^,都是 C 的*声明符设计的直接扩展,区别无非是可以声明指针的指针而不能声明引用的引用。
    而 C 事实上就从来就没什么左边结果类型右边表达式的语法要求。
    一个权威实例:<signal.h>的 signal 函数的原型声明在 ISO C 原文中的写法:
    void (*signal(int sig, void (*func)(int)))(int);
    请自行脑补什么叫左边什么叫右边。
    (顺便,所谓“右左法则”也是错的,实际的 parser 不照这个方式工作。)

    @dongfang 不用解释这么多,言多必失。
    对年轻人讲清楚最简单重要的一点即可:指针是对象类型的一类,引用是和对象类型的并列的一大类类型。
    (我终于想起来这种经问题我怎么会没总结过……)
    https://stackoverflow.com/a/54731129
    你的说法的一些技术问题:
    1.&作为声明符会和操作符混淆,首先是因为上下文到底是声明还是表达式就有可能混淆——你不能把不存在混淆的假设作前提。这种问题没法用括号消除(括号自己同时能构成声明符和操作符而具有歧义),像 C 的*( C 的语法光是声明和表达式部分就不是 CFG )。而 C++因为标点在各种上下文的滥用,远远严重得多。语言明确了消歧义规则也不表示没问题,例如很多时候不经过实例化根本就不能区分到底是不是 well-formed ,导致语法分析依赖语义的判断(实际的实现套路是把各种路径都试一遍看看是否恰好有一种是通的)。这个意义上来讲,C++基本上就没有传统意义上的语法(syntax)。当然,这个问题和 OP 的抱怨没有关系。
    2.别名是过于笼统的说法,正经的“别名”(按提案和标准里的规则)是专属于 structural binding 的(虽然我有提过这个设计很烂)。而引用声明之后引入的是正儿八斤的变量,并不只是别名。
    3.指针和引用首先是类型而不是类型的居留(如表达式的值)。
    4.指针的值不保证是所谓“内存”中的“地址”。(先无视 memory 是不是该翻译成内存的问题。)函数指针就不保证值对应所谓内存中的地址;事实上标准中虽然提了函数地址,却根本就没扯清楚含义(跟对象指针有 memory model 和 object model 罩着不一样),至今云里雾里(但是因为先前 CWG 的低效我暂且没有心情去找茬)。
    5.基本上所有的 C++编译器都不会用指针去实现引用,因为基本上在前端就已经把类型信息都消化完了。两者可能翻译成同样的 IR 并且可能二进制兼容,但翻译的路径是并行的,并非一个代替另一个。生成的代码里有 ISA 汇编意义上的指针,但和前端的指针完全两码事。
    6.重写为#define a b 违反了比几乎所有语义规则原则性上都重要得多的 translation phase 的要求,基本上除非你把预处理器实现成一个完整的解释器都无法使用这种方式实现。

    另外几个认为引用是指针的对号入座重修吧,不回了。
    vcfghtyjc
        46
    vcfghtyjc  
    OP
       2022-03-16 02:53:26 +08:00
    @FrankHB 左值 /右值能详细讲讲吗?或者有比较好的资料可以分享吗? wiki 上看的一知半解。
    wffnone
        47
    wffnone  
       2022-03-16 05:56:59 +08:00
    @vcfghtyjc 我建议是,你问问题最好讲清楚你到底哪里不懂。我知道,我知道,你不知道你哪里不懂,但是你也要尽量去说一说。。好心给你们讲知识的网友是见一次少一次,,,,且问且珍惜啊。
    yulon
        48
    yulon  
       2022-03-16 06:21:18 +08:00
    @FrankHB 不是很懂你解释了半天在解释什么东西?

    void 是结果类型,(*signal(int sig, void (*func)(int)))(int) 是表达式,我也说过函数指针嵌套就是一个经典例子啊?

    就像 int *a, b; 你给我说说看,这个 * 是属于左边的 int ,还是右边的 a 啊?你不会和我说 b 也是 int 指针吧?
    vcfghtyjc
        49
    vcfghtyjc  
    OP
       2022-03-16 07:28:02 +08:00
    @wffnone 哈哈,我没有看不懂,我就是吐槽一下。😛
    darknoll
        50
    darknoll  
       2022-03-16 07:47:28 +08:00 via Android
    用引用避免了 null 的情况,引用其实就是 type* const
    zxCoder
        51
    zxCoder  
       2022-03-16 08:22:28 +08:00   ❤️ 2
    兼容 C 加设计考虑不全

    至于楼上 c++大佬们解释的那些都是强行解释。。。

    c++这个语言就是这样,很强大,但是乱,难,懂的人少,而且这些人也以乱,难为傲
    wffnone
        52
    wffnone  
       2022-03-16 09:24:42 +08:00 via iPhone
    @yulon 我是路过的让我说两句
    结果表达式是你的表达啦,iso 里面的确没有这个说法。当然你这样理解也有你的道理。
    咱也不是新手程序员了吧,就别争了。我觉得 FrankHB 讲的非常细节,普通写应用程序可能用不到,但是在写库或者做一些对代码的自动化处理的时候,都是需要考虑的点。。
    aneostart173
        53
    aneostart173  
       2022-03-16 10:11:35 +08:00
    个人除了 const 引用之外,一律用指针。。
    Cloutain
        54
    Cloutain  
       2022-03-16 11:03:11 +08:00
    C++的语法有些地方确实糟糕。。。但不用生气,换种语法舒服的就行了,比如 C#
    yulon
        55
    yulon  
       2022-03-16 12:49:12 +08:00
    @wffnone 一门语言的初期设计本来就是混乱的,所以后期才要标准来硬性规定,C 初期的奇怪语法也删了很多。

    计算机编程本就是运用逻辑的过程,你不尝试去理解其中逻辑,只想着死记硬背,这固然是没有问题的,个人选择罢了。

    但是拿标准当令箭,动不动就让别人回炉重修,我好像也没说我说的是标准,你怎么知道我没看过标准,这两件事冲突吗,这两件事完全不冲突啊,难道在第一份标准出台以前,这个语言是不存在设计与逻辑与应用的?

    Dennis Ritchie 和 Bjarne Stroustrup 都明确表达过标准是用来约束实现 /供应厂商的( 2000 年 7 月的采访)。

    比如你花钱买了一份编译器,这个编译器却故意不实现某个功能,这样用户就受到了的损害,其实就是类似合约的东西。

    而不是让一些用户把这种精炼过的规则当圣经来制裁他人用的。

    我只能说做卷子可能确实是一种 gift 吧。
    FrankHB
        56
    FrankHB  
       2022-03-16 12:55:37 +08:00
    @yulon 指出你的问题就没花什么时间过脑子。显示的是自以为是的理解荼毒的状况。
    (*signal(int sig, void (*func)(int)))(int) 是个声明符,压根就不是什么表达式。
    我建议你把教会你这样混淆表达式和声明符语法的理解的文献都列出来,方便给人排雷。

    int *a, b; 里的 *a 和 b 都是声明符,*当然是属于(appertain)a 的。但挨在声明符边上的语法元素是否同时影响整个声明又是另一回事,要推断出对声明了什么的影响还早得很,更别说从属还得消歧义的了(比如属性)。

    排开混淆问题,这例子正好你是继续在给自己挖坑。
    void (*f1(int sig, void (*func)(int)))(int), *(*f2(int sig, void (*func)(int)))(int);
    ——什么叫左边什么叫右边?右边是个什么?
    FrankHB
        57
    FrankHB  
       2022-03-16 13:27:03 +08:00   ❤️ 1
    @yulon C 的初期设计可未必有 C++现在这么混乱,然而规定了也没卵用。
    “奇怪语法删了很多”你这是在打 C 的脸么,K&R C 的无原型函数声明一开始就被标记过时结果 C23 才被提议移除: http://www.open-std.org/JTC1/SC22/WG14/www/docs/n2841.htm

    “不尝试去理解其中逻辑,只想着死记硬背”亏你说得出来。你所谓的左边右边根本就不是死记硬背,而是生造原本不在语言设计中的逻辑。
    这反而才是你所谓的“精炼过的规则”。因为这种逻辑撑死只是覆盖了语言规则子集的二道贩子理解,并没能力涵盖原本的规则。
    然而正经的实现又不可能迁就这种半吊子理解。按你“固然是没有问题的”说法,结果就是得两套共存。
    (也不是说这算是你的独创。类似的二道贩子到处都是,还有各种二次创作的演绎,比这个更过分的公然颠倒黑白篡改原始设计都有,比如“C 没有对象”。)
    这种状况市场上是验证过的,典型例子是谭浩强——这类二次创作讲啥啥不清楚,就是非常擅长和你类似的自以为是的发明,然而自己又 fork 不了语言规范自己开发实现当作一个新的方言,所以注定就是不入流。
    有用户看到这种“个人选择”就往里跳,结果就是浪费时间凭空制造碎片。为了纠正二道贩子理解还得回炉,给正常教学和用户社区添乱。
    所以吃饱了撑着推销这种错误理解?
    选择半吊子发明的规则代替健全的规则,且不说理解多少内容——会支持这种思路的,本来就不算“学会”;这自然应当回炉,毫无疑问。

    注意 DMR 和 BS 所谓的标准约束厂商说的是标准化过程中的问题,不表示其他用户就应该有更优先的来源覆盖标准作为语言规范的效力。
    以为只有厂商才参考标准,正是这种学习思路下明确的误解。事实是不管什么用户想要报关于语言而不是方言的 bug ,首先参考的就是语言规范这种对任何想要符合规范的实现而言的规格说明书。
    忽略规范的后果就是用方言偷换主题。你说 C 人家会有兴趣跟你讨论,你说我就要谭浩强 C ,除了有兴趣研究历史和挂婊数落的,几个人鸟你?
    挂羊头卖狗肉还理直气壮,这就是道德问题了。
    yulon
        58
    yulon  
       2022-03-16 13:37:32 +08:00
    @FrankHB 啥叫继续挖坑,这不是很明显的事吗,左边依旧是 void ,右边是 (*f1(int sig, void (*func)(int)))(int) 和 *(*f2(int sig, void (*func)(int)))(int) 啊,不管你再写得怎么花里胡哨都一样。

    为什么符号是声明符的一部分,而不是类型的一部分,为什么类型只用在左边写一次,这个部分标准有说明吗?

    说白了咱俩讨论的根本就不是一个层次的东西,我本来就在讲标准以外的东西,我之所以讲标准以外的东西,因为标准所有人讲都一样,那么自己去看就好了,而你误以为我没看过标准,然后不用脑子举一些根本不能反驳我的例子,就非常的奇怪。

    举个例子:
    小孩:为什么国际象棋一方只有一个王。
    大人 A:因为大部分国家只有一个王。
    大人 B:建议 A 回炉重修规则,规则里一方只有一个王。
    FrankHB
        59
    FrankHB  
       2022-03-16 14:17:47 +08:00
    @yulon 所以你所谓的右边是个啥?“右边表达式”?
    得了吧。
    你到现在还不承认你混淆表达式和声明符的入门级错误。

    我反对左边右边的笼统分法(而不只是左边具体是什么右边又是什么),首先是因为这东西也就是一种文法的实例(都算不上特例)——文法上极少有递归到自己的分类,可以说到处都构成文法的元素是“××”在“××”的左边或者右边(或者还有递归的),比如表达式中某个操作符和操作数之间的顺序关系。(考虑到操作符数量不少还有更多子类,划分优先级结合性之类辅助记忆倒是情有可原,起码没直球“左”“右”上来就那么白开水废话。)
    声明的文法何德何能体现需要死记硬背的独特性?鼓吹这种死记硬背只会挖更多坑。
    如果你非要说左边和右边,左边是声明指示符,而右边是可能带有初值符的声明符列表。声明符列表进一步 parse 才 reduce 掉逗号。
    这种东西在标准里声明的形式文法里非常清楚,被你这么一描述反而引起混乱。
    你所谓的左边也有问题。声明指示符就不只是表示类型,也可以是其它的。像 inline 这样的函数指示符,也是左边的一部分。
    所以 [左边结果类型,右边表达式] 明显就没一个是对的。

    关于声明的文法规则,我懒得抄,但是回答一些基本的问题,至少比你自己造个漏洞百出的来说,毫不费劲。
    “为什么符号是声明符的一部分?”(我姑且理解所谓的符号是指 * )——文法规定。
    “不是类型的一部分”(我理解为类型指示符)——文法规定。
    “为什么类型只用在左边写一次”(我理解为“左边”的声明指示符)——文法规定。

    “因为标准所有人讲都一样,那么自己去看就好了”——不同版本的形式文法其实可能有修改,所以这里我都没对形式文法原文引用(讲清楚你的问题也还用不到那么具体)。
    当然,跨语言的差异很可能更大。构成声明符的 * 在 C 的文法里叫 pointer ;这部分在 C++ 对应的叫 ptr-operator ,这个叫法引起歧义和违反 PL 传统,就比较山寨。正好,理解了 ptr-operator 里可以有 * 也可以有 &,OP 的直接问题就算是基本解决了。而你在这里和稀泥公然对着干,就很莫名其妙。

    另一方面,硬说声明文法的独特性,那会涉及具体是什么的问题——这种划分声明指示符和声明符的设计的反人类味儿太冲了。而且确实也就 C 和 C++ 这样的过气古董设计里使用。
    跟 B 语言引入 ++ -- 主要就是为了省代码长度(一开始我猜的,因为想不出技术好处;后来看 K. Thompson 的理由确实如此)一样,中缀声明符语法设计也算是一种复用左侧元素的“压缩”技术,实践上没什么别的技术优势。
    这种设计非常不便于非形式地简单描述规则,反而逼得二道贩子出世。你这种错漏描述也是拜其所赐。
    wffnone
        60
    wffnone  
       2022-03-16 17:40:01 +08:00
    啊好看。

    我就不 at 你们了,我觉得你们的视角不一样。一个是工程师的视角,一个是技术专家的视角。对于新手来说,根据自己的偏好,学习的方向会有取舍。这些知识有什么用?对有些人,知道太多会降低生产效率;对有些人,他们就使用这些知识创造价值。

    我觉得来论坛的网友大部分是初学者。什么是好的心态呢?一方面,不要认为任何技术难点是难以理解的,你如果觉得对某个概念的模糊理解妨碍了编程,总是可以在任意时候花一点时间去学透任意概念。如何去学透呢,当然是看第一手的资料和资深技术专家给出的第二手资料。另一方面,也不用刻意追求把一切概念都搞清楚:你没有那么多时间,你会慢慢找到自己的方向。

    这个时代的垃圾太多了。一些无良商人和无知的初级程序员写的教程,遍布了整个互联网。他们不去学着怎么把文章写得容易懂,怎么把概念描述清晰,只是挖空心思想着怎么吸引人。作为技术人,我们都讨厌这些忽悠,这些可恶的骗子,这些社会的寄生虫。啊这是我们的共同点吧。
    dongfang
        61
    dongfang  
       2022-03-16 17:42:46 +08:00
    @FrankHB 涨知识了。
    其实,我是对上面一些评论的回复,我想表达的是,引用和指针不一样,是两回事。
    具体的编译器实现,我确实不了解。
    另外,函数指针难道不也是函数的入口地址吗?也是指向内存空间的某个位置吧。
    这里面的细节我确实理解不够深入。
    但是,我一直把指针百分百地当做内存地址,目前来说没有遇到反例。
    dongfang
        62
    dongfang  
       2022-03-16 17:51:55 +08:00
    @FrankHB
    关于指针和内存地址的讨论,能展开说说吗?
    比如函数指针,难道不是指向函数的入口地址吗?
    比如某个对象的指针,不能理解成存储对象的内存地址吗?
    如果这些指针都能当做内存地址,把指针和内存地址等价,是不是也可以呢?
    个人理解确实不够深入,真心地请教一下,感谢感谢。
    codehz
        63
    codehz  
       2022-03-17 11:49:47 +08:00 via Android
    @dongfang c++指针还真没那么简单(
    考虑有指向成员的指针( T C::*),指向函数成员的指针(T (C::*)())(要求在基类取指针后在子类对象上用能正确调用到正确实现,成员指针也是能在继承链中正确取得特定对象的成员)
    另外关于语法的问题,会出问题的反而不是取地址运算符,而是类似 a*b 的序列,在不知道符号表的情况下,无法确定是乘法运算还是声明(考虑到 if 可以在条件里写声明,这实际上是可以存在冲突的,虽然对于编译器来说符号表永远存在,但是编辑器和人类来说就困难了)
    ColorfulBoar
        64
    ColorfulBoar  
       2022-03-25 14:09:25 +08:00
    @dongfang
    真把指针当内存地址就等着哪天被 UB 干爆吧……心(抽)理(象)上(机)把指针当成是 provenance 和地址打包在一起的一个东西比较好(如果你喜欢也可以说指针空间是地址空间上的纤维丛,我是没有任何意见),至于具体编译出什么玩意来就不好说了,反正肯定不能保证是你想象中的那个地址。
    细节可以参考
    https://www.ralfj.de/blog/2018/07/24/pointers-and-bytes.html
    https://www.ralfj.de/blog/2020/12/14/provenance.html
    这个系列,里面简单解释了一下我就不重复了。另外
    https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/memory-interface.md
    也有点用,虽然是讲 Rust 的。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3502 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 30ms · UTC 11:35 · PVG 19:35 · LAX 03:35 · JFK 06:35
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.