1. 程式人生 > >C++ 快速入門筆記:進階程式設計

C++ 快速入門筆記:進階程式設計

C++入門筆記:高階程式設計

檔案和流

  1. 開啟檔案

    void open (const char *filename, ios::openmode mode);
    
    • ios::app 追加模式。所有寫入都追加到檔案末尾
    • ios::ate 檔案開啟後定位到檔案末尾
    • ios::in 開啟檔案用於讀取
    • ios::out 開啟檔案用於寫入
    • ios::trunc 如果該檔案已經存在,其內容將在開啟檔案之前被截斷,即把檔案長度設為 0。
  2. 關閉檔案

    void close();
    
  3. 寫入檔案

    • 使用流插入運算子 ( << ),向 ofstream / fstream 流中寫入資訊
  4. 讀取檔案

    • 使用流提取運算子 ( >> ),向 ifstream / fstream 流中寫入資訊
  5. 檔案讀寫例項

    #include <fstream>
    #include <iostream>
    using namespace std;
    int main ()
    {
       char data[100];
       ofstream outfile;
       outfile.open("afile.dat");
       cout << "Writing to the file" << endl;
       cout
    << "Enter your name: "; cin.getline(data, 100); outfile << data << endl; cout << "Enter your age: "; cin >> data; cin.ignore(); outfile << data << endl; outfile.close(); ifstream infile; infile.open("afile.dat"); cout << "Reading from the file"
    << endl; infile >> data; cout << data << endl; infile >> data; cout << data << endl; infile.close(); return 0; }
  6. 檔案位置指標

    • stream 和 ostream 都提供了用於重新定位檔案位置指標的成員函式。

      • istream 的 seekg ("seek & get")
      • ostream 的 seekp ("seek & put")
      // 定位到 fileObject 的第 n 個位元組(假設是 ios::beg)
      fileObject.seekg( n );
      
      // 把檔案的讀指標從 fileObject 當前位置向後移 n 個位元組
      fileObject.seekg( n, ios::cur );
      
      // 把檔案的讀指標從 fileObject 末尾往回移 n 個位元組
      fileObject.seekg( n, ios::end );
      
      // 定位到 fileObject 的末尾
      fileObject.seekg( 0, ios::end );
      

異常處理

  1. try / catch / throw

    • try: try 塊中的程式碼標識將被啟用的特定異常。它後面通常跟著一個或多個 catch 塊。
    • catch: 在您想要處理問題的地方,通過異常處理程式捕獲異常。catch 關鍵字用於捕獲異常。
    • throw: 當問題出現時,程式會丟擲一個異常。這是通過使用 throw 關鍵字來完成的。
  2. C++ 標準的異常

    異常 描述
    std::exception 該異常是所有標準 C++ 異常的父類。
    std::bad_alloc 該異常可以通過 new 丟擲。
    std::bad_cast 該異常可以通過 dynamic_cast 丟擲。
    std::bad_exception 這在處理 C++ 程式中無法預期的異常時非常有用。
    std::bad_typeid 該異常可以通過 typeid 丟擲。
    std::logic_error 理論上可以通過讀取程式碼來檢測到的異常。
    std::domain_error 當使用了一個無效的數學域時,會丟擲該異常。
    std::invalid_argument 當使用了無效的引數時,會丟擲該異常。
    std::length_error 當建立了太長的 std::string 時,會丟擲該異常。
    std::out_of_range 該異常可以通過方法丟擲,例如 std::vector 和 std::bitset<>::operator
    std::runtime_error 理論上不可以通過讀取程式碼來檢測到的異常。
    std::overflow_error 當發生數學上溢時,會丟擲該異常。
    std::range_error 當嘗試儲存超出範圍的值時,會丟擲該異常。
    std::underflow_error 當發生數學下溢時,會丟擲該異常。
  3. 定義新的異常

    • 通過繼承或過載 exception 類來定義新的異常
      #include <iostream>
      #include <exception>
      using namespace std;
      
      struct MyException : public exception
      {
          const char * what () const throw ()
          {
              return "C++ Exception";
          }
      };
       
      int main()
      {
          try
          {
              throw MyException();
          }
          catch(MyException& e)
          {
              std::cout << "MyException caught" << std::endl;
              std::cout << e.what() << std::endl;
          }
          catch(std::exception& e)
          {
              //其他的錯誤
          }
      }
      

