C++關(guān)鍵字的思考

C++關(guān)鍵字的思考

本章內(nèi)容:
1 關(guān)鍵字的相關(guān)理解
1.1 const關(guān)鍵字
1.2 static關(guān)鍵字
1.3 非局部變量的初始化順序
1.4 非局部變量的銷毀順序


1 關(guān)鍵字的相關(guān)理解

  • 在C++中,關(guān)鍵字const和static非常讓人困惑。這兩個(gè)關(guān)鍵字有很多的含義,每種用法都很微妙,下面內(nèi)容將講解各種具體的用法。

1.1 const關(guān)鍵字

  • constconstant的縮寫,指保持不變的量。編譯器會(huì)執(zhí)行這一要求,任何嘗試改變常量的行為都會(huì)當(dāng)做錯(cuò)誤處理。此外,當(dāng)啟用了優(yōu)化時(shí),編譯器可以利用此信息生成更好的代碼。關(guān)鍵字const有兩種相關(guān)的用法,可以用這個(gè)關(guān)鍵字標(biāo)記變量或者參數(shù),也可以用其標(biāo)記方法。本小節(jié)將討論這兩種含義。

1.1.1 const變量和參數(shù)

  • 可以使用const來“保護(hù)”變量不被修改。這個(gè)關(guān)鍵字的一個(gè)重要用法是替換#define來定義常量。這是const最直接的應(yīng)用。例如,可以這樣聲明常量PI

      const double PI = 3.141592653589792;
    
  • 可以將任何變量標(biāo)記為const,包括全局變量和類成員數(shù)據(jù)。

  • 還可以使用const指定函數(shù)或者方法的參數(shù)保持不變。例如,下面的函數(shù)接受一個(gè)const參數(shù)。在函數(shù)體內(nèi)不能修改整數(shù)param。如果試圖修改這個(gè)變量,編譯器將會(huì)生成一個(gè)錯(cuò)誤。

      void func(const int param)
      {
          // 不允許修改param...
      } 
    
  • 下面將討論兩種特殊的const變量或者參數(shù):const指針和const引用。

(1) const指針

  • 當(dāng)變量通過指針包含一層或者多層間接取值時(shí),const的應(yīng)用變得十分微妙。考慮如下代碼:

      int* pIP;//(1)
      pIP = new int[10];//(2)
      pIP[4] = 5;//(3)
    
  • 假定要將const應(yīng)用到pIP。暫時(shí)不考慮這么做有沒有作用,只考慮這樣做意味著什么。是想阻止修改pIP變量本身,還是阻止修改pIP所指向的值?

  • 為了阻止修改pIP所指向的值(第三行),可在pIP的聲明中這樣添加關(guān)鍵字const

      const int* pIP;(1)
      pIP = new int[10];//(2)
      pIP[4] = 5;//(3)    編譯出錯(cuò)!
    
  • 現(xiàn)在無法修改pIP所指向的值。

  • 下面是在語義上等價(jià)的另一種方法:

      int const* pIP;(1)
      pIP = new int[10];//(2)
      pIP[4] = 5;//(3)    編譯出錯(cuò)!
    
  • const放在int前面還是后面并不影響其功能。

  • 如果要將pIP本身標(biāo)記為const(而不是pIP所指向的值),可以這樣做:

      int* const pIP = nullptr;//(1)
      pIP = new int[10];//(2) 編譯出錯(cuò)!
      pIP[4] = 5;//(3)    錯(cuò)誤:不能為空指針賦值!
    
  • 現(xiàn)在pIP本身無法修改,編譯器要求在聲明pIP時(shí)就執(zhí)行初始化,可以用前面的代碼中的nullptr,也可以是用新分配的內(nèi)存,如下所示:

      int* const pIP = new int[10];
      pIP[4] = 5;
    
  • 還可以將指針和所指的值全部標(biāo)記為const,如下所示:

      const int* const pIP = nullptr;
    
  • 下面是另一種等價(jià)的語法:

      int const* const pIP = nullptr;
    
  • 盡管這些語法看上去有點(diǎn)混淆,但規(guī)則實(shí)際上很簡單:const關(guān)鍵字應(yīng)用于直接位于它左側(cè)的任何內(nèi)容,再次思考這一行:

      int const* const pIP = nullptr;
    
  • 從左到右,第一個(gè)const直接位于int的右邊,因此const應(yīng)用到pIP所指的int。從而指定無法修改pIP所指向的值。第二個(gè)const直接位于*的右邊,因此,它應(yīng)用于指向int的指針,也就是pIP變量。因此,無法修改pIP(指針)本身。

  • 這一條規(guī)則由于一個(gè)例外而變得令人費(fèi)解:第一個(gè)const可以出現(xiàn)在變量前面,如下所示:

      const int* const pIP = nullptr;
    
  • 這個(gè)“異常的”語法比其他語法更加常見。

  • 可以將這個(gè)規(guī)則引用到任意層次的間接取值,例如:

      const int* const* const* const pIP = nullptr;
    

