C/C++語言中的變量分為全局變量和局部變量。這種劃分方式的依據是變量的可見范圍或者叫做作用域。
局部變量指的是定義在{}中的變量,其作用域也在這個范圍內。雖然常見的局部變量都是定義在函數體內的,也完全可以人為的增加一對大括號來限定變量作用域。
如下所示:
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()的匯編代碼。
局部變量作用域是由編譯器強制實施的,這樣一旦出現作用域外訪問,編譯時就會報錯,從而幫助程序員排除錯誤。
全局變量的作用域是整個工程,也就是在所有參與鏈接的文件中都是可見的。這就會導致一個問題-名稱沖突。例如下面工程中有3個源文件main.c, 1.c, 2.c。
main.c
#include
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++看到了這種需要,所以索性把這些狀態數據和算法函數綁定到了一起,形成了類的概念,從而簡化了代碼設計。
很多時候,其實程序員需要變量的可見范圍既不是整個工程,也不是函數內部,而是在當前文件中可見。C語言為此提供了靜態全局變量。static global variable。這個名稱完全沒有能夠反映出變量作用域的范圍,是一個非常糟糕的名字。而且起關鍵字static更是讓人摸不著頭腦。
static int a = 100;
C語言的設計者或許是為了節省關鍵字的使用,很多關鍵字用在不同的地方都有完全不同的含義。這種設計應該是仁者見仁的事情,我個人覺得如果此處使用其他的關鍵字如internal來標識,會更容易讓人理解。
internal int a = 100;
好像在C#中確實存在類似的關鍵字來表示作用域。
言歸正傳,static 修飾的全局變量只在定義它的文件內部有效,其他文件內無法引用它。上面的例子改為:
main.c
#include
int main(int argc, char** argv)
{
return 0;
}
1.c
static int a = 1;
2.c
int a = 2;
此時,項目會鏈接成功。因為全局范圍內只有一個名為a值為2的全局變量,值為1的那個a只在1.c內有效。
C++編譯器對const常量會自動增加static關鍵字,使其作用域為文件級別。而C語言編譯器則不會。如下代碼:
main.c
#include
int main(int argc, char** argv)
{
return 0;
}
1.c
const int a = 1;
2.c
int a = 2;
使用C++編譯器可以順利編譯鏈接成功,但是使用C編譯器則在連接時報錯。為了代碼的可移植性,最好還是手動把 static const都寫上。
main.c
#include
int main(int argc, char** argv)
{
return 0;
}
1.c
static const int a = 1;
2.c
int a = 2;
上述代碼則在C和C++編譯器下均可編譯鏈接成功。