動態記憶體

  1. C++ 程式中的記憶體分為兩個部分

    • 棧:在函式內部宣告的所有變數都將佔用棧記憶體。
    • 堆:這是程式中未使用的記憶體,在程式執行時可用於動態分配記憶體。
  2. 可以使用 new 運算子為給定型別的變數在執行時分配堆內的記憶體,這會返回所分配的空間地址。

  3. 不需要動態分配記憶體時,可以使用 delete 運算子,刪除之前由 new 運算子分配的記憶體。

  4. new 和 delete 運算子

    • 使用 new 運算子來為任意的資料型別動態分配記憶體

      // new data-type;
      // 如果自由儲存區已被用完,可能無法成功分配記憶體。所以建議檢查 new 運算子是否返回 NULL 指標。
      double* pvalue  = NULL;
      if( !(pvalue  = new double ))
      {
          cout << "Error: out of memory." <<endl;
          exit(1);
      
      }
      
    • 使用 delete 操作符釋放它所佔用的記憶體

      #include <iostream>
      using namespace std;
      
      int main ()
      {
         double* pvalue  = NULL; // 初始化為 null 的指標
         pvalue  = new double;   // 為變數請求記憶體
         *pvalue = 29494.99;     // 在分配的地址儲存值
         cout << "Value of pvalue : " << *pvalue << endl;
         delete pvalue;         // 釋放記憶體
         return 0;
      }
      
  5. 陣列的動態記憶體分配

    int ROW = 2;
    int COL = 3;
    double **pvalue  = new double* [ROW]; // 為行分配記憶體
    
    // 為列分配記憶體
    for(int i = 0; i < COL; i++) {
        pvalue[i] = new double[COL];
    }
    for(int i = 0; i < COL; i++) {
        delete[] pvalue[i];
    }
    delete [] pvalue; 
    
  6. 物件的動態記憶體分配

    #include <iostream>
    using namespace std;
    
    class Box
    {
        public:
             Box() { 
                cout << "呼叫建構函式!" <<endl; 
             }
             ~Box() { 
                cout << "呼叫解構函式!" <<endl; 
             }
    };
    
    int main( )
    {
        Box* myBoxArray = new Box[4];
        delete [] myBoxArray; // Delete array
        return 0;
    }
    

