C/C++语言中的变量分为全局变量和局部变量。这种划分方式的依据是变量的可见范围或者叫做作用域。
1 局部变量局部变量指的是定义在{}中的变量,其作用域也在这个范围内。虽然常见的局部变量都是定义在函数体内的,也完全可以人为的增加一对大括号来限定变量作用域。
如下所示:
void f() { float x = 0; { int a; } }别小看这个作用域问题,这对于C++的影响远比纯C要大。C语言中局部变量离开作用域时,编译器会插入一个POP 指令来清理变量占用的栈空间。而在C++中,除了POP指令,还要调用析构函数。
class MyClass { MyClass(){} ~MyClass(){} }; void f() { { MyClass a; } // 此处会C++编译器被插入调用~MyClass()的代码 do_someting(); }C++编译器在对象a的作用域结束之前,自动插入调用~MyClass()的汇编代码。
局部变量作用域是由编译器强制实施的,这样一旦出现作用域外访问,编译时就会报错,从而帮助程序员排除错误。
2 全局变量全局变量的作用域是整个工程,也就是在所有参与链接的文件中都是可见的。这就会导致一个问题-名称冲突。例如下面工程中有3个源文件main.c, 1.c, 2.c。
main.c
#include <stdio.h> int main(int argc, char** argv) { return 0; }1.c
int a = 1;2.c
int a = 2;编译每个文件都是可以通过的,但是链接时会报错,因为1.c和2.c使用了同一个名称的全局变量。为此,C语言的全局变量被给予了极坏的形象。甚至不使用全局变量的教条在很大范围内盛行。
然而全局变量在很多时候还是必须的,至少是使用它会让问题变得方便。例如当一个变量是很多函数的参数时。
void f1(int a); void f2(int a); void f3(int a);这样每次调用函数都需要传递这个变量a,当这样的参数个数增多时,会让人变得发狂。如
void f1(int a, int b, int c, int d, int e); void f2(int a, int b, int c, float g); void f3(int a, int b, int c, int d);这种情况在需要保存状态的程序中很常见,如GDI库,OpenGL库等。此时采用全局变量来维护状态数据是非常好的选择。C++看到了这种需要,所以索性把这些状态数据和算法函数绑定到了一起,形成了类的概念,从而简化了代码设计。
3 文件级变量很多时候,其实程序员需要变量的可见范围既不是整个工程,也不是函数内部,而是在当前文件中可见。C语言为此提供了静态全局变量。static global variable。这个名称完全没有能够反映出变量作用域的范围,是一个非常糟糕的名字。而且起关键字static更是让人摸不着头脑。
static int a = 100;C语言的设计者或许是为了节省关键字的使用,很多关键字用在不同的地方都有完全不同的含义。这种设计应该是仁者见仁的事情,我个人觉得如果此处使用其他的关键字如internal来标识,会更容易让人理解。
internal int a = 100;好像在C#中确实存在类似的关键字来表示作用域。
言归正传,static 修饰的全局变量只在定义它的文件内部有效,其他文件内无法引用它。上面的例子改为:
main.c
1.c
static int a = 1;2.c
int a = 2;此时,项目会链接成功。因为全局范围内只有一个名为a值为2的全局变量,值为1的那个a只在1.c内有效。
4 C和C++编译器对const常量的一点不同C++编译器对const常量会自动增加static关键字,使其作用域为文件级别。而C语言编译器则不会。如下代码:
main.c
#include <stdio.h> int main(int argc, char** argv) { return 0; }1
1.c
const int a = 1;1
2.c
int a = 2;使用C++编译器可以顺利编译链接成功,但是使用C编译器则在连接时报错。为了代码的可移植性,最好还是手动把 static const都写上。
main.c
#include <stdio.h> int main(int argc, char** argv) { return 0; }1.c
static const int a = 1;2.c
int a = 2;上述代码则在C和C++编译器下均可编译链接成功。