文章目錄
- Ⅰ 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= 拷貝賦值函數
-
先殺死調用者
-
重新申請指定大小的空間
-
復制字符串內容到調用者
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

- 分配內存:先用一個特殊函數,按 class 的定義分配了兩個
double
的大小 - 轉型(忽視)
- 調用構造函數,賦值
(1,2)
7.3.2 delete
delete:先調用 dtor, 再釋放 memory

- 調用析構函數——釋放的是
m_date
指向的字符串Hello
的空間(即構造函數中new
申請的空間) - 釋放內存:用一個特殊函數釋放了
ps
指向的空間(即String* ps = new String("Hello");
中new
申請的空間)
7.4 內存動態分配
7.4.1 在VC下內存動態分配
在VC下(不同編譯器的內存動態分配可能不同)

-
調試模式:
(4*3)
是3個指針的大小(32+4)
是調試模式所需空間(橘色部分)(4*2)
是上下兩個 cookie ——表示內存塊的開始與結束4
是數組才有的長度記錄由于分配內存塊需要是16的倍數,所以需要 pad 來填充到
64
-
執行模式:
去掉調試模式的空間即可
因為內存塊是16的倍數,因此最后四位bit一定都是0,cookie 就借用最后的一位
1
表示占用內存,0
表示釋放內存如上圖
41h
中1
即表示占用內存
7.4.2 array new/delete
array new
一定要搭配 array delete
new
后有[ ]
—> delete
后加[ ]
普通的delete只調用一次析構函數——剩下兩個指針的指向的空間沒有調用析構函數,內存泄漏
這種情況發生在有指針的類,但最好都這樣寫
8 靜態 模板 namespace
8.1 static
對于非靜態的函數和數據:
非靜態的成員函數通過this
指針來處理不同的數據(一份函數—>多個對象)


對于靜態的函數和數據:
靜態函數沒有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
(單體)

- 構造函數放在private中,外界無法調用
- 設計了
getInstance
靜態函數,來生成并返回唯一的一份
8.2 template
8.2.1 class template 類模板

T
來代替某種類型- 使用時
classname<type1> xxx
,編譯器會把T
全部替換為type1
8.2.2 function template 函數模板

比較函數——任何類型都可以進行比較;T
來代替某種類型
應用時,不需要寫某種類型——編譯器自己會推導
8.3 namespace
對東西進行一個包裝(不一定要一次性全寫在一起,可分開包裝在一起)
namespace name
{
...
}
-
用法一:using directive
#include <iostream> using namespace std; //直接把包裝全打開 int main() {cin << ...;cout << ...;return 0; }
-
用法二:using declaration
#include <iostream> using std::cout; //只打開一條 int main() {std::cin << ...; //沒打開要寫全名cout << ...;return 0; }
-
用法三:都寫全名
#include <iostream> int main() {std::cin << ; std::cout << ...;return 0; }
9 復合 委托
9.1 Composition 復合
類似于c中結構里有結構——class里有class

deque
是一個已經存在的功能很多的類(兩頭進出的隊列);利用deque
的功能來實現queue
的多種操作
該例只是復合的一種情況——設計模式 Adapter
9.1.1 復合下的構造和析構

-
構造是由內而外
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依然在共享
![]()
10 繼承與虛函數
10.1 Inheritance 繼承

語法::public base_class_name
public
只是一種繼承的方式,還有protect
,private
子類會擁有自己的以及父類的數據
10.1.1 繼承下的構造和析構
與復合下的構造和析構相似

-
構造是由內而外
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 虛函數

-
pure virtual 函數:
derived class 一定要重新定義 (override 覆寫) 它;其沒有定義只有聲明
語法:
virtual xxxxxx =0;
-
virtual 函數:
derived class 可以重新定義 (override, 覆寫) 它,且它已有默認定義
語法:
virtual xxxxxx;
-
non-virtual 函數:
不希望 derived class 重新定義 (override, 覆寫) 它
10.3 繼承 with virtual
例子:在 Windows 平臺下用某個軟件打開文件——分為好幾步,但基本所有軟件大多數操作都是一致的,只有一個操作如讀取方式是不一樣的