名稱空間

  1. 名稱空間可作為附加資訊來區分不同庫中相同名稱的函式、類、變數等。使用了名稱空間即定義了上下文。本質上,名稱空間就是定義了一個範圍。

  2. 定義名稱空間

    #include <iostream>
    using namespace std;
    
    // 第一個名稱空間
    namespace first_space{
        void func(){
            cout << "Inside first_space" << endl;
        }
    }
    // 第二個名稱空間
    namespace second_space{
        void func(){
            cout << "Inside second_space" << endl;
        }
    }
    int main ()
    {
         
        // 呼叫第一個名稱空間中的函式
        first_space::func();
           
        // 呼叫第二個名稱空間中的函式
        second_space::func(); 
        
        return 0;
    

}
```

  1. using 指令

    #include <iostream>
    using namespace std;
    
    // 第一個名稱空間
    namespace first_space{
        void func(){
            cout << "Inside first_space" << endl;
        }
    }
    // 第二個名稱空間
    namespace second_space{
        void func(){
            cout << "Inside second_space" << endl;
        }
    }
    using namespace first_space;
    int main ()
    {
         
        // 呼叫第一個名稱空間中的函式
        func();
           
        return 0;
    }
    
    #include <iostream>
    using std::cout;
    
    int main ()
    {
        cout << "std::endl is used with std!" << std::endl;
       
        return 0;
    }
    
  2. 不連續的名稱空間

    • 名稱空間可以定義在幾個不同的部分中,因此名稱空間是由幾個單獨定義的部分組成的。一個名稱空間的各個組成部分可以分散在多個檔案中。
  3. 巢狀的名稱空間

    #include <iostream>
    using namespace std;
        
    // 第一個名稱空間
    namespace first_space{
        void func(){
            cout << "Inside first_space" << endl;
        }
        // 第二個名稱空間
        namespace second_space{
             void func(){
                cout << "Inside second_space" << endl;
             }
        }
    }
    using namespace first_space::second_space;
    int main ()
    {
        // 呼叫第二個名稱空間中的函式
        func();
        return 0;
    }
    

模板

  1. 模板是泛型程式設計的基礎,泛型程式設計即以一種獨立於任何特定型別的方式編寫程式碼。

  2. 模板是建立泛型類或函式的藍圖或公式。庫容器,比如迭代器和演算法,都是泛型程式設計的例子,它們都使用了模板的概念。每個容器都有一個單一的定義,比如 向量,我們可以定義許多不同型別的向量,比如 vector <int> 或 vector <string>。

  3. 函式模板

    template <class type> ret-type func-name(parameter list)
    {
       // 函式的主體
    } 
    
    • 在這裡,type 是函式所使用的資料型別的佔位符名稱。這個名稱可以在函式定義中使用。
    #include <iostream>
    #include <string>
    
    using namespace std;
    
    template <typename T>
    inline T const& Max (T const& a, T const& b) 
    { 
        return a < b ? b:a; 
    } 
    int main ()
    {
     
        int i = 39;
        int j = 20;
        cout << "Max(i, j): " << Max(i, j) << endl; 
        
        double f1 = 13.5; 
        double f2 = 20.7; 
        cout << "Max(f1, f2): " << Max(f1, f2) << endl; 
        
        string s1 = "Hello"; 
        string s2 = "World"; 
        cout << "Max(s1, s2): " << Max(s1, s2) << endl; 
        
        return 0;
    }
    
  4. 類模板

    template <class type> class class-name {
    
    }
    
    • 在這裡,type 是佔位符型別名稱,可以在類被例項化的時候進行指定。您可以使用一個逗號分隔的列表來定義多個泛型資料型別。
    #include <iostream>
    #include <vector>
    #include <cstdlib>
    #include <string>
    #include <stdexcept>
    
    using namespace std;
    
    template <class T>
    class Stack { 
        private: 
            vector<T> elems;     // 元素 
    
        public: 
        void push(T const&);  // 入棧
        void pop();               // 出棧
        T top() const;            // 返回棧頂元素
        bool empty() const{       // 如果為空則返回真。
            return elems.empty(); 
        } 
    }; 
    
    template <class T>
    void Stack<T>::push (T const& elem) 
    { 
        // 追加傳入元素的副本
        elems.push_back(elem);    
    } 
    
    template <class T>
    void Stack<T>::pop () 
    { 
        if (elems.empty()) { 
            throw out_of_range("Stack<>::pop(): empty stack"); 
        }
        // 刪除最後一個元素
        elems.pop_back();         
    } 
    
    template <class T>
    T Stack<T>::top () const 
    { 
        if (elems.empty()) { 
            throw out_of_range("Stack<>::top(): empty stack"); 
        }
        // 返回最後一個元素的副本 
        return elems.back();      
    } 
    
    int main() 
    { 
        try { 
            Stack<int> intStack;  // int 型別的棧 
            Stack<string> stringStack;    // string 型別的棧 
    
            // 操作 int 型別的棧 
            intStack.push(7); 
            cout << intStack.top() <<endl; 
    
            // 操作 string 型別的棧 
            stringStack.push("hello"); 
            cout << stringStack.top() << std::endl; 
            stringStack.pop(); 
            stringStack.pop(); 
        } 
        catch (exception const& ex) { 
            cerr << "Exception: " << ex.what() <<endl; 
            return -1;
        } 
    }
    

前處理器

  1. #define 預處理

    • define 預處理指令用於建立符號常量。該符號常量通常稱為巨集。

      #define macro-name replacement-text 
      
  2. 函式巨集

    • 使用 #define 來定義一個帶有引數的巨集:

      #define MIN(a,b) (((a)<(b)) ? a : b)
      
  3. 條件編譯

    • 有幾個指令可以用來有選擇地對部分程式原始碼進行編譯。這個過程被稱為條件編譯。

    • ifdef & endif

      #ifndef NULL
         #define NULL 0
      #endif
      
    • DEBUG 模式

      #ifdef DEBUG
          cerr <<"Variable x = " << x << endl;
      #endif
      
    • 可以使用 #if 0 語句註釋掉程式的一部分。

      #if 0
          不進行編譯的程式碼
      #endif
      
  4. # 和 ## 運算子

    • 運算子會把 replacement-text 令牌轉換為用引號引起來的字串。

      #include <iostream>
      using namespace std;
      #define MKSTR( x ) #x
      
      int main ()
      {
          cout << MKSTR(HELLO C++) << endl;
          return 0;
      }
      
    • 運算子用於連線兩個令牌。

      #include <iostream>
      using namespace std;
      #define concat(a, b) a ## b
      int main()
      {
          int xy = 100;   
          cout << concat(x, y);
          return 0;
      }
      
  5. 預定義巨集

    巨集 描述
    _LINE_ 這會在程式編譯時包含當前行號。
    _FILE_ 這會在程式編譯時包含當前檔名。
    _DATE_ 這會包含一個形式為 month/day/year 的字串,它表示把原始檔轉換為目的碼的日期。
    _TIME_ 這會包含一個形式為 hour:minute:second 的字串,它表示程式被編譯的時間。

訊號處理

  1. 訊號是由作業系統傳給程序的中斷,會提早終止一個程式。在 UNIX、LINUX、Mac OS X 或 Windows 系統上,可以通過按 Ctrl+C 產生中斷。

  2. 可捕獲的訊號表(定義在 <csignal> 中)

    訊號 描述
    SIGABRT 程式的異常終止,如呼叫 abort。
    SIGFPE 錯誤的算術運算,比如除以零或導致溢位的操作。
    SIGILL 檢測非法指令。
    SIGINT 接收到互動注意訊號。
    SIGSEGV 非法訪問記憶體。
    SIGTERM 傳送到程式的終止請求。
  3. signal() 函式

    • 用來捕獲突發事件

      void (*signal (int sig, void (*func)(int))(int);
      
    • 接收兩個引數:第一個引數是一個整數,代表訊號編號;第二個引數是一個指向訊號處理函式的指標。

    • 無論要在程式中捕獲什麼訊號,都必須使用 singal 函式來註冊訊號,並將其與訊號處理程式相關聯。

      #include <iostream>
      #include <csignal>
      using namespace std;
      void signalHandler( int signum )
      {
          cout << "Interrupt signal (" << signum << ") received.\n";
          // 清理並關閉
          // 終止程式  
          exit(signum);  
      }
      
      int main ()
      {
          // 註冊訊號 SIGINT 和訊號處理程式
          signal(SIGINT, signalHandler);  
          while(1){
              cout << "Going to sleep...." << endl;
              sleep(1);
          }
          return 0;
      }
      
  4. raise() 函式

    • 使用函式 raise() 生成訊號。

      int raise (signal sig);
      
    • sig 是要傳送的訊號編號,這些訊號包括:SIGINT SIGABRT SIGFPE SIGILL SIGSEGV SIGTERM SIGHUP

      #include <iostream>
      #include <csignal>
      using namespace std;
      void signalHandler( int signum )
      {
          cout << "Interrupt signal (" << signum << ") received.\n";
          // 清理並關閉
          // 終止程式 
          exit(signum);  
      }
      int main ()
      {
          int i = 0;
          // 註冊訊號 SIGINT 和訊號處理程式
          signal(SIGINT, signalHandler);  
          while(++i){
              cout << "Going to sleep...." << endl;
              if( i == 3 ){
                  raise( SIGINT);
              }
              sleep(1);
          }
          return 0;
      }