多重定义错误 #include -ing .h header
Multiple definition error #include -ing .h header
[环境为QTCreator 4.2.1,编译器为MinGW 5.8]
我创建了一个 header “header.h”
使用以下代码:
#ifndef HEADER_H
#define HEADER_H
int variable = 0;
#endif // HEADER_H
我试图将 header 包含在两个 .cpp 文件中,出现多个定义错误。
但是我像这样更改了代码并在两个 .cpp 文件之一中提到了静态变量 (int Class::variable;):
#ifndef HEADER_H
#define HEADER_H
class Class{
public:
static int variable;
};
#endif // HEADER_H
错误消失了。但是为什么我不能在没有错误的情况下两次包含 header 并在两个 .cpp 文件中使用全局变量(并直接使用它,而不是作为静态 class 成员)?
结合起来可以解释这三个主要原因:
- 它们是两个完全不同的“东西”。语法可能看起来很相似,但它们在 C++ 中做了两件完全不同的事情。
- The One Definition Rule
#include
头文件在逻辑上是等价的,并且完全等同于将头文件的内容物理插入任何 #include
d 它。
因此,在第一种情况下,对于包含 int variable = 0;
的头文件,#include
这个文件的每个翻译单元都变得完全一样
int variable = 0;
在 #include
的每个 .cpp
文件中逐字显示。完全相同的。而且,很明显,这违反了单一定义规则(如果不止一个 .cpp
有这个,并且它们链接在一起)。这实际上 创建了 一个 int
变量。它定义了它。一个名为 variable
的对象凭空出现。每个 .cpp
包含此文件的文件都成为 int variable
的骄傲拥有者。它存在于每个 .cpp
个文件中,这违反了一个定义规则。
另一方面,您有一个 class 声明,此处:
class Class{
public:
static int variable;
};
这不定义任何东西。它只是声明一个 Class
,它有一个 static
class 成员。而已。它不定义任何对象。它不会突然出现任何东西,也不会凭空物化任何东西。这只是声明了一个class,以及里面的内容。由于此处未定义任何内容的基本原因,单一定义规则没有发挥作用。
因此,尽管语法非常相似,但在 C++ 中看起来非常相似的代码的最终结果却大不相同。
在经历了同样的问题并做了一些研究之后,我想补充上面的答案!共享变量有多种方式,根据您的情况,您需要使用不同的选项。
全局变量声明
变量前的关键字 extern
允许您声明一个完全全局的变量,之后可以在您的所有代码中使用(只要您在该变量所在的位置包含特定的 header声明)。这样做的优点是设置起来非常简单,但缺点是随着您的代码变得越来越广泛,跟踪这些全局变量的含义变得更加困难。因此,一般情况下,不建议使用全局变量。
例如:
Header 文件 - header
#ifndef HEADER_H
#define HEADER_H
extern int global_x;
void print_global_x();
#endif
来源 1 - 主要
#include "header.h"
// since global_x still needs to be defined somewhere,
// we define it (for example) in this source file
int global_x;
int main()
{
//set global_x here:
global_x = 5;
print_global_x();
}
来源 2 - header.cpp
#include <iostream>
#include "header.h"
void print_global_x()
{
//print global_x here:
std::cout << global_x << std::endl;
}
(示例取自 this answer)
使用命名空间
在某些情况下使用全局变量可能不清楚。如果您在许多文件中使用它,您可能会忘记该变量的来源以及它的确切编辑位置。最重要的是,您可能有许多全局变量和方法,每个都特定于代码的各个方面。命名空间允许您指定每个变量的相关内容。例如:
Header 文件
#ifndef HEADER_H
#define HEADER_H
#include <iostream>
namespace x_storage
{
int x;
void print_x()
{
std::cout << x << std::endl;
}
}
namespace complicated_algorithms
{
int x;
void add_10_to_x_and_print()
{
x = x + 10;
std::cout << x << std::endl;
}
}
#endif
来源 1 - 主要
#include "header.h"
int main()
{
x_storage::x = 3; //set x in the x_storage namespace to 3
complicated_algorithms::x = 3; // set x in complicated_algorithms namespace to 3
int x = 4; //set x in the global namespace to 4
x_storage::print_x(); // prints 3
complicated_algorithms::add_10_to_x_and_print(); // prints 13
x_storage::print_x(); // prints 3
std::cout << x << std::endl; //prints 4
}
Class 带有静态元素
最后,您可以拥有一个所有元素都是静态的 class。当您希望这些变量在所有代码中共享但这些变量需要不断修改时,这就派上用场了。这样,所有修改都发生在 class 内。我发现这个有用的地方是在我的测试环境中,因为我有一些变量在整个测试过程中重复使用,但我想在每次测试开始时重置它们的值。示例:
Header 文件 - Counter.h
#ifndef COUNTER_H
#define COUNTER_H
class Counter
{
public:
static int get_counter();
static void add_one();
static void reset();
private:
static int counter;
}
#endif
来源 1 - Counter.cc
int Counter::counter = 0; //must always declare even if not set to anything
int Counter::get_counter()
{
return counter;
}
void Counter::add_one()
{
counter++;
}
void Counter::reset()
{
counter = 0;
}
来源 2 - 使用计数器
#include "Counter.h"
void method1()
{
Counter::reset();
if(first_condition_met()) Counter::add_one();
}
void method2()
{
if(another_condition_met()) Counter::add_one();
else Counter::reset();
}
这是一个简单的代码,您可以在其中尝试满足条件,以了解计数器在给定不同真值表时的行为。在其他更复杂的情况下,类似的方法可能非常有用。
[环境为QTCreator 4.2.1,编译器为MinGW 5.8]
我创建了一个 header “header.h” 使用以下代码:
#ifndef HEADER_H
#define HEADER_H
int variable = 0;
#endif // HEADER_H
我试图将 header 包含在两个 .cpp 文件中,出现多个定义错误。
但是我像这样更改了代码并在两个 .cpp 文件之一中提到了静态变量 (int Class::variable;):
#ifndef HEADER_H
#define HEADER_H
class Class{
public:
static int variable;
};
#endif // HEADER_H
错误消失了。但是为什么我不能在没有错误的情况下两次包含 header 并在两个 .cpp 文件中使用全局变量(并直接使用它,而不是作为静态 class 成员)?
结合起来可以解释这三个主要原因:
- 它们是两个完全不同的“东西”。语法可能看起来很相似,但它们在 C++ 中做了两件完全不同的事情。
- The One Definition Rule
#include
头文件在逻辑上是等价的,并且完全等同于将头文件的内容物理插入任何#include
d 它。
因此,在第一种情况下,对于包含 int variable = 0;
的头文件,#include
这个文件的每个翻译单元都变得完全一样
int variable = 0;
在 #include
的每个 .cpp
文件中逐字显示。完全相同的。而且,很明显,这违反了单一定义规则(如果不止一个 .cpp
有这个,并且它们链接在一起)。这实际上 创建了 一个 int
变量。它定义了它。一个名为 variable
的对象凭空出现。每个 .cpp
包含此文件的文件都成为 int variable
的骄傲拥有者。它存在于每个 .cpp
个文件中,这违反了一个定义规则。
另一方面,您有一个 class 声明,此处:
class Class{
public:
static int variable;
};
这不定义任何东西。它只是声明一个 Class
,它有一个 static
class 成员。而已。它不定义任何对象。它不会突然出现任何东西,也不会凭空物化任何东西。这只是声明了一个class,以及里面的内容。由于此处未定义任何内容的基本原因,单一定义规则没有发挥作用。
因此,尽管语法非常相似,但在 C++ 中看起来非常相似的代码的最终结果却大不相同。
在经历了同样的问题并做了一些研究之后,我想补充上面的答案!共享变量有多种方式,根据您的情况,您需要使用不同的选项。
全局变量声明
变量前的关键字 extern
允许您声明一个完全全局的变量,之后可以在您的所有代码中使用(只要您在该变量所在的位置包含特定的 header声明)。这样做的优点是设置起来非常简单,但缺点是随着您的代码变得越来越广泛,跟踪这些全局变量的含义变得更加困难。因此,一般情况下,不建议使用全局变量。
例如:
Header 文件 - header
#ifndef HEADER_H
#define HEADER_H
extern int global_x;
void print_global_x();
#endif
来源 1 - 主要
#include "header.h"
// since global_x still needs to be defined somewhere,
// we define it (for example) in this source file
int global_x;
int main()
{
//set global_x here:
global_x = 5;
print_global_x();
}
来源 2 - header.cpp
#include <iostream>
#include "header.h"
void print_global_x()
{
//print global_x here:
std::cout << global_x << std::endl;
}
(示例取自 this answer)
使用命名空间
在某些情况下使用全局变量可能不清楚。如果您在许多文件中使用它,您可能会忘记该变量的来源以及它的确切编辑位置。最重要的是,您可能有许多全局变量和方法,每个都特定于代码的各个方面。命名空间允许您指定每个变量的相关内容。例如:
Header 文件
#ifndef HEADER_H
#define HEADER_H
#include <iostream>
namespace x_storage
{
int x;
void print_x()
{
std::cout << x << std::endl;
}
}
namespace complicated_algorithms
{
int x;
void add_10_to_x_and_print()
{
x = x + 10;
std::cout << x << std::endl;
}
}
#endif
来源 1 - 主要
#include "header.h"
int main()
{
x_storage::x = 3; //set x in the x_storage namespace to 3
complicated_algorithms::x = 3; // set x in complicated_algorithms namespace to 3
int x = 4; //set x in the global namespace to 4
x_storage::print_x(); // prints 3
complicated_algorithms::add_10_to_x_and_print(); // prints 13
x_storage::print_x(); // prints 3
std::cout << x << std::endl; //prints 4
}
Class 带有静态元素
最后,您可以拥有一个所有元素都是静态的 class。当您希望这些变量在所有代码中共享但这些变量需要不断修改时,这就派上用场了。这样,所有修改都发生在 class 内。我发现这个有用的地方是在我的测试环境中,因为我有一些变量在整个测试过程中重复使用,但我想在每次测试开始时重置它们的值。示例:
Header 文件 - Counter.h
#ifndef COUNTER_H
#define COUNTER_H
class Counter
{
public:
static int get_counter();
static void add_one();
static void reset();
private:
static int counter;
}
#endif
来源 1 - Counter.cc
int Counter::counter = 0; //must always declare even if not set to anything
int Counter::get_counter()
{
return counter;
}
void Counter::add_one()
{
counter++;
}
void Counter::reset()
{
counter = 0;
}
来源 2 - 使用计数器
#include "Counter.h"
void method1()
{
Counter::reset();
if(first_condition_met()) Counter::add_one();
}
void method2()
{
if(another_condition_met()) Counter::add_one();
else Counter::reset();
}
这是一个简单的代码,您可以在其中尝试满足条件,以了解计数器在给定不同真值表时的行为。在其他更复杂的情况下,类似的方法可能非常有用。