我正在处理的一个程序有许多适用于所有类的常量。我想制作一个头文件“Constants.h”,并且能够声明所有相关的常量。然后在我的其他课程中,我可以只包括#include "Constants.h
.
我使用#ifndef
...#define ...
语法让它正常工作。但是,我更喜欢使用const int...
常量的形式。我不太确定怎么做。
const ints
您可以简单地在头文件中定义一系列:
// Constants.h
#if !defined(MYLIB_CONSTANTS_H)
#define MYLIB_CONSTANTS_H 1
const int a = 100;
const int b = 0x7f;
#endif
这是因为在 C++ 中,显式声明为 const 而未显式声明 extern 的命名空间范围(包括全局命名空间)的名称具有内部链接,因此当您将翻译单元链接在一起时,这些变量不会导致重复符号。或者,您可以将常量显式声明为静态。
static const int a = 100;
static const int b = 0x7f;
这与 C 更兼容,对于可能不熟悉 C++ 链接规则的人来说更具可读性。
如果所有常量都是整数,那么您可以使用的另一种方法是将标识符声明为枚举。
enum mylib_constants {
a = 100;
b = 0x7f;
};
所有这些方法都只使用一个标头,并允许将声明的名称用作编译时常量。使用extern const int
和单独的实现文件可防止将名称用作编译时常量。
请注意,使某些常量隐式内部链接的规则确实适用于指针,就像其他类型的常量一样。棘手的是,将指针标记为const
需要的语法与大多数人用来制作其他类型 const 的变量的语法略有不同。你需要做:
int * const ptr;
制作一个常量指针,以便规则适用于它。
另请注意,这是我更喜欢始终放在const
type:int const
而不是const int
. 我还将变量放在*
旁边:即int *ptr;
代替int* ptr;
(也比较此讨论)。
我喜欢做这些事情,因为它们反映了 C++ 真正工作的一般情况。替代品 ( const int
, int* p
) 只是特殊情况,以使一些简单的事情更具可读性。问题是,当您跳出这些简单案例时,特殊案例的替代方案会变得积极误导。
因此,尽管前面的示例显示了 的常见用法const
,但我实际上建议人们这样编写它们:
int const a = 100;
int const b = 0x7f;
和
static int const a = 100;
static int const b = 0x7f;
为了这种目的,我更喜欢命名空间。
选项1 :
#ifndef MYLIB_CONSTANTS_H
#define MYLIB_CONSTANTS_H
// File Name : LibConstants.hpp Purpose : Global Constants for Lib Utils
namespace LibConstants
{
const int CurlTimeOut = 0xFF; // Just some example
...
}
#endif
// source.cpp
#include <LibConstants.hpp>
int value = LibConstants::CurlTimeOut;
选项 2:
#ifndef MYLIB_CONSTANTS_H
#define MYLIB_CONSTANTS_H
// File Name : LibConstants.hpp Purpose : Global Constants for Lib Utils
namespace CurlConstants
{
const int CurlTimeOut = 0xFF; // Just some example
...
}
namespace MySQLConstants
{
const int DBPoolSize = 0xFF; // Just some example
...
}
#endif
// source.cpp
#include <LibConstants.hpp>
int value = CurlConstants::CurlTimeOut;
int val2 = MySQLConstants::DBPoolSize;
而且我永远不会使用 Class 来保存这种类型的 HardCoded Const 变量。
C++17inline
变量
这个很棒的 C++17 特性使我们能够:
constexpr
:如何声明 constexpr extern?主文件
#include <cassert>
#include "notmain.hpp"
int main() {
// Both files see the same memory address.
assert(¬main_i == notmain_func());
assert(notmain_i == 42);
}
不是main.hpp
#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP
inline constexpr int notmain_i = 42;
const int* notmain_func();
#endif
非main.cpp
#include "notmain.hpp"
const int* notmain_func() {
return ¬main_i;
}
编译并运行:
g++ -c -o notmain.o -std=c++17 -Wall -Wextra -pedantic notmain.cpp
g++ -c -o main.o -std=c++17 -Wall -Wextra -pedantic main.cpp
g++ -o main -std=c++17 -Wall -Wextra -pedantic main.o notmain.o
./main
另请参阅:内联变量如何工作?
内联变量的 C++ 标准
C++ 标准保证地址是相同的。C++17 N4659 标准草案 10.1.6“内联说明符”:
6 具有外部链接的内联函数或变量在所有翻译单元中应具有相同的地址。
cppreference https://en.cppreference.com/w/cpp/language/inline解释说如果static
没有给出,那么它有外部链接。
内联变量实现
我们可以观察它是如何实现的:
nm main.o notmain.o
其中包含:
main.o:
U _GLOBAL_OFFSET_TABLE_
U _Z12notmain_funcv
0000000000000028 r _ZZ4mainE19__PRETTY_FUNCTION__
U __assert_fail
0000000000000000 T main
0000000000000000 u notmain_i
notmain.o:
0000000000000000 T _Z12notmain_funcv
0000000000000000 u notmain_i
并man nm
说u
:
"u" 该符号是唯一的全局符号。这是标准 ELF 符号绑定集的 GNU 扩展。对于这样的符号,动态链接器将确保在整个过程中只有一个具有此名称和类型的符号在使用中。
所以我们看到有一个专门的 ELF 扩展。
关于“全局”的 C++17 标准草案const
暗示static
这是引用的内容:https ://stackoverflow.com/a/12043198/895245
C++17 n4659标准草案6.5“程序与链接”:
3 具有命名空间范围 (6.3.6) 的名称如果是
- (3.1) — 显式声明为静态的变量、函数或函数模板;或者,
- (3.2) — 非 volatile const 限定类型的非内联变量,既没有显式声明 extern,也没有先前声明为具有外部链接;或者
- (3.3) — 匿名工会的数据成员。
“命名空间”作用域就是我们俗称的“全局”作用域。
附录 C(资料性)兼容性,C.1.2 第 6 条:“基本概念”给出了从 C 更改的理由:
6.5 [还有 10.1.7]
更改:显式声明为 const 而未显式声明 extern 的文件范围的名称具有内部链接,而在 C 中它将具有外部链接。
理由:因为 const 对象可以在 C++ 中的翻译过程中用作值,所以这个特性敦促程序员为每个 const 对象提供一个显式的初始化程序。此功能允许用户将 const 对象放入包含在多个翻译单元中的源文件中。
对原始特征的影响:改变定义明确的特征的语义。
转换难度:语义转换。
使用范围:很少。
另请参阅:为什么 const 在 C++ 中暗示内部链接,而在 C 中却没有?
在 GCC 7.4.0、Ubuntu 18.04 中测试。
如果它包含在多个源文件中,通常不应const int
在头文件中使用 eg。那是因为每个源文件(从技术上讲是翻译单元)将定义一次变量,因为全局const
变量是隐式的 static,占用的内存比需要的多。
相反,您应该有一个特殊的源文件,Constants.cpp
它实际上定义了变量,然后将变量声明为extern
在头文件中。
像这样的头文件:
// Protect against multiple inclusions in the same source file
#ifndef CONSTANTS_H
#define CONSTANTS_H
extern const int CONSTANT_1;
#endif
这在源文件中:
const int CONSTANT_1 = 123;
与其创建一堆全局变量,不如考虑创建一个包含一堆公共静态常量的类。它仍然是全局的,但是通过这种方式,它被包装在一个类中,这样你就知道常量来自哪里,并且它应该是一个常量。
常量.h
#ifndef CONSTANTS_H
#define CONSTANTS_H
class GlobalConstants {
public:
static const int myConstant;
static const int myOtherConstant;
};
#endif
常量.cpp
#include "Constants.h"
const int GlobalConstants::myConstant = 1;
const int GlobalConstants::myOtherConstant = 3;
然后你可以像这样使用它:
#include "Constants.h"
void foo() {
int foo = GlobalConstants::myConstant;
}
似乎 bames53 的答案可以扩展到在命名空间和类声明中定义整数和非整数常量值,即使它们包含在多个源文件中。不必将声明放在头文件中,而将定义放在源文件中。以下示例适用于 Microsoft Visual Studio 2015、OS/390 上的 z/OS V2.2 XL C/C++ 和 GNU/Linux 4.16.14 (Fedora 28) 上的 g++ (GCC) 8.1.1 20180502。请注意,常量仅在包含在多个源文件中的单个头文件中声明/定义。
在 foo.cc 中:
#include <cstdio> // for puts
#include "messages.hh"
#include "bar.hh"
#include "zoo.hh"
int main(int argc, const char* argv[])
{
puts("Hello!");
bar();
zoo();
puts(Message::third);
return 0;
}
在messages.hh中:
#ifndef MESSAGES_HH
#define MESSAGES_HH
namespace Message {
char const * const first = "Yes, this is the first message!";
char const * const second = "This is the second message.";
char const * const third = "Message #3.";
};
#endif
在 bar.cc 中:
#include "messages.hh"
#include <cstdio>
void bar(void)
{
puts("Wow!");
printf("bar: %s\n", Message::first);
}
在 zoo.cc 中:
#include <cstdio>
#include "messages.hh"
void zoo(void)
{
printf("zoo: %s\n", Message::second);
}
在 bar.hh 中:
#ifndef BAR_HH
#define BAR_HH
#include "messages.hh"
void bar(void);
#endif
在动物园.hh:
#ifndef ZOO_HH
#define ZOO_HH
#include "messages.hh"
void zoo(void);
#endif
这会产生以下输出:
Hello!
Wow!
bar: Yes, this is the first message!
zoo: This is the second message.
Message #3.
数据类型char const * const
是指向常量字符数组的常量指针。第一个const
是必需的,因为(根据 g++)“ISO C++ 禁止将字符串常量转换为 'char*'”。第二个const
是为了避免由于(然后不够恒定的)常量的多个定义而导致的链接错误。如果您省略其中一个或两个const
s,您的编译器可能不会抱怨,但源代码的可移植性较差。