【问题标题】:How do I avoid both global variables and magic numbers?如何避免全局变量和幻数?
【发布时间】:2011-04-10 12:23:42
【问题描述】:

我知道并理解全局变量和幻数是编程时要避免的事情,尤其是随着项目中代码量的增加。但是,我想不出避免两者的好方法。

假设我有一个预先确定的表示屏幕宽度的变量,并且在多个文件中都需要该值。我可以...

doSomethingWithValue(1920);

但这是一个神奇的数字。但为了避免这种情况,我会这样做......

const int SCREEN_WIDTH = 1920;

//In a later file...
extern const int SCREEN_WIDTH;
doSomethingWithValue(SCREEN_WIDTH);

现在我正在使用一个全局变量。这里有什么解决办法?

【问题讨论】:

  • 所以我听说全局常量通常没问题,是全局变量导致了真正的问题。谢谢大家!

标签: c++ c global-variables magic-numbers


【解决方案1】:

在您的第二个示例中,SCREEN_WIDTH 并不是真正的变量1,它是一个命名常量。使用命名常量没有任何问题。

在 C 中,如果枚举是整数常量,您可能希望使用它,因为 const 对象不是常量。在 C++ 中,最好使用原始问题中的 const 对象,因为在 C++ 中,const 对象是一个常量。

1.从技术上讲,是的,它是一个“变量”,但这个名称并不真正“正确”,因为它永远不会改变。

【讨论】:

  • 所以全局变量没问题,只要声明为 const ?
  • 像往常一样,您不应该有黄金不可变的规则,您应该了解最佳实践背后的原因,以了解它们是否适用于您的案例。
  • @Lewis,命名常量可以使用大括号“{}”适当地限定范围。您的代码 sn-p 未指示命名常量的声明位置。将其置于必要的最高范围,但不要更高。
  • @Lewis:这是一种看待它的方式,是的。可变全局变量的问题在于知道代码的哪些位修改它们以及何时以及为什么。如果“变量”是常量,则其他代码修改它们没有问题;它们对所有事物始终具有相同的含义。因此,它们是共享公共信息的一种方式,而且非常有效且相对无害。
  • 为什么不在 C 中使用枚举(而不是#define)?如果没有别的,它更容易在调试器中显示。
【解决方案2】:

我建议在头文件的命名空间中定义常量。那么这个作用域就不是全局的,你不需要在多个地方重新定义它(即使使用 extern 关键字)。

【讨论】:

    【解决方案3】:

    为什么首先需要硬编码屏幕宽度?它从何而来? 在大多数实际应用中,它来自一些系统 API,它会告诉您当前正在运行哪种分辨率,或者系统能够显示哪些分辨率。

    然后您只需获取该值并将其传递到需要它的任何地方。

    简而言之,在这条线上:doSomethingWithValue(SCREEN_WIDTH); 你已经在这样做了。 SCREEN_WIDTH 在此特定示例中可能是全局变量,但不一定是全局变量,因为函数访问它 作为全局变量。您在运行时将值传递给函数,因此函数看到的不是全局变量,它只是一个普通的函数参数。

    另一个重要的一点是,不可变全局数据通常没有任何问题。

    全局常量通常没问题。当您具有 可变 全局状态时会出现问题:可以在整个应用程序中访问的对象,并且可能具有不同的值,具体取决于您查看的 when。这使得它难以推理,并导致许多问题。

    但全局常量是安全的。以 pi 为例。它是一个数学常数,让每个函数都看到 pi 是 3.1415 并没有什么坏处......因为它就是 ,它不会改变。

    如果屏幕宽度是一个硬编码常量(如您的示例中所示),那么它也可以是一个全局变量而不会造成破坏。 (尽管出于显而易见的原因,它可能一开始就不应该是一个常数9

    【讨论】:

      【解决方案4】:

      全局变量的主要问题是当它们是非常量时。不变的全局变量几乎不是问题,因为您在任何使用它们的地方都知道它们的价值。

      在这种情况下,一种明智的方法是创建一个常量命名空间并将常量值放在那里,以供程序中的任何位置引用。这很像你的第二个例子。

      【讨论】:

        【解决方案5】:

        它们必须在某个地方定义。为什么不将定义放在 .h 文件或构建文件中?

        【讨论】:

          【解决方案6】:

          不是变量,是常量,编译时解析。

          无论如何,如果您不喜欢这样的“浮动”常量,您可以将它放在命名空间或其他任何地方,以便将所有该类型的常量收集在一起。在某些情况下,您还可以考虑使用枚举来对相关常量进行分组。

          更好的是,如果这适用于您的情况,请避免使用固定的预定屏幕宽度并使用正确的 API 在运行时检索它。

          【讨论】:

            【解决方案7】:

            如果全局是必须的,那么将它包装在一个函数中并使用该函数获取值通常是一个好主意。

            另一个可能help的帖子

            【讨论】:

            • 我的一个误读问题。我指的是全局变量,问题是关于 const 值。道歉。
            【解决方案8】:

            虽然第二种情况下的全局是一个相当无害的情况,但除非你在设计它时确定屏幕宽度不会改变,否则我会使用一些东西来动态获取屏幕宽度(例如,Windows 上的GetSystemMetrics,或X 上的XDisplayWidth)。

            【讨论】:

              【解决方案9】:

              避免这种情况的一种方法是将您的程序设置为一个对象并在该对象上具有一个属性 (my_prog.screen_width)。要运行您的程序,请让 main() 实例化对象并在对象上调用 ->go 方法。

              Java 就是这样做的。很多。好主意。

              程序扩展的额外功能:

              • 有一天,当您让您的程序可定制时,您可以让它在构造函数中设置属性,而不是重新编译所有内容。
              • 当您想在同一进程中运行程序的两个实例时,它们可以有不同的设置。

              不过,对于一个快速的一次性计划来说,这并不是什么大不了的事情。

              【讨论】:

                【解决方案10】:

                重要的是要认识到,如果将来需要更改全局常量,有时也会导致问题。在某些情况下,您可能决定根本不让他们改变,但其他时候事情可能并不那么容易。例如,您的程序可能包含大小与屏幕匹配的图形图像;如果需要调整它以在不同尺寸的屏幕上运行会发生什么?仅仅改变命名常量不一定能修复程序中嵌入的图形图像。如果它需要能够在运行时决定使用什么尺寸的屏幕呢?

                不可能处理所有可能发生的意外事件,而且人们不应该过分努力地防范那些根本不会发生的事情。尽管如此,还是应该注意事情会发生什么变化,以免陷入困境。

                在设计一个类时,拥有一个返回不变值的虚拟只读属性将允许该类的未来扩展返回不同的值。使用属性而不是常量可能会对性能产生一些影响,但在某些情况下,灵活性是值得的。如果有一种方法来定义虚拟常量可能会很好,而且我认为 .net 理论上没有理由允许它(在表中包含带有虚拟方法指针的常量值),但就目前而言我知道不会。

                【讨论】:

                  猜你喜欢
                  • 2015-12-26
                  • 2012-12-19
                  • 2016-03-19
                  • 2012-02-02
                  • 1970-01-01
                  • 2010-12-22
                  • 2011-03-24
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多