注意:另一種易于記憶的,用于指出復(fù)雜變量聲明的規(guī)則:從右向左讀,考慮示例int* const pIP;從右向左讀這條語句,就可以知道pIP是一個(gè)指向intconst指針。另一方面,int const* pIP讀作pIP是一個(gè)指向const int的指針。

(2) const引用

  • const應(yīng)用于引用通常比它應(yīng)用于指針更簡單:首先,引用默認(rèn)為const,無法改變引用所指的對(duì)象。因此,無需顯示地將引用標(biāo)記為const。其次,無法創(chuàng)建一個(gè)引用的引用,所以引用通常只有一層間接取值。獲取多層間接取值的唯一方法是創(chuàng)建指針的引用。

  • 因此,C++程序員提到“const引用”時(shí),含義如下所示:

      int z;
      const int& zRef = z;
      zRef = 4;   // 編譯出錯(cuò)!
    
  • 由于將const引用到int,因此無法對(duì)zRef賦值,如前所示。記住,const int& zRef等價(jià)于int const& zRef。然而需注意,將zRef標(biāo)記為const對(duì)z沒有影響。仍然可以修改z的值。具體做法是直接改變z,而不是通過引用。

  • const引用通常用作參數(shù),這非常有用。如果為了提高效率,想按引用傳遞某個(gè)值,但不想修改這個(gè)值,可將其標(biāo)記為const引用。例如:

      void doSomething(const BigClass& arg)
      {
          // do something here...
      }
    

(2) const方法

  • 常量對(duì)象的值不能改變。如果使用常量對(duì)象,常量對(duì)象的引用和指向常量對(duì)象的指針,編譯器將不允許調(diào)用對(duì)象的任何方法,除非這些方法承諾不改變?nèi)魏螖?shù)據(jù)成員。為了確保方法不改變數(shù)據(jù)成員,可以使用const關(guān)鍵字來標(biāo)記方法本身。下面的SpreadsheetCell類包含了用const標(biāo)記的不改變?nèi)魏螖?shù)據(jù)成員的方法。

      Class SpreadsheetCell
      {
      public:
          double getValue() const;
          const string& getString() const;
      };
    
  • const規(guī)范是方法原型的一部分,必須放在方法的定義中:

      double SpreadsheetCell::getValue() const
      {
          return mValue;
      }
      const string& SpreadsheetCell::getString() const
      {
          return mString;
      }
    
  • 將方法標(biāo)記為const,就是與客戶代碼立下契約,承諾不會(huì)在方法內(nèi)改變對(duì)象內(nèi)部的值。如果將實(shí)際上修改了數(shù)據(jù)成員的方法聲明為const,編譯器將會(huì)報(bào)錯(cuò)。不能將靜態(tài)方法聲明為const,因?yàn)檫@個(gè)是多余的,靜態(tài)方法沒有類的實(shí)例,因此不能改變類內(nèi)部數(shù)據(jù)成員的值。const工作原理:是將方法內(nèi)的數(shù)據(jù)成員都標(biāo)記為const引用,因此如果試圖修改數(shù)據(jù)成員,編譯器會(huì)報(bào)錯(cuò)。

  • const對(duì)象可以調(diào)用const方法和非const方法。然而,const對(duì)象只能調(diào)用const方法,下面是一些示例:

      SpreadsheetCell myCell(5);
      cout << myCell.getValue() << endl;      // OK
      myCell.setString("6");                  // OK
      const SpreadsheetCell& anotherCell = myCell;
      cout << anotherCell.getValue() << endl; // OK
      anotherCell.setString("6");             // 編譯錯(cuò)誤!
    
  • 應(yīng)該養(yǎng)成習(xí)慣,將不修改對(duì)象的所有方法聲明為const,這樣就可以在程序中引用const對(duì)象。注意const對(duì)象也會(huì)被銷毀,它們的析構(gòu)函數(shù)也會(huì)被調(diào)用,因此不應(yīng)該將析構(gòu)函數(shù)標(biāo)記為const

