static和extern是C/C++中和函數的聲明有關的兩個關鍵字,特別是涉及到全局變量時,所以做此總結。
1. static關鍵字
1.1 函數和變量聲明(C/C++)
static全局變量:
當聲明一個static全局變量,則表示靜態全局變量,和其他變量一樣,存放在.data(初始化了)或者.bss(未初始化)內,但只在定義它的源文件中有效,其餘文件無法訪問它。
static局部變量:
具有以下特點:
函數中聲明一個static局部變量,不分配在堆或者棧上,也分配在.data(不分配在.bss)上
雖然是局部變量,整個進程生命週期都存在,不被釋放。
雖然一直存在,其餘函數不能訪問,其餘源文件更不能
會被自動初始化(所以不在.bss)上(普通局部變量不會,普通全局變量會自動初始化)
更進一步,每次調用這個變量時,其初始值都是上一次調用時修改的結果
static函數:
類似面向對象中private封裝,其只能被所定義的源文件調用,不能再其他源文件中調用(即使包含頭文件。。。。)
1.2 類成員的static(C++)
所有對象共有數據成員或者成員函數,類的靜態成員,在類創建時就存在(不需要對象)。
static關鍵字只能用於類內部的聲明,不能用於類外部的定義。
static成員函數沒有this形參(因爲不屬於某個對象),可以直接訪問static數據成員,不能直接使用非static成員。
static成員函數不能聲明爲const,因爲const成員函數真正的含義是不修改該成員函數所屬的對象,而static成員函數不屬於任何對象。
同上,static成員函數不能聲明爲虛函數!
static不是對象的組成部分,只是類的組成部分
2. extern關鍵字
extern關鍵字常用於頭文件中變量的聲明,表示這個變量的定義在其他文件中的全局變量,提示編譯器當遇到這個變量時,在其他文件中查找。
現代編譯器一般採用按文件編譯的方式,因此在編譯時,各個文件中定義的全局變量是互相不透明的。也就是說,在編譯時,全局變量的可見域限制在文件內部。
- 編譯階段是對各個文件進行單獨編譯,所以不會出錯,鏈接階段則出現了衝突,因爲都是全局變量,變量名也一樣
- 需要注意的是,此時兩個文件並沒有使用include包含,只是一同編譯。(如果使用include包含,則不需extern)
結合static來說,舉例說明:
a.cpp如下:
int i = 1;
b.cpp如下:
#include <iostream>
using namespace std;
extern int i;
int main() {
int a = i + 1;
cout << a << endl;
}
編譯:
g++ -O0 -std=c++11 -o run ./a.cpp b.cpp
./run
結果:
2
- 如果將b.cpp中extern去掉,則因爲i在a.cpp中已經定義爲了全局變量,當兩個文件編譯後進行鏈接時,會產生衝突,報錯如下:
/tmp/ccednFyK.o:(.data+0x0): multiple definition of `i'
- 如果將a.cpp中變量i加上static,同樣出錯,因爲static限制了i只能在a.cpp中使用,報錯如下:
b.cpp:(.text+0xa): undefined reference to `i'
值得注意的是:
C++將聲明和定義分開就是爲了防止重複編譯導致大量不必要的開銷,所以採用了“一處定義,多處聲明”的策略,也就是頭文件中只包含聲明,將定義放在同名的代碼源文件中。
Essential C++中強調了const object和inline函數是“一次定義”的例外:
inline函數因爲需要編譯器對其進行展開,所以需要編譯器在每個函數的調用點上都需要知悉函數的定義,所以常常將inline函數置於頭文件中。
const object因爲一出文件之外便不可見(const關鍵字的特性),所以需要頭文件中進行定義(因爲include頭文件,所以可以調用)
- 如果希望在其他文件中調用(非include包含)const全局變量,需要在變量的定義時顯示聲明爲extern。這樣在其他文件中,通過extern可以調用這個全局const object。(其實,非const的普通全局變量默認就是extern的)。
// a.cpp
extern const int i = 1;
int j = 2;
const int k = 3;
// b.cpp
extern const int i;
extern int j;
extern const in k;
int num = i + 1; // OK
int num = j + 1; // OK
int num = k + 1; // ERROR