在C ++标头中定义常量变量


75

我正在研究的程序具有适用于所有类的许多常量。我想制作一个头文件“ Constants.h”,并能够声明所有相关的常量。然后在其他课程中,我可以只包含#include "Constants.h

我使用#ifndef...#define ...语法使其正常工作。但是,我更喜欢使用const int...常量的形式。我不太确定该怎么做。


5
您能解释一下您不了解的内容const int吗?如果这是您的问题,请发布未能编译的示例代码?
djechlin

Answers:


98

您可以简单地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;

创建一个常量指针,以便将规则应用于该指针。

另请注意,这是我更喜欢始终放在consttype后面int const而不是的原因之一const int。我还把*下一个放在变量旁边:即int *ptr;而不是int* ptr;(也比较这个讨论)。

我喜欢做这些事情,因为它们反映了C ++真正工作原理的一般情况。替代(const intint* p)是套管做一些简单的事情更易读只是特别。问题在于,当您退出那些简单的案例时,特殊案例的替代方案会变得容易产生误导。

因此,尽管前面的示例显示了的常用用法const,但实际上我还是建议人们这样写:

int const a = 100;
int const b = 0x7f;

static int const a = 100;
static int const b = 0x7f;

1
您的带有隐式静态链接的规则不适用于指针
yanpas

9
@yanpas确实如此,但是您必须确保将放在const正确的位置。制作指针const需要在const后面加上*。例如int * const x。如果仅使用,const int *x;const指的是指针指向的内容,而不是指针本身。
bames53 '16

31

为此,我更喜欢名称空间

选项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变量。


我喜欢这个主意。但是,这是否意味着我必须初始化valueval2以及所有其他常量?有没有办法说在函数中包含名称空间并使用与名称空间相同的常量名称?
talekeDskobeDa

还记得使用const char * constchar *
罗勒

21

如果const int头文件中包含多个源文件,则通常不应该使用它。那是因为因为全局const变量是隐式静态的,因此占用了比所需更多的内存,因此每个源文件(从技术上来说,翻译单元)将定义一次变量

相反,您应该有一个特殊的源文件,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;

1
@arasmussen如果只想使用一个头文件,则不行。请参阅我的修订答案。
程序员花了

3
最好在头文件中对其进行初始化,因此可以将其用作编译时常量。
Mike Seymour 2012年

13
此外,您还可以使用const int在头文件,自const变量的缺省内部链接。是否要而不是单个实例是另一个问题。通常,只要标头中的值可用,它的差别就很小。
Mike Seymour 2012年

4
对于可以使用该版本的用户,C ++ 17内联变量是实现此目的的一种更
出色的方法

1
@Jamāl除非有以前的声明强制其具有外部链接,例如包括包含该extern声明的头文件。
某位程序员花了

15

C ++ 17inline变量

这个很棒的C ++ 17功能使我们能够:

main.cpp

#include <cassert>

#include "notmain.hpp"

int main() {
    // Both files see the same memory address.
    assert(&notmain_i == notmain_func());
    assert(notmain_i == 42);
}

notmain.hpp

#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP

inline constexpr int notmain_i = 42;

const int* notmain_func();

#endif

notmain.cpp

#include "notmain.hpp"

const int* notmain_func() {
    return &notmain_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

GitHub上游

另请参阅:内联变量如何工作?

内联变量的C ++标准

C ++标准保证地址相同。C ++ 17 N4659标准草案 10.1.6“内联说明符”:

6具有外部链接的内联函数或变量在所有翻译单元中应具有相同的地址。

cppreference https://zh.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 nmu

“ 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)—非易失性,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中测试。


1

您可以考虑创建一个具有一堆公共静态常量的类,而不是创建一堆全局变量。它仍然是全局的,但是通过这种方式,它包装在一个类中,因此您可以知道常量来自何处,并且应该将其视为常量。

常数h

#ifndef CONSTANTS_H
#define CONSTANTS_H

class GlobalConstants {
  public:
    static const int myConstant;
    static const int myOtherConstant;
};

#endif

Constants.cpp

#include "Constants.h"

const int GlobalConstants::myConstant = 1;
const int GlobalConstants::myOtherConstant = 3;

然后,您可以像这样使用它:

#include "Constants.h"

void foo() {
  int foo = GlobalConstants::myConstant;
}

48
当只能使用名称空间时,为什么要使用类来模拟名称空间?
Mike Seymour 2012年

0

看来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

在zoo.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需要第二种方法来避免由于常量(然后常量不足)的多个定义而导致的链接错误。如果省略consts或两者,则编译器可能不会抱怨,但是源代码的可移植性较差。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.