mutable數(shù)據(jù)成員

  • 有時(shí)編寫的方法“邏輯上”是const,但是碰巧改變了對(duì)象的數(shù)據(jù)成員。這個(gè)改動(dòng)對(duì)于用戶可見的數(shù)據(jù)沒有任何影響,但在技術(shù)上確實(shí)做了改動(dòng),因此編譯器不允許將這個(gè)方法聲明為const。例如,假設(shè)電子表格應(yīng)用程序要獲取數(shù)據(jù)的讀取頻率。完成這個(gè)任務(wù)的基本辦法是在SpreadsheetCell類中加入一個(gè)計(jì)數(shù)器,計(jì)算getValue()getString()調(diào)用的次數(shù)。遺憾的是,這樣做使編譯器認(rèn)為這些方法是非const的,這并非你的本意。解決方法是將計(jì)數(shù)器變量設(shè)置為mutable,告訴編譯器在const()方法中允許改變這個(gè)值。下面是新的SpreadsheetCell類的定義:

      class SpreadsheetCell
      {
      private:
          double mValue;
          string mString;
          mutable int mNumAccesses = 0;
      };
    
  • 下面是getValue()getString()的定義:

      double SpreadsheetCell::getValue() const
      {
          mNumAccesses++;
          return mValue;
      }
      const string& SpreadsheetCell::getString() const
      {
          mNumAccesses++;
          return mString;
      }
    

1.1.2 constexpr關(guān)鍵字

  • C++一直存在常量表達(dá)式的概念,在某些情況下需要常量表達(dá)式。例如,當(dāng)定義數(shù)組時(shí),數(shù)組的大小就必須是一個(gè)常量表達(dá)式。由于這一限制,下面的代碼在C++中是無效的:

      const int getArraySize() { return 32; }
      int main(void)
      {
          int myArray[getArraySize()];        // 在C++中無效
          return 0;
      }
    
  • 可以使用constexpr關(guān)鍵字重新定義getArraySize()函數(shù),把它變成常量表達(dá)式。常量表達(dá)式在編譯時(shí)計(jì)算。(constexprC++11中的內(nèi)容)

      constexpr int getArraySize() { return 32; }
      int main(void)
      {
          int myArray[getArraySize()];    // OK
          return 0;
      }
    
  • 甚至可以這樣寫:

      int myArray[getArraySize() + 1];    // OK
    

