課件內(nèi)容:
part1 C++模板介紹
part2 泛型編程
part3 容器
part4 一些進(jìn)階問題
C++主要有兩種類型的模板
1.類模板(Class template):使用泛型參數(shù)的類
2.函數(shù)模板(Function template):使用泛型參數(shù)的函數(shù)
模板實例化:顯示和隱式兩種方式
類模板實參可以是某一型別或常量(僅限int或enum)
C++類模板的聲明注意事項:
1.聲明類模板和聲明函數(shù)模板類似
2.關(guān)鍵字class和typename都可以用涂屁,但還是傾向于使用typename
templateclass Stack{......};
template class Stack{......};
3.在類模板內(nèi)部搞乏,T可以像其他型別一樣(比如int,char等)定義成員變量和成員函數(shù)
void Push(const T const& element);
int Pop(T& element);
int Top(T& element) const;
std::vector m_Members;
泛型編程(Generic programming)
1.概觀(OverView):泛型編程作為一種編程思維和想法柔昼,它是一種編程方法蜒茄,不依賴于具體的語言兄墅。
2.關(guān)聯(lián)特性(Traits)
3.迭代器(Iterators):迭代器是指針的泛化(generalization of pointers)
3.1迭代器本身是一個對象,指向另外一個(可以被迭代的)對象芋膘。
3.2用來迭代一組對象祭钉,即如果迭代器指向一組對象中的某個元素,則通過increment以后它就可以指向下一組對象中的一個元素它改。
迭代器(1)
迭代器是指針的泛化(generalization of pointers)
迭代器本身是一個對象疤孕,指向另外一個(可以被迭代的)對象
用來迭代一組對象,即如果迭代器指向一組對象中的某個元素央拖,則通過increment以后它就可以指向這組對象的下一個元素
在STL中迭代器是容器與算法之間的接口
算法通常以迭代器作為輸入?yún)?shù)
容器只要提供一種方式祭阀,可以讓迭代器訪問容器中的元素即可。
迭代器(2)
迭代器的基本思想
1)在STL中鲜戒,迭代器最重要的思想就是分離算法和容器专控,使之不需要相互依賴
2)迭代器將算法和容器粘合(stick)在一起從而使得一種算法的實現(xiàn)可以運用到多種不同的容器上,如下面的例子所示遏餐,find算法接收一對迭代器伦腐,分別指向容器的開始和終止位置:
templalte
inline _InIt find(_InIt _First,_InIt _Last,const _Ty& _Val){
//find frist matching _Val
for(;_First != _Last;++_First)
if(*_First == _Val)
break;
return (_First);
}
Vector
1.Vector是一個能夠存放任意型別的動態(tài)數(shù)組
2.Vector的數(shù)據(jù)結(jié)構(gòu)和操作于數(shù)組類似,在內(nèi)存中的表示是一段地址連續(xù)的空間失都。
3.Vector與數(shù)組的區(qū)別在于柏蘑,數(shù)組大小往往是定義的時候已經(jīng)指定,而Vector支持動態(tài)空間大小調(diào)整粹庞,隨著元素的增加咳焚,Vector內(nèi)部會自動擴(kuò)充內(nèi)存空間。
4.為了使用Vector,必須用include指令包含如下文件庞溜,并通過std命名空間去訪問:
include
int main(){
std::vector v;
}
創(chuàng)建Vector
向Vector中添加元素
判斷Vector是否為空
訪問Vector里面的元素:
vector::at() //會進(jìn)行邊界檢查革半,判斷是否越界
vector::operator[] //不進(jìn)行邊界檢查,性能較好
刪除元素:
Deque是一個能夠存放任意型別的雙向隊列
Deque提供的函數(shù)于Vector類似强缘,新增了兩個函數(shù):
push_front:在頭部插入一個元素
pop_front:在頭部彈出一個元素
Deque采用了于Vector不同內(nèi)存管理方法:大塊分配內(nèi)存
為了使用deque督惰,必須用include指令包含如下文件不傅,并通過std命名空間去訪問:
include
int main(){
std::deque dq;
}
List的優(yōu)勢和劣勢
[圖片上傳中旅掂。。访娶。(7)]
常用的操作方法:
作者:易班熊鏈接:http://www.reibang.com/p/fa296bf91369來源:簡書著作權(quán)歸作者所有商虐。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。課件內(nèi)容:
part1 C++模板介紹
part2 泛型編程
part3 容器
part4 一些進(jìn)階問題
C++主要有兩種類型的模板
1.類模板(Class template):使用泛型參數(shù)的類
2.函數(shù)模板(Function template):使用泛型參數(shù)的函數(shù)
模板實例化:顯示和隱式兩種方式
類模板實參可以是某一型別或常量(僅限int或enum)
C++類模板的聲明注意事項:
1.聲明類模板和聲明函數(shù)模板類似
2.關(guān)鍵字class和typename都可以用秘车,但還是傾向于使用typename
templateclass Stack{......};
template class Stack{......};
3.在類模板內(nèi)部典勇,T可以像其他型別一樣(比如int,char等)定義成員變量和成員函數(shù)
void Push(const T const& element);
int Pop(T& element);
int Top(T& element) const;
std::vector m_Members;
泛型編程(Generic programming)
1.概觀(OverView):泛型編程作為一種編程思維和想法,它是一種編程方法叮趴,不依賴于具體的語言割笙。
2.關(guān)聯(lián)特性(Traits)
3.迭代器(Iterators):迭代器是指針的泛化(generalization of pointers)
3.1迭代器本身是一個對象,指向另外一個(可以被迭代的)對象眯亦。
3.2用來迭代一組對象伤溉,即如果迭代器指向一組對象中的某個元素,則通過increment以后它就可以指向下一組對象中的一個元素妻率。
迭代器(1)
迭代器是指針的泛化(generalization of pointers)
迭代器本身是一個對象乱顾,指向另外一個(可以被迭代的)對象
用來迭代一組對象,即如果迭代器指向一組對象中的某個元素宫静,則通過increment以后它就可以指向這組對象的下一個元素
在STL中迭代器是容器與算法之間的接口
算法通常以迭代器作為輸入?yún)?shù)
容器只要提供一種方式走净,可以讓迭代器訪問容器中的元素即可。
迭代器(2)
迭代器的基本思想
1)在STL中孤里,迭代器最重要的思想就是分離算法和容器伏伯,使之不需要相互依賴
2)迭代器將算法和容器粘合(stick)在一起從而使得一種算法的實現(xiàn)可以運用到多種不同的容器上,如下面的例子所示捌袜,find算法接收一對迭代器舵鳞,分別指向容器的開始和終止位置:
templalte
inline _InIt find(_InIt _First,_InIt _Last,const _Ty& _Val){
//find frist matching _Val
for(;_First != _Last;++_First)
if(*_First == _Val)
break;
return (_First);
}
Vector
1.Vector是一個能夠存放任意型別的動態(tài)數(shù)組
2.Vector的數(shù)據(jù)結(jié)構(gòu)和操作于數(shù)組類似,在內(nèi)存中的表示是一段地址連續(xù)的空間琢蛤。
3.Vector與數(shù)組的區(qū)別在于蜓堕,數(shù)組大小往往是定義的時候已經(jīng)指定,而Vector支持動態(tài)空間大小調(diào)整博其,隨著元素的增加套才,Vector內(nèi)部會自動擴(kuò)充內(nèi)存空間。
4.為了使用Vector,必須用include指令包含如下文件慕淡,并通過std命名空間去訪問:
include
int main(){
std::vector v;
}
創(chuàng)建Vector
向Vector中添加元素
判斷Vector是否為空
訪問Vector里面的元素:
vector::at() //會進(jìn)行邊界檢查背伴,判斷是否越界
vector::operator[] //不進(jìn)行邊界檢查,性能較好
刪除元素:
Deque是一個能夠存放任意型別的雙向隊列
Deque提供的函數(shù)于Vector類似峰髓,新增了兩個函數(shù):
push_front:在頭部插入一個元素
pop_front:在頭部彈出一個元素
Deque采用了于Vector不同內(nèi)存管理方法:大塊分配內(nèi)存
為了使用deque傻寂,必須用include指令包含如下文件,并通過std命名空間去訪問:
include
int main(){
std::deque dq;
}
List的優(yōu)勢和劣勢
常用的操作方法:
[圖片上傳中携兵。疾掰。。(8)]
作者:易班熊鏈接:http://www.reibang.com/p/fa296bf91369來源:簡書著作權(quán)歸作者所有徐紧。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán)静檬,非商業(yè)轉(zhuǎn)載請注明出處炭懊。課堂大綱
這周的課程大致上應(yīng)該是這三個部分
- object model部分
- const部分
- new與delete部分
每個部分都講了一些細(xì)節(jié)
對象模型(Object Model):
- 對象模型(Object Model):關(guān)于vptr和vtbl
在C++中,每個對象實例有不同的vtbl(虛函數(shù)表)用圖的方式表達(dá)如下圖拂檩;
我對其理解是侮腹, 當(dāng)類中有虛函數(shù)的時候,編譯器會為類插入一個我們看不見的數(shù)據(jù)并建立一個表稻励。這個表就是虛函數(shù)表(vtbl)父阻,那個我們看不見的數(shù)據(jù)就是指向虛函數(shù)表的指針——虛表指針(vptr)。虛函數(shù)表就是為了保存類中的虛函數(shù)的地址望抽。我們可以把虛函數(shù)表理解成一個數(shù)組至非,數(shù)組中的每個元素存放的就是類中虛函數(shù)的地址。當(dāng)調(diào)用虛函數(shù)的時候糠聪,程序不是像普通函數(shù)那樣直接跳到函數(shù)的代碼處荒椭,而是先取出vptr即得到虛函數(shù)表的地址,根據(jù)這個來到虛函數(shù)表里舰蟆,從這個表里取出該函數(shù)的地址趣惠,最后調(diào)用該函數(shù)。所以只要不同類的vptr不同身害,他對應(yīng)的vtbl就不同味悄,不同的vtbl裝著對應(yīng)類的虛函數(shù)地址,這樣虛函數(shù)就可以完成它的任務(wù)了塌鸯。
但是侍瑟,那如果A有兩個virtual func B繼承并重寫了這兩個virtual func 而C只重寫了一個virtual fuc 那么當(dāng)調(diào)用的時候 c會調(diào)用A的另外一個virtual function還是調(diào)用B重寫的那一個呢?看一下下面的測試代碼
#includeusing namespace std;
class A{
public:
int a;
public:
virtual void vfun1(){
cout << " A::vfun1! " << endl;
}
virtual void vfun2(){
cout << " A::vfun2! " << endl;
}
};
class B: public A{
public:
int b;
public:
virtual void vfun1(){
cout << " B::vfun1! " << endl;
}
virtual void vfun2(){
cout << " B::vfun2! " << endl;
}
};
class C:public B{
public:
int c;
public:
virtual void vfun2(){
cout << " C::vfun2! " << endl;
}
};
class D: public C{
public:
int d;
};
int main(int argc, const char * argv[]) {
C* c = new C;
c->vfun1();
D* d = new D;
d->vfun1();
d->vfun2();
return 0;
}
運行結(jié)果為:
B::vfun1!
B::vfun1!
C::vfun2!
Program ended with exit code: 0
那結(jié)論就是:只要重寫virtual function 子類就會繼承最新重寫的那一個 而非一開始的基類丙猬。
- 對象模型(Object Model):關(guān)于this
關(guān)于this指針涨颜,侯老師化的那張圖給我們展示了this指針的作用,想起以前在知乎上看過的一個例子形容指針茧球,class類好比一間屋子庭瑰,類成員比作屋里的東西,而this指針就好比開房子的鑰匙抢埋,通過他就可以進(jìn)到屋子弹灭,可以拿屋內(nèi)的各種東西了。
用專業(yè)術(shù)語來說揪垄,this是指向?qū)嵗瘜ο蟊旧頃r候的一個指針穷吮,里面存儲的是對象本身的地址,通過該地址可以訪問內(nèi)部的成員函數(shù)和成員變量饥努。
看一下下面的demo代碼:
class Point
{
int x, y;
public:
Point(int a, int b) { x=a; y=b;}
Void MovePoint( int a, int b){ x+=a; y+=b;}
Void print(){ cout<<"x="< };
void main( )
{
Point point1( 10,10);
point1.MovePoint(2,2);
point1.print( );
}
a. 對象point1調(diào)用MovePoint(2,2)的時候捡鱼,即將point1對象的地址傳遞給了this指針
b. 編譯器編譯后的原型應(yīng)該是void MovePoint(Point *this,int a, int b)
c. 在函數(shù)體中可以寫成{this->x += a; this->y+= b;}
d. 也等價為point1.x += a;point1.y += b肪凛。(指針變量通過->訪問成員變量(函數(shù))堰汉,對象通過.)
- 對象模型(Object Model):關(guān)于Dynamic Binding
關(guān)于C++對象模型中的動態(tài)綁定辽社,在看了一些相關(guān)資料可認(rèn)為編譯器用靜態(tài)分析的方法加上虛擬函數(shù)的設(shè)計實現(xiàn)在程序運行時動態(tài)智能執(zhí)行正確虛擬函數(shù)的技術(shù)伟墙。
在很多資料上都是通過下列demo來理解的
#include <iostream>
using namespace std;
class A
{
public:
void fA() { cout << "A::fA()" << endl; }
virtual void vfA() { cout << "A::vfA()" << endl; }
void emptyB() { cout << "A::emptyB()" << endl; }
void vfAonly() { cout << "A::vfAonly()" << endl; }
};
class B : public A
{
public:
void fB() { cout << "B::fB()" << endl; }
virtual void vfA() { cout << "B::vfA()" << endl; }
virtual void vfB() { cout << "B::vfB()" << endl; }
void emptyA() { cout << "B::emptyA()" << endl; }
virtual void vfAonly() { cout << "B::vfAonly()" << endl; }
};
int main()
{
A* p = new B;
B& r = *(B*)p;
p->fA(); // 1
//p->fB(); // 2
p->vfA(); // 3
//p->vfB(); // 4
//p->emptyA(); // 5
p->emptyB(); // 6
p->vfAonly(); // 7
cout << endl;
r.fA(); // 8
r.fB(); // 9
r.vfA(); // 10
r.vfB(); // 11
r.emptyA(); // 12
r.emptyB(); // 13
r.vfAonly(); // 14
delete p;
return 0;
}
輸出結(jié)果如下:
A::fA()
B::vfA()
A::emptyB()
A::vfAonly()
A::fA()
B::fB()
B::vfA()
B::vfB()
B::emptyA()
A::emptyB()
B::vfAonly()
談?wù)刢onst
這個在C語言中就是常用的翘鸭,例如申明常量成員變量函數(shù),想重點總結(jié)了一下const在函數(shù)中的使用戳葵,這部分我以前經(jīng)常出錯就乓。
- const修飾函數(shù)參數(shù)
a. 傳遞過來的參數(shù)在函數(shù)內(nèi)不可以改變(無意義,因為Var本身就是形參)
void function(const int Var);
b. 參數(shù)指針?biāo)竷?nèi)容為常量不可變
void function(const char* Var);
c. 參數(shù)指針本身為常量不可變(也無意義拱烁,因為char* Var也是形參)
void function(char* const Var);
d. 參數(shù)為引用生蚁,為了增加效率同時防止修改。修飾引用參數(shù)時:
void function(const Class& Var); //引用參數(shù)在函數(shù)內(nèi)不可以改變
void function(const TYPE& Var); //引用參數(shù)在函數(shù)內(nèi)為常量不可變
這樣的一個const引用傳遞和最普通的函數(shù)按值傳遞的效果是一模一樣的,他禁止對引用的對象的一切修改,唯一不同的是按值傳遞會先建立一個類對象的副本,然后傳遞過去,而它直接傳遞地址,所以這種傳遞比按值傳遞更有效.另外只有引用的const傳遞可以傳遞一個臨時對象,因為臨時對象都是const屬性, 且是不可見的,他短時間存在一個局部域中,所以不能使用指針,只有引用的const傳遞能夠捕捉到這個家伙.
- const 修飾函數(shù)返回值
const修飾函數(shù)返回值其實用的并不是很多戏自,它的含義和const修飾普通變量以及指針的含義基本相同邦投。
a.
const int fun1() //這個其實無意義,因為參數(shù)返回本身就是賦值擅笔。
b.
const int * fun2() //調(diào)用時 const int *pValue = fun2(); //我們可以把fun2()看作成一個變量志衣,即指針內(nèi)容不可變。
c.
int* const fun3() //調(diào)用時 int *const pValue = fun2(); //我們可以把fun2()看作成一個變量猛们,即指針本身不可變念脯。
一般情況下,函數(shù)的返回值為某個對象時弯淘,如果將其聲明為const時绿店,多用于操作符的重載。通常庐橙,不建議用const修飾函數(shù)的返回值類型為某個對象或?qū)δ硞€對象引用的情況假勿。
關(guān)于new,delete和重載
new和delete也是常用的,新申明的數(shù)組需要使用內(nèi)存時态鳖,需要new一段size大小的空間废登,當(dāng)使用結(jié)束時delete掉,即釋放內(nèi)存郁惜,他倆是配合著使用的堡距,不能忘寫其中一個。
- new的六種重載形式:
全局的new有六種重載形式,
void *operator new(std::size_t count)
throw(std::bad_alloc); //一般的版本
void *operator new(std::size_t count, //兼容早版本的new
const std::nothrow_t&) throw(); //內(nèi)存分配失敗不會拋出異常
void *operator new(std::size_t count, void *ptr) throw();
//placement版本
void *operator new[](std::size_t count) //
throw(std::bad_alloc);
void *operator new[](std::size_t count, //
const std::nothrow_t&) throw();
void *operator new[](std::size_t count, void *ptr) throw();
所以, 剛才的用法, 就是使用new函數(shù)的一種重載形式.
如果A這個對象以同樣實行重載了new函數(shù)的化, 作為成員函數(shù)
會被優(yōu)先調(diào)用.
C++的各種new簡介
- new T
第一種new最簡單,調(diào)用類的(如果重載了的話)或者全局的operator new分配空間,然后用
類型后面列的參數(shù)來調(diào)用構(gòu)造函數(shù),用法是
new TypeName(initial_args_list). 如果沒有參數(shù),括號一般可以省略.例如
int *p=new int;
int *p=new int(10);
int *p=new foo("hello");
通過調(diào)用delete來銷毀:
delete p;
- new T[]
這種new用來創(chuàng)建一個動態(tài)的對象數(shù)組,他會調(diào)用對象的operator new[]來分配內(nèi)存(如果
沒有則調(diào)用operator new,搜索順序同上),然后調(diào)用對象的默認(rèn)構(gòu)造函數(shù)初始化每個對象
用法:
new TypeName[num_of_objects];
例如
int *p= new int[10];
銷毀時使用
operator delete[]
- new()T 和new() T[]
這是個帶參數(shù)的new,這種形式的new會調(diào)用operator new(size_t,OtherType)來分配內(nèi)存兆蕉。
這里的OtherType要和new括號里的參數(shù)的類型兼容, 這種語法通常用來在某個特定的地址構(gòu)件對象,稱為placement new,前提是operator new (size_t,void*)已經(jīng)定義, 通常編譯器已經(jīng)提供了一個實現(xiàn), 包含頭文件即可, 這個實現(xiàn)只是簡單的把參數(shù)的指定的地址返回,因而new()運算符就會在括號里的地址上創(chuàng)建對象羽戒。
需要說明的是,第二個參數(shù)不是一定要是void*,可以識別的合法類型,這時候由C++的重載機(jī)制來決定調(diào)用那個operator new。 當(dāng)然,我們可以提供自己的operator new(size_,Type),來決定new的行為,比如
char data[1000][sizeof(foo)];
inline void* operator new(size_t ,int n)
{
return data[n];
}
就可以使用這樣有趣的語法來創(chuàng)建對象:
foo *p=new(6) foo(); //把對象創(chuàng)建在data的第六個單元上
的確很有意思
標(biāo)準(zhǔn)庫還提供了一個nothrow的實現(xiàn):
void* operator new(std::size_t, const std::nothrow_t&) throw();
void* operator new[](std::size_t, const std::nothrow_t&) throw();
就可以實現(xiàn)調(diào)用new失敗時不拋出異常
new(nothrow) int(10);
// nothrow 是std::nothrow_t的一個實例
placement new 創(chuàng)建的對象不能直接delete來銷毀, 而是要調(diào)用對象的析夠函數(shù)來銷毀對象, 至于對象所占的內(nèi)存如何處理,要看這塊內(nèi)存的具體來源虎韵。
- operator new(size_t)
這個的運算符分配參數(shù)指定大小的內(nèi)存并返回首地址,可以為自定義的類重載這個運算符,
方法就是在類里面聲明加上
void *operator new(size_t size)
{
//在這里分配內(nèi)存并返回其地址
}
無論是否聲明,類里面重載的各種operator new和operator delete都是具有static屬性的
一般不需要直接調(diào)用operator new,除非直接分配原始內(nèi)存(這一點類似于C的malloc)
在沖突的情況下要調(diào)用全局的operator加上::作用域運算符:
::operator new(1000); // 分配1000個字節(jié)
返回的內(nèi)存需要回收的話,調(diào)用對應(yīng)的operator delete
這個也是分配內(nèi)存, 只不過是專門針對數(shù)組, 也就是new T[]這種形式, 當(dāng)然, 需要時可以顯式調(diào)用
- operator new(size_t size, OtherType other_value)
和operator new[](size_t size, OtherType other_value)
參見上面的new()
需要強(qiáng)調(diào)的是,new用來創(chuàng)建對象并分配內(nèi)存,它的行為是不可改變的,可以改變的是各種
operator new,我們就可以通過重載operator new來實現(xiàn)我們的內(nèi)存分配方案.
題目說明:
為上周題目中的 Fruit和Apple 添加 構(gòu)造函數(shù)與 析構(gòu)函數(shù)易稠,并在構(gòu)造函數(shù)與析構(gòu)函數(shù)中打印控制臺信息,觀察構(gòu)造和析構(gòu)調(diào)用過程包蓝。然后為Apple類重載::operator new和::operator delete驶社,在控制臺打印信息企量,并觀察調(diào)用結(jié)果。