在C++头文件中初始化变量

5

编辑:更正了函数名称,并添加了 #pragma once

以下是对我的问题的一个非常简化的描述,如果我这样做:

A.h

#pragma once
static int testNumber = 10;
void changeTestNumber();

A.cpp

#pragma once
#include "A.h"

void changeTestNumber()
{
    testNumber = 15;
} 

B.h

#pragma once
#include "A.h"
// some other stuff

B.cpp

#pragma once
#include "B.h"
// some other stuff

main.cpp

#pragma once
#include "B.h"
#include <iostream>

int main(){

changeTestNumber();
std::cout<<testNumber<<std::endl;

return 0;
}

为什么在调用时我没有得到testNumber = 15?

当我使用一个被包含在我的头文件中的函数时,到底发生了什么?

如果我去掉int testNumber前面的static,我会得到testNumber初始化两次的错误。

那么当我这样做时,我的头文件是否被编译两次?

提前致谢!


1
一个函数被称为 changeNumber(你的原型),另一个被称为 changeTestNumber(定义)。 - Goodies
https://dev59.com/U3M_5IYBdhLWcg3w8H82 - kkhipis
简而言之:是的,有多个“testNumber”变量,每个头文件都有一个。通常,开发人员会将静态变量放在cpp文件中,并使用“#pragma once”来控制头文件定义其值的次数。有关“static”的许多用途的更多信息可以在此网站的其他地方找到: https://dev59.com/VWUp5IYBdhLWcg3wf3us - Jason De Arte
函数名错误是一个打字错误,将进行编辑。 我在我的“真实”代码中有#pragma once,也应该把它放进去。 谢谢,我会检查那个链接! - remi
3个回答

9
除了显而易见的名称不正确(我想这只是 hurriedly 创建类似示例时的问题,而不是代码中的实际问题),你需要在 .h/.hpp 文件中将变量声明为extern。你不能同时拥有一个既是 static 又是 extern 的变量,因为 static 的用途之一是使变量仅保留在单个.cpp 文件中。
如果你更改:
static int testNumber = 10;

在您的A.h文件中进行以下操作:
extern int testNumber;

然后在您的A.cpp文件中,执行以下操作:

#include "A.h"
int testNumber = 10;

现在运行以下命令:
int main() {
    //changeNumber();
    std::cout << testNumber << std::endl; // prints 10
    changeTestNumber(); // changes to 15
    std::cout << testNumber << std::endl; // prints 15
    std::cin.ignore();
    return 0;
}

请务必修复函数名称!


这里有许多好的答案,谢谢!这个有效 :) 我现在在使用向量方面遇到了一些小问题,但在开始提问之前,我会在有时间时尝试更多。 - remi

7

Goodies和其他人都是正确的,但让我再提出一步:

  1. static 使定义局限于翻译单元。因此,在头文件中定义静态全局变量将导致与它所包含的翻译单元一样多的副本。除非这不是您想要的特定方式,否则不应采用这种方式。

  2. extern 声明全局变量存在于某个位置,但未定义,并且必须在链接阶段进行搜索。为了使链接器成功,您需要在某个地方定义它(通常是更有意义的源文件)。

  3. 省略它们两者都会导致“多重定义”链接器错误,其中一个以上的源文件包含一个头文件。

现在,第二种情况有两个限制:

  • 即使在提供模板库(或“仅头文件”库)的情况下,它也会强制您拥有可编译的源代码来实例化全局对象,从而使交付变得更加复杂。
  • 它暴露给所谓的全局初始化灾难:如果您使用从其他定义在别处的全局对象中获取的值初始化全局对象,由于C++不保证它们的构造顺序(最终归属于链接器的方式),您可能会遇到全局对象的适当初始化和销毁问题。

为了避免所有这些问题,请考虑以下内容:

  • 如果明确声明为inline的全局定义函数可以被链接多次
  • 模板函数以及类内定义的成员函数默认情况下是内联的
  • 静态局部对象只会在第一次遇到它们时创建

您可以通过将全局值作为静态局部函数进行定义来在头文件中定义全局值,例如

inline int& global_val() //note the &
{ static int z = 0; return z; }

唯一的缺点是你必须在每次访问时都放置一个()。
由于本地值是唯一的并且在调用时实例化,这将确保如果全局变量之间存在依赖关系(想到 int z=0 作为 int z=something_else()),它们将按需要创建并以相反的顺序销毁,即使在递归和多个线程的情况下(自 C++14 起)。
考虑到 C++ 向泛型和函数式范式的发展,以及考虑到将所有源放在单个编译单元中有时比链接许多源更可取......不要使用全局变量,而是用内联实例化函数替换它们。
大约两年后的编辑:
C++17 最终引入了内联指令,也适用于变量声明,只是作为函数扩展的语法快捷方式。
所以 -今天- 你可以简单地写
inline const float PI = 3.14159;
inline int goodies = 25;

感谢提供有用的解释,非常感谢 :) - remi
C++17的跟进非常出色。谢谢。 - Pablo Adames

1

A.h

extern int testNumber;
void changeNumber();

A.cpp

#include "A.h"

int testNumber = 10;

void changeTestNumber()
{
    testNumber = 15;
} 

B.h

#include "A.h"
// some other stuff

B.cpp

#include "B.h"
// some other stuff

main.cpp

#include "B.h"
#include <iostream>

int main(){

    changeTestNumber();
    std::cout<<testNumber<<std::endl;

    return 0;

}

请尝试像这样做。

这里有很多好的答案,谢谢!这个方法有效 :) 我现在在使用向量方面遇到了一些小问题,但在开始提问之前,我会再尝试一下。 - remi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接