C++核心編程—筆記

C++筆記

  1. C++概述

    • C++兩大編程思想
      • 面向對象:三大特性
        • 封裝
        • 繼承
        • 多態
      • 泛型編程
  2. 雙冒號作用域運算符

    • ::如果前面沒有任何作用域,代表使用全局作用域
  3. 命名空間

    • 用途:解決命名衝突

    • 可以存放變量、函數、結構體、類 …

    • 必須聲明在全局作用域下

    • 可以嵌套命名空間

    • 是開放的,可以隨時向命名空間下添加新的成員,同名命名空間會合並

    • 可以匿名

      namespace{
          int a = 100; //相當於在變量前加了一個關鍵字 static
      }
      
    • 可以起別名

      namespace veryLongName{
          int a = 100;
      }
      void test(){
          namespace veryShortName = NameveryLongName;
          cout << veryShortName::a << endl;
      }
      
  4. using

    • using聲明:當就近原則和using聲明同時出現時,需要避免二義性
    • using編譯指令:如果有就近原則,那麼優先使用就近原則;存在多個並且有同名出現,需要加作用域區分
  5. C++對C語言增強

    • 全局變量檢測增強

    • 函數檢測增強

      • 聲明函數返回值
      • 形參類型 檢測
      • 返回值檢測增強
      • 調用時參數傳參個數檢測增強
    • 類型檢測增強 malloc 返回void* C++必須強轉

    • struct增強

      • C++可以放入函數
      • 使用時C++可以省略關鍵字struct
    • 三目運算符

      • C語言返回值
      • C++返回變量
    • const增強

      • 全局const修飾變量 受到常量區保護,即使語法通過,運行也會報錯(C和C++效果一樣)

      • 局部const修飾的變量可以間接修改成功(C可修改,C++不可修改)

        {
        	const int b = 100;
        	int *p = &b;
        	*p = 200;
        }
        
      • C++中const修飾變量可以用來初始化數組 int arr[b];

      • 連接屬性

        • C默認外部鏈接屬性
        • C++默認內部鏈接屬性
    • const分配內存

      • 對const修飾變量取地址時會分配內存
      • const前加external關鍵字後也會分配內存
      • 使用變量來初始化const修飾變量
      • 對於自定義數據類型,也會分配內存
  6. 引用

    • 給一個內存空間起別名

    • 一旦初始化後不可修改指向了

      • 本質是指針常量,所以必須初始化,並且初始化後不可修改
    • 先定義數組類型,在定義引用

      int arr[10];
      int (&Arr)[10] = arr;
      
  7. 參數傳遞方式

    • 值傳遞
    • 地址傳遞
    • 引用傳遞
  8. 引用注意事項

    • 不要返回局部變量的引用
    • 如果函數返回值是引用,那麼這個函數可以作爲左值進行運算
  9. 指針的引用

    • 通過引用技術 可以簡化指針
  10. 常量的引用

    • 類型前加const

      int & ref = 10; //錯誤
      const & ref = 10; //編譯會優化,類似於: int temp = 10; const int & int ref = temp;
      
    • 使用場景:修飾函數中形參,防止誤操作

    • class + 名 { 成員 }
    • 類構成:成員函數和成員屬性
    • 作用域:public、protected、private
      • public: 類內可以訪問 類外也可以訪問
      • protected:類內可以訪問 類外不可以訪問(子類可以訪問)
      • private: 類內可以訪問 類外不可以訪問(子類不可以訪問)
  11. 內聯函數

    • 內聯函數引出–宏函數
      • 必須保證運算完整性,加括號
      • 即使加括號,有些情況依然和預期結果不符合
    • 函數的聲明和實現必須同時加inline
    • 內部成員函數 默認前面加了inline關鍵字
    • 內聯限制(編譯器決定是否爲內聯)
      • 不能存在任何形式的循環語句
      • 不能存在過多的條件判斷語句
      • 函數體不能過大
      • 不能對函數進行取地址操作
  12. 函數默認參數

    • 在形參後面=默認值
    • 所有默認參數在參數列表最右邊
    • 函數的聲明和實現只能有一個有默認參數
  13. 佔位參數

    • 函數參數列表中只寫類型,調用時必須要傳入參數
    • 佔位參數也可以有默認參數
  14. 函數重載

    • 滿足函數重載條件
      • 作用域必須相同
      • 函數名稱相同
      • 函數的參數類型、個數或順序不同
      • 返回值不可以作爲重載的條件
    • 引用的重載函數
      • 加const和不加const也可以作爲重載條件
    • 函數重載碰到函數參數默認值時,避免二義性
  15. extern “C”

    • 函數重載原理,編譯器在底層會將函數名字做兩次修飾,方便內部訪問函數

    • 在C++下運行C語言文件

      #ifdef __cplusplus
      extern "C" {
      #endif
      
          ...
          
      #ifdef __cplusplus
      }
      #endif
      
  16. 類的封裝

    • C語言的封裝
      • 缺點:C語言下沒有做類型轉換的檢測;將屬性和行爲分離
    • C++封裝
      • 將屬性和行爲作爲一個整體,來表現生活中的事和物
      • 將成員加權限控制
    • struct和class區別在於默認權限
      • struct:public
      • class:private
  17. 構造和析構

    • 構造
      • 沒有返回值,也不寫void,函數名與類型相同
      • 可以有參數,可以發生重載
      • 由編譯器自動調用,不需要手動調用,而且編譯器只會調用一次
    • 析構
      • 沒有返回值,也不寫void,函數名與類型相同 在函數名前加~
      • 不可以有參數,不可以發生重載
      • 由編譯器自動調用,不需要手動調用,而且編譯器只會調用一次
    • 構造函數分類
      • 按照參數進行分類
        • 有參構造:Person(int age){ }
        • 無參構造(默認構造):Person( ){ }
      • 按照類型分類
        • 普通構造
        • 拷貝構造:Person(const Person &p){ }
    • 構造函數調用規則
      • 系統默認提供三個函數:默認構造函數、析構函數、拷貝構造函數
      • 如果提供了有參構造函數,那麼系統就不會提供默認構造函數,但依然提供拷貝構造函數
      • 如果提供了拷貝構造函數,那麼系統就不會提供其他的普通構造函數了
  18. 深拷貝和淺拷貝

    • 系統提供拷貝構造函數只會做簡單的值拷貝
    • 如果類中有屬性開闢到堆區,那麼在釋放的時候,由於淺拷貝問題導致堆區內容會重複釋放,程序崩潰
  19. 初始化列表

    • 構造函數名( ) : 屬性 ( 值 ) , 屬性 ( 值 ) …
  20. 類對象作爲類成員

    • 當其他類作爲本來成員,先構造其他類對象,再構造自身,釋放順序與構造相反
  21. explicit關鍵字

    • 防止隱式類型轉換方式來初始化化對象
  22. new和delete運算符

    • malloc和new、free和delete區別
      • new、delete是運算符,malloc、free是庫函數
      • malloc返回void *, new返回的是new出來的對象指針
      • malloc需要判斷是否開闢成功,而new內部做了操作(內部會malloc數據在堆區,判斷內存是否分配成功,調用構造函數)
      • malloc不會調用構造函數, 而new調用構造函數
      • malloc對應釋放是free,new對應釋放是delete
    • 注意
      • 不要用void *去接受new出來的對象,原因是不能夠釋放
      • 用new在堆區創建數組,類中必須要存在默認構造函數,否則無法創建
      • 如果是數組釋放的時候要在delete後加 [ ]
  23. 靜態成員

    • 靜態變量

      • 數據共享
      • 在編譯階段分配內存
      • 在類內聲明、類外進行初始化
      • 訪問方式:對象訪問、類名訪問
      • 也有訪問權限
    • 靜態函數

      • 訪問方式:對象訪問、類名訪問
      • 靜態成員函數不可以訪問非靜態成員變量
      • 靜態成員函數可以訪問靜態成員變量,因爲都是共享數據
      • 非靜態成員函數可以訪問靜態成員變量和非靜態成員變量
      • 也有訪問權限
  24. 單利模式

    • 主席類:一個類中只有唯一的一個實例對象
    • 數據共享,而且只許拿到一個主席的對象的指針即可
  25. 成員變量和成員函數存儲

    • 成員屬性算在類大小中
    • 成員函數、靜態成員變量、靜態成員函數並不算在類大小中
  26. this指針

    • this指針指向的是被調用的成員函數所屬的對象
    • *this對象本體
    • this解決名稱衝突
  27. 空指針訪問成員函數

    • 如果是一個空指針
    • 可以訪問沒有this的一些成員函數
  28. 常函數和常對象

    • this指針本質

      • 指針常量 type * const this;
      • 指針的指向不可以修改,指針指向的值可以修改
    • 常函數:

      • 成員函數後面加const,不可以修改成員屬性
    • 常對象:

      • 不可以修改內部屬性

      • 常對象只能調用常函數,不能調用普通成員函數

    • 如果特殊要修改常函數或常對象:在屬性前加mutable修飾

  29. 友元

    • 全局函數作爲友元函數
      • 全局函數作爲本類友元函數,可以訪問私有內容
        在類內部寫入:friend + 函數聲明
    • 類作爲友元類
      • 告訴編譯器一個類是本類的好朋友,那麼他就可以訪問到裏面私有內容
      • 在類內部寫入:friend class 類名
    • 成員函數作爲友元函數
      • 告訴編譯器一個類中的成員函數是本類的好朋友,那麼他就可以訪問到裏面私有內容
  30. 加號運算符重載

    • 對於內置數據類型,編譯器知道該如何進行運算
    • 但是對於自定義數據類型,編譯器並不知道該如何運算
    • 利用運算符重載可以解決問題
    • 實現兩個類數據類型相加
      • 分別利用成員函數和全局函數來實現
      • 成員函數本質 p1.operator+(p2)
      • 全局函數本質operator+(p1,p2)
      • 簡化p1+p2
  31. 左移運算符重載

    • 對於內置數據類型,編譯器知道如何cout進行<<運算輸出
    • 對於自定義數據類型,無法輸出
    • 重載左移
      • 利用成員函數:失敗,原因不能讓cout在左側
      • 利用全局函數:ostream& operator<<(ostream& cout, class_name &p)
  32. 遞增運算符重載

    • 前置++
      • 返回引用,class_name & operator++( )
    • 後置++
      • 返回值,class_name operator++(int )
    • 前置++的效率略高於後置++,原因是前置不會調用拷貝構造函數
  33. 智能指針(指針運算符重載)

    • 智能指針:用來託管堆區創建的對象的釋放
    • 如果想讓sp對象創建當做一個指針去對待,需要重載-> *
  34. 賦值運算符重載

    • 系統會默認給一個類添加4個函數:默認構造、拷貝構造、析構、operator=
    • 由於系統提供的operator會進行簡單的值拷貝,導致如果屬性中有堆區的數據,會進行重複釋放
    • 解決方案:需要重載operator=
    • 返回值 class_name & operator=(const class_name & p)
  35. [ ]重載

    • 案例

      int& class_name::operator[](int index){
          return this->pAddress[index];
      }
      
  36. 關係運算符重載

    • 重載 == 和 !=
    • 實現兩個自定義數據類型對比操作
  37. 不要重載 && 和 ||

    • 原因:無法實現短路規則
    • 總結 ( )、[ ]、->、= 只能卸載成員函數中進行重載
    • << 和 >> 通常寫在全局函數配合友元進行重載
  38. 繼承

    • 基本語法

      • 語法:class 子類:繼承方式 父類
      • 父類:基類
      • 子類:派生類
    • 繼承好處:可以減少重複代碼出現

    • 繼承方式

      • 公共繼承
        • 父類中public到子類中public
        • 父類中protected到子類中protected
        • 父類中private到子類中private
      • 保護繼承
        • 父類中public到子類中protected
        • 父類中protected到子類中protected
        • 父類中private到子類中 訪問不到
      • 私有繼承
        • 父類中public到子類中private
        • 父類中protected到子類中private
        • 父類中private到子類中 訪問不到
    • 構造和析構

      • 先調用父類構造,在調用子類構造,析構順序和構造相反
      • 可以利用初始化列表方法顯示制定出調用父類的哪個構造函數
    • 同名成員處理

      • 如果子類和父類擁有同名成員,優先調用子類成員,可以通過作用域調用父類的成員
      • 同名成員函數,子類會隱藏掉父類中的所有版本,如果想調用父類中的其他版本,加上作用域即可
    • 同名靜態成員處理

      • 如果子類和父類擁有同名成員,優先調用子類成員,可以通過作用域調用父類的成員
      • 同名成員函數,子類會隱藏掉父類中的所有版本,如果想調用父類中的其他版本,加上作用域即可
      • 訪問方式有兩種
        • 通過對象進行訪問
        • 通過類名進行訪問
    • 多繼承

      • class 子類:繼承方式 父類1, 繼承方式 父類2
      • 當兩個父類中有同名的成員被子類繼承後,調用時候需要加上作用域區分
    • 菱形繼承(Animal父類,Sheep和Tuo同時繼承Animal,SheepTuo進行多繼承,父類 Sheep和Tuo)

      • 問題1:訪問父類中的數據,需要加作用域區分具體數據
      • 問題2:由於菱形繼承導致繼承的數據有一份浪費
      • 解決方案:利用虛繼承 virtual Animal類屬於虛基類
      • 在Sheep和Tuo類中繼承的內容爲vbptr 虛基類
        • v:virtual
        • b:base
        • ptr:pointer
      • vbptr指針 指向虛基類表 vbtable
      • vbtable中有偏移量,通過偏移量可以找到唯一的一份數據
  39. 多態

    • 靜態多態 – 函數重載、運算符重載
    • 動態多態 – 父子之間繼承 + 虛函數
    • 動態多態滿足條件
      • 父類中有虛函數
      • 子類重寫父類的虛函數
      • 父類的指針或引用指向子類的對象
    • 重寫:子類重寫新實現父類中的虛函數,必須返回值、函數名、參數一致才成爲重寫
    • 子類在做重寫的時候,可以不加關鍵字virtual
    • 多態原理
      • 當父類中存在虛函數後,內部發生結構變化
      • 多了指針 vfptr 虛函數表指針 – 指向 虛函數表 vftable
      • 虛函數表內部記錄着虛函數的地址
      • 當子類發生重寫後,會修改子類中的虛函數表中的函數地址,但是並不會影響父類中的虛函數表
    • 多態好處
      • 對擴展提高
      • 組織性強
      • 可讀性強
    • 如果父類中有虛函數,子類並沒有重寫父類的虛函數,那麼這樣的代碼毫無意義(沒有用到多態帶來的好處,反而內部結構變的複雜了)
  40. 抽象類和純虛函數

    • 純虛函數 語法:virtual void func( ) = 0;
    • 如果一個類中有純虛函數,那麼這個類就無法實例化對象
    • 有純虛函數的類,也稱爲 抽象類
    • 如果子類繼承了抽象類,那麼子類必須重寫父類的純虛函數,否則子類也屬於抽象類
  41. 虛析構和純虛析構

    • 如果子類中有屬性創建在堆區,那麼多態情況下,不會調用子類的析構代碼,導致內存泄漏
    • 解決方案:利用虛析構或者純虛析構
    • 虛析構:在析構前加 virtual
    • 純虛析構 virtual ~函數名() = 0
    • 純虛析構類內聲明、類外必須要實現
    • 如果一個類中有了純虛析構函數,那麼這個類也屬於抽象類
  42. 向上和向下類型轉換

    • 父類轉子類 向下類型轉換 不安全
    • 子類轉父類 向上轉換安全
    • 如果發生多態,總是安全的
  43. 重載 重寫 重定義

    • 重載 函數重載
      • 同一個作用域 函數名相同
      • 參數 個數 類型 順序不同滿足條件
      • 返回值不可以作爲重載條件
    • 重寫
      • 繼承關係
      • 父類中有虛函數
      • 子類可以重寫父類中的函數,返回值、函數名、參數類別都一致
    • 重定義
      • 繼承關係
      • 非虛函數 子類重新定義 父類中同名的成員函數
  44. 函數模板

    • template 告訴編譯器 T是萬能數據類型,下面緊跟的函數或者類中出現 T類型,不要報錯
    • 調用模板函數
      • 自動類型推到,必須讓編譯器推導出一致 T類型纔可以使用模板
      • 顯示指定類型,顯示的告訴編譯類型 T的類型
    • 模板使用的時候必須要告訴編譯器 T是什麼類型,否則無法使用
  45. 普通函數和函數模板的區別和調用規則

    • 區別
      • 普通函數可以隱式類型轉換
      • 函數模板如果是自動類型推到的使用,是不可以發生隱式類型轉換
    • 調用規則
      • 如果函數模板和普通函數都可以實現調用,那麼優先調用普通函數
      • 可以通過空參數列表語法來強制調用函數模板
      • 函數模板也可以發生函數重載
      • 如果函數模板可以產生更好的匹配,那麼優先使用的是函數模板
  46. 模板機制

    • 編譯器並不是把函數模板處理成能夠處理任何類型的函數
    • 函數模板通過具體類型產生不同的函數(產生了 模板函數)
    • 編譯器會對函數模板進行兩次編譯,在聲明的地方對模板代碼本身進行編譯,在調用的地方對參數替換後的代碼進行編譯
  47. 模板侷限性以及利用具體化技術解決

    • 模板並不是真正的通用代碼,對於一些自定義的數據類型,模板有時不能實現效果
    • 可以通過具體化實現對自定義數據類型進行操作
    • template<> bool myCompare(Person & a, Person & b)
  48. 類模板

    • template下面緊跟的是個類,那麼這個類成爲類模板
    • 類模板和函數模板區別
      • 類模板使用時不可以用自動類型推導,必須顯示指定類型
      • 類模板中的類型可以有默認參數
    • 泛型編程 – 體現在模板技術 – 特點 將類型參數化
    • 類模板中的成員函數創建時機
      • 類模板中的成員,並不是一開始創建出來的,而是在運行階段才創建出來
    • 類模板作爲參數
      • 指定傳入類型
      • 參數模板化
      • 整個類模板化
      • 查看T類型名稱:typeid(T).name()
    • 類模板遇到繼承問題及解決
      • 如果父類是一個類模板,子類在做繼承時,必須指定出父類中T的類型,否則無法給父類中的T分配內存
    • 類模板的份文件編寫問題以及解決
      • 類模板不建議分文件編寫,因爲成員函數創建時機運行階段,使用時必須要包含.cpp纔可以
      • 解決方法:將類中成員函數的聲明和實現都寫到一個文件彙總,並且將文件後綴名改爲.hpp即可(約定俗成)
    • 類模板遇到友元函數
      • 全局函數做友元的類內實現
      • 全局函數做友元的類外實現
        • 模板函數聲明 模板函數實現
  49. 類型轉換

    • 靜態類型轉換 static_cast
      • 語法:static_cast<目標類型>(原對象)
      • 對於內置數據類型可以轉換
      • 對於自定義數據類型,必須是父子之前的指針或者引用可以轉換
    • 動態類型轉換 dynamic_cast
      • 語法:dynamic_cast<目標類型>(原對象)
      • 對於自定義數據類型
        • 父轉子 不安全 轉換失敗
        • 子轉父 安全 轉換成功
        • 如果發生多態,那麼總是安全的都可以轉成功
    • 常量類型轉換 const_cast
      • 只能對 指針 或者引用之間使用
    • 重新解釋類型轉化 reinterpret_cast
      • 不建議使用 不安全
  50. 異常處理

    • 基本語法
      • 三個關鍵字:try throw catch
      • try:試圖執行一段可能會出現異常的代碼
      • throw:出現異常後拋出異常關鍵字 throw+類型
      • catch:捕獲異常 catch(類型)
      • 如果捕獲到的異常不想處理,想繼續向上拋出 throw
      • 異常必須要有人處理,如果沒有處理,程序會自動調用terminate函數,使用程序中斷
    • 可以拋出一個自定義類型的異常 myException
    • 棧解旋:從try代碼開始,到throw拋出異常,所有棧上的對象都被釋放掉,釋放的順序和構造順序是相額反的,這個過程稱爲棧解旋
    • 異常的接口聲明
      • 如果只允許函數體中拋出某種異常,可以使用異常的接口聲明
      • void func(類型),如果throw(空),代表不允許拋出異常
    • 異常變量聲明週期
      • MyException e會調用拷貝構造
      • MyException &e引用方式 接受 建議使用這種方式 節省開銷
      • MyException *e指針方式 接受 拋出&MyException();匿名對象,對象被釋放掉,不可以再操作了
      • MyException *e指針方式 接受 拋出 new MyException();堆區創建的對象,記得手動釋放 delete e;
      • 建議使用 引用的方式 去接受對象
    • 異常的多態使用
      • 提供基類異常類BaseException 純虛函數 virtual void printError() = 0;
      • 提供兩個子類 繼承 異常基類 重寫父類中的純虛函數
      • 測試 利用父類引用去接受子類對象 實現多態
      • 拋出哪種異常就打印哪種異常printError函數
    • 使用系統標準異常
      • 標準異常頭文件 #include
      • 使用系統異常類 out_of_range(char *)
      • 捕獲 catch(exception &e) {cout << e.what();}
  51. 標準輸入和輸出

    • 標準輸入
      • cin.get() 緩衝區中讀取一個字符
      • cin.get(兩個參數) 不讀取換行符
      • cin.getline() 讀取換行並且扔掉
      • cin.ignore() 忽略 (N) N代表忽略字符數
      • cin.peek() 偷窺 偷看1個字符然後放回去
      • cin.putback() 返回 把字符放回緩衝區
      • cin.fail() 緩衝區的標誌位 0:正常 1:異常
      • cin.clear() cin.sync() 重置標誌位 並且刷新緩衝區
    • 標準輸出
      • out.put() out.write() 利用成員函數輸出內容
      • 通過流成員函數
        • int number = 99;
        • cout.width(20); 預留20空間
        • cout.fill("*"); 填充
        • cout.setf(ios::left); 左對齊
        • cout.unsetf(ios::dec); 卸載十進制
        • cout.setf(ios::hex); 安裝十六進制
        • cout.setf(ios::showbase); 設置顯示進制 基數
        • cout.unsetf(ios::hex); 卸載十六進制
        • cout.setf(ios::oct); 安裝八進制
        • cout << number << endl;
      • 使用控制符 頭文件#include
        • int number = 99;
        • cout << setw(20); 設置寬度
        • ​ << setfill("~") 填充
        • ​ << setiosflag(ios::showbase) 顯示進制基數
        • ​ << setiosflag(ios::left) 設置左對齊
        • ​ << hex 安裝十六進制
        • ​ << number
        • ​ << endl;
  52. 文件讀寫操作

    • 讀寫頭文件 #include
    • 寫 ofstream ofs
      • ofs.open 指定打開方式 ios::out | ios::trunk
      • 判斷是否打開成功 ofs.is_open
      • ofs << “aaa”;
      • 關閉流對象 ofs.close();
      • ifstream ifs
      • 指定打開方式 ios::in
      • 判斷是否打開成功 is_open
      • 三種方式 可以讀文件中的數據
      • 關閉流對象 ifs.close();
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章