將函數(shù)聲明為constexpr對(duì)函數(shù)的行為施加了一些限制,因?yàn)榫幾g器必須在編譯期間對(duì)constexpr函數(shù)求值,函數(shù)也不允許有任何副作用。下面是幾個(gè)限制:

  • 函數(shù)體是一個(gè)return語句,它不包含goto語句或try catch塊,也不能拋出異常,但是可以調(diào)用其他constexpr函數(shù)。
  • 函數(shù)的返回類型應(yīng)該是字面量類型,返回值不能是void
  • 如果constexpr函數(shù)是類的一個(gè)成員,這個(gè)函數(shù)不能是虛函數(shù)。
  • 函數(shù)所有的參數(shù)都應(yīng)該是字面量類型。
  • 在編譯單元(translation unit)中定義了constexpr函數(shù)之后,才能調(diào)用這個(gè)函數(shù),因?yàn)榫幾g器需要知道完整的定義。
  • 不允許使用dynamic_cast
  • 不允許使用newdelete

通過定義constexpr構(gòu)造函數(shù),可以創(chuàng)建用戶自定義類型的常量表達(dá)式變量。constexpr構(gòu)造函數(shù)應(yīng)該滿足以下要求:

  • 構(gòu)造函數(shù)的所有參數(shù)都應(yīng)該是字面量類型。
  • 構(gòu)造函數(shù)體不應(yīng)該是function-try-block
  • 構(gòu)造函數(shù)體應(yīng)該滿足于constexpr函數(shù)體相同的要求。
  • 所有數(shù)據(jù)成員都應(yīng)該用常量表達(dá)式初始化。

例如,下面的Rect類定義了一個(gè)滿足上述要求的constexpr構(gòu)造函數(shù),此外還定義了一個(gè)constexpr getArea()方法,執(zhí)行一些計(jì)算。

    class Rect
    {
    public:
        constexpr Rect(int width, int height) : mWidth(width), mHeight(height) {}
        constexpr int getArea() const 
        { 
            return mWidth * mHeight; 
        }
    private:
        int mWidth;
        int mHeight;
    };
  • 使用這個(gè)類聲明constexpr對(duì)象相當(dāng)直接:

      constexpr Rect r(8, 2);
      int myArray[r.getArea()]; // OK
    

1.2 static關(guān)鍵字

  • 在C++中static有多種用法,這些用法之間沒有太多關(guān)系。“重載”這個(gè)關(guān)鍵字的部分原因是避免在語言中引入新的關(guān)鍵字。

1.1.1 靜態(tài)數(shù)據(jù)成員和方法

  • 可以聲明類的靜態(tài)數(shù)據(jù)成員和方法。靜態(tài)數(shù)據(jù)成員與非靜態(tài)數(shù)據(jù)成員不同,它不是對(duì)象的一部分。相反,這個(gè)數(shù)據(jù)成員只有一份副本,這個(gè)副本存在于類的任何對(duì)象之外。
  • 靜態(tài)方法與此類型,存在于類層次(而不是對(duì)象層次)。靜態(tài)方法不會(huì)在某個(gè)特定對(duì)象環(huán)境中執(zhí)行。

