第一節(jié) 導(dǎo)讀
知識清單:
首先來列一張清單,清點(diǎn)一下后面課程將會深入的細(xì)節(jié):
- operator type()const;
- explicit complex(...):initialization list{}
- pointer-like object
- function-like object
- Namespace
- template specialization
- Standard Library
- variadic template(since C++11)
- move ctor(since C++11)
- Rvalue reference(since C++11)
- auto(since C++11)
- lambda(since C++11)
- range-base for loop(since C++11)
- unordered containers(cince C++11)
目標(biāo):
- 在先前的基礎(chǔ)課程所培養(yǎng)的正規(guī)蛤奢、大氣的編程素養(yǎng)上俭驮,繼續(xù)探討更多技術(shù)。
- 泛型編程(Generic Programming)和面向?qū)ο缶幊蹋∣bject-Oriented Programming)雖然分屬不同思維赡若,但它們正是C++的技術(shù)主線达布,所以本課程也討論template(模板)。
- 深入探索面向?qū)ο笾^承關(guān)系(inheritance)所形成的對象模型(Object Model)逾冬,包括隱藏于底層的this指針黍聂,vptr(虛指針),vtbl(虛表)身腻,virtual mechanism(虛機(jī)制)产还,以及虛函數(shù)(virtual functions)造成的polymorphism(多態(tài))效果。
推薦書目
- 《C++ Primer》
- 《The C++ Programming Language》
- 《Effective Modern C++》
- 《Effective C++》
- 《The C++ Standard Library》
- 《STL源碼剖析》
第二嘀趟、三節(jié) non-explicit one argument constructor & Conversion Function(轉(zhuǎn)換構(gòu)造與類型轉(zhuǎn)換函數(shù))
為了方便對照學(xué)習(xí)脐区,記憶,決定把二三節(jié)內(nèi)容放在一起講解她按。
轉(zhuǎn)換構(gòu)造函數(shù)
定義
在CPP中牛隅,類的構(gòu)造函數(shù)可以省略不寫,這時CPP會為它自動創(chuàng)建一個隱式默認(rèn)構(gòu)造函數(shù)(implicit default constructor)酌泰;也可以由用戶定義帶參數(shù)的構(gòu)造函數(shù)媒佣,構(gòu)造函數(shù)也是一個成員函數(shù),他可以被重載陵刹;當(dāng)一個構(gòu)造函數(shù)只有一個參數(shù)默伍,而且該參數(shù)又不是本類的const引用時,這種構(gòu)造函數(shù)稱為轉(zhuǎn)換構(gòu)造函數(shù)(non-explicit ont argument constructor)衰琐。(該段引自百度百科)
class Complex
{
private:
double real,imag; //復(fù)數(shù)的實(shí)部和虛部
public:
Complex(double x)
{
real=x;
imag=0;
}
//與下方等價(jià)
/*
Complex(double x,double y=0):real(x),imag(y)
{
}
*/
};
這個構(gòu)造函數(shù)即 轉(zhuǎn)換構(gòu)造函數(shù)也糊。
如上文。構(gòu)造函數(shù)只有一個參數(shù) double x羡宙,它也不是本類的const引用狸剃。
應(yīng)用
通過轉(zhuǎn)換構(gòu)造函數(shù)可以將一個指定類型的數(shù)據(jù)轉(zhuǎn)換為類的對象。
1.用于定義
轉(zhuǎn)換構(gòu)造函數(shù)一般由系統(tǒng)自動調(diào)用(當(dāng)然代碼里自己調(diào)用完全沒問題)狗热,這點(diǎn)很利于編程捕捂。
例如:
- Complex t=5.0;
- Complex t(5.0);
- Complex t=Complex(5.0);
- Complex t=(Complex)5.0;
這時系統(tǒng)就自動調(diào)用了 Complex(double x)將 5.0轉(zhuǎn)換成Complex類瑟枫,再賦值給t。
2.用于計(jì)算
通常來講指攒,轉(zhuǎn)換構(gòu)造函數(shù)更多搭配運(yùn)算符重載用來計(jì)算慷妙。
class Complex
{
public:
Complex(double x,double y=0)//轉(zhuǎn)換構(gòu)造
:real(x),imag(y){}
Complex operator+(const Complex& f)//操作符重載
{
return Complex(......);
}
private:
double real;
double imag;
};
Complex t=5.0;
Complex b=t + 4.8;
編譯器會隱式調(diào)用轉(zhuǎn)換構(gòu)造函數(shù)將5.0轉(zhuǎn)換為Complex成員并賦值給t。第二步同理允悦,將4.8轉(zhuǎn)換成Complex成員后調(diào)用'+'的重載函數(shù)完成計(jì)算膝擂。
類型轉(zhuǎn)換函數(shù)
通過轉(zhuǎn)換構(gòu)造函數(shù)可以將一個指定類型的數(shù)據(jù)轉(zhuǎn)換為類的對象。但是不能反過來將一個類的對象轉(zhuǎn)換為一個其他類型的數(shù)據(jù)(例如將一個Complex類對象轉(zhuǎn)換成double類型數(shù)據(jù))隙弛。
C++提供類型轉(zhuǎn)換函數(shù)(type conversion function)來解決這個問題架馋。類型轉(zhuǎn)換函數(shù)的作用是將一個類的對象轉(zhuǎn)換成另一類型的數(shù)據(jù)。如果已聲明了一個Complex類全闷,可以在Complex類中這樣定義類型轉(zhuǎn)換函數(shù):
operator double() const//類型轉(zhuǎn)換函數(shù)
{
return real;
}
從函數(shù)結(jié)構(gòu)來看叉寂,與重載函數(shù)類似,都需要關(guān)鍵字operator总珠,只不過這里的轉(zhuǎn)換的是類型而已屏鳍,double在Complex類中經(jīng)過重載后,Complex就被賦予了一種新的含義局服,既可以當(dāng)做Complex類型本身使用钓瞭,也可以當(dāng)做double類型來使用。
我們來舉一個簡單的例子:
class Complex
{
public:
Complex():real(0),imag(0)
{}
Complex(double x,double y):real(x),imag(y)
{}
operator double() const
{
return real;
}
private:
double real;
double imag淫奔;
};
Complex t(5,0);
Complex b=t + 4.8;
此時我們的b=t+4.8運(yùn)算有了另一種解法山涡,即將Complex對象t通過隱式調(diào)用類型轉(zhuǎn)換函數(shù)轉(zhuǎn)換為double對象完成計(jì)算。
小結(jié):
- 轉(zhuǎn)換構(gòu)造函數(shù)可以將一個指定類型的數(shù)據(jù)轉(zhuǎn)換為類的對象唆迁。
- 類型轉(zhuǎn)換函數(shù)可以將一個類的對象轉(zhuǎn)換為一個其他類型的數(shù)據(jù)鸭丛。
我們了解了轉(zhuǎn)換構(gòu)造函數(shù)與類型轉(zhuǎn)換函數(shù)可以為Complex b=t+4.8這樣的運(yùn)算提供兩個不同角度的解法,那么如果Complex類同時擁有了兩種函數(shù)唐责,又會怎樣呢系吩?
class Complex
{
public:
Complex():real(0),imag(0)
{}
Complex(double x,double y=0)//轉(zhuǎn)換構(gòu)造
:real(x),imag(y){}
Complex(double x,double y):real(x),imag(y)
{}
Complex operator+(const Complex& f)//操作符重載
{
return Complex(......);
}
operator double() const//
{
return real;
}
private:
double real;
double imag;
};
Complex t(5,0);
Complex b=t + 4.8;
編譯器會提示ambiguous(歧義)妒蔚,即有多重解。當(dāng)編譯器可選擇的方案不止一種月弛,會出現(xiàn)這種提示肴盏。在案例中,編譯器既可以通過轉(zhuǎn)換構(gòu)造函數(shù)將4.8轉(zhuǎn)換為Complex對象帽衙,與可以通過類型轉(zhuǎn)換函數(shù)將t轉(zhuǎn)換為double對象完成計(jì)算菜皂。
但是在實(shí)際使用的過程中,難免會遇到這種情況厉萝,好在CPP為我們提供解決的辦法:explicit恍飘。
explicit關(guān)鍵字多用在轉(zhuǎn)換構(gòu)造函數(shù)之前榨崩,其作用是指定該構(gòu)造函數(shù)只能被顯示調(diào)用(即創(chuàng)建實(shí)例時的調(diào)用,如Complex a(5,0))章母,而不可以再被隱式調(diào)用母蛛,這樣就解決了程序的ambiguous問題。
第四節(jié) pointer-like classes(關(guān)于智能指針)
我們知道智能指針能夠比原生指針做更多事情乳怎,例如處理線程安全彩郊,提供寫時復(fù)制,確保協(xié)議蚪缀,并且提供遠(yuǎn)程交互服務(wù)等等等等許許多多強(qiáng)大的功能秫逝。但其實(shí)不論是多牛的智能指針,在它的內(nèi)部一定至少有一個原生指針在工作⊙叮現(xiàn)在就我們從語法的角度來初窺智能指針违帆。
以shared_ptr為例:
template<class T>
class shared_ptr
{
public:
T& operator*() const//重載*
{return *px;}
T* operator->() const//重載->
{return px;}
shared_ptr(T* p):px(p){}
private:
T* px;
long* pn;
}
struct Foo
{
void method(){}
};
shared_ptr<Foo> sp(new Foo);
Foo f(*sp);
sp->method();
//px->method();
解析:
智能指針的本質(zhì),其實(shí)就是把一個原生指針包裝在類中金蜀,再向類中寫進(jìn)各種對指針操作符的重載刷后,使用戶在使用類時可以完全按照指針的語法去使用。這樣做的優(yōu)勢很明顯廉油,我們可以根據(jù)自己的需求向類中寫入各種功能惠险,相當(dāng)于“組裝一個無所不能的指針”。
語法其實(shí)不難理解抒线,只是重載一下指針的操作符"*"與"->"班巩,但是其中有一個小細(xì)節(jié)很容易被忽略,及時是有多年經(jīng)驗(yàn)的工程師也未必能解釋清楚嘶炭,在這里再次感謝侯老師抱慌。我舉個例子:
我們已經(jīng)對操作符“*”和“->”進(jìn)行了重載,當(dāng)編譯器在執(zhí)行*sp時眨猎,返回值是*px抑进,這很好理解,可是在執(zhí)行sp->時睡陪,返回值是sp寺渗,為什么能起到和sp->一樣的效果呢?
原來兰迫,CPP為了支持這種做法信殊,在這里進(jìn)行了特殊的處理,使得->可以無限次的使用汁果,即在sp之后自動補(bǔ)齊->涡拘。(注:只有在這種情況下)
看完了shared_ptr,我們再來看看迭代器据德。
迭代器(iterator)是一種對象鳄乏,它能夠用來遍歷標(biāo)準(zhǔn)模板庫容器中的部分或全部元素跷车,每個迭代器對象代表容器中的確定的地址。迭代器修改了常規(guī)指針的接口橱野,所謂迭代器是一種概念上的抽象:那些行為上像迭代器的東西都可以叫做迭代器朽缴。然而迭代器有很多不同的能力,它可以把抽象容器和通用算法有機(jī)的統(tǒng)一起來仲吏。
迭代器提供一些基本操作符:*不铆、++、==裹唆、!=誓斥、=。這些操作和C/C++“操作array元素”時的指針接口一致许帐。不同之處在于劳坑,迭代器是個所謂的復(fù)雜的指針,具有遍歷復(fù)雜數(shù)據(jù)結(jié)構(gòu)的能力成畦。其下層運(yùn)行機(jī)制取決于其所遍歷的數(shù)據(jù)結(jié)構(gòu)距芬。因此,每一種容器型都必須提供自己的迭代器循帐。事實(shí)上每一種容器都將其迭代器以嵌套的方式定義于內(nèi)部框仔。因此各種迭代器的接口相同,型號卻不同拄养。這直接導(dǎo)出了泛型程序設(shè)計(jì)的概念:所有操作行為都使用相同接口离斩,雖然它們的型別不同。(以上定義摘自百度百科)
下面我們來看一下迭代器的實(shí)現(xiàn):
template<class T>
struct __list_node
{
void* prev;
void* next;
T data;
}
template<class T,class Ref,class Ptr>
struct __list_iterator
{
typedef __list_iterator<T,Ref,Ptr> self;
typedef Ptr pointer;
typedef Ref reference;
typeder __list_node<T>* link_type;
link_type node;
bool operator==(const self& x)const{return node==x.node;}
bool operator!=(const self& x)const{return node!=x.node;}
reference operator*()const{return (*node).data;}
pointer operator->()const{return &(operator*());}
self& operator++(){node=(link_type)((*node).next);return *this;}
self operator++(int){self tmp=*this;++*this;return tmp;}
self& operator--(){node=(link_type)((*node).prev);return *this;}
self& operator--(int){self tmp=*this;--*this;return tmp;}
};
我們把其中隔開的兩個函數(shù)抽出瘪匿,簡單的分析一下跛梗。
從使用者的角度來講,只會通過右上角的方式來調(diào)用棋弥,然而實(shí)際的處理過程如左側(cè)所示:
- 當(dāng)執(zhí)行*ite時,會獲得(*node).data;其中*node為一個object核偿,data為其中的成員。
- 當(dāng)執(zhí)行ite->method()時顽染,會調(diào)用上方的operator*()獲得(*node).data,返回其地址漾岳。
這樣一來就完美的將原生指針node包裹在了迭代器__list_iterator中。
第五節(jié) function-like classes (仿函數(shù))
仿函數(shù)在標(biāo)準(zhǔn)庫中有著廣泛的應(yīng)用粉寞,這節(jié)課我們將從標(biāo)準(zhǔn)庫中抽取一個案例來探討仿函數(shù)的用法尼荆,對于為什么要讓一個類模仿函數(shù)行為,這節(jié)我們不做討論仁锯。
通常來講,如果一個東西可以接收小括號這種操作符我們就叫它函數(shù)翔悠,或者像函數(shù)的東西业崖。
上面是標(biāo)準(zhǔn)庫中的一段代碼(有省略)野芒。
select1st與select2st分別通過對()的重載提取pair對象的第一個元素和第二個元素。
圖片中灰色處省略了部分代碼双炕,展開如下:
再來看看標(biāo)準(zhǔn)庫中其它的仿函數(shù):
我們發(fā)現(xiàn)標(biāo)準(zhǔn)庫中的仿函數(shù)通常要繼承一些古怪的base山叮,下面是base的原型:
在這里我們不對base做任何討論总放,在后面有專門講解STL的課程會深入講解。
第六節(jié) namespace 經(jīng)驗(yàn)談
#include<iostream>
namespace lalala
{
int a=5;
}
namespace lalala1
{
int a=10;
}
int main()
{
std::cout<<lalala::a<<std::endl;
std::cout<<lalala1::a<<std::endl;
return 0;
}
很小的話題,給出一段示例代碼贺纲,相信有一定C++基礎(chǔ)的人都可以理解。
第七節(jié) class template
template<typename T>
class complex
{
public:
complex(T r=0,T i=0)
:re(r),im(i)
{}
complex& operator +=(const complex&);
T real () const { return re; }
T imag () const { return im; }
private:
T re,im;
friend complex& _doapl(complex*,const complex&);
};
complex<double> c1(2.5,1.5);
complex<int> c2(2,6);
解析:
template的基本使用方法企锌,不做贅述角寸。
在定義object時指明模板類型,傳入后與符號T綁定死相。
PS:在template<...>的尖括號中融求,class與typename是等價(jià)的。
第八節(jié) Function Template(函數(shù)模板)
class stone
{
public:
stone(int w,int h,int we)
:_w(w),_h(h),_weight(we)
{}
bool operator< (const stone& rhs) const
{ return _weight < rhs._weight; }
private:
int _w,_h,_weight;
}
template<class T>
inline
const T& min(const T& a,const T& b)
{
return b<a?b:a;
}
stone r1(2,3),r2(3,3),r3;
r3=min(r1,r2);
解析:
stone兩個object r1算撮,r2生宛,傳入min函數(shù)。min函數(shù)在接收參數(shù)后將參數(shù)類型與模板類型T綁定(實(shí)參引導(dǎo))肮柜,確認(rèn)類型后a,b進(jìn)行'<'操作陷舅,編譯器會進(jìn)入T類(stone)類內(nèi)尋找對應(yīng)的重載函數(shù)來執(zhí)行。
PS:該用法在“(GeekBand)C++面向?qū)ο蟾呒壘幊蹋ㄉ希┑诙芄P記(1)”中有過介紹审洞。