1. 導讀
我們的目標
- 在先前基礎課程所培養的正規、大氣的編程素養上,繼續探討更多技術。
- 泛型編程(Generic Programming)和面向對象編程(Object-Oriented Programming)雖然分屬不同思維,但它們正是 C++的技術主線,所以本課程也討論template(模板)。
- 深入探索面向對象的繼承關系(inheritance)所形成的對象模型(Object Model),包括隱藏于底層的 this 指針,vptr(虛指針),vtbl(虛表),virtual mechanism(虛機制),以及虛函數(virtual functions)造成的 polymorphism(多態)效果。
2. 類型轉換
轉換有兩種,一種是轉出去,一種是轉過來。
2.1 Conversion Function 轉換函數
轉換函數的作用是轉出去,把這個類的值轉換成其他的類型。
operator double() const {……;}
定義了一個轉換為 double 的函數,
沒有參數,轉換時不會帶有參數。
不寫返回類型,返回類型就是名稱里這個double
- 如果不改變值,就該加 const,否則后面可能會出錯。
- 只要認為合理,可以設計好幾個轉換函數。
模板的偏特化?
操作符重載
2.2 只有一個參數的構造函數
只有一個參數的構造函數可以將一個 int 或 float 值轉換為該類對象。
2.2.1 non-explicit-on-argument ctor
Fraction (int num, int den=1): m_numerator(num), m_denominator(den) { }
分母默認是1,這樣一個值構造為對象時,實際值不變。
Fraction operator+(const Fraction& f) { }
只能分數加分數
Fraction d2=f+4;
4可以轉換為 Fraction,因為有一個構造函數只有一個int 參數。
2.2.2 conversion function vs. non-explicit-one argument ctor
當轉換函數與非顯式聲明的一個參數的構造函數同時存在時,就會出現歧義。因為兩種方式都可以編譯。
多于一條路徑可以編譯,就會出現歧義,編譯器就會報錯。
[Error] ambiguous
2.2.3 explicit-one-argument ctor
顯式聲明就可以避免這個問題。
explicit Fraction(int num, int den=1):……
此時就會調用構造函數,而不會調用 double 轉換函數。
3. 模仿的類
3.1 pointer-like classes
設計一個類,模擬 pointer
3.1.1 關于智能指針
->
用掉后,還有一個->
,所以
sp->method();
px->method(); //轉換完依舊有->
3.1.2 關于迭代器
注意操作符重載
++
--
適用于指針移動return (*node).data; //取的是node 指向的塊的數據
return &(operator()); //返回迭代器中內容的指針,而不是指向迭代器塊的 node 的指針。
3.2 function-like classes,所謂仿函數
unary_function 一個操作數
binary_function 兩個操作數
4. namespace 經驗談
namespace jj01
{
}//namespace
5. 模板 template
5.1 class template
template<typename T>
5.2. Function Template
template <class T>
5.3 Member Template
template <class T1, class T2>
struct pair {
……
template <class U1, class U2>
……
}
Base1* ptr = new Derived1; //up-cast
shared_ptr<Base1>sptr(new Derived1); // 模擬 up-cast
5.4 specialization,模板特化
template<class Key>
struct hash { } ;
template<>
struct hash<char> { };
泛化對應特化,共性中的個性,用來應對特例。
5.4.1 partial specialization,模板偏特化
又稱為局部特化
5.4.1.1 個數的偏
< >
尖括號內叫做模板參數
class vector<bool, Alloc> { }; // Alloc沒改變,而 bool 設定了。
一定要從左到右,不能跳,不能135固定,24改變。
5.4.1.2 范圍的偏
指針指向的偏
class c<T*> { }; // 限定為指針
如果 T 是指針,使用偏特化模板。
5.5 template template parameter,模板模板參數
5.5.1 容器需要參數
using Lst = list<T, allocator<>>;
模板需要好幾個參數,必須替換
5.5.2 智能指針
對應 SmartPtr,可以是……,不可以是……
5.5.3 這不是模板模板參數
已經綁定了,必須是這個,所以就沒有模糊地帶了。
6. 關于 C++ 標準庫
所有的容易算法都要用一遍。
編譯器需要設定到 C++ 11
6.1 variadic templates (since C++11)
6.2 auto (since C++11)
用 auto 時一定要讓編譯器能推出來。
auto ite = find(……)
find 的類型就是 auto 給 ite 的類型。
auto 不能亂用,太長了、寫不出來可以用。
我們一定要知道每個變量的定義是什么。
ranged-bas for (since C++11)
for ( decl : coll ) //左邊是一個變量,右邊必須是一個collector,容器
for ( int i : { 2, 3, 5, 7 } ){ }
vector<double> vec;
……
for ( auto elem : vec ) { cout << elem << endl ;} //傳值
for ( auto& elem : vec ) { elem *= 3; } //傳引用,盡量傳引用
15. reference(引用)
聲明時一定要有初值,設完之后就不能再變了。
object 和其 reference 的大小相同,地址也相同(全都是假象)
???那么新建一個 reference 會讓程序新占用多少內存呢?
常見用途
函數傳參
void func3(Cls& obj) { obj.xxx(); }
func3(obj);
reference 通常不用于聲明變量,而用于參數類型和返回類型的描述。
相同聲明結構,僅僅傳遞引用和變量,會導致模糊,簽名相同,所以不能同時存在。
是否加const,
???有什么區別?
加 const 會改變簽名,是可以定義的。
7. 復合&繼承關系下的構造和析構
7.1 Composition(復合)關系下的構造和析構
Container 在外 Component 在內
7.2 Inheritance(繼承)關系下的構造和析構
Derive在外,Base 在內
由內而外構建
由外而內析構
子類析構函數會自動調用父類析構函數
Inheritance + Composition 關系下的構造和析構
不同編譯器可能不同
觀察到的結論是先調用 Base,后調用 Component