(1) 靜態(tài)數(shù)據(jù)成員

  • 有時(shí)讓類的所有對(duì)象都包含某個(gè)變量的副本是沒必要的,或者無法完成特定的任務(wù)。數(shù)據(jù)成員有可能只對(duì)類有意義,而每個(gè)對(duì)象都擁有其副本是不合適的。例如,每個(gè)電子表格或許需要一個(gè)唯一的數(shù)字ID,這需要一個(gè)從0開始的計(jì)數(shù)器,每個(gè)對(duì)象都可以從這個(gè)計(jì)數(shù)器得到自身的ID。電子表格的計(jì)數(shù)器確實(shí)屬于Spreadsheet類,但沒必要使每個(gè)Spreadsheet對(duì)象都包含這個(gè)計(jì)數(shù)器的副本,因?yàn)楸仨氉屗械挠?jì)數(shù)器都保持同步。

  • C++用靜態(tài)(static)數(shù)據(jù)成員解決了這個(gè)問題。靜態(tài)數(shù)據(jù)成員是屬于類而不是對(duì)象的數(shù)據(jù)成員,可將靜態(tài)數(shù)據(jù)成員當(dāng)做類的全局變量。下面是Spreadsheet類的定義,其中包含了新的靜態(tài)數(shù)據(jù)成員計(jì)數(shù)器:

      class Spreadsheet
      {
      private:
          static int sCounter;
      }
    
  • 不僅要在類定義中列出static類成員,還需要在源文件中為其分配內(nèi)存,通常是定義類方法的那個(gè)源文件。在此還可以初始化靜態(tài)成員,但注意與普通變量和數(shù)據(jù)成員不同,在默認(rèn)情況下它會(huì)被初始化為0。static指針會(huì)初始化為nullptr。下面為sCounter分配空間并初始化的代碼:

      int Spreadsheet::sCounter;
    
  • 這行代碼在函數(shù)或者方法外部,與聲明全局變量類似,只是使用了作用域解析Spreadsheet::指出這是Spreadsheet類的一部分。

    i. 在類方法內(nèi)訪問靜態(tài)數(shù)據(jù)成員

  • 在類方法內(nèi)部,可以像使用普通數(shù)據(jù)成員那樣使用靜態(tài)數(shù)據(jù)成員。例如,為Spreadsheet類創(chuàng)建一個(gè)mId成員,并在Spreadsheet構(gòu)造函數(shù)中用sCounter成員初始化它。下面是包含了mId成員的Spreadsheet類定義:

      class Spreadsheet
      {
      public:
          int getId() const;
      private:
          static int sCounter;
          int mId;
      };
    
  • 下面是Spreadsheet構(gòu)造函數(shù)的實(shí)現(xiàn),在此賦予初始ID值:

      Spreadsheet::Spreadsheet(int inWidth, int inHeight) : mWidth(inWidth), mHeight(inHeight)
      {
          mId = sCounter++;
          mCells = new SpreadsheetCell* [mWidth];
          for (int i=0; i<nWidth; i++)
          {
              mCells[i] = new SpreadsheetCell[mHeight];
          }
      }
    
  • 可以看出,構(gòu)造函數(shù)可以訪問sCounter,就像這是一個(gè)普通成員。在復(fù)制構(gòu)造函數(shù)中,也要對(duì)ID賦值:

      Spreadsheet::Spreadsheet(const Spreadsheet& src)
      {
          mId = sCounter++;
          copyFrom(&src);
      }
    
  • 在賦值運(yùn)算符中不應(yīng)該復(fù)制ID。一旦給某個(gè)對(duì)象指定了ID,就不應(yīng)該再改變。建議把mId設(shè)置為const數(shù)據(jù)成員。

    ii. 在方法外訪問靜態(tài)數(shù)據(jù)成員

  • 訪問控制限定符適用于靜態(tài)數(shù)據(jù)成員:sCounterprivate,因此不能在類方法之外訪問。如果sCounter是公有的,就可以在類方法外訪問,具體方法是用::作用域解析運(yùn)算符指出這個(gè)變量是Spreadsheet類的一部分:

      int c = Spreadsheet::sCounter;
    
  • 然而,建議不要使用公有數(shù)據(jù)成員,應(yīng)該提供公有get/set()方法來授予訪問權(quán)限。如果要訪問靜態(tài)的數(shù)據(jù)成員,應(yīng)該實(shí)現(xiàn)靜態(tài)的get/set()方法。

