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

楼主今天遇到一件很蛋疼的事,写了不到30行代码居然出错,尼玛气人的是半天找不到原因,真是少壮不努力老大徒伤悲啊!废话少说,先看代码:(IDE=vs2010)Head.h#ifndef HEAD_H //编译预处理,避免多次包含#define HEAD_H struct Node{int data;};Node Dt; //定义结构体变量。
摘要由CSDN通过智能技术生成

楼主今天遇到一件很蛋疼的事,写了不到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

代码段A
B.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。



      

      




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值