1. 程式人生 > >C++學習筆記(更新中)

C++學習筆記(更新中)

  1. C和C++的區別
    a. C是結構化的語言,面向過程,重點在於資料結構和演算法
    b. C語言的API比較簡潔
    c. C++包含了絕大部分C語言的功能,並且提供OOP(面向物件程式設計)和GP(類屬程式設計)
    d. C++有更嚴格的型別檢查系統、大量額外的語言特性(RTTI,異常)
    e. C++也比較簡潔,有運算子過載,隱式轉換,
    f. C語言的struct不能宣告函式,c語言沒有模板,異常,繼承

  2. C++中四個與型別轉換相關的關鍵字,比較他們
    a. /最常用的型別轉換符,在正常狀況下的型別轉換/
    static_cast(varible)

  3. /用於取出const屬性,把const型別的指標變為非const型別的指標

    /
    const_cast(varible)
    c. /*dynamic_cast 主要用於執行“安全的向下轉型,在程式執行期間判斷\
    但只在源型別具有多型型別時合法,即該類至少具有一個虛擬方法。*/
    dynamic_cast(varible)RTTI
    i. 通常用於向下轉型,父類轉換為子類(可以呼叫父類和子類共有的物件,呼叫子類獨有的會錯誤),其中父類必須 要有虛擬函式(多型型別),否則錯誤
    iv. /*interpret是解釋的意思,reinterpret即為重新解釋,此識別符號的\
    reinterpret_cast(varible)

  4. 優先順序佇列,less greater

  5. 排序演算法
    a. 穩定的只有:插入,冒泡,歸併(空間為O(N)),基數排序
    b. 基數排序是先按個位排序,然後十位,依次到最高位時間複雜度和空間複雜度O(d(n+r)),d是基數,r是位數
    c. 插入排序在最優的情況下只需O(n).
    d. 快速排序的空間複雜度是O(logN),因為快速排序是需要返回一個結果值,需要O(logN)個結果值。

  6. 大端和小端
    a. Little-Endian
    i. 對位元組:低位位元組排放在記憶體的低地址端,高位位元組排放在記憶體的高地址端。
    ii. 對位域:按照結構體從上往下,分配到記憶體的從低到高
    b. Big-Endian
    i. 對位元組:就是高位位元組排放在記憶體的低地址端,低位位元組排放在記憶體的高地址端。
    ii. 同小端相反
    c. 比如0x1234,34是低位位元組,12是高位位元組

  7. 位域
    a. 一個位域必須儲存在同一個位元組中,不能跨兩個位元組,故位域的長度不能大於一個位元組的長度
    b. 位域的對齊

  8. 如果相鄰位域欄位的型別相同,且其位寬之和小於型別的sizeof大小,則後面的欄位將緊鄰前一個欄位儲存,直到不能容納為止;
  9. 如果相鄰位域欄位的型別相同,但其位寬之和大於型別的sizeof大小,則後面的欄位將從新的儲存單元開始,其偏移量為其型別大小的整數倍;
    3.如果相鄰的兩個位域欄位的型別不同,則各個編譯器的具體實現有差異,VC6採取不壓縮方式,GCC和Dev-C++都採用壓縮方式;
  10. 整個結構體的總大小為最寬基本型別成員大小的整數倍
  11. 如果位域欄位之間穿插著非位域欄位,則不進行壓縮;(不針對所有的編譯器)

  12. int x = 1,int y = ~x;則 y 為 -2
    在計算機中整數的真值用補碼形式表示,正數的補碼是它本身,負數的補碼是原數值除符號位按位取反再加一,由補碼求原數值也是按位取反再加一,那麼1111 1110 除符號位按位取反再加一變成 1000 0010,即 -2。又如0x80000000代表最小的整數

  13. volatile關鍵字是一種型別修飾符
    a. 用它宣告的型別變量表示可以被某些編譯器未知的因素更改。
    b. 用volatile關鍵字宣告的變數i每一次被訪問時,執行部件都會從i相應的記憶體單元中取出i的值。
    c. 沒有用volatile關鍵字宣告的變數i在被訪問的時候可能直接從cpu的暫存器中取值(因為之前i被訪問過,也就是說之前就從記憶體中取出i的值儲存到某個暫存器中),之所以直接從暫存器中取值,而不去記憶體中取值,是因為編譯器優化程式碼的結果(訪問cpu暫存器比訪問ram快的多)。
    以上兩種情況的區別在於被編譯成彙編程式碼之後,兩者是不一樣的。之所以這樣做是因為變數i可能會經常變化,保證對特殊地址的穩定訪問。

  14. C++的深拷貝和淺拷貝
    當在一個類中使用指標時,要特別注意拷貝建構函式的呼叫,如果不定義拷貝建構函式會執行淺拷貝,只是兩個指標指向同一個地址,當析構的時候也許會出現重複釋放一個地址,造成錯誤:
    a. 拷貝建構函式的第一個引數必須是引用型別(不是引用型別會呼叫建構函式);
    b. 拷貝初始化(用 = 號來初始化時)通常使用拷貝建構函式來完成(Myclass my1 = my2)
    c. 還在下列情況下使用拷貝建構函式
    i. 講一個物件作為實參傳遞給一個非引用型別的形參
    ii. 從一個返回型別為非引用型別的函式返回一個物件
    iii. 一個物件需要通過另一個物件初始化
    d. 深拷貝需要在函式中為被初始化的類成員分配空間,而不是簡單的讓指標指向同一個記憶體地址(淺拷貝)

j. C++的父類指標可以指向派生類的物件,如果呼叫的是虛擬函式,則在執行時會動態繫結到子類的函式。
如果不是虛擬函式,只能呼叫父類中的成員,若呼叫子類的成員,則出錯

  1. 基類和派生類的構造和解構函式
    a. 一般情況下,物件登出時,先呼叫派生類的解構函式,然後呼叫基類的析構;
    b. 當用new建立時,只調用基類的建構函式,例如:
    Parent *p = new Child();
    delete p;
    則只調用基類的建構函式,
  2. 若基類的解構函式宣告為virtual,則先呼叫派生類的析構,然後呼叫基類
    為了防止下列這種情況:
    Parent *base;
    child c;
    base = &c;
    如果不把基類的解構函式宣告為virtual,則base銷燬時只調用parent的解構函式,而不呼叫child的,會造成記憶體洩漏。
    d. 當基類的解構函式宣告為virtual,則後面所有子類的解構函式自動宣告為virtual
    e. 建構函式不能宣告為虛擬函式
    f. 虛擬函式會有額外的開銷,因為要維護一個虛擬函式表
    g. 解構函式也可以是行內函數

  3. 派生類和基類的繼承需要梳理一下,很混亂

  4. 自己實現幾個C語言的庫函式,比如strcpy等 每日一個

  5. 使用sizeof()計算類大小的一些基本原則:
    a. 類的大小為類的非靜態成員資料的型別大小之和,也就是說靜態成員資料不作考慮;
    b. 類的總大小也遵守類似class位元組對齊的(記憶體對齊需注意)
    c. 成員函式都是不會被計算的;
    d. 如果是子類,那麼父類中的成員也會被計算;
    e. 虛擬函式由於要維護虛擬函式表,所以要佔據一個指標大小,也就是4位元組(32位系統)。

  6. This指標
    a. 本質上是一個函式引數,只能在成員函式中使用,全域性函式和靜態函式不能使用
    b. this在成員函式的開始前構造,在成員函式的結束後清除;
    c. this指標不佔用物件的空間
    d. this指標的存放位置根據編譯器的不同而不同

  7. C++構造一個空類會產生四個函式
    a. 建構函式 解構函式 拷貝建構函式 賦值函式

  8. 類的靜態成員變數和成員函式
    a. 類的靜態成員變數使用時必須要先定義
    b. 靜態成員函式中不能呼叫非靜態成員,其沒有this指標(因為靜態是在編譯前就產生了記憶體)
    c. 非靜態成員函式中可以呼叫靜態成員。因為靜態成員屬於類本身,在類的物件產生之前就已經存在了,所以在非靜態成員函式中是可以呼叫靜態成員的
    d. 可以通過類名來呼叫靜態成員和函式(因為在物件產生前就已經分配了記憶體)

  9. 類的const
    a. 類中宣告變數為const型別,但是不可以初始化
    b. const常量的初始化必須在建構函式初始化列表中初始化,而不可以在建構函式函式體內初始化

  10. 如果將類型別物件定義為const,則其只能呼叫類中的const成員函式

  11. 多型

  12. C++多型性是通過虛擬函式來實現的,虛擬函式允許子類重新定義成員函式,而子類重新定義父類的做法稱為覆蓋(override),或者稱為重寫。(過載不是多型)
  13. 那麼多型的作用是什麼呢,封裝可以使得程式碼模組化,繼承可以擴充套件已存在的程式碼,他們的目的都是為了程式碼重用。而多型的目的則是為了介面重用。也就是說,不論傳遞過來的究竟是那個類的物件,函式都能夠通過同一個介面呼叫到適應各自物件的實現方法。
  14. 最常見的用法就是宣告基類的指標,利用該指標指向任意一個子類物件,呼叫相應的虛擬函式,可以根據指向的子類的不同而實現不同的方法。如果沒有使用虛擬函式的話,即沒有利用C++多型性,則利用基類指標呼叫相應的函式的時候,將總被限制在基類函式本身,而無法呼叫到子類中被重寫過的函式
  15. 只有指標或引用呼叫虛擬函式時才會執行動態繫結,如果是物件呼叫虛擬函式還是使用靜態繫結。
  16. 如果不想要執行動態繫結,可以通過在呼叫時增加作用域運算子 p->parent::fun();

  17. 組合是在新類中以原有類的物件作為資料成員,繼承是在不改變現有的類的基礎上,採用現有類的形式並在其中新增新程式碼,組合一般用於在新類中使用現有類的功能而不是他的介面的情況,就是新類使用者看到的只是為新類所定義的介面。而繼承則是用於在新類需要向基類轉化的情況(多型),這也是組合和繼承使用的最清晰的判斷方法。

  18. C++的繼承

  19. 派生類中的基類成員也必須需通過基類的建構函式初始化(所以基類必須要有預設建構函式)
    i. 可以直接列表初始化呼叫基類的建構函式
    ii. 否則會呼叫基類的預設建構函式初始化基類的成員
  20. final,和override在行參列表之後,override可以防止由於派生類中虛擬函式的形參列表與基類的不同,而變成了過載不是覆蓋,一旦如此會立即引發錯誤。final關鍵字可以保證之後任何嘗試覆蓋該函式的操作都將引發錯誤。
  21. 派生類向基類的轉換隻能是指標或引用(public繼承才行),不存在物件之間的轉換
    i. 基類的拷貝建構函式,和賦值操作不是虛擬函式,傳遞一個派生類物件時,只會處理基類的部分,派生類的部分被切掉了,所以不能物件間轉換
  22. 繼承說明符:
    i. 基類中protect成員不受繼承說明符的影響
    ii. 如果採用public繼承,則基類成員的訪問許可權在派生類中不變
    iii. protected繼承,則基類的pubilc成員在派生類中會變成protected
    iv. private,則基類的所有成員在派生類中都變成了private(除了protect成員)

  23. 引用的型別必須與其所應用的物件的型別一致(沒有型別轉換),常量的引用可以引用非常量,非常量的引用不能引用一個右值。 Int & I = 5; //(error)

  24. 頂層和底層const

  25. const表示指標本身是個常量(作為實參會被忽略),底層const表示指標所指的物件是個常量。
    函式形參使用const的引用可以保證函式能用於不能拷貝的型別;
    //例如

const vector::iterator iter;
*iter = 10; //合法
iter++; //不合法
vector::const_iterator iter;
*iter = 10; //不合法
++iter; //合法

  1. 左值和右值:
    • 當一個物件被用作右值的時候用的是物件的值(內容),當一個物件被用作左值的時候,用的是物件的身份(在記憶體中的位置)
    • 左值表示有特定的名字的引用,右值沒有。
    • 左值可以當成右值來用,反之不行
    • 返回左值引用的函式,連同賦值,下標,解引用,和前置遞增遞減運算子,都返回左值
    • 返回非引用的函式,連通算術,關係,位,以及後置遞增遞減運算子,都返回右值
    • 右值引用
    • 通過&&來獲得右值的引用
    • 右值引用只能繫結到一個將要銷燬的物件

    C++繼承體系中兩種常用的關係:IS-A(是一個) HAS-A(有一個)
    c++的多型是允許子類型別的指標或引用,賦值給父類型別(可以通過多型呼叫子類的函式)

    typedef typename 的使用
    typedef 型別 定義名; typedef unsigned int uint;
    型別說明只定義了一個數據型別的新名字而不是定義一種新的資料型別
    Typename關鍵字告訴了編譯器把一個特殊的名字解釋成一個型別,在下列情況下必須對一個name使用typename關鍵字:typedef typename COne::one_value_type two_value_type
    1. 一個唯一的name(可以作為型別理解),它巢狀在另一個型別中的。
    2. 依賴於一個模板引數,就是說:模板引數在某種程度上包含這個name。當模板引數使編譯器在指認一個型別時產生了誤解

    尾置返回型別
    auto fcn(T beg,T end) -> decltype(*beg)

    c++中如果vector做成員變數,不可以在類中直接初始化如 vector v(10,0) 錯誤的這樣

    undef 就是取消巨集的定義,然後可以用#define重新定義

    define的巨集定義是從定義的開始到檔案的末尾,處理#define時應該忽略程式碼的邏輯

    模板類是類模板例項化出來的。類模板中的成員函式全部都是模板函式
    原來printf(“%d,%d\n”,Pre A,Pre B)是按照從右向左的順序執行,尤其是自增自減運算要注意
    靜態變數是存放在全域性資料區(初始化資料段或未初始化資料段),sizeof是計算棧中分配的大小,所以不會計算在內
    不能給指標分配一個任意的地址 int ptr; ptr = (int )0x8000; //不允許

    括號中逗號運算子返回值是最後的表示式的值,例如:
    Func((1,2,3),(4,5)) 第一個括號返回3,第二個括號返回5

    explicit關鍵字
    在C++中,如果一個類有隻有一個引數的建構函式,C++允許一種特殊的宣告類變數的方式。在這種情況下,可以直接將一個對應於建構函式引數型別的資料直接賦值給類變數,編譯器在編譯時會自動進行型別轉換,將對應於建構函式引數型別的資料轉換為類的物件。如果在建構函式前加上explicit修飾詞,則會禁止這種自動轉換,在這種情況下,即使將對應於建構函式引數型別的資料直接賦值給類變數,編譯器也會報錯。只能使用直接初始化,而不能使用賦值初始化

    volatile限定符
    當物件的值可能在程式的控制或檢測之外被改動時,應該將物件宣告為volatile。告訴編譯器不應對這樣的物件進行優化。(比如程式有一個由系統時鐘定時更新的變數)

    虛擬函式表
    帶有虛擬函式的類中的每一個物件都有一個虛指標指向該類的虛擬函式表
    C++為每個有虛擬函式的類建立一個虛擬函式表,虛擬函式表存在於類的記憶體中,是按照虛擬函式宣告的順序儲存的(連續的)
    如果子類覆蓋了父類的虛擬函式,那麼子類的虛擬函式指標會在虛擬函式表裡覆蓋父類的該虛擬函式的地址
    當多重繼承時,每個父類都有一個虛擬函式表。若無覆蓋,子類的虛擬函式指標的地址放在第一個父類的虛擬函式表中
    http://blog.csdn.net/haoel/article/details/1948051

    虛擬繼承
    為了解決從不同途徑繼承來的同名的資料成員在記憶體中有不同的拷貝造成資料不一致問題,將共同基類設定為虛基類。這時從不同的路徑繼承過來的同名數據成員在記憶體中就只有一個拷貝,同一個函式名也只有一個對映。這樣不僅就解決了二義性問題,也節省了記憶體,避免了資料不一致的問題
    在菱形繼承中,直接繼承會多拷貝一份基類,浪費空間
    虛繼承出現在多重繼承中,。對給定的虛基類,無論該類在派生層次中作為虛基類出現多少次,只繼承一個共享的基類子物件,共享基類子物件稱為虛基類
    http://www.cnblogs.com/BeyondAnyTime/archive/2012/06/05/2537451.html

    虛繼承和直接繼承的sizeof()計算
    虛繼承計算時需要加上父類的虛擬函式表指標(4個位元組),父類的記憶體大小,自己的記憶體大小
    直接繼承時需要計算父類的成員變數記憶體大小(虛擬函式不算),自己的記憶體大小
    記憶體計算時,類中多個虛擬函式只佔用4個位元組的虛擬函式指標

    虛擬函式和普通函式入口地址的區別
    每個虛擬函式在虛擬函式表中都佔了一個表項,儲存著它的入口地址。當一個包含虛擬函式的物件(不是物件的指標)被建立的時候,它在頭部附加一個指標,指向虛擬函式表中相應的位置。呼叫虛擬函式時,不管是什麼物件呼叫的,它都要先根據虛擬函式表查詢函式入口地址,實現了“動態聯編”,而不是像普通函式那樣有個固定的入口地址。

    C++的RTTI(執行時型別識別)
    typeid:返回指標或引用所指物件的實際型別。
    i. typeid能夠獲取一個表示式的型別:typeid(e)。返回型別為type_info的類
    ii. 如果運算元不是類型別或者是沒有虛擬函式的類,則獲取其靜態型別;如果運算元是定義了虛擬函式的類型別,則計算執行時型別。
    iii. typeid最常見的用途是比較兩個表示式的型別,或者將表示式的型別與特定型別相比較。

  2. dynamic_cast:將基類型別的指標或引用安全的轉換為派生型別的指標或引用(基類必須要有虛擬函式)

  3. 轉換成功返回1,失敗返回0

  4. C++編譯器何時生成預設建構函式(只在被需要時才會呼叫)

  5. 預設函式有2種,1. 沒有提供實參的建構函式;2. 提供了預設實參的建構函式
  6. 第一種是類成員中有成員是類物件,並且該成員的類含有預設建構函式,若無預設建構函式,則不生成;
  7. 基類帶有預設建構函式的派生類。
  8. 帶有虛擬函式的類(1、類本身帶有虛擬函式;2、繼承而來的虛擬函式),因為虛表指標vptr需要在預設建構函式中初始化(執行期間)
  9. 帶有虛基類的類(虛繼承產生的)

  10. C++為類過載賦值運算子時:

  11. 一定要防範對自我賦值的操作;(先分配,後析構)
  12. 大多數賦值運算子組合了析構和拷貝建構函式的功能;
  13. 首先分配新記憶體(構造內容),然後釋放就記憶體,重新賦值。

  14. 重入:

  15. 主要用於多工環境中,一個可重入的函式簡單來說就是可以被中斷的函式,也就是說,可以在這個函式執行的任何時刻中斷它,轉入OS排程下去執行另外一段程式碼,而返回控制時不會出現什麼錯誤;而不可重入的函式由於使用了一些系統資源,比如全域性變數區,中斷向量表等,所以它如果被中斷的話,可能會出現問題,這類函式是不能執行在多工環境下的。如果有多個程序呼叫此函式就需要使用訊號量來保證。
  16. 滿足下列條件的函式多數是不可重入的:
    1) 函式體內使用了靜態的資料結構;
    2) 函式體內呼叫了malloc()或者free()函式;
    3) 函式體內呼叫了標準I/O函式(標準I/O函式很多實現都以不可重入方式使用了全域性資料結構)。

  17. 智慧指標(1.防止記憶體洩漏;2.在多個物件間共享記憶體)

  18. auto_ptr : 不支援引用計數,只能有一個指標享用一個物件。對物件的所有權會隨著賦值轉移,很容易出錯。
  19. shared_ptr : 允許多個指標指向同一個物件,而且可以在建構函式中指定自己的刪除器(函式物件)
  20. weak_ptr :
    i. 弱共享,通常配合shared_ptr使用,不增加引用計數;
    ii. 可以使用lock()函式判斷其所指向的shared_ptr物件是否存在,存在的話返回這個指標,不存在返回一個空的shared_ptr指標。use_count()檢視引用計數,reset()置空shared_ptr;
    iii. 防止迴圈引用
  21. unique_ptr:獨佔的指向一個物件
  22. 實現一個智慧指標(手寫的程度)
    i. 注意幾點,建構函式,拷貝建構函式,賦值運算子過載,析構時的引用計數變化
    ii. 引用計數需要使用int *count;這樣的話當多個指標共同指向一個物件時,引用計數在所有的指標中都是共享的

  23. delete和delete[]的區別

  24. 原則上是 new和delete,new[]和delete[]對應
  25. 基本型別的物件沒有解構函式,銷燬基本型別的陣列物件時呼叫delete和delete[]的結果一樣,都不會造成記憶體洩漏
  26. 對於自定義型別的陣列銷燬時,只能使用delete[](會呼叫說有陣列成員的解構函式),而delete只會呼叫第一個的成員
  27. 對於所有型別的單個物件,只能使用delete而不能使用delete[]。

  28. 使用靜態函式可以加快執行速度(空間換時間)

  29. 經常需要呼叫的函式
  30. 無需例項化的
  31. 執行緒安全的函式

  32. 怎麼判斷何時需要定義自己類的(析構,拷貝構造,拷貝賦值)

  33. 先判斷是否需要自己的解構函式,如果需要,肯定也需要一個拷貝構造和拷貝賦值

  34. 棧中定義的變數就是區域性變數,malloc從堆中申請的記憶體可以讓棧中指標指向它;
    void test()
    {
    char p=(char )malloc(100,1); // p為棧中的指標,指向堆中單元,這完全可以;
    }
    隨著子程式的執行結束,棧中的區域性變數將釋放,p變數消失。則堆中這塊變數
    得不到釋放,就會變成無主地,導致記憶體洩漏。所以,應主動先釋放堆塊。

  35. 防止記憶體洩漏

  36. 當獲取資源的時候,就放進資源管理類(shared_ptr),通過解構函式自動釋放。
  37. shared_ptr和auto_ptr的解構函式都是使用delete,所以不應該用動態陣列來使用,動態陣列可以使用容器來取代。
  38. Auto_ptr只是指向一個物件,而且它管理的物件的所有權會轉移,所以淘汰了·