(2) 靜態(tài)方法

  • 與數(shù)據(jù)成員類似,方法有時(shí)會(huì)應(yīng)用于全部類對(duì)象而不是單個(gè)對(duì)象,此時(shí)可以像靜態(tài)數(shù)據(jù)成員那樣編寫靜態(tài)方法。以SpreadsheetCell中的兩個(gè)輔助方法為例:stringToDouble()doubleToString()。這些方法沒有訪問特定對(duì)象的信息,因此可以是靜態(tài)的。下面的類定義將這些方法設(shè)置為靜態(tài):

      class SpreadsheetCell
      {
      private:
          static string doubleToString(double val);
          static double StringTodouble(string& str);
      };
    
  • 這兩個(gè)方法的實(shí)現(xiàn)與前面的實(shí)現(xiàn)相同,在方法定義前不需要重復(fù)static關(guān)鍵字。然而,注意靜態(tài)方法不屬于特定對(duì)象,因此沒有this指針,當(dāng)用某個(gè)特定對(duì)象調(diào)用靜態(tài)方法時(shí),靜態(tài)方法不會(huì)訪問這個(gè)對(duì)象的非靜態(tài)數(shù)據(jù)成員。實(shí)際上,靜態(tài)方法就像一個(gè)普通函數(shù),唯一的區(qū)別在于這個(gè)方法可以訪問類的privateprotected靜態(tài)數(shù)據(jù)成員。如果同一個(gè)類型的其他對(duì)象對(duì)于靜態(tài)方法可見(例如傳遞了對(duì)象的指針或引用),靜態(tài)方法也可以訪問其他對(duì)象的privateprotected非靜態(tài)數(shù)據(jù)成員。

  • 類中任何方法都可以像調(diào)用普通函數(shù)那樣調(diào)用靜態(tài)方法,因此SpreadsheetCell中所有方法的實(shí)現(xiàn)都沒有改變。如果要在類的外面調(diào)用靜態(tài)方法,需要用類名和作用域解析運(yùn)算符來限定方法的名稱(就像靜態(tài)數(shù)據(jù)成員那樣),靜態(tài)方法的訪問控制與普通方法一樣。

  • stringToDouble()doubleToString()設(shè)置為public,這樣類外面的代碼也可以使用它們。此時(shí),可以在任意位置這樣調(diào)用這兩個(gè)方法:

      string str = SpreadsheetCell::doubleToString(5.0);
    

1.1.2 靜態(tài)鏈接(staitc Linkage)

  • 在解釋用于鏈接的static關(guān)鍵字之前,首先要理解C++中鏈接的概念。C++每個(gè)源文件都是單獨(dú)編譯的,編譯得到的目標(biāo)文件會(huì)彼此鏈接。C++源文件中的每個(gè)名稱,包括函數(shù)和全局變量,都有一個(gè)內(nèi)部或者外部的鏈接。外部鏈接意味著這個(gè)名稱在其他源文件中也有效,內(nèi)部鏈接(也稱作靜態(tài)鏈接)意味著在其他源文件中無效。默認(rèn)情況下:函數(shù)和全局變量都擁有外部鏈接。然而,可在聲明前面使用關(guān)鍵字static指定內(nèi)部(或者靜態(tài))鏈接。例如,假定有兩個(gè)源文件FirstFile.cppAnotherFile.cpp,下面是FirstFile.cpp

      void f();
      int main(void)
      {
          f();
          return 0;
      }
    
  • 注意這個(gè)文件提供了f()的原型,但沒有給出定義。下面是AnotherFile.cpp

      #include <iostream>
      using namespace std;
      void f();
      void f()
      {
          cout << "f()\n";
      }
    
  • 這個(gè)文件同時(shí)提供了f()的原型和定義。注意在兩個(gè)不同文件中編寫的相同函數(shù)的原型是合法的。如果將原型放在頭文件中,并在每個(gè)源文件中都使用#include包含這個(gè)頭文件,預(yù)處理器就會(huì)自動(dòng)在每個(gè)源文件中給出函數(shù)原型。使用頭文件的原因是便于維護(hù)(并保持同步)原型的一個(gè)副本。

  • 這兩個(gè)源文件都可以編譯成功,程序鏈接也沒有問題:因?yàn)?code>f()具有外部鏈接,main()可從另一個(gè)文件調(diào)用這個(gè)函數(shù)。

  • 現(xiàn)在假定在AnotherFile.cpp中將static應(yīng)用到f()原型。注意不需要在f()的定義前面重復(fù)使用static關(guān)鍵字。只要在函數(shù)名稱的第一個(gè)實(shí)例前面使用這個(gè)關(guān)鍵字,就不需要重復(fù)它:

      #include <iostream>
      using namespace std;
      static void f();
      void f()
      {
          cout << "f()\n";
      }
    
  • 現(xiàn)在每個(gè)源文件都可以成功編譯,但是鏈接時(shí)將失敗,因?yàn)?code>f()具有內(nèi)部(靜態(tài))鏈接,FirstFile.cpp無法使用這個(gè)函數(shù)。如果在源文件中定義了靜態(tài)方法但是沒有使用它,有些編譯器會(huì)給出警告(指出這些方法不應(yīng)該是靜態(tài)的,因?yàn)槠渌募赡軙?huì)用到它們)。

  • static用于內(nèi)部鏈接的另一種方式是使用匿名名稱空間(anonymous namespace)。可將變量或者函數(shù)封裝到一個(gè)沒有名字的名稱空間,而不是使用static,如下所示:

      #include <iostram>
      using namespace std;
      namespace {
          void f();
          void f()
          {
              cout << "f()\n";
          }
      }
    
  • 在同一源文件中,可在聲明匿名名稱空間之后的任何位置訪問名稱空間中的項(xiàng),但不能在其他源文件中訪問。這語義與static關(guān)鍵字相同。

