- 要進入文章之前,要先依序理解變數的宣告及定義 & 變數可視範圍及優先順序
> 變數的宣告及定義
以下都以整數型態做示範
- 註:「變數」可以多次宣告,但只能一次定義。
- 變數的宣告:
extern int num;
注意:int num; 並不是宣告,是定義。
- 變數的定義
ex1:int i;
ex2:int i = 1 (定義與初始化)
ex3:extern int i = 1 (定義+初始化)
> 變數的可視範圍
- 可視範圍:全域變數 > 區域變數 > 區塊變數
- 同名稱變數優先順序:區塊變數 > 區域變數 > 全域變數
- 全域變數:
在任何函數與類別之外所宣告,一般情況下作用域與生命週期是整個程式以及直到程式結束才終止。- 區域變數:
在函式之類所宣告的變數,作用域與生命週期只在函數之內,離開函數之外就消失。- 區塊變數:
在迴圈類型(while、for…)的區塊內所宣告,作用域及生命僅在迴圈之內,離開之後便消失。
> 關鍵字的使用
- extern (呼叫外部的全域變數)
使用在外部檔案(不須使用include引進)的全域變數,概念是在外部檔案A宣告並定義全域變數之後,在本地檔案B使使用extern再次宣告該變數為外部變數,來達到使用到外部變數。
例子一 :一般情況下 “extern” 宣告全域變數
//外部檔案A.h
int num = 3; //在外部檔案定義一個全域變數
//上述也可寫成以下方式:
//extern num = 3; //一般全域變數預設都是 extern宣告,所以省略不寫
//本地檔案B
//不需要include "A.h",若引進會發生重複定義的錯誤
extern int num; //extern所呼叫的外部變數,不可同時宣告+初始化,若需初始化則要分開執行
num = 10 //分開後初始化的外部變數
int main (){
std::cout << num << std::endl; //輸出10,若不在此文件中初始化為 10,則保持外部所儲存的值 3
}
例子二 :當要使用 “extern” 使用型態為 “const” 的外部全域變數的特殊情況
// 外部檔案 A.h
extern const int num = 3; //欲在外部使用 const型態的全域變數,需在宣告/定義時便加入"extern"宣告
// 本地檔案 B.cpp
extern const int num; //這邊 extern所呼叫的外部變數也須加上 "const" 修飾詞
//num = 10; //ERROR,當然的 const外部變數也是無法更動
int main (){
std::cout << num << std::endl; //輸出3
}
- static (靜態宣告)
類似全域變數,變數宣告之後,就會一直存在在記憶體之中值到整個程式結束,即便視宣告在函式中的靜態區域變數,函數結束之後該靜態變數也不會在記憶體中消失,但卻保有只有該函數可以訪問此靜態變數的權限。
例子一 :使用 “static” 宣告靜態區域變數
// A.cpp
void fun1(){
static int num = 0; //靜態區域變數
num++;
std::cout << num << std::endl;
}
// B.cpp
int main(){
fun1(); //輸出 1
fun1(); //輸出 2 ,狀況1
std::cout << num << std::ednl; //此處會出現錯誤,num未宣告,狀況2
return 0;
}
上述狀況1 : 因為靜態變數會持續保有在記憶體之中,即便變數離開函式之後還是存在在記憶體,所以可達到累加的作用
上述狀況2 : 因為該靜態變數是宣告在fun1函數裡頭的區域變數,雖然仍保有在記憶體之中,但有訪問該變數權限的只有函數fun1()
例子二 :使用 “static” 宣告靜態變數
// A.cpp
static int num = 0; //宣告靜態全域變數,狀況1
//int num = 0; //若是這樣宣告一般的全域變數便可讀取到
void fun1(){
static num2 = 2; //宣告靜態區域變數,保有區域變數的特性無法在函式以外使用,更別說是在別的檔案使用
}
// B.cpp
int main(){
extern int num; //ERROR,無法讀取到外部全域變數 num
std::cout << num << std::ednl;
return 0;
}
上述狀況1 : 因為靜態宣告有著只有宣告此靜態變數的原始檔才可使用此靜態變數的特性,無論是區域變數還是全域變數 ,即便使用#include包進來也無法使用,所以上述檔案B無法讀取到在檔案A所宣告的靜態全域變數
例子三 :使用 “static” 宣告靜態函式
// A.cpp
static void fun1(int x) {retrun x;} //靜態全域函式,與例子二一樣,無法在此原始檔以外的地方使用
//void fun1(int x) {retrun x;} //若是這樣宣告便可被讀取到
// B.cpp
int main(){
extern void fun1(int x); //ERROR,無法讀取到外部全域函式 fun1(int x)
std::cout << num << std::ednl;
return 0;
}
例子四 :使用 “static” 宣告靜態類別成員
- 特性1:靜態類別成員屬於 “類別” 所有,在類別成立時便存在,並不像一般成員個別屬於類別實例化的物件所有,靜態成員可視為每個物件實例所共享的資料成員,因此可直接(建議)藉由類別直接使用靜態類別成員。
- 特性2:靜態類別函式不可宣告為const型態,因為宣告const型態的成員函式用意是不想讓該函式修改動所屬物件,但靜態類別函式不屬於物件屬於類別,故沒有這方面問題也無法如此宣告。
- 特性3:靜態成員函式不可宣告為虛擬函式,且不具備 “this” 指標,原因如特性2,
- 特性5:靜態成員函式可以直接訪問靜態資料成員,但不可以直接使用非靜態成員 (原因是靜態成員函式不須實例化類別物件便可直接使用,而非靜態成員則需要實例化才會產生出來並存在記憶體中,若當還沒有實例化任何物件時便藉由靜態成員函式使用還未存在的非靜態成員,此時便會出錯)。
// A.h
class myClassA {
public:
static void fun1(int &x); //宣告靜態類別函式
static int num; //定義靜態類別變數
}
// A.cpp
// "static"可用在類別內的成員宣告及定義,但不可用於類別外的成員定義
int myClassA::num = 999; //靜態類別成員要在類別定義區塊之外初始化
void myClassA::fun1(int &x) {
x = 100;
}
}
// B.cpp
#include "A.h"
#include <iostream>
int main() {
int i = 0;
cout << myClassA::num << endl; //輸出 999
cout << i << endl; //輸出 0
myClassA::fun1(i); //使用 myclassA的靜態類別函式fun1()
cout << i << endl; //輸出 100
return 0;
static 總結:
把函式內的區域變數改變為靜態區域變數後是改變了變數的儲存方式&生命週期;把全域變數改變為靜態全域變數後,是改變並限制了變數的使用範圍