侯捷 八部曲 C++面向對象高級開發(上)+(下)【C++學習筆記】 超詳細 萬字筆記總結 筆記合集

文章目錄

  • Ⅰ C++ part1 面向對象編程
    • 1 頭文件與類的聲明
      • 1.1 c vs cpp關于數據和函數
      • 1.2 頭文件與類
        • 1.2.1 頭文件
        • 1.2.2 class的聲明
        • 1.2.3 模板初識
    • 2 構造函數
      • 2.1 inline 函數
      • 2.2 訪問級別
      • 2.3 ctor 構造函數
        • 2.3.1 ctor 的寫法
        • 2.3.2 ctor/函數 重載
        • 2.3.3 ctor 放在 private 區
      • 2.4 const 常量成員函數
    • 3 參數傳遞與返回值——引用
      • 3.1 參數傳遞
      • 3.2 返回值傳遞
    • 4 友元 friend
      • 4.1 友元
      • 4.2 相同 class 的 object 互為 friends
    • 5 操作符重載與臨時對象
      • 5.1 操作符重載
        • 5.1.1 成員函數實現 / this
        • 5.1.2 非成員函數實現
        • 5.1.3 output函數 << 的重載
      • 5.2 臨時對象
    • 6 帶指針的類:三大函數
      • 6.1 ctor 和 dtor (構造和析構函數)
        • 6.1.1 ctor 構造函數
        • 6.1.2 dtor 析構函數
      • 6.2 copy ctor 拷貝構造函數
      • 6.3 copy op= 拷貝賦值函數
    • 7 堆,棧,內存管理
      • 7.1 堆和棧
      • 7.2 object 生命期
      • 7.3 new 和delete
        • 7.3.1 new
        • 7.3.2 delete
      • 7.4 內存動態分配
        • 7.4.1 在VC下內存動態分配
        • 7.4.2 array new/delete
    • 8 靜態 模板 namespace
      • 8.1 static
      • 8.2 template
        • 8.2.1 class template 類模板
        • 8.2.2 function template 函數模板
      • 8.3 namespace
    • 9 復合 委托
      • 9.1 Composition 復合
        • 9.1.1 復合下的構造和析構
      • 9.2 Delegation 委托
    • 10 繼承與虛函數
      • 10.1 Inheritance 繼承
        • 10.1.1 繼承下的構造和析構
      • 10.2 虛函數
      • 10.3 繼承 with virtual
      • 10.4 縮略圖
      • 10.5 繼承+復合
      • 10.6 繼承+委托
        • 10.6.1 例一 Observer
        • 10.6.2 例二 Composite
        • 10.6.3 例三 Prototype
  • Ⅱ C++ part2 兼談對象模型
    • 1 轉換
      • 1.1 轉換函數
      • 1.2 non-explicit-one-argument ctor
      • 1.3 explicit
    • 2 xxx-like classes
      • 2.1 pointer-like classes
        • 2.1.1 智能指針
        • 2.1.2 迭代器
      • 2.2 function-like classes
    • 3 模板
      • 3.1 類模板/函數模板
      • 3.2 成員模板
      • 3.3 模板模板參數
    • 4 specialization 特化
      • 4.1 全特化 full specialization
      • 4.2 偏特化 partial specialization
        • 4.2.1 個數上的偏
        • 4.2.2 范圍上的偏
    • 5 三個C++11新特性
      • 5.1 variadic templates
      • 5.2 auto
      • 5.3 ranged-base for
    • 6 多態 虛機制
      • 6.1 虛機制
      • 6.2 動態綁定
    • 7 reference、const、new/delete
      • 7.1 reference
      • 7.2 const
      • 7.3 new delete
        • 7.3.1 全局重載
        • 7.3.2 class中成員重載
        • 7.3.3 placement new delete

Ⅰ C++ part1 面向對象編程

1 頭文件與類的聲明

1.1 c vs cpp關于數據和函數

c語言中,data和函數都是分別定義,根據類型創建的。這樣創建出的變量,是全局的

cpp中,將數據data和函數都包含在一起(class),創建出一個對象,即為面向對象;數據和函數(類的方法)都是局部的,不是全局的

class的兩個經典分類:

  • 無指針成員的類(complex)——復數
  • 有指針成員的類(string)——字符串

1.2 頭文件與類

1.2.1 頭文件

引用自己寫的頭文件,用雙引號

頭文件的標準寫法:

complex.h:

#ifndef _COMPLEX_  // 如果沒有被定義過就定義 (防衛式聲明)
#define _COMPLEX_#endif
  • 首先是防衛式聲明,如果沒定義這個名詞,那么就定義一下。ifndef+define。(這樣如果程序是第一次引用它,則定義,后續則不需要重復定義,不需要重復進入下面的過程)
  • 1要寫的類的聲明,2是要寫類的具體定義,寫1 2的時候發現有一些東西需要提前聲明,寫在0

1.2.2 class的聲明

在C++中 struct和class唯一的區別就在于默認的訪問權限不同

  • struct 默認權限為公共
  • class 默認權限為私有
class complex  //class head
{              //class body  /*有些函數直接在此定義,另一些在 body 之外定義*/
public:complex (double r = 0, double i = 0): re (r), im (i) { }complex& operator += (const complex&);double real () const { return re; }double imag () const { return im; }
private:double re, im;friend complex& __doapl (complex*, const complex&); 
};
{complex c1(2,1);complex c2;...
}

1.2.3 模板初識

{complex<double> c1(2.5, 1.5);complex<int> c2(2, 6);...
}
  • 因為實部和虛部的類型不確定,可能是 double float int,定義一個模板類型叫做 T
  • T作為一個類型參數來傳入,在調用的時候就可以指定類型了
  • 通過在定義類的前面加入一行代碼 template<typename T> 來實現

2 構造函數

2.1 inline 函數