extern關(guān)鍵字

  • extern關(guān)鍵字將它后面的名稱指定為外部鏈接。在某些情況下面可以使用這種方法。例如,consttypedefe在默認(rèn)情況下面是內(nèi)部鏈接,可以使用extern使其變?yōu)橥獠挎溄印?/p>

  • 然而,extern有一點(diǎn)復(fù)雜。當(dāng)指定某個(gè)名稱為extern時(shí),編譯器將這條語句當(dāng)做聲明,而不是定義。對(duì)于變量而言,這意味著編譯器不會(huì)為這個(gè)變量分配空間。必須為這個(gè)變量提供單獨(dú)的、不適用extern關(guān)鍵字的定義行,例如:

      extern int x;
      int x = 4;
    
  • 也可以在extern行初始化x,這一行既是聲明又是定義:

      extern int x = 1;
    
  • 這種情形下的extern并不是非常有用,因?yàn)?code>x默認(rèn)具有外部鏈接。當(dāng)另一個(gè)源文件FirstFile.cpp使用x時(shí),才會(huì)真正用到extern

      #include <iostream>
      using namespace std;
      extern int x;
      int main(void)
      {
          cout << x << endl;
      }
    
  • 在此FirstFile.cpp使用了extern聲明,因此可以使用x。編譯器需要一個(gè)x的聲明,才能在main()函數(shù)中使用這個(gè)變量。然而,如果聲明x時(shí)未使用extern關(guān)鍵字,編譯器認(rèn)為這是個(gè)定義,就會(huì)為x分配空間,導(dǎo)致鏈接步驟失敗(因?yàn)橛袃蓚€(gè)全局作用域的x變量)。使用extern,就可以在多個(gè)源代碼中全局訪問這個(gè)變量。

  • 然而,建議不要使用全局變量。全局變量會(huì)令人迷惑,并且容易出錯(cuò),在大型程序中尤其如此。為了獲取類似功能,可使用類的靜態(tài)數(shù)據(jù)成員和方法。

