1. 程式人生 > 程式設計 >詳解C++作用域與生命週期

詳解C++作用域與生命週期

Pascal之父Nicklaus Wirth曾經提出一個公式,展示出了程式的本質:程式=演算法+資料結構。後人又給出一個公式與之遙相呼應:軟體=程式+文件。這兩個公式可以簡潔明瞭的為我們展示程式和軟體的組成。

程式的執行過程可以理解為演算法對資料的加工過程,程式的執行的結果,就是演算法加工資料產生的結果資料。演算法描述的是對資料加工的步驟,對應於程式中的函式。資料結構描述的是資料在計算機中的組織結構,對應於程式中的資料型別。程式中資料對應的就是無處不在變數。對於我們程式設計人員,面對的無非就是函式,資料型別和變數。因此,C++談及作用域與生命週期針對的就是這三大程式的組成要素:函式、資料型別和變數。下面將一一講述。

1.作用域與生命週期的區別

作用域與生命週期是兩個完全不同的概念。在英文中,作用域用“scope”表示,生命週期則用“duration”表示。作用域是一個靜態概念,只在編譯源程式的時候用到。一個識別符號的作用域指在原始檔中該識別符號能夠獨立地合法出現的區域。生命週期則是一個執行時(Runtime)概念,它是指一個變數在整個程式從載入到結束執行的過程中存在的時間週期。由於函式和資料型別是靜態的概念,它們沒有生命週期的說法,它們從編譯、程式的執行到結束整個過程是一直存在的。

C++中作用域的級別由高到低,主要有檔案域(全域性作用域)、名字空間域、類域、函式作用域和程式碼塊作用域,其中函式作用域和程式碼塊作用域又統稱為區域性域。

2.函式的作用域

函式分為類的成員函式和全域性函式。

類的成員函式:

  • 作用域:類域。
  • 生命週期:無(程式執行期一直存在)。
  • 引用方法:其他檔案中要使用點操作符(.)或指標操作符(->)或作用域運算子(::)來引用。
  • 記憶體分佈:程式碼區。
  • 注意:類成員函式可以定義在類體內,即定義在標頭檔案,當類被不同原始檔包含時不會報重定義的錯誤,因為類體內實現的函式具有inline特性。

舉例如下:

//main.cpp
class test
{
private:
 int i;
public:
 void show()
 {
 cout<<"i:"<<i<<endl;
 }
};
int main(int argc,char* argv[])
{
 test t;
 t.show()
}

全域性函式:

  • 作用域:檔案域(全域性作用域)。
  • 生命週期:無(程式執行期一直存在)。
  • 引用方法:其他檔案中要先進行函式原型宣告,再使用。
  • 記憶體分佈:程式碼段。
  • 注意:如果在兩個原始檔中定義了同名的全域性函式,連線時會出現重定義錯誤。

舉例如下:

//function.cpp
void printHello()
{
 cout<<"hello world"<<endl;
}

//main.cpp
void printHello();
int main(int argc,char* argv[])
{
 printHello();
}

3.資料型別的作用域

C++中的資料型別分為基本資料型別和非基本資料型別,非基本資料型別中又分為複合資料型別和構造資料型別。關於C++中的資料型別,詳見本人另一篇blog: C++資料型別。

基本資料型別:
基本資料型別包括整型(int)、實型(float和double)、字元型(char)、布林型(bool)和無值型(void)。

  • 作用域:檔案域(全域性作用域)。
  • 生命週期:無(程式執行期一直存在)。
  • 引用方法:無需申明,直接使用。
  • 記憶體分佈:程式碼段。

複合資料型別:

複合資料型別包括:陣列(type[])、指標(type*)、引用(type&)、列舉(enum)。

如果複合資料型別是構造資料型別參與的複合,其作用域與構造資料型別一致。enum列舉型別的作用域與構造型別相同。

構造資料型別:

  • 作用域:型別定義所在的域,其他檔案不可見。
  • 生命週期:無(程式執行期一直存在)。
  • 引用方法:其他檔案中要先進行定義,再通過作用域運算子進行使用。
  • 記憶體分佈:程式碼區。
  • 注意:只要檔案不互相包含,如果在兩個原始檔中定義了同名的構造,不會出現重定義錯誤,因為資料型別不具有外部連線性。

