C++關鍵字的思考

C++關鍵字的思考

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


1 關鍵字的相關理解

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

1.1 const關鍵字

  • constconstant的縮寫,指保持不變的量。編譯器會執行這一要求,任何嘗試改變常量的行為都會當做錯誤處理。此外,當啟用了優化時,編譯器可以利用此信息生成更好的代碼。關鍵字const有兩種相關的用法,可以用這個關鍵字標記變量或者參數,也可以用其標記方法。本小節將討論這兩種含義。

1.1.1 const變量和參數

  • 可以使用const來“保護”變量不被修改。這個關鍵字的一個重要用法是替換#define來定義常量。這是const最直接的應用。例如,可以這樣聲明常量PI

      const double PI = 3.141592653589792;
    
  • 可以將任何變量標記為const,包括全局變量和類成員數據。

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

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

(1) const指針

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

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

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

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

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

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

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

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

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

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

      int const* const pIP = nullptr;
    
  • 盡管這些語法看上去有點混淆,但規則實際上很簡單:const關鍵字應用于直接位于它左側的任何內容,再次思考這一行:

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

  • 這一條規則由于一個例外而變得令人費解:第一個const可以出現在變量前面,如下所示:

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

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

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

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

(2) const引用

  • const應用于引用通常比它應用于指針更簡單:首先,引用默認為const,無法改變引用所指的對象。因此,無需顯示地將引用標記為const。其次,無法創建一個引用的引用,所以引用通常只有一層間接取值。獲取多層間接取值的唯一方法是創建指針的引用。

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

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

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

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

(2) const方法

  • 常量對象的值不能改變。如果使用常量對象,常量對象的引用和指向常量對象的指針,編譯器將不允許調用對象的任何方法,除非這些方法承諾不改變任何數據成員。為了確保方法不改變數據成員,可以使用const關鍵字來標記方法本身。下面的SpreadsheetCell類包含了用const標記的不改變任何數據成員的方法。

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

      double SpreadsheetCell::getValue() const
      {
          return mValue;
      }
      const string& SpreadsheetCell::getString() const
      {
          return mString;
      }
    
  • 將方法標記為const,就是與客戶代碼立下契約,承諾不會在方法內改變對象內部的值。如果將實際上修改了數據成員的方法聲明為const,編譯器將會報錯。不能將靜態方法聲明為const,因為這個是多余的,靜態方法沒有類的實例,因此不能改變類內部數據成員的值。const工作原理:是將方法內的數據成員都標記為const引用,因此如果試圖修改數據成員,編譯器會報錯。

  • const對象可以調用const方法和非const方法。然而,const對象只能調用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");             // 編譯錯誤!
    
  • 應該養成習慣,將不修改對象的所有方法聲明為const,這樣就可以在程序中引用const對象。注意const對象也會被銷毀,它們的析構函數也會被調用,因此不應該將析構函數標記為const

mutable數據成員

  • 有時編寫的方法“邏輯上”是const,但是碰巧改變了對象的數據成員。這個改動對于用戶可見的數據沒有任何影響,但在技術上確實做了改動,因此編譯器不允許將這個方法聲明為const。例如,假設電子表格應用程序要獲取數據的讀取頻率。完成這個任務的基本辦法是在SpreadsheetCell類中加入一個計數器,計算getValue()getString()調用的次數。遺憾的是,這樣做使編譯器認為這些方法是非const的,這并非你的本意。解決方法是將計數器變量設置為mutable,告訴編譯器在const()方法中允許改變這個值。下面是新的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關鍵字

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

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

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

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

將函數聲明為constexpr對函數的行為施加了一些限制,因為編譯器必須在編譯期間對constexpr函數求值,函數也不允許有任何副作用。下面是幾個限制:

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

通過定義constexpr構造函數,可以創建用戶自定義類型的常量表達式變量。constexpr構造函數應該滿足以下要求:

  • 構造函數的所有參數都應該是字面量類型。
  • 構造函數體不應該是function-try-block
  • 構造函數體應該滿足于constexpr函數體相同的要求。
  • 所有數據成員都應該用常量表達式初始化。

例如,下面的Rect類定義了一個滿足上述要求的constexpr構造函數,此外還定義了一個constexpr getArea()方法,執行一些計算。

    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;
    };
  • 使用這個類聲明constexpr對象相當直接:

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

1.2 static關鍵字

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

