楼主今天遇到一件很蛋疼的事,写了不到30行代码居然出错,尼玛气人的是半天找不到原因,真是少壮不努力老大徒伤悲啊!
废话少说,先看代码:
(IDE=vs2010)
Head.h
#ifndef HEAD_H //编译预处理,避免多次包含
#define HEAD_H
struct Node{
int data;
};
Node Dt; //定义结构体变量。
void setData(int dt);
#endif;
Head.cpp
#include"Head.h"
void setData(int dt)
{
Dt.data=dt;
}
Main.cpp
#include<iostream>
#include"Head.h"
using namespace std;
int main()
{
int input=1000;
setData(input);
cout<<Dt.data;
return 0;
}
编译时,错误提示:
Main.obj : error LNK2005: "struct Node Dt" (?Dt@@3UNode@@A) 已经在 Head.obj 中定义
d:\document_x64\documents\visual studio 2010\Projects\Test\Debug\Test.exe : fatal error LNK1169: 找到一个或多个多重定义的符号
哎呀,没道理啊,怎么会重定义呢,编译器大哥你抽风了吧!仔细想想估计还是自己的问题。结果在多方求助的情况下进行进一步尝试:
分析认为:Node Dt; //定义结构体变量。此变量要在其他文件使用必须声明为extern类型,即extern Dt; Head.h#ifndef HEAD_H //编译预处理,避免多次包含#define HEAD_H struct Node{int data;};Node Dt; //定义结构体变量。void setData(int dt);#endif;
Head.cpp
#include"Head.h"
void setData(int dt)
{
Dt.data=dt;
}
Main.cpp
#include<iostream>
#include"Head.h"
using namespace std;
extern Dt;//语法错误
int main()
{
int input=1000;
setData(input);
cout<<Dt.data;
return 0;
}
哎呀,怎么回事,直接语法错误,纠结。查书一看,貌似extern 用法是在不用include文件时需要使用的。
一计不成,再生一计:直接在Head.h里面声明为extern Node Dt,这回该成了吧。
Head.h
#ifndef HEAD_H //编译预处理,避免多次包含
#define HEAD_H
struct Node{
int data;
};
extern Node Dt; //定义结构体变量。
void setData(int dt);
#endif;
Head.cpp
#include"Head.h"
void setData(int dt)
{
Dt.data=dt;
}
Main.cpp
#include<iostream>
#include"Head.h"
using namespace std;
int main()
{
int input=1000;
setData(input);
cout<<Dt.data;
return 0;
}
这回更叫人郁闷了:编译器提示找不到定义的标识符Dt。这叫老夫如何是好啊。
仔细想想最开始就没错啊,为什么会出现重定义呢:
#ifndef HEAD_H
#define HEAD_H
代码段
#endif;
这个不是可以避免重复定义吗?怎么会,我又一次怀疑编译器大哥了。后来纠结中,经高人点化终于明白了是怎么回事,原来上述代码段只能保证一个文件中不能多次包含同一个文件,并不能保证多个文件不能多次包含并编译该文件。
这个其实说完我自己也不理解,举个例子吧:
我们有文件:A,B,C
B包含(include)A ; C包含B,A;那么在C中就会出现两次包含A,若没有上面的预处理就会导致同一文件中的重定义事件发生。
然而B中包含A,C中包含A,这样会导致A会编译两次,若其中有定义语句,那么也会被定义两次,这就会出现全局变量在不同文件中重定义事件发生。
例如:
A.h
int i;
B.cpp
#include"A.h"
cout<<i;
C.cpp
#include"A.h"
#include"B.h"
cout<<i;
事实上B.cpp也可以写成:
int i; //#include就是把代码导入
cout<<i;
同理C.cpp也可写为:(这里为思路更加清晰分两个阶段,编译器具体怎么操作就不知道了)
第一阶段:导入B.h A.h
int i;
#include"A.h"
cout<<i;
cout<<i;
第二阶段:导入从B.h中导入的A.h
int i;
int i; //很显然发生了重定义
cout<<i;
cout<<i;这就是最终的C.cpp
如果加入预处理模块:那么C.cpp会是:
int i;
cout<<i;
cout<<i;
但是由于全局变量的默认可见性是external(外部),所以在同一个工程中A.h,B.h,C.h都执行了int i;这样i被定义了三次,出现了全局变量在不同文件中的重定义。
好的至此我们依然解开了#include的面纱,也明白了错误之所在。
解决办法是,我们在A.h中不是定义全局变量,而是声明(我们也应想到,函数也不应在.h中定义,而只是声明)。改进后A为
A.h
extern int i; //extern声明了一个外部可见且可用的变量。
注:即使加了extern在现代编译器中也必须包含文件A才可以使用,因为现代编译器是先对单个文件编译的,编译时各文件间是透明的。
这样问题就解决了,下面贴下正确代码:
Head.h
#ifndef HEAD_H //编译预处理,避免多次包含
#define HEAD_H
struct Node{
int data;
};
extern Node Dt; //定义结构体变量。
void setData(int dt);
#endif;
Head.cpp
#include"Head.h"
Node Dt;
void setData(int dt)
{
Dt.data=dt;
}
Main.cpp
#include<iostream>
#include"Head.h"
using namespace std;
int main()
{
int input=1000;
setData(input);
cout<<Dt.data;
return 0;
}
输出结果:1000
最后补充一句:
extern Node Dt;
换成
static Node Dt;
也可以。
因为static只会在最开始声明的地方定义,也仅仅定义这一次,所以不会出现重定义。
总结篇
A.h 代码段A | B.h #include“A.h” 代码段B | 等价B.h 代码段A 代码段B |
#define 标识符
//代码段
#endif;
这段代码的作用是:保证一个文件中不会多次包含同一个文件(即不会多次导入同一个文件代码,这也就避免了同文件中的重定义问题)
注意:该代码段中的标识符只在文件中有效,不同文件互不影响,如A中定义可Head_H不代表B中定义了Head_H(因为现代编译器是静态编译,是以文件为单位,不同文件间编译是透明的)。
(3)全局变量的可见性:全局变量在默认情况下是external(外部)可见,故应该尽量少使用全局变量,否则很容易出现命名冲突,而导致重定义错误。如果非得使用全局变量则必须在头文件中声明,然后在某一个cpp文件中定义(记住只在一个cpp中定义),然后在其他需要的地方使用。编程风格应该尽量保持一个风格:.cpp文件只包含.h。最好不要.h包含.h,.h包含.cpp;.cpp包含.cpp。