在 C 中正确使用全局 const 变量?

发布于 2024-12-05 18:45:27 字数 678 浏览 0 评论 0原文

我正在为我的计算机科学课程编写一个程序。它模拟了一家快递公司在机场的活动。

这是一个非常简单的小程序,由一些头文件和源文件以及一个用于编排模拟的 main.cpp 源文件组成。

有一些给定的常量值,例如货物到达的频率、飞机的负载能力、工人处理某些物品所花费的时间等(均为整数值)。我有必要在 main.cpp 中的多个函数中访问这些变量将

main() 函数上方的这些变量声明为 const ints 似乎是合理的,有效地使它们成为全局变量,例如

const int kTotalTime = 2000;
const int kPlaneCapacity = 25;
int main(){//...program code}

我知道在大多数情况下应该避免使用全局变量,因为它们的调用和/或修改位置没有限制,这可能会导致意外破坏程序的某些部分,进而可能难以调试,并导致未来代码的兼容性问题等。 然而,由于这些是原始数据类型的只读值,在整个程序中使用,因此这似乎是一个合理的解决方案。此外,它还向任何阅读代码的人和编译器明确声明了变量的用途。

提问:我的逻辑有问题吗?为何如此?什么时候使用全局变量(const 或非 const)是合理的?如果这是一个糟糕的解决方案,那么您建议如何声明诸如此类的常量只读值?

非常感谢您抽出时间!

I am working on a program for my CS class. It is a simulation of a delivery company's activities at an airport.

This is a very simple, small program consisting of a few header and source files and a main.cpp source file that orchestrates the simulation.

There are certain given constant values, such as the frequency of shipment arrival, the load capacity of planes, the amount of time it takes a worker to process certain items, etc. (all are integer values). It is necessary for me to access these variables throughout several functions in main.cpp

It seemed reasonable to declare these above the main() function as const ints, effectively making them global, e.g.

const int kTotalTime = 2000;
const int kPlaneCapacity = 25;
int main(){//...program code}

I am aware that global variables are to be avoided in most situations, as there are no restrictions on where they can be called and/or modified, which can lead to accidentally breaking parts of the program which in turn may be difficult to debug, as well as causing compatibility issues for future code, etc.
However since these are read-only values of a primitive data type, which are used throughout the program, it seemed like a reasonable solution. Also, it makes an explicit statement about the purpose of the variables to anyone reading the code as well as to the compiler.

Questions: Is my logic flawed? How so? When are global variables (const or not) reasonable to use? If this is a bad solution, then how would you suggest declaring constant read-only values such as these?

Thank you very much for your time!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

难如初 2024-12-12 18:45:27

关于您的程序的大小和目的(正如我从您的描述中理解的那样)可能并不重要,但由于它具有教育背景,我建议“做对”。

在这种情况下,我会选择一个 Config 结构(或类,如果你想让它更智能一点,请参见下文),它携带配置值并且可以在你的程序中使用。它的优点是,如果您必须从文件或命令行获取选项,您可以轻松更改它。

至于类与结构的区别(请注意,我在这里进行逻辑上的区别,而不是技术上的区别)。要么将所有值作为成员放入结构中并传递它的 const 引用,要么将其设为一个完整的类,其中包含隐藏数据来源(以及数据生成方式)的访问器。编程始终是决策,这是您要做的决定。如果您认为将来必须允许更多配置可能性(如上所述),您可能需要进行类抽象。

另一种选择是将数据分散在程序中,这实际上比听起来要聪明得多。如果每个类只知道其配置选项(并隐藏它们),那么您实际上可以使用您正在使用的 OOP 语言。示例:

// footype.h
class FooType {
  private:
    static const int fooOption;
};
// bartype.h
class BarType {
  private:
    static const float barOption;
};

问题是如何初始化它。一种方法可能是创建一个如下所示的 config.cpp:

#include "footype.h"
#include "bartype.h"

const int FooType::fooOption = 42;
const float BarType::barOption = 7.4;

因此您可以隐藏信息,并且仍然将所有配置选项集中在一处(config.cpp >)。

编辑:

如果您有许多(多个)不同模块所需的配置选项,您可以进行一些复杂的操作(使用间接),如下所示:

// footype.h
class FooType {
  private:
    static const int& fooOption;
    static const bool& dumpLevel;
};
// bartype.h
class BarType {
  private:
    static const float& barOption;
    static const bool& dumpLevel;
};

config.cpp:

#include "footype.h"
#include "bartype.h"

static const int opt_foo = 42;
static const float opt_bar = 7.4;
static const bool opt_dumpLevel = false;

const int& FooType::fooOption = opt_foo;
const bool& FooType::dumpLevel = opt_dumpLevel;
const float& BarType::barOption = opt_bar;
const bool& BarType::dumpLevel = opt_dumpLevel;

您甚至可以使选项成为非常量,如果您想要(但我没有看到配置选项中可变的点)。

Regarding the size and purpose of your program (as I understand it from your description) it probably doesn't matter, but since it has an educational context, I'd suggest to "do it right".

In such a situation I would go for a Config struct (or class, if you want to make it a bit smarter, see below) which carries the configuration values and can be tossed around your program. It has the advantage that you can easily change it if you have to, say, fetch your options from a file or from the command line.

As for the class versus struct thingy (note that I am making a logical distinction here, not a technical). Either you just put all values as members in your struct and pass around const refs of it, or you make it a full fledged class with accessors that hide where the data is coming from (and how it is generated). Programming is always decision making and this is your decision to make. If you think you will have to allow more configuration possibilities in the future (like mentioned above) you may want to go for class abstraction.

Yet another option is to scatter your data across your program, which is actually a lot smarter than it sounds. If every class knows only its configuration options (and hides them) you can actually make use of the OOP language, you're using. Example:

// footype.h
class FooType {
  private:
    static const int fooOption;
};
// bartype.h
class BarType {
  private:
    static const float barOption;
};

The question is, how to initialise this. One way could be to create a config.cpp that looks like this:

#include "footype.h"
#include "bartype.h"

const int FooType::fooOption = 42;
const float BarType::barOption = 7.4;

So you have information hiding, and you still have all the config options together at one place (config.cpp).

Edit:

If you have config option that is required by many (more than one) different modules, you can go for a bit of sophistication (with indirection) like so:

// footype.h
class FooType {
  private:
    static const int& fooOption;
    static const bool& dumpLevel;
};
// bartype.h
class BarType {
  private:
    static const float& barOption;
    static const bool& dumpLevel;
};

config.cpp:

#include "footype.h"
#include "bartype.h"

static const int opt_foo = 42;
static const float opt_bar = 7.4;
static const bool opt_dumpLevel = false;

const int& FooType::fooOption = opt_foo;
const bool& FooType::dumpLevel = opt_dumpLevel;
const float& BarType::barOption = opt_bar;
const bool& BarType::dumpLevel = opt_dumpLevel;

You can even make the options non-const if you want (but I don't see the point in a configuration option that is mutable).

爱的那么颓废 2024-12-12 18:45:27

我认为最好将常量放在类中静态。

我假设您有 Plane 类,只需执行以下操作:

Plane.h

class Plane{
   static const int kPlaneCapacity;
   //....
}

Plane.cpp

const int Plane::kPlaneCapacity = 25;

另外,请注意您对常量的理解。 Pi 是一个常数。 10 是常数。我确实明白你会如何认为飞机载客量是恒定的,但想一想:如果你的老师说你的下一次作业,你的飞机载客量应该是 30 架,而不是 25 架,该怎么办?

I think it's best to put your constants as static inside the class.

I assume you have the class Plane, just do this:

Plane.h

class Plane{
   static const int kPlaneCapacity;
   //....
}

Plane.cpp

const int Plane::kPlaneCapacity = 25;

Also, take good care of what you understand by constant. Pi is a constant. 10 is a constant. I do see how you would think a plane capacity is constant, but think about this: What if your teacher says that for your next assignment, your plane capacity should be 30, and not 25.

記柔刀 2024-12-12 18:45:27

什么时候使用全局变量(const 或非 const)是合理的?

如果您的程序是一个多线程程序,那么您应该认真考虑使用全局变量,因为它们需要适当的同步以避免竞争条件。通常,正确的同步并不是一项非常琐碎的任务,需要一些认真的理解和思考。

以下是好文章的摘录:

非局部性——当源代码的各个元素的范围受到限制时,源代码最容易理解。全局变量可以由程序的任何部分读取或修改,因此很难记住或推理每种可能的用途。
无访问控制或约束检查——程序的任何部分都可以获取或设置全局变量,并且有关其使用的任何规则都可以很容易地被破坏或忘记。

隐式耦合——具有许多全局变量的程序通常在其中一些变量之间以及变量和函数之间存在紧密耦合。将耦合的项目分组为有凝聚力的单元通常会产生更好的程序。

内存分配问题——某些环境的内存分配方案使全局变量的分配变得棘手。在“构造函数”具有分配以外的副作用的语言中尤其如此(因为在这种情况下,您可以表达两个全局变量相互依赖的不安全情况)。此外,当动态链接模块时,可能不清楚不同的库是否有自己的全局实例,或者全局变量是否是共享的。

测试和限制 - 使用全局变量的源测试起来有些困难,因为无法在运行之间轻松设置“干净”的环境。更一般地说,由于同样的原因,使用未明确提供给该源的任何种类的全局服务的源很难进行测试。

考虑到上述所有内容,只要您了解这些陷阱并了解您使用全局变量的方式确实可以使您的程序免受这些陷阱的影响,那么您就可以继续并很好地使用全局变量。

When are global variables (const or not) reasonable to use?

If your program is a multithreaded program then you should be giving a serious thought about using globals for they would need proper synchronization to avoid race conditions. Usually proper synchronization is not a very trivial task and requires some serious undrestanding and thought.

Here is an excerpt from a nice article:

Non-locality -- Source code is easiest to understand when the scope of its individual elements are limited. Global variables can be read or modified by any part of the program, making it difficult to remember or reason about every possible use.
No Access Control or Constraint Checking -- A global variable can be get or set by any part of the program, and any rules regarding its use can be easily broken or forgotten.

Implicit coupling -- A program with many global variables often has tight couplings between some of those variables, and couplings between variables and functions. Grouping coupled items into cohesive units usually leads to better programs.

Memory allocation issues -- Some environments have memory allocation schemes that make allocation of globals tricky. This is especially true in languages where "constructors" have side-effects other than allocation (because, in that case, you can express unsafe situations where two globals mutually depend on one another). Also, when dynamically linking modules, it can be unclear whether different libraries have their own instances of globals or whether the globals are shared.

Testing and Confinement - source that utilizes globals is somewhat more difficult to test because one cannot readily set up a 'clean' environment between runs. More generally, source that utilizes global services of any sort that aren't explicitly provided to that source is difficult to test for the same reason.

Given all the above, As long as you understand the pitfalls and understand that the way you are using the globals does insulate your program against those pitfalls, then you can go ahead and very well use globals.

少女七分熟 2024-12-12 18:45:27

Arduino 代码是何时适合使用不同类型的全局变量的示例。

每个 Arduino 草图都包含一个在启动时运行一次的 setup() 函数,以及一个重复运行的 loop() 函数。 setup() 中声明的任何内容在 loop() 中都不可用。因此实例化的类(例如MqttClient)需要是全局的。

同样,在 loop() 中声明的任何内容都会在 loop() 的下一次迭代中重新声明。有些传感器需要几毫秒的时间来轮询。轮询某些传感器会导致它们变热。因此,Arduino 草图通常会在不同时间执行多个操作。

使用温度传感器的示例:您可以每 4 秒轮询一次并将其值打印到终端,每 10 秒将值打印到 LCD 屏幕,每 20 秒将值发布到 MQTT 代理。

loop() 中使用 delay() 是一种不好的形式,因为这是一个阻塞函数,会阻止 CPU 执行任何其他操作。因此,改为使用计时器:

if( ( millis() - lastPollTime ) > pollInterval )
{
  tempC = bme280.readTemperature();
  lastPollTime = millis();
}

要在不使用非常量全局变量的情况下存储我的 lastPollTime,我需要使用一个类来跟踪实例变量中的上次运行时间。对于只需要两行代码的情况来说,这是很大的开销,因此我的代码对于我需要使用的每个唯一计时器都有一个全局 unsigned long

由于我可能不会在 loop() 的另外一千次迭代中使用 tempC 变量,因此它也需要是一个非常量全局变量。

1在写这个答案时发现了一个:https://github.com/contrem/arduino-timer

Arduino code is an example of when it is appropriate to use different types of global variables.

Every Arduino sketch contains a setup() function which is run once at startup, and a loop() function which is run repeatedly. Anything declared in setup() is unavailable in loop(). So instantiated classes (e.g. MqttClient) need to be global.

Similarly, anything declared in loop() is redeclared in the next iteration of loop(). Some sensors take a few milliseconds to poll. Polling some sensors causes them to heat up. Because of all that, Arduino sketches will often have multiple actions being performed at different times.

An example using a temperature sensor: you may poll it and print its value to the terminal every 4 seconds, print the value to a LCD screen every 10 seconds, and publish the value to a MQTT broker every 20 seconds.

It is bad form to use delay() in loop() because that is a blocking function which prevent the CPU from doing anything else. So timers are used instead:

if( ( millis() - lastPollTime ) > pollInterval )
{
  tempC = bme280.readTemperature();
  lastPollTime = millis();
}

To store my lastPollTime without using a non-const global variable, I would need to use a class¹ which keeps track of the last-run time in an instance variable. That's a lot of overhead for what otherwise takes only two lines of code, so my code has one global unsigned long for each unique timer I need to use.

And since I may not use the tempC variable for another thousand iterations of loop(), it too needs to be a non-const global variable.

¹Found one while writing this answer: https://github.com/contrem/arduino-timer

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文