CC++QT变量以及(静态、⾮静态)全局变量定义问题(巨详
细)
⾸先介绍⼏个概念:
1. 编译单元(模块):
在IDE开发⼯具⼤⾏其道的今天,对于编译的⼀些概念很多⼈已经不再清楚了,很多程序员最怕的就是处理连接错误(LINK ERROR), 因为它不像编译错误那样可以给出你程序错误的具体位置,你常常对这种错误感到懊恼,但是如果你经常使⽤gcc,makefile等⼯具在linux或者嵌 ⼊式下做开发⼯作的话,那么你可能⾮常的理解编译与连接的区别!当在VC这样的开发⼯具上编写完代码,点击编译按钮准备⽣成exe⽂件时,VC其实做了两 步⼯作,第⼀步,将每个.cpp(.c)和相应.h⽂件编译成obj⽂件;第⼆步,将⼯程中所有的obj⽂件进⾏LINK⽣成最终的.exe⽂件,那么错 误就有可能在两个地⽅产⽣,⼀个是编译时的错误,这个主要是语法错误,另⼀个是连接错误,主要是重复定义变量等。我们所说的编译单元就是指在编译阶段⽣成 的每个obj⽂件,⼀个obj⽂件就是⼀个编译单元,也就是说⼀个cpp(.c)和它相应的.h ⽂件共同组成了⼀个编译单元,⼀个⼯程由很多个编译单元组 成,每个obj⽂件⾥包含了变量存储的相对地址等 。
2. 声明与定义的区别
函数或变量在声明时,并没有给它实际的物理内存空间,它有时候可以保证你的程序编译通过, 但是当函数或变量定义的时候,它就在内存中有了实际的物理空间,如果你在编译模块中引⽤的外部变量没有在整个⼯程中任何⼀个地⽅定义的话, 那么即使它在编译时可以通过,在连接时也会报错,因为程序在内存中不到这个变量!你也可以这样理解, 对同⼀个变量或函数的声明可以有多次,⽽定义只能有⼀次!
extern int a;    //声明⼀个全局变量a
int a;    //定义⼀个全局变量a
extern int a =0 ;    //定义⼀个全局变量a 并给初值。⼀旦给予赋值,⼀定是定义,定义才会分配存储空间。
int a =0;    //定义⼀个全局变量a,并给初值,
声明之后你不能直接使⽤这个变量,需要定义之后才能使⽤。
(注:⼀般在头⽂件中只进⾏变量的声明,因为⼀个头⽂件⼀般会被另⼀个头⽂件包含,所以如果定义变量的话会造成重复定义,会抛出error,但是static定义的变量除外,下⾯会讲到)
3. extern的作⽤
extern有两个作⽤,第⼀个,当它与"C"⼀起连⽤时,如: extern "C" void fun(int a, int b); 则告诉编译器在编译fun这个函数名时按着C 的规则去翻译相应的函数名⽽不是C++的, C++的规则在翻译这个函数名时会把fun这个名字变得⾯⽬全⾮(根本原因是因为C++⽀持重载,所以函数编译时就会加上其⾥⾯的参数类型之类的东西),可能是fun@aBc_int_int#%$也可能是别的,这要看编译器的"脾⽓"了 (不同的编译器采⽤的⽅法不⼀样),为什么这么做呢,因为C++⽀持函数的重载啊,在这⾥不去过多的论述这个问题,如果你有兴趣可以去⽹上搜索,相信你可 以得到满意的解释!
当extern不与"C"在⼀起修饰变量或函数时,如在头⽂件中: extern int g_Int;  它的作⽤就是声明函数或全局变量的作⽤范围的关键字,其声明的函数和变量可以在本模块活其他模块中使⽤,记住它是⼀个声明不是定义!也就是说B模块(编译 单元)要是引⽤模块(编译单元)A中定义的全局变量或函数时,它只要包含A模块的头⽂件即可, 在编译阶段,模块B虽然不到该函数或变量,但它不会报错,它会在连接时从模块A⽣成的⽬标代码中到此函数。
如果你对以上⼏个概念已经⾮常明⽩的话,那么让我们⼀起来看以下⼏种全局变量/常量的使⽤区别:
1. ⽤extern修饰的全局变量
以上已经说了extern的作⽤,下⾯我们来举个例⼦,如:
test1.h
#ifndef TEST1H
#define TEST1H
extern char g_str[]; // 声明全局变量g_str
void fun1();
#endif
test1.cpp
#include "test1.h"
#include <iostream>
using namespace std;
char g_str[] = "123456"; // 定义全局变量g_str
void fun1()
{
cout << g_str << endl;
}
以上是test1模块, 它的编译和连接都可以通过,如果我们还有test2模块也想使⽤g_str,只需要在原⽂件中引⽤就可以了
test2.cpp
#include "test1.h"
#include <iostream>
using namespace std;
void fun2()
{
cout << g_str << endl;
}
以上test1和test2可以同时编译连接通过,如果你感兴趣的话可以⽤ultraEdit打开test1.obj,你可以在⾥⾯着"123456"这 个字符串,但是你却不能在test2.obj⾥⾯到,这是因为g_str是整个⼯程的全局变量,在内存中只存在⼀份, test2.obj这个编译单元不需要再有⼀份了,不然会在连接时报告重复定义这个错误!
有些⼈喜欢把全局变量的声明和定义放在⼀起,这样可以防⽌忘记了定义,如把上⾯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[];
void fun2()const的作用
{
cout << g_str << endl;
}
这个时候编译器就知道g_str是引⾃于外部的⼀个编译模块了,不会在本模块中再重复定义⼀个出来,但是我想说这样做⾮常糟糕,因为你由于⽆法在 test2.cpp中使⽤#include "test1.h", 那么test1.h中声明的其他函数你也⽆法使⽤了,除⾮也⽤都⽤extern修饰,这样的话你光声明的函数就要⼀⼤串,⽽且头⽂件的作⽤就是要给外部提 供接⼝使⽤的,所以 请记住, 只在头⽂件中做声明,真理总是这么简单。
2. ⽤static修饰的“全局”变量 (注:类 的静态变量 除外,特殊(可认为就是真正的全局变量),因为只存在⼀份拷贝,所以和extern效果⼀样,改变⼀个则都改变)
⾸先,我要告诉你static与extern是⼀对“⽔⽕不容”的家伙,也就是说extern和static不能同时修饰⼀个变量;其次,static修 饰的全局变量声明与定义同时进⾏,也就是说当你在头⽂件中使⽤static声明了全局变量后,它也同时被定义了(是同时发⽣的);最后,static 修饰全局变量的作⽤域 只能是本⾝的编译单元,也就是说它的“全局”只对本编译单元有效(其作⽤域只是该源⽂件),其他编译单元则看
不到它, (也就是说你⽤static定义的变量只是可以全局使⽤,但是每个⽂件中的该变量虽然名字相同,但是内存地址不⼀样,也就是说谁还是谁,谁的改变都不会影响到另⼀个⽂件中的该变量)如:
test1.h:
test1.h:
#ifndef TEST1H
#define TEST1H
static char g_str[] = "123456";
void fun1();
#endif
test1.cpp:
#include "test1.h"
void fun1()
{
cout << g_str << endl;
}
test2.cpp
#include "test1.h"
void fun2()
{
cout << g_str << endl;
}
以上两个编译单元可以连接成功, 当你打开test1.obj时,你可以在它⾥⾯到字符串"123456", 同时你也可以在test2.obj中到它们,它们之所以可以连接成功⽽没有报重复定义的错误是因为虽然它们有相同的内容,但是存储的物理地址并不⼀样, 就像是两个不同变量赋了相同的值⼀样,⽽这两个变量
分别作⽤于它们各⾃的编译单元。
也许你⽐较较真,⾃⼰偷偷的跟踪调试上⾯的代码,结果你发现两个编译单元(test1, test2)的g_str的内存地址相同,于是你下结论static修饰的变量也可以作⽤于其他模块,但是我要告诉你,那是你的编译器在欺骗你,⼤多数编 译器都对代码都有优化功能,以达到⽣成的⽬标程序更节省内存,执⾏效率更⾼,当编译器在连接各个编译单元的时候,它会把相同内容的内存只拷贝⼀份,⽐如上 ⾯的"123456", 位于两个编译单元中的变量都是同样的内容,那么在连接的时候它在内存中就只会存在⼀份了, 如果你把上⾯的代码改成下⾯的样⼦,你马上就可以拆穿编译器的谎⾔:
test1.cpp:
#include "test1.h"
void fun1()
{
g_str[0] = 'a';
cout << g_str << endl;
}
test2.cpp
#include "test1.h"
void fun2()
{
cout << g_str << endl;
}
void main()
{
fun1(); // a23456
fun2(); // 123456
}
这个时候你在跟踪代码时,就会发现两个编译单元中的g_str地址并不相同,因为你在⼀处修改了它,所以编译器被强⾏的恢复内存的原貌,在内存中存在了两份拷贝给两个模块中的变量使⽤。
貌,在内存中存在了两份拷贝给两个模块中的变量使⽤。
正是因为static有以上的特性,所以⼀般定义static全局变量时,都把它放在原⽂件中⽽不是头⽂件,这样就不会给其他模块造成不必要的信息污染(也就是说定义的变量不放在头⽂件中,这样就不会被其他⽂件包含,因此就不会出现同名变量的问题,从⽽导致后续有什么⿇烦(⽐如认为在另⼀个⽂件中修改了该“全局”变量,其实没有)),同样记住这个原则吧!
(注:类 的静态变量 除外,特殊(可认为就是真正的全局变量),因为只存在⼀份拷贝,所以和extern效果⼀样,改变⼀个则都改变)
3 const修饰的全局常量
const修饰的全局常量⽤途很⼴,⽐如软件中的错误信息字符串都是⽤全局常量来定义的。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".

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。