定義類的時候,可以直接在body中定義函數(inline函數,在body中定義完成),也可以只是在body中聲明函數

  • inline內聯函數:如果定義的函數是內聯函數,那么會運行比較快,盡可能定義為內聯函數
  • 在body外,通過inline關鍵字來指定該函數為inline函數

注意的是,上面所有的inline函數,都只是我們指定的,希望它為inline,具體是不是,要看編譯器來決定

2.2 訪問級別

  • 數據應該被定義為private

  • 函數要被外界使用,定義為public;若只是內部處理,定義為private

2.3 ctor 構造函數

2.3.1 ctor 的寫法

方式一:(推薦)

complex(T r = 0, T i = 0) //函數名稱與class的名稱一致: re(r), im(i)        //中間這一行就是初始化
{ }

方式二:(不推薦)

complex(double r = 0, double i = 0)  
{re = r; im = i;       //用賦值來進行初始化
}

通過構造函數來創建對象。會自動調用構造函數進行創建。

  • 構造函數名稱需要與類的名稱一樣
  • 函數的參數可以有默認參數
  • 構造函數沒有返回類型

2.3.2 ctor/函數 重載

構造函數可以有很多個,可以重載;但是上面的1 2兩個構造函數沖突了

complex c2();   // "()" 可以不要,一樣的

上面的調用方式對兩個構造函數都適用,沖突


double real () const { return re; }
void real (double r) {  re = r;  }  //不能有const
  • 同名的函數可以有多個,編譯器會編成不同的名稱,實際調用哪個會根據哪個適用

2.3.3 ctor 放在 private 區

  • 通常構造函數不要放在private中,這樣外界沒法調用,也就無法創建對象
  • 在設計模式 Singleton(單體)中,將構造函數放在了private中;這個class只有一份,外界想要調用的時候,只能使用定義的 getInstance() 函數來取得這一份;外界無法創建新的對象

2.4 const 常量成員函數

對于不會改變數據內容的函數,一定要加上const

{const complex c1(2, 1);cout << c1.real();cout << c1.imag();
}

對于上面調用方式,我們創建一個常量復數然后調用函數輸出實部虛部,如果上面real和imag函數定義的時候,沒有加const,那么這里函數默認的意思是可能會改變數據,與我們的常量復數就矛盾了,編譯器會報錯;因此,對于不會改變數據內容的函數,一定一定要加const

3 參數傳遞與返回值——引用

3.1 參數傳遞

  • 值傳遞 pass by value,傳遞value是把整個參數全傳過去,盡量不要直接value傳遞double r

  • 引用傳遞 pass by reference,傳引用相當于傳指針,快,形式也漂亮 例 complex&

  • 如果只是為了提升速度,不向改變數據,那么傳const引用;這樣傳進去的東西,不能被修改

    const complex&

3.2 返回值傳遞

返回值的傳遞,盡量返回引用

在函數中創建的變量 (local 變量),要返回——這種情況是不能返回引用的;因為函數結束后函數中創建的變量就消失了,無法引用


傳遞者無需知道接受者是以reference形式接受——所以用reference形式很便捷

4 友元 friend

4.1 友元

友元:friend,修飾在函數定義之前表示這個函數可以直接拿該類對象的private數據

inline complex&
__doapl(complex* ths, const complex& r)
{ths->re += r.re;  //直接拿private的數據,不需要函數ths->im += r.im;return *ths;
}
  • 如上面所示,聲明為friend之后,函數可以直接取到re和im,如果不被聲明為friend,只能通過調用real和imag函數來得到,效率較低

4.2 相同 class 的 object 互為 friends

{complex c1(2, 1);complex c2;c2.func(c1);
}

相同class的不同對象互為友元,即可以直接取另一個 object 的 private data

5 操作符重載與臨時對象

5.1 操作符重載

在c++里我們可以定義加法等操作符,比如我們可以定義兩個石頭的加法

5.1.1 成員函數實現 / this

成員函數: complex :: function .... 前面帶有class的名稱(在class里先聲明了的)

inline complex&
complex::operator += (const complex& r) {return __doapl(this, r);   //do assignment plus
}

所有的成員函數都帶有一個隱藏的參數this是一個指針),this指向調用這個函數的調用者

  • 定義函數的時候,在參數中不能寫出來this,直接用即可

  • 函數里可寫可不寫,但當傳入參數成員變量名相同時要寫

    public:double real () const { return this->re; }  //這里的this->可省略 
    

c3 += c2 += c1;    // c2 加了 c1 后如果返回 void 就無法進行 c3 的操作了

將操作符寫為void函數也可以,但為了可以兼容c3+=c2+=c1的形式,寫成返回引用更好。

5.1.2 非成員函數實現

非成員函數沒有this

應對三種使用方法,寫出三種方式

  • 非成員函數是global函數——為了后面兩種使用方法

  • 這些函數不能返回引用,必須值傳遞

    在函數中創建的新變量 (local 變量),要返回

5.1.3 output函數 << 的重載

cout不認識新定義的這種復數,因此也需要對<<進行操作符重載

只能全局函數,不能成員函數——導致使用時方向相反

#include <iostream.h>
ostream&
operator<<(ostream& os, const complex& x)
{return os << '(' << real(x) << ',' << imag(x) << ')';  //自定義輸出
}
  • ostream&cout 的 classname

參數傳遞:os 在函數中會變化,所以不能加 const

返回值傳遞:為了避免 cout << c1 << conj(c1); 連續輸出,不用 void

cout << c1 返回值需要與 cout 類型一致

5.2 臨時對象

classname () 創建一個classname類型的臨時對象——不需要名稱,生命只有一行

6 帶指針的類:三大函數

  • 析構函數:~String();

  • 拷貝構造函數 copy ctor : String (const String& str); —— string s3(s1)

  • 拷貝賦值函數 copy op= : String& operator=(const String& str); —— s3=s2

    編譯器默認的拷貝構造賦值(一個bit一個bit的復制),編譯器默認的只是拷貝了指針(淺拷貝),而不是指針指向的數據

    alias(別名)和 memory leak(內存泄漏)都是十分危險的

    因此,如果類中有指針,一定自己寫這兩個函數