- 現有一個框架 Application framework 其寫好了所有必要的函數,其中
Serialize()
就是一個 pure virtual 函數 - 使用這個框架寫自己軟件的打開文件,就繼承這個框架,其中就需要自己 override 覆寫
Serialize()
這個函數 - 在執行中,執行
myDoc.OnFileOpen();
中到Serialize()
時,是通過this
來指引到自己寫的Serialize()
中去的
把關鍵動作延緩到子類再做,這是一個經典的設計模式——Template Method
10.4 縮略圖
-
復合:
-
委托:
-
繼承:
-
類中的元素:
變量名稱 : 變量類型(與代碼剛好相反
-
變量下面加下劃線 表示
static
-
前面加一個
-
表示private
-
前面加一個
#
表示protected
-
前面加一個
+
表示public
(一般可以省略)
-
10.5 繼承+復合
這種關系下的構造和析構與之前的類似
-
第一種:
-
構造由內到外 先 Base 再 Component
Derived 的構造函數首先調用 Base 的 default 構造函數,然后調用 Component 的 default 構造函數,然后才執行自己
Derived::Derived(…): Base(),Component() { … };
-
析構由外而內 先 Component 再 Base
Derived 的析構函數首先執行自己,然后調用 Component 的析構函數,然后調用 Base 的析構函數
Derived::~Derived(…){… /*~Component() ~Base()*/};
-
-
第二種:
同理構造由內到外,析構由外而內
10.6 繼承+委托
10.6.1 例一 Observer
設計模式—— Observer
例如一串數據,可以用餅圖來觀察,也可以用條形圖來觀察,這種種的觀察方式都是繼承于 Observer

通過 vector<Observer> m_views;
來進行委托
當數據改變的時候,Observer 也需要更新,即 notify
函數,來將目前所有的觀察者更新
10.6.2 例二 Composite
設計模式—— Composite
例如文件系統,文件夾里可以有文件夾(與自己相同的類),也可以有文件,其中文件就是最基本的 Primitive,而文件夾就是復合物 Composite

要達成目的,就可以再設計一個父類 Component ,文件和文件夾就繼承于同一父類;
其中 Composite 要用委托到父類的方式 Component*
設計容器和操作——使其 Primitive 和 Composite 都可以適用
//父類 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 *clone()
,還有兩個靜態函數Image FindAndClone(imageType);
void addPrototype(Image *image){...}
-
子類中,創建一個靜態的自己
_LAST
,把它放到父類的一個空間中,這樣父類就可以找到新創建的子類private 的構造函數
LandSatImage()
中是addPrototype(this); //這里的 this 就是 _LAST
將自己的原型放到了父類中去 -
子類中,準備一個
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->
得到的東西會繼續用->
作用上去,相當于這個->符號用了兩次![]()
2.1.2 迭代器
以標準庫中的鏈表迭代器為例,這種智能指針還需要處理 ++
--
等符號
node
是迭代器包著的一個真正的指針,其指向 _list_node

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

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 類模板/函數模板
補充:只有模板的尖括號中<>,關鍵字 typename
和 class
是一樣的
3.2 成員模板
它即是模板的一部分,自己又是模板,則稱為成員模板
其經常用于構造函數
- ctor1 這是默認構造函數的實現;它初始化
first
和second
分別為T1
和T2
類型的默認構造函數生成的默認值 - ctor2 這是帶參數的構造函數的實現;它接受兩個參數
a
和b
,并將它們分別用來初始化first
和second
成員變量 - ctor3 這是一個==模板構造函數==,接受一個不同類型的
pair
對象作為參數;它允許從一個不同類型的pair
對象構造當前類型的pair
對象,在構造過程中,它將源pair
對象的first
和second
成員變量分別賦值給當前對象的成員變量,使其具有一定的靈活性和通用性
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
-
例二,父類指針是可以指向子類的,叫做 up-cast;智能指針也必須可以,所以其構造函數需要為==模板構造函數==
3.3 模板模板參數
即模板中的一個模板參數也為模板,下圖黃色高亮部分

XCLs<string, list> mylist
中即表示:容器 list 是 string 類型的—— 創建一個 string 的鏈表;Container<T> c;
即表示list<srting> c;
但是這樣
Container<T> c;
語法過不了,容器 list 后面還有參數,需要用中間框和下面框下一行的代碼 —— c++11的內容
注:下面不是模板模板參數
![]()
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
特別設計

注意綁定模板參數不能跳著綁定,需要從左到右
4.2.2 范圍上的偏
例如:想要當模板參數是指針時特別設計

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
編譯器通過賦值的返回值類型,自動匹配返回類型

注:下面這樣是不行的,第一行編譯器找不到返回值類型
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)

編譯器在滿足以下三個條件時就會做==動態綁定==:
- 通過指針調用
- 指針是向上轉型 up-cast ——
Base* basePtr = new Derived;
- 調用的是虛函數
編譯器就會編譯成 ( *(p->vptr[n]) )(p)
這樣來調用
例如:用一個 Shape(父類)的指針,調用 Circle(子類)的 draw 函數(每個形狀的 draw 都不一樣,繼承自 Shape)
多態:同樣是 Shape 的指針,在鏈表中卻指向了不同的類型
list<Shape*> Mylist
![]()
多態優點:代碼組織結構清晰,可讀性強,利于前期和后期的擴展以及維護
6.2 動態綁定

a.vfunc1()
是通過對象來調用,是 static binding 靜態綁定
在匯編代碼中,是通過 call 函數的固定地址來進行調用的

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 通常不用于聲明變量,用于參數類型和返回類型的描述

以下 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 object | non-const object | |
---|---|---|
const member function(保證不改變 data members) | ?? | ?? |
non-const member function(不保證 data members 不變) | ? | ?? |
COW:Copy On Write
多個指針共享一個 “Hello”;但當a要改變內容時, 系統會單獨復制一份出來給a來改,即 COW
![]()
在常量成員函數中,數據不能被改變所以不需要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 new
、operator delete
、operator 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 new
、operator delete
、operator new[]
、operator delete[]
- 重載之后,new 這個類時,編譯器會使用重載之后的
class Foo
{
public:void* 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 占用的內存