作用域
前面已经创建了由多个文件构成的项目,是时候再来讨论更复杂的变量作用域了。
简单的理解,变量的作用域就是你可以再什么范围内访问这个变量。
我们都知道,一个在任何函数之前定义的变量可以在任何一个函数里使用(这是一个全局变量),而在某个函数里定义的变量只能在那一个函数里使用(这是一个局部变量)。
那么,当一个项目由多个文件构成时,变量的作用域也会受到一定的影响!
链接
与作用域有关的另一个概念是链接,当你同时编译多个文件时:
- g++ -o test main.cpp rational.cpp(不用管这个是啥,暂时不用懂)
每个源文件都被称为一个翻译单元(translation unit),在某一个翻译单元里定义的东西在另一个翻译单元里使用正是链接发挥作用的地方。
作用域、链接和存储类是相互关联的概念,它们有许多共同的术语,只是观察和描述问题的角度不同罢了。
存储类
每个变量都有一个存储类,它决定着程序将把变量的值存储在计算机上的什么地方、如何存储,以及变量应该有着怎样的作用域。
auto
默认的存储类是auto(自动),但你不会经常看到这个关键字,因为它是默认的。
自动变量存储在栈(stack)的临时内存里并有着最小的作用域,当程序执行到语句块或函数末尾的右花括号时,它们将被系统回收(栈回收),不复存在。
static
与auto不同的是static,static变量在程序的生命期内将一直保有它的值而不会消亡,因为它们是存储在静态存储区,声明周期从申请到程序退出(和全局变量一样)。
另外我们稍后就会提到,一个static变量可以有external和internal链接。
extern
第三种存储类是extern,它在有多个翻译单元时非常重要。这个关键字用来把另一个翻译单元里的某个变量声明为本翻译单元里的一个同名全局变量。
注意,编译器不会为extern变量分配内存,因为在其他地方已经为它分配过内存。
用extern关键字相当于告诉编译器:“请相信我,我发誓我知道这个变量在其他翻译单元李肯定存在,它只是没在这个文件里声明而已。”
register
还有一个存储类是register,它要求编译器把一个变量存储在CPU的寄存器里。但有着有自动变量相同的作用域。
register变量存储速度最快,但有些编译器可能不允许使用这类变量。
变量的链接和作用域
在使用编译器建立程序时,它实际上是由3个步骤构成:
1. 执行预处理指令;
2. 把.cpp文件变异成.o文件;
3. 把.o文件链接成一个可执行文件。
如今的编译器都是一次完成所有的处理,所以你看不到各个步骤。
步骤一前面我们已经讨论过:执行预处理指令,例如把#include指令替换为相应的头文件里的代码,总的效果是头文件里的代码就像一开始就在.cpp文件里面似的。
步骤二是我们司空见惯的事情:把C++代码转换为一个编译目标文件,在这一步骤里,编译器将为文件里的变量分配必要的内存并进行各种错误检查。
如果只有一个C++源文件,步骤三通常只是增加一些标准库代码和生成一个可执行文件。但当你同时编译多个源文件来生成一个可执行文件的时候,在编译好每一个组件之后,编译器还需要把它们链接在一起才能生成最终的可执行文件。
当一个编译好的对象(即翻译单元)引用一个可能不存在于另一个翻译单元里的东西时,潜在的混乱就开始出现,就像上面的那个extern骗了编译器。
链接分为三种情况
凡是有名字的东西(函数、类、常量、变量、模板、命名空间等等)必然属于其中之一:
外链接(external)
外链接的意思是每个翻译单元都可以访问这个东西(前提是只要它知道有这么个东西存在)。
普通的函数、变量、模板和命名空间都有外链接。
就像main.cpp可以使用rational.cpp文件里定义的类和函数一样,其实我们一直在使用,只是今天来次总结。
说到变量,你可以这样试一试:
//this.cpp
int i1 = 1;
//that.cpp
int i2 = i1;
上面这个意思是在this里面编译了一个,在that里面又编译了一个,但是这样是不行的,因为在编译that.cpp文件时,编译器并不知道i1变量的存在。我们并没有用一个头文件把它们链接在一块。
为了解决这个问题,我们可以在that.cpp使用extern关键字去访问第一个翻译单元的变量。如:
//this.cpp
int i1 = 1;
//that.cpp
extern int i1;
int i2 = i1;
内链接(internal)
内链接的含义是:在某个翻译单元李定义的东西只能在翻译单元里使用,在任何函数以外定义的静态变量都有内链接:
//this.cpp
static int d = 8;
//that.cpp
static int d = 9;
这两个文件各有一个同名的变量,但它们是毫不相干的两样东西。
无链接(none)
最后,在函数里定义的变量只存在于该函数的内部,根本没有任何链接(none)。