c++多个⽂件中如何共⽤⼀个全局变量
例⼦:
头⽂件:state.h 源⽂件:state.cpp
其它源⽂件:t1.cpp t2.cpp t3.cpp, 这些源⽂件都包含头⽂件state.h。
需要定义⼀个全局变量供这些源⽂件中使⽤:⽅法如下
1、在 state.h声明全局变量: extern inta;
2、在state.cpp中定义该全局变量:int a =10;
这样其它源⽂件就可以使⽤该变量啦
这⾥需要的是“声明”,不是“定义”!根据C++标准的规定,⼀个变量声明必须同时满⾜两个条件,否则就是定义:
(1)声明必须使⽤extern关键字;(2)不能给变量赋初值
extern int a; //声明
int a; //定义
int a = 0; //定义
extern int a =0; //定义
头⽂件中应使⽤extern关键字声明全局变量(不定义),如果这个变量有多个⽂件⽤到,可以新建⼀个cpp,在其中定义,把这个cpp加⼊⼯程即可。头⽂件请不要定义任何变量,那是⾮常业余的⾏为……
⼀般在头⽂件中申明,⽤extern,在cpp中定义。如果在头⽂件中定义,如果这个头⽂件被多个cpp引⽤,会造成重复定义的链接错误。
头⽂件只能申明全局变量(extern),不可定义(不推荐使⽤) .cpp⾥,在最外层定义即可(int gi),直接引⽤
如果在.cpp⾥使⽤static定义,则该变量只在当前cpp⽂件中有效,在别的⽂件中⽆效
在.h⾥使⽤static定义,不会进⾏编译(.h⽂件不编译),只会在其每个include的cpp⽂件中包含编译,相当于在.cpp⾥使⽤static定义。
1 基本解释:extern可以置于变量或者函数前,以标⽰变量或者函数的定义在别的⽂件中,提⽰编译器遇到此变量和函数时在其他模块中寻其定义。此外extern也可⽤来进⾏链接指定。
也就是说extern有两个作⽤,第⼀个,当它与"C"⼀起连⽤时,如: extern "C" void fun(int a, int b);则告诉编译器在编译fun这个函数名时按着C的规则去翻译相应的函数名⽽不是C++的,C++的规则在翻译这个函数名时会把fun这个名字变得⾯⽬全⾮,可能是fun@aBc_int_int#%$也可能是别的,这要看编译器的"脾⽓"了(不同的编译器采⽤的⽅法不⼀样),为什么这么做呢,因为C++⽀持函数的重载啊,在这⾥不去过多的论述这个问题,如果你有兴趣可以去⽹上搜索,相信你可以得到满意的解释!(C++ 和 C 采⽤的名称修饰规则不同)
第⼆,当extern不与"C"在⼀起修饰变量或函数时,如在头⽂件中: extern int g_Int; 它的作⽤就是声明函数或全局变量的作⽤范围的关键字,其声明的函数和变量可以在本模块活其他模块中使⽤,记住它是⼀个声明不是定义!也就是说B模块(编译单元)要是引⽤模块(编译单元)A中定义的全局变量或函数时,它只要包含A 模块的头⽂件即可,在编译阶段,模块B虽然不到该函数或变量,但它不会报错,它会在连接时从模块A⽣成的⽬标代码中到此函数。
2 问题:extern 变量
在⼀个源⽂件⾥定义了⼀个数组:char a[6];
在另外⼀个⽂件⾥⽤下列语句进⾏了声明:extern char *a;
请问,这样可以吗?
答案与分析:
1)、不可以,程序运⾏时会告诉你⾮法访问。原因在于,指向类型T的指针并不等价于类型T的数组。extern char *a声明的是⼀个指针变量⽽不是字符数组,因此与实际的定义不同,从⽽造成运⾏时⾮法访问。应该将声明改为extern char a[ ]。
2)、例⼦分析如下,如果a[] = “abcd”,则外部变量a=0x61626364 (abcd的ASCII码值),*a显然没有意义
显然a指向的空间(0x61626364)没有意义,易出现⾮法内存访问。
3)、这提⽰我们,在使⽤extern时候要严格对应声明时的格式,在实际编程中,这样的错误屡见不鲜。
4)、extern⽤在变量声明中常常有这样⼀个作⽤,你在.c⽂件中声明了⼀个全局的变量,这个全局的变量如果要被引⽤,就放在.h中并⽤extern来声明。
3 问题:单⽅⾯修改extern 函数原型
当函数提供⽅单⽅⾯修改函数原型时,如果使⽤⽅不知情继续沿⽤原来的extern申明,这样编译时编译器不会报错。但是在运⾏过程中,因为少了或者多了输⼊参数,往往会照成系统错误,这种情况应该如何解决?
答案与分析:
⽬前业界针对这种情况的处理没有⼀个很完美的⽅案,通常的做法是提供⽅在⾃⼰的xxx_pub.h中提供对外部接⼝的声明(extern),然后调⽤⽅include该头⽂件,从⽽省去extern这⼀步。以避免这种错误。
宝剑有双锋,对extern的应⽤,不同的场合应该选择不同的做法。
4 问题:extern “C”
在C++环境下使⽤C函数的时候,常常会出现编译器⽆法到obj模块中的C函数定义,从⽽导致链接失败的情况,应该如何解决这种情况呢?
答案与分析:
C++语⾔在编译的时候为了解决函数的多态问题,会将函数名和参数联合起来⽣成⼀个中间的函数名称,⽽C语⾔则不会,因此会造成链接时不到对应函数的情况,此时C函数就需要⽤extern “C”进⾏链接指定,这告诉编译器,请保持我的名称,不要给我⽣成⽤于链接的中间函数名。
下⾯是⼀个标准的写法:
//在.h⽂件的头上
#ifdef __cplusplus
#if __cplusplus
extern"C"{
#endif
#endif /* __cplusplus */
…
…
//.h⽂件结束的地⽅
#ifdef __cplusplus
#if __cplusplus
}
#endif
#endif /* __cplusplus */
5 问题:extern 函数声明
常常见extern放在函数的前⾯成为函数声明的⼀部分,那么,C语⾔的关键字extern在函数的声明中起什么作⽤?
答案与分析:
如果函数的声明中带有关键字extern,仅仅是暗⽰这个函数可能在别的源⽂件⾥定义,没有其它作⽤。即下述两个函数声明没有明显的区别:
extern int f();
int f();
当然,这样的⽤处还是有的,就是在程序中取代include “*.h”来声明函数,在⼀些复杂的项⽬中,我⽐较习惯在所有的函数声明前添加extern修饰。关于这样做的原因和利弊可见下⾯的这个例⼦:“⽤extern修饰的全局变量”
(1) 在test1.h中有下列声明:
#ifndef TEST1H
#define TEST1H
extern char g_str[]; // 声明全局变量g_str
void fun1();
#endif
(2) 在test1.cpp中
static修饰的变量#include "test1.h"
char g_str[] = "123456"; // 定义全局变量g_str
void fun1() { cout << g_str << endl; }
(3) 以上是test1模块, 它的编译和连接都可以通过,如果我们还有test2模块也想使⽤g_str,只需要在原⽂件中引⽤就可以了
#include "test1.h"
void fun2() { cout << g_str << endl; }
以上test1和test2可以同时编译连接通过,如果你感兴趣的话可以⽤ultraEdit打开test1.obj,你可以在⾥⾯到”123456”这个字符串,但是你却不能在test2.obj⾥⾯到,这是因为g_str是整个⼯程的全局变量,在内存中只存在⼀份,test2.obj这个编译单元不需要再有⼀份了,不然会在连接时报告重复定义这个错误!
注意:如果使⽤到sizeof,声明数组需要指定⼤⼩ extern char g_str[7]; 因为test2模块中只是声明了⼀个数组,但具体多⼤,并不知道。若没有在.h⽂件中声明数组⼤⼩,会报“⾮法的sizeof操作数”的错误。
(4) 有些⼈喜欢把全局变量的声明和定义放在⼀起,这样可以防⽌忘记了定义,如把上⾯test1.h改为
extern char g_str[] = "123456"; // 这个时候相当于没有extern
然后把test1.cpp中的g_str的定义去掉,这个时候再编译连接test1和test2两个模块时,会报连接错误,这是因为你把全局变量g_str的定义放在了头⽂件之后,test1.cpp这个模块包含了test1.h所以定义了⼀次g_str,⽽test2.cpp也包含了test1.h所以再⼀次定义了g_str,这个时候连接器在连接test1和test2时发现两个g_str。如果你⾮要把g_str的定义放在test1.h中的话,那么就把test2的代码中#include
“test1.h”去掉 换成:
extern char g_str[]; // 没有include “test1.h”
void fun2() { cout << g_str << endl; }
这个时候编译器就知道g_str是引⾃于外部的⼀个编译模块了,不会在本模块中再重复定义⼀个出来,但是我想说这样做⾮常糟糕,因为你由于⽆法在test2.cpp中使⽤#include “test1.h”,那么test1.h中声明的其他函数你也⽆法使⽤了,除⾮也⽤都⽤extern修饰,这样的话你光声明的函数就要⼀⼤串,⽽且头⽂件的作⽤就是要给外部提供接⼝使⽤的,所以 请记住, 只在头⽂件中做声明,真理总是这么简单。
1. extern 和 static
(1) extern 表明该变量在别的地⽅已经定义过了,在这⾥要使⽤那个变量.
(2) static 表⽰静态的变量,分配内存的时候, 存储在静态区,不存储在栈上⾯.
static 作⽤范围是内部连接的关系, 和extern有点相反.它和对象本⾝是分开存储的,extern也是分开存储的,但是extern可以被其他的对象⽤extern 引⽤,⽽static 不可以,只允许对象本⾝⽤它. 具体差别⾸先,static与extern是⼀对“⽔⽕不容”的家伙,也就是说extern 和static不能同时修饰⼀个变量;其次,static修饰的全局变量声明与定义同时进⾏,也就是说当你在头⽂件中使⽤static声明了全局变量(类外部)后(如果在头⽂件的class内部声明static变量,那么其定义式通常位于实现(.cpp)⽂件中,例外的,只有const int 类型可以in class 初值设定),它也同时被定义了;最后,static修饰全局变量的作⽤域只能是本⾝的编译单元(内部链接,参见 存储持续性、作⽤域和链接性),也就是说它的“全局”只对本编译单元有效,其他编译单元则看不到它,如:
(1) test1.h:
#ifndef TEST1H
#define TEST1H
static char g_str[] = "123456";
void fun1();
#endif
(2) test1.cpp:
#include "test1.h"
void fun1() { cout << g_str << endl; }
(3) test2.cpp
#include "test1.h"
void fun2() { cout << g_str << endl; }
以上两个编译单元可以连接成功, 当你打开test1.obj时,你可以在它⾥⾯到字符串”123456”,同时你也可以在test2.obj中到它们,它们之所以可以连接成功⽽没有报重复定义的错误是因为虽然它们有相同的内容,但是存储的物理地址并不⼀样,就像是两个不同变量赋了相同的值⼀样,⽽这两个变量
分别作⽤于它们各⾃的编译单元。 也许你⽐较较真,⾃⼰偷偷的跟踪调试上⾯的代码,结果你发现两个编译单元(test1,test2)的g_str的内存地址相同,于是你下结论static修饰的变量也可以作⽤于其他模块,但是我要告诉你,那是你的编译器在欺骗你,⼤多数编译器都对代码都有优化功能,以达到⽣成的⽬标程序更节省内存,执⾏效率更⾼,当编译器在连接各个编译单元的时候,它会把相同内容的内存只拷贝⼀份,⽐如上⾯的”123456”, 位于两个编译单元中的变量都是同样的内容,那么在连接的时候它在内存中就只会存在⼀份了,如果你把上⾯的代码改成下⾯的样⼦,你马上就可以拆穿编译器的谎⾔:
(1) test1.cpp:
#include "test1.h"
void fun1()
{
g_str[0] = ''a'';
cout << g_str << endl;
}
(2) test2.cpp
#include "test1.h"
void fun2() { cout << g_str << endl; }
(3)
void main() {
fun1(); // a23456
fun2(); // 123456
}
这个时候你在跟踪代码时,就会发现两个编译单元中的g_str地址并不相同,因为你在⼀处修改了它,所以编译器被强⾏的恢复内存的原貌,在内存中存在了两份拷贝给两个模块中的变量使⽤。正是因为static有以上的特性,所以⼀般定义static全局变量时,都把它放在原⽂件中⽽不是头⽂件,这样就不会给其他模块造成不必要的信息污染,同样记住这个原则吧!
1. extern 和const
C++中const修饰的全局常量据有跟static相同的特性,即它们只能作⽤于本编译模块中,但是const可以与extern连⽤来声明该常量可以作⽤于其他编译模块中, 如extern const char g_str[];
然后在原⽂件中别忘了定义: const char g_str[] = “123456”;
所以当const单独使⽤时它就与static相同,⽽当与extern⼀起合作的时候,它的特性就跟extern的⼀样了!所以对const我没有什么可以过多的描述,我只是想提醒你,const char* g_str = “123456” 与 const char g_str[] =”123465”是不同的, 前⾯那个const 修饰的是char ⽽不是g_str,它的g_str并不是常量,它被看做是⼀个定义了的全局变量(可以被其他编译单元使⽤), 所以如果你像让char*g_str遵守const的全局常量的规则,最好这么定义const char const g_str=”123456”.
/***************************************************************/
extern ⽤法,全局变量与头⽂件(重复定义)
⽤#include 可以包含其他头⽂件中变量、函数的声明,为什么还要extern关键字,如果我想引⽤⼀个全局变量或函数a,我只要直接在源⽂件中包含#include
include””
编译,链接都很顺利的过去了,由此可知,头⽂件仅仅为阅读代码作⽤,没其他的作⽤了!
不管是C还是C++,你把你的函数,变量或者结构体,类啥的放在你的.c或者.cpp⽂件⾥。然后编译成lib,dll,obj,.o等等,然后别⼈⽤的时候最基本的gcc hisfile.bj|dll|lib 等等。
但对于我们程序员⽽⾔,他们怎么知道你的lib,dll…⾥⾯到底有什么东西?要看你的头⽂件。你的头⽂件就是对⽤户的说明。函数,参数,各种各样的接⼝的说明。
那既然是说明,那么头⽂件⾥⾯放的⾃然就是关于函数,变量,类的“声明”了。记着,是“声明”,不是“定义”。
那么,我假设⼤家知道声明和定义的区别。所以,最好不要傻嘻嘻的在头⽂件⾥定义什么东西。⽐如全局变量:
ifndef XX头⽂件.H
define XX头⽂件.H
int A;
endif
那么,很糟糕的是,这⾥的int A是个全局变量的定义,所以如果这个头⽂件被多次引⽤的话,你的A会被重复定义.
显 然语法上错了。只不过有了这个#ifndef的条件编译,所以能保证你的头⽂件只被引⽤⼀次,不过也许还是会岔⼦,但若多个c⽂件包含这个头⽂件时还是会 出错的,因为宏名有效范围仅限于本c源⽂件,所以在这多个c⽂件编译时是不会出错的,但在链接时就会报错,说你多处定义了同⼀个变量,
Linking…
incl2.obj : error LNK2005: “int glb” (?glb@@3HA) already defined in incl1.obj
: fatal error LNK1169: one or more multiply defined symbols found
注意
extern
这个关键字真的⽐较可恶,在声明的时候,这个extern居然可以被省略,所以会让你搞不清楚到底是声明还是定义,下⾯分变量和函数两类来说:
(1)变量
尤其是对于变量来说。
extern int a;//声明⼀个全局变量a
int a; //定义⼀个全局变量a
extern int a =0 ;//定义⼀个全局变量a 并给初值。
int a =0;//定义⼀个全局变量a,并给初值,
第四个 等于 第 三个,都是定义⼀个可以被外部使⽤的全局变量,并给初值。
糊涂了吧,他们看上去可真像。但是定义只能出现在⼀处。也就是说,不管是int a;还是extern int a=0;还是int a=0;都只能出现⼀次,⽽那个extern int a可以出现很多次。
当你要引⽤⼀个全局变量的时候,你就要声明,extern int a;这时候extern不能省略,因为省略了,就变成int a;这是⼀个定义,不是声明。
(2)函数
对于函数也⼀样,也是定义和声明,定义的时候⽤extern,说明这个函数是可以被外部引⽤的,声明
的时候⽤extern说明这是⼀个声明。但由于函数的定义和声明是有区别的,定义函数要有函数体,声明函数没有函数体,所以函数定义和声明时都可以将extern省略掉,反正其他⽂件也是知道这个函数是在其他地⽅定义的,所以不加extern也⾏。两者如此不同,所以省略了extern也不会有问题。
⽐如:
int fun(void)
{
return 0;
}
很好,我们定义了⼀个全局函数
int fun(void);
我们对它做了个声明,然后后⾯就可以⽤了, 加不加extern都⼀样.
我们也可以把对fun的声明 放在⼀个头⽂件⾥,最后变成这样
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论