浅析C/C++作用域之全局变量

来源:互联网 发布:同志软件有那些 编辑:程序博客网 时间:2024/06/06 12:26

楼主今天遇到一件很蛋疼的事,写了不到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只会在最开始声明的地方定义,也仅仅定义这一次,所以不会出现重定义。

总结篇

(1)#Include 的理解:就是当被包含文件代码导入当前文件(有点类似内联函数)
A.h

代码段AB.h
#include“A.h”

代码段B等价B.h

代码段A
代码段B正因为这样,可能会出现多个文件包含同一个文件I,若被包含的文件I中有全局变量的定义(而全局变量是外部可见的),则会导致同一变量在不同文件的多次定义

即使只有两个文件,如上例,A中定义了一次,B中代码段A会重新编译,还会再定义一次,同样发生了重定义。
(2)#ifndef   标识符

    #define  标识符

     //代码段

    #endif;

这段代码的作用是:保证一个文件中不会多次包含同一个文件(即不会多次导入同一个文件代码,这也就避免了同文件中的重定义问题

注意:该代码段中的标识符只在文件中有效,不同文件互不影响,如A中定义可Head_H不代表B中定义了Head_H(因为现代编译器是静态编译,是以文件为单位,不同文件间编译是透明的)。

(3)全局变量的可见性:全局变量在默认情况下是external(外部)可见,故应该尽量少使用全局变量,否则很容易出现命名冲突,而导致重定义错误。如果非得使用全局变量则必须在头文件中声明,然后在某一个cpp文件中定义(记住只在一个cpp中定义),然后在其他需要的地方使用。编程风格应该尽量保持一个风格:.cpp文件只包含.h。最好不要.h包含.h,.h包含.cpp;.cpp包含.cpp。



      

      




原创粉丝点击