V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
• 请不要在回答技术问题时复制粘贴 AI 生成的内容
GPU
V2EX  ›  程序员

关于符号常量 , 恳请解答一下

  •  
  •   GPU · 2014-04-11 11:17:09 +08:00 · 3841 次点击
    这是一个创建于 3909 天前的主题,其中的信息可能已经有所发展或是发生改变。
    看 K&R 时 , 说到了符号常量 , 文中提到

    [ 在程序中使用 300、20等类似的"幻数"并不是一个好习惯,它们几乎无法向以后阅读该程序的人提供什么信息,而且使程序的修改变得更加困难. ]

    从而尽可能小地使用"幻数"而是使用符号常量 .

    在百度百科看到这么一段话
    [在c语言中,把直接使用的常数叫做幻数。在编程时,应尽量避免使用幻数,因为当常数需要改变时,要修改所有使用它的代码,工作量巨大,还可能有遗漏。因此通常把幻数定义为宏或枚举。建议使用枚举,因为它是编译阶段存在的符号,编译器的提示会更清晰、更准确。]


    那么幻数我search了一下,说是一个不确定的数 , 我理解貌似就是一个广义的意思,每个领域理解都不一样 .

    然后有说到 ,如果只使用幻数 会令源码作者以为的人很难知道这个幻数是做什么用的 ,
    我的问题就来了 , 在定义一个数的时候也会有一个变量名啊, 怎么会很难理解呢?

    然后如果是用符号变量 "#define" 也是有一个名字 , 在代码中也是和使用变量名一样的.

    以上所有的 , 能否有人给一些列子让我了解一下呢 ??




    ==========================================================


    然后 我有看到一个符号变量解释说 .
    [符号常量 pi 和常变量 PI 都代表3.1415926,在程序中都能使用,只是二者的性质有所差别,定义符号常量使用#define 指令,是预编译指令,只是使用一个符号常量代表一个字符串,在预编译是仅仅进行字符替换,在预编译之后符号常量不存在了(全部换成了3.1415926),对符号常量的名字是不分配内存的。常变量要占据内存空间,有变量值,只是这个值不改变。]

    这个解释说到了程序的效率 或者是性能方面的. 这个又是如何呢?




    文章有点长 . 希望能有一个好点的解答啊.
    第 1 条附言  ·  2014-04-11 11:52:26 +08:00
    我长编大论的说幻数这个东西,导致了楼下都是讨论幻数这个东西 .

    但是我理解的是 为什么非要用 这个符号常量 , 而不用变量 .

    感觉关系好复杂
    28 条回复    1970-01-01 08:00:00 +08:00
    icylogic
        1
    icylogic  
       2014-04-11 11:21:17 +08:00 via Android
    我觉得你是没搞清变量和常量的区别。。。
    GPU
        2
    GPU  
    OP
       2014-04-11 11:23:38 +08:00
    @icylogic 是完全不明白 .
    icylogic
        3
    icylogic  
       2014-04-11 11:29:22 +08:00 via Android
    @GPU 3是常量,#define M 3 那m也是常量,但int a=3 a就是变量,所以你的问题其实是为什么不用变量用常量
    icylogic
        4
    icylogic  
       2014-04-11 11:30:48 +08:00 via Android
    @GPU 实践中用常量的最主要的原因是,c语言只支持用常量定义数组大小。
    GPU
        5
    GPU  
    OP
       2014-04-11 11:31:04 +08:00
    @icylogic 就是这样 .不明白 . 问题还没解决.
    icylogic
        6
    icylogic  
       2014-04-11 11:31:42 +08:00 via Android
    准确的说是常量表达式
    jsonline
        7
    jsonline  
       2014-04-11 11:34:03 +08:00   ❤️ 1
    xia0ta0
        8
    xia0ta0  
       2014-04-11 11:34:31 +08:00
    幻数就是一般的数字,但是这个数字是有特定含义的,比如:
    你写 int testNum = 3.14; 别人可能不是到是干啥的,你最好这样写
    #define PI 3.14
    float testNum = PI;
    别人就知道你这是在测试圆周率,如果以后你要测得更精确,可以改一下:
    #define PI 3.14159
    define PI 3.14 与const float pi = 3.14;的区别就是编译之前,编译器会将所有代码有PI的地方替换成3.14,而pi只是一个变量(常量,不能修改),会编译到二进制文件中。
    jsonline
        9
    jsonline  
       2014-04-11 11:35:19 +08:00
    引文:
    不过,没过一会,发现了一个bug,经过大家的调查(2.6.38版没有发现这个问题),很快,找到了原因,是因为一个内存地址的问题,一个叫Yinghai Lu的人(看其名字应该是中国人,其邮件是@kernel.org)找到了原因—— radeon card使用了一个不正确的内存地址[0xa0000000 - 0xc000000]。Joerg Roedel跟贴说,这个地址超出了4GB的内存,然后他和Alex Deucher聊了一会,觉得不应该是这个问题,因为这个地址应该是GPU的,而不是系统内存的。

    好像,Yinghai Lu没有理会他们说的不应该是这个问题,给出了个fix:

    看到这个fix,Linus Torvalds不高兴了,他回贴问道:

    为什么全都是Magic Numbers?
    为什么0×80000000就那么特殊?
    为什么我们这样改就行?
    还说了这样一句话——


    This kind of “I broke things, so now I will jiggle things randomly until they unbreak” is not acceptable. 这种“我把事搞砸了,就随意地调整直到事情又工作”的方式是不可接受的。

    还说,这里即没有说明为什么我们fix在了正确的地方(也没有解释那些Magic Number是什么),也没有回滚那个有问题的patch。还说——

    Don’t just make random changes. There really are only two acceptable models of development: “think and analyze” or “years and years of testing on thousands of machines”. Those two really do work.

    不要乱改。那里只有两个可行的开发模式:“思考和分析” 或是 “数年数年地不断地在几千台机器上测试”。这两个方式才是真正可行的。

    当然,Yinghai Lu对其做了解释,说我们的确调查过了,老的代码用的内存地址是0×80000000,新的则是用0xa0000000,而0xa0000000不工作。这又引发了 Linus Torvalds 的不满的回贴。Linus说——
    jsonline
        10
    jsonline  
       2014-04-11 11:37:30 +08:00
    Magic Number 最大的问题是:
    为什么是这个 number,不是另一个 number 呢?
    如果有一天需求变更了,这个 number 还适用吗?

    所以 Magic Number 看起来就像 Magic 一样,无法琢磨!
    jsonline
        11
    jsonline  
       2014-04-11 11:40:37 +08:00
    Linus 大神每一次骂人我都看得好爽啊!
    zhujinliang
        12
    zhujinliang  
       2014-04-11 11:41:26 +08:00   ❤️ 1
    幻数 有多层含义
    1. 在代码中使用的常量,如果没有给出说明,而且他人难以推测如何得出的这个数,那就称作幻数
    2. 随意指定的值,一般该值的多少对程序没有太大影响,比如 MD5 等哈希算法中的初始因子
    3. 一个数值,如果得出它需要复杂的科学计算,或者通过穷举等试验得出的,可以直接写上,然后旁边写一个注释,意思是这个你不必深究了

    例如:
    setTimeout(fn, 86400) // 这个86400我认为是幻数,你不能一眼就知道这个指令想做什么
    更好的写法是
    setTimeout(fn, 24*60*60) // 一看就知道是延时一天
    jsonline
        13
    jsonline  
       2014-04-11 11:43:25 +08:00
    使用常量是为了不让别人(和自己修改),所以 PI 不能作为变量来声明。这跟 Magic Number 无关。
    slixurd
        14
    slixurd  
       2014-04-11 11:51:42 +08:00
    把Magic Number用在大型工程中不可取,define其实也不太好
    Effective C++条款3
    如果用define可能导致记号未被编译器看见,从而无法获取到编译错误信息指向的信号,只有一个Magic Number,包括调试当中,无法用记号来检索
    另外用常量可能比define生成更小的代码
    GPU
        15
    GPU  
    OP
       2014-04-11 11:55:00 +08:00
    @slixurd 你的最后一句话 . 为什么把常量和 define 又扯开了呢.
    mrsatangel
        16
    mrsatangel  
       2014-04-11 12:00:44 +08:00 via iPhone
    从汇编来看,就是内存操作数和立即数的区别,操作两者占用的总线周期是不一样的。当然如果不是大规模使用变量代替常量,区别可以忽略。另外赞同前面有人讲的,实践中最大的因素是c只支持常量长度的数组,不然只有动态分配。
    ETiV
        17
    ETiV  
       2014-04-11 12:05:00 +08:00
    变量: 运行时可以对他进行处理的数值. 数字上来讲, 就是加减乘除.
    "变量名"只是编程语言里一种对于变量的引用. 当然变量名是给人看的. 所以需要简单易懂, 望文生义.

    常量: 运行时不可变而且一定不能变的量. 所以就需要对他进行保护, 以避免代码上产生的漏洞, 对"常量"进行修改.
    后半句就解释了, "为什么都可以起名字, 但还非要区分变量常量"的问题.
    slixurd
        18
    slixurd  
       2014-04-11 12:18:06 +08:00
    @GPU 因为常数和常量是不同的东西/w\
    一个是magic number,一个是const/enum
    xia0ta0
        19
    xia0ta0  
       2014-04-11 12:18:59 +08:00
    使用上,符号常量用PI 和常变量用pi;程序执行效率上肯定是符号常量快,因为它只是一个立即数;变常量还要操作内存。

    使用变常量还是符合常量主要是从原理上考虑,如果你想把这个常量编译到程序中,就用变常量;如果只是作为一个便于记录和辨识的符合,不需要编译到二进制文件,就用符合变量。

    当然,符号变量的宏定义还有很多强大的功能。
    xia0ta0
        20
    xia0ta0  
       2014-04-11 12:20:54 +08:00
    一般习惯用大写表示符号常量,常变量用小写。
    GPU
        21
    GPU  
    OP
       2014-04-11 12:52:52 +08:00
    @xia0ta0 啊, 我终于明白原因了. !!! . 你这个解释终于能令我这个初学者明白啦 .

    书本上要提到这个东西是因为 , 他上一个题目是说 for语句的. 但是他是直接用了"幻数"直接放在for语句里面的条件 . 然后他下一篇就立马提到 "符号常量" 意思就是把这些"幻数"定义一下 , 这样当程序被作者以外的人看,也可以明白这个"幻数"是做什么用的.


    再然后 , 劈开你说的"宏定义" 这些强大的功能 ,那么这个符号常量是没有用途的 ,只是让人看代码时更容易明白代码的意思 , 而书上要提到这个东西 , 意义就是让程序员们能良好的编写代码的习惯.! .

    就想到这么多了.
    GPU
        22
    GPU  
    OP
       2014-04-11 12:58:48 +08:00
    @slixurd 那你14楼所说的常量是什么意思?
    lukic
        23
    lukic  
       2014-04-11 13:17:39 +08:00
    既然楼主是初学者就没必要纠结这么多,多实践就慢慢明白了。
    xia0ta0
        24
    xia0ta0  
       2014-04-11 13:24:38 +08:00
    @GPU 选本好的入门教材很重要,推荐《c程序语言设计》,组织的很有条理,不会让读者自动脑补。。。。
    GPU
        25
    GPU  
    OP
       2014-04-11 13:36:46 +08:00
    @xia0ta0 《c程序语言设计》 是不是那本K&R? . 徐宝文 先生 译著的. 我现在就是看这个本 .发的现在这个问题 . 只是就像#16 说的那样我自己纠结而已. 总是把问题想得太复杂,非要弄懂他不可 这样子.
    xia0ta0
        26
    xia0ta0  
       2014-04-11 13:41:31 +08:00
    好吧,且读且珍惜。。。。
    levie
        27
    levie  
       2014-04-11 16:08:52 +08:00
    我理解啊,lz不是要弄清楚常量和变量,而是什么样的数是幻数,以及我们为什么要避免使用幻数。
    比如

    int height = (2 + 100 + 5) * 2;

    这里面2、100、5、2都是幻数,一般人读到这行代码都会困惑这些数怎么来的,所以也有称它们是神仙数的,只有神仙才能猜到那些数是什么意思。
    如果改成这样写:

    int topMargin = 2, contentHeight = 100, bottomMargin = 5, scale = 2;
    int height = (topMargin + contentHeight + bottomMargin) * scale;

    这样的代码就好读了,当然写起来也累_(:з」∠)_

    另外一个好处是改参数的时候容易,比如你要把改缩放比例从2为3,那么你只要改scale = 3就好了,而不用查找所有2,然后费劲地把其中一部分2改成3.

    所以,幻数与否于不是看常量变量。
    GPU
        28
    GPU  
    OP
       2014-04-11 16:48:17 +08:00
    @levie 嗯. .我当初就是不明白书上面为什么要特意提及这个. 现在已经明白了. 不过这个 变量 常量这些深入的东西还是不会的. 我只是刚刚初学 .看的K&R .哈哈.
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3565 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 35ms · UTC 10:43 · PVG 18:43 · LAX 02:43 · JFK 05:43
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.