1.1.3 函數(shù)中的靜態(tài)變量

  • C++中static關(guān)鍵字的最終目的是創(chuàng)建離開和進(jìn)入作用域時(shí)都可以保留值得局部變量。函數(shù)中的靜態(tài)變量就像是一個(gè)只能在函數(shù)內(nèi)部訪問的全局變量。靜態(tài)變量最常用的用法是“記住”某個(gè)函數(shù)是否執(zhí)行了特定的初始化操作。例如,下面的代碼就使用了這一技術(shù):

      void performTask()
      {
          static bool initialized = false;
          if (!initialized)
          {
              cout << "Initializing\n";
              // Perform Initialization.
              initialized = true;
          }
          // Perform the desired task.
      }
    
  • 然而靜態(tài)變量容易令人迷惑,在構(gòu)建代碼時(shí)通常有更好的方法,以避免使用靜態(tài)變量。在此情況下,可編寫一個(gè)類,用構(gòu)造函數(shù)執(zhí)行所需的初始化。

1.3 非局部變量的初始化順序

  • 上面討論了靜態(tài)數(shù)據(jù)成員和全局變量的相關(guān)內(nèi)容,以下討論下這些變量的初始化順序。程序中所有的全局變量和類的靜態(tài)數(shù)據(jù)成員都會(huì)在main()開始之前初始化。給定源文件中的變量以在源文件中出現(xiàn)的順序初始化。例如,在下面的文件中,Demo::x一定會(huì)在y之前初始化:

      class Demo
      {
      public:
          static int x;
      };
      int Demo::x = 4;
      int y = 5;
    
  • 然而,C++沒有提供規(guī)范,說明在不同源文件中初始化非局部變量的順序。如果在某個(gè)源文件中有一個(gè)全局變量x,在另一個(gè)文件中有一個(gè)全局變量y,無法知道哪個(gè)變量先初始化。通常,不需要關(guān)注這一規(guī)范的缺失,但是如果某個(gè)全局變量或者靜態(tài)變量依賴于另一個(gè)變量,就可能引發(fā)問題。對(duì)象的初始化意味著調(diào)用構(gòu)造函數(shù),全局對(duì)象的構(gòu)造函數(shù)可能會(huì)訪問另一個(gè)全局對(duì)象,并假定另一個(gè)對(duì)象已經(jīng)構(gòu)建。如果這兩個(gè)全局對(duì)象在不同的源文件中聲明,就不能指望一個(gè)對(duì)象在另一個(gè)對(duì)象之前構(gòu)建,也無法控制他們的初始化順序。不同的編譯器可能有不同的初始化順序,即使同一編譯器的不同版本也可能如此,甚至項(xiàng)目中添加另一個(gè)文件也會(huì)影響初始化順序。

  • 警告:不同源文件中的非局部變量的初始化順序是不確定的。

1.4 非局部變量的銷毀順序

  • 非局部變量按照其初始化的逆序進(jìn)行銷毀。不同源文件中的非局部變量的初始化順序是不確定的。所以其銷毀順序也是不確定的。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 注:這是第三遍讀《C語言深度解剖》,想想好像自從大學(xué)開始就沒讀完過幾本書,其中譚浩強(qiáng)的那本《C語言程序設(shè)計(jì)(第四版...
    HavenXie閱讀 1,774評(píng)論 1 6
  • C++文件 例:從文件income. in中讀入收入直到文件結(jié)束,并將收入和稅金輸出到文件tax. out。 檢查...
    SeanC52111閱讀 2,857評(píng)論 0 3
  • 來說下c/c++中的const的用法。在英語中常數(shù)的一種表達(dá)是“中的const的”,在編程中可能是借用了這個(gè)單詞(...
    Jack_Cui閱讀 3,792評(píng)論 0 3
  • 幾種語言的特性 匯編程序:將匯編語言源程序翻譯成目標(biāo)程序編譯程序:將高級(jí)語言源程序翻譯成目標(biāo)程序解釋程序:將高級(jí)語...
    囊螢映雪的螢閱讀 2,953評(píng)論 1 5
  • 1. C++基礎(chǔ) 大多數(shù)編程語言通過兩種方式來進(jìn)一步補(bǔ)充其基本特征1)賦予程序員自定義數(shù)據(jù)類型的權(quán)利,從而實(shí)現(xiàn)對(duì)語...
    王偵閱讀 768評(píng)論 0 3