6.1 ctor 和 dtor (構造和析構函數)

6.1.1 ctor 構造函數

這里的 new 是申請的字符串的空間

inline
String::String(const char* cstr = 0)
{if (cstr) {       // 指定了初值—— String s2("hello");m_data = new char[strlen(cstr) + 1];  // 字符串長度 + /0strcpy(m_data, cstr);}else {            // 未指定初值—— String s1();m_data = new char[1];*m_data = '\0';}
}

這里的 new 是申請的指針的空間String()里面還有一個 new

String* p = new String("hello");  
delete p;

6.1.2 dtor 析構函數

inline  
String::~String()
{delete[] m_data;
}

每個 new 都對應一個 delete —— 一定要釋放

類對象死亡的時候(離開作用域),析構函數會被自動調用

例:這里結束會調用三次 dtor

{String s1(),String s2("hello");String* p = new String("hello");delete p;
}

6.2 copy ctor 拷貝構造函數

inline
String::String(const String& str)
{m_data = new char[strlen(str.m_data) + 1]; // “str.m_data” 兄弟之間互為友元 strcpy(m_data, str.m_data); // 深拷貝
}
String s1("hello ");
String s2(s1);

6.3 copy op= 拷貝賦值函數

  1. 先殺死調用者

  2. 重新申請指定大小的空間

  3. 復制字符串內容到調用者

inline
String& String::operator=(const String & str)
{if (this == &str)  // 檢測自我賦值 self assignmentreturn *this;delete[] m_data;                               // 第一步m_data = new char[strlen(str.m_data) + 1];     // 第二步strcpy(m_data, str.m_data);                    // 第三步return *this;
}

一定要在開始就檢測自我賦值,因為a=a時第一步 delete 了后,會使第三步出現問題

7 堆,棧,內存管理

7.1 堆和棧

Stack ,是存在于某作用域 (scope) 的一塊內存空間。

例如當你調用函數,函數本身即會形成一個 stack 用來放置它所接收的參數,以及返回地址;在函數本體 (function body) 內聲明的任何變量其所使用的內存塊都取自上述 stack

Heap ,或稱為 system heap ,是指由操作系統提供的一塊 global 內存空間,程序可動態分配 (dynamic allocated) 從中獲得若干區塊 (blocks)

可以用 new 來動態取得

在 stack 中的是自動生成的空間,作用域結束空間會自動釋放

在 heap 中的是自己申請的空間,需要自己釋放

{complex c1(1,2);              /*c1空間來自stack*/complex* p = new complex(3);  /*complex(3) 是個臨時對象其所用的空間是以new從heap動態分配而得,并由p指向*/
}

7.2 object 生命期

  • stack objects 的生命期

    c1 便是所謂 stack object,其生命在作用域 (scope) 結束之際結束這種作用域內的 object,又稱為 auto object,因為它會被“自動”清理(結束自動調用析構函數)

    {complex c1(1,2);
    }
    
  • static local objects 的生命期

    若在前面加上 static 后,其會存在到整個程序結束

    {static complex c2(1,2);
    }
    
  • global objects 的生命期

    寫在任何作用域之外的對象,其生命在整個程序結束之后才結束,你也可以把它視為一種 static object,其作用域是整個程序

    ...
    complex c3(1,2);int main()
    {...
    }
    
  • heap objects 的生命期

    p 所指的便是 heap object,其生命在它被 delete 之際結束

    {complex* p = new complex;...delete p;
    }

7.3 new 和delete

7.3.1 new

new:先分配 memory , 再調用 ctor

image-20230731092332395
  1. 分配內存:先用一個特殊函數,按 class 的定義分配了兩個 double 的大小
  2. 轉型(忽視)
  3. 調用構造函數,賦值(1,2)

7.3.2 delete

delete:先調用 dtor, 再釋放 memory

image-20230731092947259
  1. 調用析構函數——釋放的是 m_date 指向的字符串 Hello 的空間(即構造函數中 new 申請的空間)
  2. 釋放內存:用一個特殊函數釋放了 ps 指向的空間(即String* ps = new String("Hello");new 申請的空間)

7.4 內存動態分配

7.4.1 在VC下內存動態分配

在VC下(不同編譯器的內存動態分配可能不同)

image-20230731095853726
  • 調試模式:

    (4*3) 是3個指針的大小

    (32+4) 是調試模式所需空間(橘色部分)

    (4*2) 是上下兩個 cookie ——表示內存塊的開始與結束

    4 是數組才有的長度記錄

    由于分配內存塊需要是16的倍數,所以需要 pad 來填充到64

  • 執行模式:

    去掉調試模式的空間即可

因為內存塊是16的倍數,因此最后四位bit一定都是0,cookie 就借用最后的一位1表示占用內存,0表示釋放內存

如上圖41h1即表示占用內存

7.4.2 array new/delete

image-20230731101729210

array new 一定要搭配 array delete

new后有[ ]—> delete后加[ ]

普通的delete只調用一次析構函數——剩下兩個指針的指向的空間沒有調用析構函數,內存泄漏

這種情況發生在有指針的類,但最好都這樣寫

8 靜態 模板 namespace

8.1 static

對于非靜態的函數和數據:

非靜態的成員函數通過this指針來處理不同的數據(一份函數—>多個對象)

image-20230731154548833

對于靜態的函數和數據:

靜態函數沒有this,不能處理一般的數據,只能處理靜態的數據

例1:

class Account
{
public:static double m_rate;  //靜態變量的聲明static void set_rate(const double& x) { m_rate = x; } //靜態函數
};
double Account::m_rate = 0; //靜態變量的定義 一定要有int main()
{//調用靜態函數法1——by class nameAccount::set_rate(5.0);//調用靜態函數法2——by objectAccount a;a.set_rate(7.0); //靜態函數與a無關/無this
}

例2:設計模式 Singleton(單體)

image-20230731163117925
  • 構造函數放在private中,外界無法調用
  • 設計了getInstance靜態函數,來生成并返回唯一的一份

8.2 template

8.2.1 class template 類模板

  • T來代替某種類型
  • 使用時classname<type1> xxx,編譯器會把T全部替換為type1

8.2.2 function template 函數模板

image-20230731164908165

比較函數——任何類型都可以進行比較;T來代替某種類型

應用時,不需要寫某種類型——編譯器自己會推導

8.3 namespace

對東西進行一個包裝(不一定要一次性全寫在一起,可分開包裝在一起)

namespace name
{
...    
}
  1. 用法一:using directive

    #include <iostream>
    using namespace std; //直接把包裝全打開
    int main()
    {cin << ...;cout << ...;return 0;
    }
    
  2. 用法二:using declaration

    #include <iostream>
    using std::cout; //只打開一條
    int main()
    {std::cin << ...; //沒打開要寫全名cout << ...;return 0;
    }
    
  3. 用法三:都寫全名

    #include <iostream>
    int main()
    {std::cin << ; std::cout << ...;return 0;
    }
    

9 復合 委托

9.1 Composition 復合

類似于c中結構里有結構——class里有class

image-20230801093748678

deque 是一個已經存在的功能很多的類(兩頭進出的隊列);利用deque的功能來實現queue的多種操作

該例只是復合的一種情況——設計模式 Adapter

9.1.1 復合下的構造和析構

image-20230801095529359
  • 構造是由內而外

    Container 的構造函數,編譯器會自動先調用 Component 的 default 構造函數,再執行自己

    注意如果要調用 Component 的其他構造函數需要自己寫出來

    Container::Container(…): Component() { … };

  • 析構是由外而內

    Container 的析構函數會先執行自己,之后編譯器調用 Component 的析構函數

9.2 Delegation 委托

委托就是 Composition by reference;即通過指針把任務委托給另一個類

復合中,內部和外部是一起出現的;而委托是不同步的

這是一個著名的設計模式——pimpl (pointer to implementation) 或者叫 “編譯防火墻”

  • 右邊怎么變動都不會影響左邊

  • reference counting 多個指針共享一個 “Hello”;但當a要改變內容時, 系統會單獨復制一份出來給a來改,b和c依然在共享

    image-20230801101907977

10 繼承與虛函數

10.1 Inheritance 繼承

語法::public base_class_name

public 只是一種繼承的方式,還有protectprivate

子類會擁有自己的以及父類的數據

10.1.1 繼承下的構造和析構

與復合下的構造和析構相似

image-20230801150053963
  • 構造是由內而外

    Container 的構造函數,編譯器會自動先調用 Component 的 default 構造函數,再執行自己

    注意如果要調用 Component 的其他構造函數需要自己寫出來

    Derived::Derived(…): Base() { … };

  • 析構是由外而內

    Container 的析構函數會先執行自己,之后編譯器調用 Component 的析構函數

    Derived::~Derived(…){ … /* ~Base() */ };

    注意:Base class 的 dtor 必需是 virtual

    否則下例會導致結束時只會調用 Base 的 dtor

    int main() {Base* ptr = new Derived();delete ptr; // 只會調用 Base 類的析構函數return 0;
    }
    

10.2 虛函數

image-20230801152023433
  • pure virtual 函數:

    derived class 一定要重新定義 (override 覆寫) 它;其沒有定義只有聲明

    語法:virtual xxxxxx =0;

  • virtual 函數:

    derived class 可以重新定義 (override, 覆寫) 它,且它已有默認定義

    語法:virtual xxxxxx;

  • non-virtual 函數:

    不希望 derived class 重新定義 (override, 覆寫) 它

10.3 繼承 with virtual

例子:在 Windows 平臺下用某個軟件打開文件——分為好幾步,但基本所有軟件大多數操作都是一致的,只有一個操作如讀取方式是不一樣的

image-20230801154005427
  1. 現有一個框架 Application framework 其寫好了所有必要的函數,其中 Serialize() 就是一個 pure virtual 函數
  2. 使用這個框架寫自己軟件的打開文件,就繼承這個框架,其中就需要自己 override 覆寫 Serialize() 這個函數
  3. 在執行中,執行 myDoc.OnFileOpen(); 中到 Serialize() 時,是通過 this 來指引到自己寫的 Serialize() 中去的

把關鍵動作延緩到子類再做,這是一個經典的設計模式——Template Method

10.4 縮略圖

  • 復合:image-20230802084858622

  • 委托:image-20230802085101744

  • 繼承:image-20230802085210589

  • 類中的元素:image-20230802085810812 變量名稱 : 變量類型(與代碼剛好相反

    • 變量下面加下劃線 表示 static

    • 前面加一個 - 表示 private

    • 前面加一個 # 表示 protected

    • 前面加一個 + 表示 public(一般可以省略)

10.5 繼承+復合

這種關系下的構造和析構與之前的類似

  • 第一種:

    image-20230801161457590
    • 構造由內到外 先 Base 再 Component

      Derived 的構造函數首先調用 Base 的 default 構造函數,然后調用 Component 的 default 構造函數,然后才執行自己

      Derived::Derived(…): Base(),Component() { … };

    • 析構由外而內 先 Component 再 Base

      Derived 的析構函數首先執行自己,然后調用 Component 的析構函數,然后調用 Base 的析構函數

      Derived::~Derived(…){… /*~Component() ~Base()*/};

  • 第二種:

    image-20230801162202797

    同理構造由內到外,析構由外而內

10.6 繼承+委托

10.6.1 例一 Observer

設計模式—— Observer

例如一串數據,可以用餅圖來觀察,也可以用條形圖來觀察,這種種的觀察方式都是繼承于 Observer

image-20230801163932926

通過 vector<Observer> m_views; 來進行委托

當數據改變的時候,Observer 也需要更新,即 notify 函數,來將目前所有的觀察者更新

10.6.2 例二 Composite

設計模式—— Composite

例如文件系統,文件夾里可以有文件夾(與自己相同的類),也可以有文件,其中文件就是最基本的 Primitive,而文件夾就是復合物 Composite

image-20230802082524919

要達成目的,就可以再設計一個父類 Component ,文件和文件夾就繼承于同一父類;

其中 Composite 要用委托到父類的方式 Component* 設計容器和操作——使其 PrimitiveComposite 都可以適用

//父類 Component
class Component
{
private:int value;
public:Component(int val)	{value = val;}  virtual void add( Component* ) {} //虛函數
};//復合物 Composite
class Composite : public Component
{vector <Component*> c;  
public:Composite(int val) : Component(val) {}void add(Component* elem){c.push_back(elem);}}//基本類 Primitive
class Primitive: public Component
{
public:Primitive(int val): Component(val) {}
};

component中add是虛函數(且是空函數),不能是純虛函數——Primitive 不會 override add函數(最基本的單位,不能 add 了),而 Composite 需要 override add函數

10.6.3 例三 Prototype

設計模式—— Prototype

框架(父類)要創建未來才會出現的子類——要求子類要創建一個自己當作原型 Prototype 讓框架(父類)來找到并創建 FindAndClone

補充:當一個子類繼承自父類時,它可以被視為是父類的一種類型,因此可以使用父類的指針或引用來引用子類的對象;

這種用父類的指針或引用來處理子類對象的方式稱為——**向上轉型 ** Upcasting

image-20230802163941216
  1. 父類中,有一個存放原型的數組,有純虛函數 Image *clone(),還有兩個靜態函數 Image FindAndClone(imageType); void addPrototype(Image *image){...}

  2. 子類中,創建一個靜態的自己 _LAST ,把它放到父類的一個空間中,這樣父類就可以找到新創建的子類

    private 的構造函數 LandSatImage() 中是 addPrototype(this); //這里的 this 就是 _LAST 將自己的原型放到了父類中去

  3. 子類中,準備一個 clone()函數,父類通過調用找到的相應類型的 clone 函數來創建子類的副本

    這里的 clone 函數就不能用之前的那個構造函數來創建副本了——其會放到父類中去,所以創建一個新的構造函數 LandSatImage(int) 用傳進一個無用參數(隨便傳個int型數據就好)來進行區分

Ⅱ C++ part2 兼談對象模型

1 轉換

1.1 轉換函數

將當前對象的類型轉換成其他類型

  • operator 開頭,函數名稱為需要轉成的類型,無參數
  • 前面不需要寫返回類型,編譯器會自動根據函數名稱進行補充
  • 轉換函數中,分子分母都沒改變,所以通常加 const
// class Fraction里的一個成員函數
operator double() const
{return (double) (m_numerator / m_denominator);
}
Fraction f(3,5);
double d = 4 + f; //編譯器自動調用轉換函數將f轉換為0.6

1.2 non-explicit-one-argument ctor

將其他類型的對象轉換為當前類型

one-argument 表示只要一個實參就夠了

// non-explicit-one-argument ctor
Fraction(int num, int den = 1) : m_numerator(num), m_denominator(den) {}
Fraction f(3,5);
Fraction d = f + 4; //編譯器調用ctor將4轉化為Fraction

1.3 explicit

當上面兩個都有轉換功能的函數在一起,編譯器調用時都可以用,報錯

class Fraction
{
public:Fraction(int num, int den = 1) : m_numerator(num), m_denominator(den) {}operator double() const{return (double)m_numerator / m_denominator;}Fraction operator+(const Fraction& f) const{return Fraction(...);}
private:int m_numerator; // 分子int m_denominator; // 分母
};
...Fraction f(3,5);
Fraction d = f + 4; // [Error] ambiguous

one-argument ctor 加上 explicit,表示這個 ctor 只能在構造的時候使用,編譯器不能拿來進行類型轉換了

...
explicit Fraction(int num, int den = 1) : m_numerator(num), m_denominator(den) {}
...Fraction f(3,5);
Fraction d = f + 4; // [Error] 4不能從‘double’轉化為‘Fraction’

關鍵字 explicit 主要就在這里運用

2 xxx-like classes

2.1 pointer-like classes

2.1.1 智能指針

  • 設計得像指針class,能有更多的功能,包著一個普通指針
  • 指針允許的動作,這個類也要有,其中 *-> 一般都要重載
template <typename T>
class shared_ptr
{
public:T& operator*() const { return *px; }T* operator->() const { return px; }shared_ptr(T* p) : ptr(p) {}
private:T* px;long* pn;
};

在使用時,*shared_ptr1 就返回 *px

但是 shared_ptr1-> 得到的東西會繼續用 -> 作用上去,相當于這個->符號用了兩次

image-20230807095542200

2.1.2 迭代器

以標準庫中的鏈表迭代器為例,這種智能指針還需要處理 ++ -- 等符號

node 是迭代器包著的一個真正的指針,其指向 _list_node

image-20230807100734372
  • 下圖 *ite 的意圖是取 data——即一個 Foo 類型的 object
  • 下圖 ite->method 的意圖是調用 Foo 中的函數 method
image-20230807100804223

2.2 function-like classes

設計一個class,行為像一個函數

函數行為即 —— xxx() 有一個小括號,所以函數中要有() 進行重載

template <class pair>
struct select1st ... // 這里是繼承奇特的Base classes,先不管
{const typename pair::first_type& // 返回值類型,先不管operator()(const pair& x) const{return x.first;}
};...
//像一個函數一樣在用這個類
select1st<my_pair> selector;
first_type first_element = selector(example_pair);//還可以這樣寫,第一個()在創建臨時對象
first_type first_element = select1st<my_pair>()(example_pair);...

3 模板

3.1 類模板/函數模板

補充:只有模板的尖括號中<>,關鍵字 typenameclass 是一樣的

3.2 成員模板

它即是模板的一部分,自己又是模板,則稱為成員模板

其經常用于構造函數

  1. ctor1 這是默認構造函數的實現;它初始化 firstsecond 分別為 T1T2 類型的默認構造函數生成的默認值
  2. ctor2 這是帶參數的構造函數的實現;它接受兩個參數 ab,并將它們分別用來初始化 firstsecond 成員變量
  3. ctor3 這是一個==模板構造函數==,接受一個不同類型的 pair 對象作為參數;它允許從一個不同類型的 pair 對象構造當前類型的 pair 對象,在構造過程中,它將源 pair 對象的 firstsecond 成員變量分別賦值給當前對象的成員變量,使其具有一定的靈活性和通用性
template <class T1, class T2>
struct pair
{T1 first;T2 second;pair() : first(T1()), second(T2()) {} //ctor1pair(const T1& a, const T2& b) : 	  //ctor2first(a), second(b) {}template <class U1, class U2>		  //ctor3pair(const pair<U1, U2>& p) : first(p.first), second(p.second) {}
};
  • 例一,可以使用 <鯽魚,麻雀> 對象來構造一個 <魚類,鳥類> 的pair

    image-20230807152238567
  • 例二,父類指針是可以指向子類的,叫做 up-cast;智能指針也必須可以,所以其構造函數需要為==模板構造函數==

    image-20230807152501305

3.3 模板模板參數

即模板中的一個模板參數也為模板,下圖黃色高亮部分

image-20230807161321152
  • XCLs<string, list> mylist 中即表示:容器 liststring 類型的—— 創建一個 string 的鏈表;Container<T> c; 即表示 list<srting> c;

  • 但是這樣 Container<T> c; 語法過不了,容器 list 后面還有參數,需要用中間框和下面框下一行的代碼 —— c++11的內容

注:下面不是模板模板參數

image-20230807162712548

class Sequence = deque<T> 是有一個初始值,當沒指定時就初始為 deque<T>

在要指定時,如最后一行中的 list<int> 是確切的,不是模板

4 specialization 特化

4.1 全特化 full specialization

模板是泛化,特化是泛化的反面,可以針對不同的類型,來設計不同的東西

  • 其語法為template<> struct xxx<type>
template<>
struct hash<char>
{
...size_t operator()(char& x) const {return x;}
};template<>
struct hash<int>
{
...size_t operator()(int& x) const { return x; }
};
  • 這里編譯器就會用 int 的那段代碼;注意:hash<int>() 是創建臨時變量
cout << hash<int>()(1000)

4.2 偏特化 partial specialization

4.2.1 個數上的偏

例如:第一個模板參數我想針對 bool 特別設計

image-20230807155256372

注意綁定模板參數不能跳著綁定,需要從左到右

4.2.2 范圍上的偏

例如:想要當模板參數是指針時特別設計

image-20230807160122944
C<string> obj1; //編譯器會調用上面的
C<string*> obj2; //編譯器會調用下面的

5 三個C++11新特性

5.1 variadic templates

模板參數可變化,其語法為 ... (加在哪看情況)

// 當參數pack里沒有東西了就調用這個基本函數結束輸出
void print() {
}// 用于打印多個參數的可變參數模板函數
template <typename T, typename... Args>
void print(const T& first, const Args&... args) {std::cout << first << " ";print(args...);  // 使用剩余參數進行遞歸調用
}int main() {print(1, "Hello", 3.14, "World");return 0;
}

還可以使用 sizeof...(args) 來得到參數pack里的數量

5.2 auto

編譯器通過賦值的返回值類型,自動匹配返回類型

image-20230808080207006

注:下面這樣是不行的,第一行編譯器找不到返回值類型

auto ite; // error
ite = find(c.begin(), c.end(), target);

5.3 ranged-base for

for 循環的新語法,for(聲明變量 : 容器),編譯器會從容器中依次拿出數據賦值給聲明變量中

for (decl : coll)
{statement
}//例
for (int i : {1, 3, 4, 6, 8}) // {xx,xx,xx} 也是c++11的新特性
{cout << i << endl;
}

注意:改變原容器中的值需要 pass by reference

vector<double> vec;
...for (auto elem : vec) //值傳遞
{cout << elem << endl;
}
for (auto& elem : vec) //引用傳遞
{elem *= 3;
}

6 多態 虛機制

6.1 虛機制

當類中有虛函數時(無論多少個),其就會多一個指針—— vptr 虛指針,其會指向一個 vtbl 虛函數表,而 vtbl 中有指針一一對應指向所有的虛函數

有三個類依次繼承,其中A有兩個虛函數 vfunc1() vfunc2(),B改寫了A的 vfunc1(),C又改寫了B的 vfunc1(),子類在繼承中對于虛函數會通過指針的方式進行——因為可能其會被改寫

繼承中,子類要繼承父類所有的數據和其函數調用權,但虛函數可能會被改寫,所以調用虛函數是==動態綁定==的,通過指針 p 找到 vptr,找到vtbl,再找到調用的第n個虛函數函數——( *(p->vptr[n]) )(p)

image-20230808095746683

編譯器在滿足以下三個條件時就會做==動態綁定==:

  1. 通過指針調用
  2. 指針是向上轉型 up-cast ——Base* basePtr = new Derived;
  3. 調用的是虛函數

編譯器就會編譯成 ( *(p->vptr[n]) )(p) 這樣來調用

例如:用一個 Shape(父類)的指針,調用 Circle(子類)的 draw 函數(每個形狀的 draw 都不一樣,繼承自 Shape)

多態:同樣是 Shape 的指針,在鏈表中卻指向了不同的類型

list<Shape*> Mylist

image-20230808104025485

多態優點:代碼組織結構清晰,可讀性強,利于前期和后期的擴展以及維護

6.2 動態綁定

image-20230808111646258

a.vfunc1() 是通過對象來調用,是 static binding 靜態綁定

在匯編代碼中,是通過 call 函數的固定地址來進行調用的

image-20230808112307107

pa 是指針,是向上轉型,是用其調用虛函數—— dynamic binding 動態綁定

在匯編代碼中,調用函數的時候,藍框的操作用 c語言 的形式即是 —— ( *(p->vptr[n]) )(p)

下面同理

7 reference、const、new/delete

7.1 reference

x 是整數,占4字節;p 是指針占4字節(32位);r 代表x,那么r也是整數,占4字節

int x = 0;
int* p = &x; // 地址和指針是互通的
int& r = x; // 引用是代表x

引用與指針不同,只能代表一個變量,不能改變

引用底部的實現也是指針,但是注意 object 和它的 reference 的大小是相同的,地址也是相同的(是編譯器制造的假象)

sizeof(r) == sizeof(x)
&x == &r

reference 通常不用于聲明變量,用于參數類型和返回類型的描述

image-20230808091745371

以下 imag(const double& im)imag(const double im) 的簽名signature 在C++中是視為相同的——二者不能同時存在

double imag(const double& im) /*const*/ {....}
double imag(const double im){....} //Ambiguity

注意:const 是函數簽名的一部分,所以加上后是可以共存的

7.2 const

const 加在函數后面 —— 常量成員函數(成員函數才有):表示這個成員函數保證不改變 class 的 data

const objectnon-const object
const member function(保證不改變 data members)????
non-const member function(不保證 data members 不變)???

COWCopy On Write

多個指針共享一個 “Hello”;但當a要改變內容時, 系統會單獨復制一份出來給a來改,即 COW

image-20230801101907977

在常量成員函數中,數據不能被改變所以不需要COW;而非常量成員函數中數據就有可能被改變,需要COW

charT
operator[] (size_type pos)const
{.... /* 不必考慮COW */   
}reference
operator[] (size_type pos)
{.... /* 必須考慮COW */
}

函數簽名不包括返回類型但包括const,所以上面兩個函數是共存的

當兩個版本同時存在時,const object 只能調用 const 版本,non-const object 只能調用 non-const 版本

7.3 new delete

7.3.1 全局重載

  • 可以全局重載 operator newoperator deleteoperator new[]operator delete[]
  • 這幾個函數是在 new 的時候,編譯器的分解步驟中的函數,是給編譯器調用的

注意這個影響非常大!

inline void* operator new(size_t size){....}
inline void* operator new[](size_t size){....}
inline void operator delete(void* ptr){....}
inline void operator delete[](void* ptr){....}

7.3.2 class中成員重載

  • 可以重載 class 中成員函數 operator newoperator deleteoperator new[]operator delete[]
  • 重載之后,new 這個類時,編譯器會使用重載之后的
class Foo
{
publicvoid* operator new(size_t size){....}void operator delete(void* ptr, size_t size){....} // size_t可有可無void* operator new[](size_t size){....}void operator delete[](void* ptr, size_t size){....} // size_t可有可無....
}
// 這里優先調用 members,若無就調用 globals
Foo* pf = new Foo;
delete pf;// 這里強制調用 globals
Foo* pf = ::new Foo;
::delete pf;

7.3.3 placement new delete

可以重載 class 成員函數 placement new operator new(),可以寫出多個版本,前提是每一個版本的聲明有獨特的傳入參數列,且其中第一個參數必須是 size_t,其余參數出現于 new(.....) 小括號內(即 placement arguments

Foo* pf = new(300, 'c') Foo; // 其中第一個參數size_t不用寫
// 對應的operator new
void* operator new (size_t size, long extra, char init){....}

我們也可以重載對應的 class 成員函數 operator delete(),但其不會被delete調用,只當 new 調用的構造函數拋出異常 exception 的時候,才會調用來歸還未能完全創建成功的 object 占用的內存

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/42035.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/42035.shtml
英文地址,請注明出處:http://en.pswp.cn/news/42035.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

記vite打包vue項目內存溢出問題解決

出現問題 FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory解決方法一&#xff1a; 1.根據網上的資料是通過全局下載npm包increase-memory-limit&#xff1a; npm install -g increase-memory-limit2.在項目目錄執…

學習Vue:路由參數與查詢參數傳遞

在Vue.js中&#xff0c;路由與導航不僅涉及到頁面之間的切換&#xff0c;還包括了向頁面傳遞參數以及獲取查詢參數的功能。本文將詳細介紹如何在Vue Router中傳遞路由參數和查詢參數&#xff0c;幫助您更好地理解和使用這些功能。 路由參數的傳遞 路由參數是指在URL中的動態片…

K8s內部的網路模式實現理解

overlay 網絡模式 在 Kubernetes 中&#xff0c;overlay 網絡模式被用于實現容器之間的網絡通信。 K8s 使用了一種稱為容器網絡接口&#xff08;Container Network Interface&#xff0c;簡稱CNI&#xff09;的規范&#xff0c;該規范定義了容器如何進行網絡連接。實際上&…

SDP 與Rtcp-fb

1、sdp介紹 SDP&#xff08;Session Description Protocol&#xff09;是一種用于描述多媒體會話的協議&#xff0c;它在會話層起著重要的作用。SDP的主要功能是提供會話的元數據和配置信息&#xff0c;以便參與者能夠協商和建立一致的會話。 以下是SDP在會話層的作用&#x…

生活隨筆,記錄我的日常點點滴滴.

前言 &#x1f618;個人主頁&#xff1a;曲終酣興晚^R的小書屋&#x1f971; &#x1f615;作者介紹&#xff1a;一個莽莽撞撞的&#x1f43b; &#x1f496;專欄介紹&#xff1a;日常生活&往事回憶 &#x1f636;?&#x1f32b;?每日金句&#xff1a;被人暖一下就高熱&…

catboost推理開GPU加速

核心設置 model.predict(feature, task_type‘GPU’) 代碼參考 # 訓練配置 params {"catboost": {"n_estimators": 7000,"learning_rate": 0.03,"eval_metric": "AUC","loss_function": "RMSE",&qu…

【sgDragSize】自定義拖拽修改DIV尺寸組件,適用于窗體大小調整

核心原理就是在四條邊、四個頂點加上透明的div&#xff0c;給不同方向提供按下移動鼠標監聽 &#xff0c;對應計算寬度高度、坐標變化 特性&#xff1a; 支持設置拖拽的最小寬度、最小高度、最大寬度、最大高度可以雙擊某一條邊&#xff0c;最大化對應方向的尺寸&#xff1b;再…

一次Linux中的木馬病毒解決經歷(6379端口---newinit.sh)

病毒入侵解決方案 情景 最近幾天一直CPU100%,也沒有注意看到了以為正常的服務調用,直到騰訊給發了郵件警告說我的服務器正在入侵其他服務器的6379端口,我就是正常的使用不可能去入侵別人的系統的,這是違法的. 排查 既然入侵6379端口,就懷疑是通過我的Redis服務進入的我的系統…

Vue基礎-1.知識導航

知識導航&#xff08;就問全不全&#xff09; 當學習 Vue.js 時&#xff0c;除了基本的 HTML、CSS 和 JavaScript 知識外&#xff0c;還有一些其他的技術和語法需要了解&#xff0c;例如 ES6 和 TypeScript。以下是您可能需要學習的一些基礎知識和對應的學習資源&#xff0c;我…

css中變量和使用變量和運算

變量&#xff1a; 語法&#xff1a;--css變量名&#xff1a;值&#xff1b; --view-theme: #1a99fb; css使用變量&#xff1a; 語法&#xff1a;屬性名&#xff1a;var( --css變量名 )&#xff1b; color: var(--view-theme); css運算&#xff1a; 語法&#xff1a;屬性名…

vue3 rouer params傳參的問題

route.params在頁面刷新的時候數據會丟失&#xff0c;所以vue3 棄用了params方式&#xff01; 但是&#xff0c;vue3又更新了一個替代params的方式&#xff1a;history API import { useRouter } from "vue-router" const router userRouter; // 跳轉路由&#xff…

JDBC封裝與設計模式

什么是 DAO &#xff1f; Data Access Object(數據存取對象) 位于業務邏輯和持久化數據之間實現對持久化數據的訪問 DAO起著轉換器的作用&#xff0c;將數據在實體類和數據庫記錄之間進行轉換。 ----------------------------------------------------- DAO模式的組成部分 …

數據結構--拓撲排序

數據結構–拓撲排序 AOV? A O V ? \color{red}AOV? AOV?(Activity On Vertex NetWork&#xff0c;?頂點表示活動的?)&#xff1a; ? D A G 圖 \color{red}DAG圖 DAG圖&#xff08;有向?環圖&#xff09;表示?個?程。頂點表示活動&#xff0c;有向邊 < V i , V j …

計算機網絡的性能指標

計算機網絡的性能指標 1. 速率 速率是指數據在網絡中傳送的速度&#xff0c;通常用比特率或數據率來表示&#xff0c;單位是b/s&#xff0c;或bit/s&#xff0c;即比特每秒&#xff0c;或者bps(bit per second)。 速率單位&#xff1a;1 Ybps 10^24 bps(堯), 1 Zbps 10^21…

python中的lstm:介紹和基本使用方法

python中的lstm&#xff1a;介紹和基本使用方法 未使用插件 LSTM&#xff08;Long Short-Term Memory&#xff09;是一種循環神經網絡&#xff08;RNN&#xff09;的變體&#xff0c;專門用于處理序列數據。LSTM 可以記憶序列中的長期依賴關系&#xff0c;這使得它非常適合于各…

深度思考rpc框架面經之四

7 netty機制的一些理解 推薦閱讀&#xff1a; 深度思考netty網絡編程框架 7.1 Netty支持的端口號: Netty可以綁定到任何合法的端口號&#xff0c;這與大多數網絡庫類似。有效的端口范圍是從0到65535&#xff0c;但通常建議使用1024以上的端口&#xff0c;因為0-1023的端口已…

算法與數據結構(二十四)最優子結構原理和 dp 數組遍歷方向

注&#xff1a;此文只在個人總結 labuladong 動態規劃框架&#xff0c;僅限于學習交流&#xff0c;版權歸原作者所有&#xff1b; 本文是兩年前發的 動態規劃答疑篇open in new window 的修訂版&#xff0c;根據我的不斷學習總結以及讀者的評論反饋&#xff0c;我給擴展了更多…

【STM32】高效開發工具CubeMonitor快速上手

工欲善其事必先利其器。擁有一個輔助測試工具&#xff0c;能極大提高開發項目的效率。STM32CubeMonitor系列工具能夠實時讀取和呈現其變量&#xff0c;從而在運行時幫助微調和診斷STM32應用&#xff0c;類似于一個簡單的示波器。它是一款基于流程的圖形化編程工具&#xff0c;類…

面試題:線程池的底層工作原理

線程池的幾個重要的參數&#xff1a; 1、corePoolSize&#xff1a;線程池的核心線程數&#xff08;也是默認線程數&#xff09; 2、maximumPoolSize&#xff1a;最大線程數 3、keepAliveTime&#xff1a;允許的線程最大空閑時間&#xff08;單位/秒&#xff09; 線程池內部是…

鏈表之第二回

歡迎來到我的&#xff1a;世界 該文章收入欄目&#xff1a;鏈表 希望作者的文章對你有所幫助&#xff0c;有不足的地方還請指正&#xff0c;大家一起學習交流 ! 目錄 前言第一題&#xff1a;反轉一個鏈表第二題&#xff1a;鏈表內指定區間反轉第三題&#xff1a;判斷一個鏈表…