单定义原则、外部变量与extern
C++有“单定义原则(One Definition Rule, ODR)”, 该规则决定了任何变量都只能有一次定义。为了实现这种需求,C++提供了两种变量声明。一种是定义声明(definition declaration),或者简称为定义(definition),它给变量分配存储空间;另外一种是引用声明(referencing declaration),或者简称为声明(declaration),它不给变量分配存储空间,因为它引用已有的变量。
C++中引用声明使用的关键字是"extern",并且不进行初始化,否则,声明将变成定义,编译器会给该变量分配存储空间。
extern主要针对具有多个源文件的项目。如果有多个源文件需要使用到相同的全局变量,则这些变量只需要在其中的某一个源文件中定义一次,其它的源文件如果想使用这些外部变量,需要在声明的时候前面加上"extern"。
//file01.cpp
extern int cats = 20; // 对cat定义
int dogs 22; // 定义dogs
int fleas; // 定义fleas
…
//file02.cpp
//use cat and dogs from file01.cpp
extern int cats; // 无需定义,直接使用外部变量
extern int dogs;
extern int fleas;
…
从上面这段代码中可以看出,在定义变量的时候,关键字extern并不是必须的,但是,如果想声明外部变量,则extern关键字是必不可少的。
单定义原则(ODR)≠ 不允许存在同名变量
计算机识别变量依靠的是地址(务必记住这句话,只要是地址不一样,即使它们的名字相同,计算机也会认为它们是不同的变量),全局变量和静态变量存储在静态区,而函数中声明、定义的变量则存储在栈区。不同函数之间声明的同名自动变量是彼此独立的。但是,对于每个“变量”,程序中仅允许定义一次,服从ODR。
此外,如果在函数的内部声明、定义一个与全局变量同名同类型的自动变量,那么该变量的值会覆盖原来的全局变量。以下程序对此有一个较好的诠释。
//external.cpp
// author: pengqi
// date: 2020/03/02
// reference: C++ Primer Plus P312
#include
using namespace std;
// external warming
double warming = 1.7; // define warming
void update(double dt);
void local();
int main() {
cout << "Global warming is " << warming << endl;
update(1.1);
cout << "Global warming is " << warming << endl;
local();
cout << "Global warming is " << warming << endl;
return 0;
}
------------------
//external.cpp
// author: pengqi
// date: 2020/03/02
// reference: C++ Primer Plus P312
#include
extern double warming; // Using the external warming
void update();
void local();
using std::cout;
void update(double dt) {
warming = dt;
}
void local() {
double warming = 0.5;
cout << "Local warming is " << warming << std::endl;
cout << "However, Global warming is " << ::warming << std::endl;
}
程序运行的输出:
$ g++ external.cpp support.cpp -o extern_test
$ ./extern_test
Global warming is 1.7
Global warming is 1.1
Local warming is 0.5
However, Global warming is 1.1
Global warming is 1.1
从上面程序的运行结果中可以看出,函数local同样声明了一个名为warming的变量,这时,在函数内部,warming的值使用的是新声明的这个,原来的全局变量被隐藏了。如果想使用原来的全局变量,需要在变量前面加上“::”, 称之为作用域解析运算符。这也是C++比C语言优越的一点体现。
全局变量 VS 局部变量
全局变量固然有其优势,所有的函数都可以访问,甚至是函数在调用的时候都不用传递参数。但是,过分地使用全局变量也会使得程序变得不可靠。计算经验表明,程序越能避免对数据进行不必要的访问,就越能保持数据的完整性。C++作为一门面向对象的编程语言,本身就在数据隔离方面迈出了关键性的一步。
加static的全局变量与不加的有何区别
如果整个程序只有一个源文件,那么在全局变量前加不加static将不会对其产生任何影响。但是如果程序由多个源文件组成,那么static就会对该全局变量的作用域产生影响了。加上了static的全局变量,其作用域将被限定在本文件内,虽然变量可以在文件内部全局调用,但是其链接性将变成内部。
观察下面一段代码:
//file1
int error = 20;
...
---------------------
//file2
int error = 5;
void foo() {
cout << error;
}
这种情况下,程序是编不过的,因为它违反了单定义原则(ODR)。file1已经创建了error作为外部变量的定义,file2又试图定义相同的变量。但是,如果把程序改成如下形式,则可以编译通过:
//file1
int error = 20;
...
-----------------
//file2
static int error = 5;
void foo() {
cout << error; // user will use error defined in file2
}
const对于全局变量作用域的影响
之前讲过,如果不加static修饰的话,默认全局变量的链接性是外部的。但是,如果加了const后,该全局变量的链接性就会变成内部的了。
const int fingers = 10;
以上代码等价于:
static const int finger = 10;
如果我们希望const常量具有外部链接性,就需要在定义的时候前面加上extern,如下:
extern const int finger = 10;
虽然对于普通的全局变量,加不加extern不影响。
函数的链接性(extern 与 static)
与C语言一样,C++不允许在一个函数中定义另外一个函数,因此所有的函数存储持续性都自动为静态的,即整个程序执行期间都一直存在。默认情况下,函数的链接性都是外部的,即全局函数都可以在文件之间共享。实际上,可以在函数原型前面加上extern,表明该函数是在其它文件中定义的,不过这是可选的。static关键字同样适用于函数,如果一个函数被static说明符修饰,则该函数只能在该文件中使用,且static必须在函数的声明和定义中都使用。例如:
static int private(double x);
...
static int private(double x) {
...
}
与此同时,这还意味着其它文件中可以定义同名的函数。在此文件中,静态函数会覆盖同名的外部定义的全局函数,正如静态变量会覆盖全局变量一样。