1.1.1 靜態數據成員和方法

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

(1) 靜態數據成員

  • 有時讓類的所有對象都包含某個變量的副本是沒必要的,或者無法完成特定的任務。數據成員有可能只對類有意義,而每個對象都擁有其副本是不合適的。例如,每個電子表格或許需要一個唯一的數字ID,這需要一個從0開始的計數器,每個對象都可以從這個計數器得到自身的ID。電子表格的計數器確實屬于Spreadsheet類,但沒必要使每個Spreadsheet對象都包含這個計數器的副本,因為必須讓所有的計數器都保持同步。

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

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

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

    i. 在類方法內訪問靜態數據成員

  • 在類方法內部,可以像使用普通數據成員那樣使用靜態數據成員。例如,為Spreadsheet類創建一個mId成員,并在Spreadsheet構造函數中用sCounter成員初始化它。下面是包含了mId成員的Spreadsheet類定義:

      class Spreadsheet
      {
      public:
          int getId() const;
      private:
          static int sCounter;
          int mId;
      };
    
  • 下面是Spreadsheet構造函數的實現,在此賦予初始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];
          }
      }
    
  • 可以看出,構造函數可以訪問sCounter,就像這是一個普通成員。在復制構造函數中,也要對ID賦值:

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

    ii. 在方法外訪問靜態數據成員

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

      int c = Spreadsheet::sCounter;
    
  • 然而,建議不要使用公有數據成員,應該提供公有get/set()方法來授予訪問權限。如果要訪問靜態的數據成員,應該實現靜態的get/set()方法。

(2) 靜態方法

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

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

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

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

      string str = SpreadsheetCell::doubleToString(5.0);
    

1.1.2 靜態鏈接(staitc Linkage)

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

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

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

  • 這兩個源文件都可以編譯成功,程序鏈接也沒有問題:因為f()具有外部鏈接,main()可從另一個文件調用這個函數。

  • 現在假定在AnotherFile.cpp中將static應用到f()原型。注意不需要在f()的定義前面重復使用static關鍵字。只要在函數名稱的第一個實例前面使用這個關鍵字,就不需要重復它:

      #include <iostream>
      using namespace std;
      static void f();
      void f()
      {
          cout << "f()\n";
      }
    
  • 現在每個源文件都可以成功編譯,但是鏈接時將失敗,因為f()具有內部(靜態)鏈接,FirstFile.cpp無法使用這個函數。如果在源文件中定義了靜態方法但是沒有使用它,有些編譯器會給出警告(指出這些方法不應該是靜態的,因為其他文件可能會用到它們)。

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

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

extern關鍵字

  • extern關鍵字將它后面的名稱指定為外部鏈接。在某些情況下面可以使用這種方法。例如,consttypedefe在默認情況下面是內部鏈接,可以使用extern使其變為外部鏈接。

  • 然而,extern有一點復雜。當指定某個名稱為extern時,編譯器將這條語句當做聲明,而不是定義。對于變量而言,這意味著編譯器不會為這個變量分配空間。必須為這個變量提供單獨的、不適用extern關鍵字的定義行,例如:

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

      extern int x = 1;
    
  • 這種情形下的extern并不是非常有用,因為x默認具有外部鏈接。當另一個源文件FirstFile.cpp使用x時,才會真正用到extern

      #include <iostream>
      using namespace std;
      extern int x;
      int main(void)
      {
          cout << x << endl;
      }
    
  • 在此FirstFile.cpp使用了extern聲明,因此可以使用x。編譯器需要一個x的聲明,才能在main()函數中使用這個變量。然而,如果聲明x時未使用extern關鍵字,編譯器認為這是個定義,就會為x分配空間,導致鏈接步驟失敗(因為有兩個全局作用域的x變量)。使用extern,就可以在多個源代碼中全局訪問這個變量。

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

1.1.3 函數中的靜態變量

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

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

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

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

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

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

1.4 非局部變量的銷毀順序

  • 非局部變量按照其初始化的逆序進行銷毀。不同源文件中的非局部變量的初始化順序是不確定的。所以其銷毀順序也是不確定的。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,732評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,214評論 3 426
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,781評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,588評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,315評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,699評論 1 327
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,698評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,882評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,441評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,189評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,388評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,933評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,613評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,023評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,310評論 1 293
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,112評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,334評論 2 377

推薦閱讀更多精彩內容

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