舉例如下:

//main.cpp
namespace dd
{
 class test
 {
 private:
 int i;
  public:
 void show()
   {
   cout<<"i:"<<i<<endl;
   }
 };
}
using namespace dd;//引用名稱空間域中的構造型別test,否則無法使用
int main(int argc,char* argv[])
{
 test t;
 t.show();
}

4.變數的作用域與生命週期

我們面對的變數主要分為全域性變數、全域性靜態變數、區域性變數和區域性靜態變數。下面一一講述他們的作用域與生命週期。

全域性變數:

  • 作用域:全域性作用域(全域性變數只需在一個原始檔中定義,就可以作用於所有的原始檔);
  • 生命週期:程式執行期一直存在;
  • 引用方法:其他檔案中要使用必須用extern 關鍵字宣告要引用的全域性變數。;
  • 記憶體分佈:全域性/靜態儲存區;
  • 注意:如果在兩個檔案中都定義了相同名字的全域性變數,連接出錯:變數重定義。

舉例如下:

//define.cpp 
int g_iValue = 1; 
 
//main.cpp 
extern int g_iValue; 
 
int main() 
{ 
  cout << g_iValue; 
  return 0; 
} 

全域性靜態變數:

  • 作用域:檔案作用域(只在被定義的檔案中可見);
  • 生命週期:程式執行期一直存在;
  • 記憶體分佈:全域性/靜態儲存區;
  • 定義方法:static關鍵字,const 關鍵字;
  • 注意:只要檔案不互相包含,在兩個不同的檔案中是可以定義完全相同的兩個靜態變數的,它們是兩個完全不同的變數。

舉例如下:

//define.cpp 
const int iValue=8;  

//main.cpp
int iValue; 
static const int iValue_2; 
static int iValue_3; 
int main(int argc,char* argv[])
{
 cout<<"iValue:"<<iValue<<endl;
 return 0;
}

區域性變數:

  • 作用域:區域性作用域(只在區域性作用域中可見,如函式域,程式碼塊域);
  • 生命週期:程式執行出局部作用域即被銷燬;
  • 記憶體分佈:棧區;
  • 注意:auto指示符標示。

舉例如下:

void print()
{
 int a=0;
 cout<<a<<endl;
}

區域性靜態變數:

  • 作用域:區域性作用域(只在區域性作用域中可見);
  • 生命週期:程式執行期一直存在;
  • 記憶體分佈:全域性靜態儲存區;
  • 定義方法:區域性作用域用中用static定義;
  • 注意:只被初始化一次,多執行緒中需加鎖保護。

舉例如下:

void function() 
{ 
  static int iREFCounter = 0; 
} 

5.擴充套件知識點

5.1變數儲存型別說明符

C語言中提供了四種儲存型別說明符auto,register,extern和static,四種儲存型別有兩種儲存期:自動儲存期和靜態儲存期。其中auto和register對應自動儲存期,被修飾的變數在進入宣告該變數的程式塊時被建立,它在該程式塊活動時存在,退出該程式塊時撤銷。靜態儲存期的變數從程式載入執行到程式結束一直存在。

5.2static使用建議

(1)若全域性變數僅在單個C檔案中訪問,則可以將這個變數修改為靜態全域性變數,以降低模組間的耦合度;
(2)若全域性變數僅由單個函式訪問,則可以將這個變數改為該函式的靜態區域性變數,以降低模組間的耦合度;
(3)設計和使用訪問動態全域性變數、靜態全域性變數、靜態區域性變數的函式時,需要考慮重入問題,因為他們都放在靜態資料儲存區,可被其他函式共享;
(4)如果我們需要一個可重入的函式,那麼我們一定要避免函式中使用static變數。這樣的函式被稱為帶“內部儲存器”功能的函式;
(5)函式中必須要使用static變數情況:比如當某函式的返回值為指標型別時,則必須是static的區域性變數的地址作為返回值,若為auto型別,則返回為野指標。

以上就是詳解C++作用域與生命週期的詳細內容,更多關於C++作用域與生命週期的資料請關注我們其它相關文章!