C++04模板與標準模板庫
阿新 • • 發佈:2020-12-19
目錄
一、模板 (template)
1.1、模板函式
1> 語法格式:
泛型程式設計
1. 函式不用確定其處理的資料型別
2. 把資料型別作為引數傳遞
3. 泛型程式設計的語法規則
template 的含義是開始泛型程式設計
< typename T >的含義是宣告資料型別叫T
template <typename 模板型別1, typename 模板型別2, ...>
返回型別 函式名(形參表){
函式體; // 函式中的型別可以使用模板型別替換
}
2> 模板函式的呼叫
函式名<模板實參型別表>(實參表); 順口溜:尖找尖,圓找圓。
1.2、模板類
1> 語法格式
template <typename 模板型別1, ...>
class 類名:繼承表{
成員變數;
成員函式;
};
2> 模板類的使用
類名<模板實參型別1,...> 物件名(實參表);
類名<模板實參型別1,...> 物件名 = 類名<模板實參型別1,...>(實參表);
類名<模板實參型別1,...> *物件指標 = new 類名<模板實參型別1,...>(實參表);
類名<模板實參型別1,...> &引用名 = 引用的目標;
3> 編譯器編譯模板函式/模板類的實現的機制?
採用是二次編譯或者延時編譯
當編譯器第一次“看到”模板函式定義時,由於模板函式的引數尚不明確,編譯器只做與型別無關的一般性檢查,如果檢查編譯無誤則生成模板的內部表現形式。
當編譯器第二次“看到”模板函式被呼叫時,再次使用模板函式呼叫時傳遞的型別實參,結合模板函式的內部表現形式,做與型別相關的語法檢查,如果無誤則生成具體的二次編譯的程式碼。
4> C 模板
#include <stdio.h>
#if 0
int add_int(int x, int y)
{
return x > y ? x : y;
}
float add_int(float x, float y)
{
return x > y ? x : y;
}
#endif
// 巨集定義中不允許出現換行,如果右換行需要加續行符"\"
// ## : 拼接
// int max_int(int x, int y){}
#define MAX(T) T max_##T(T x, T y) \
{ \
return x > y ? x : y; \
}
MAX(int)
MAX(short)
MAX(double)
#define max(T) max_##T
// void min_int(int *min, int x, int y){}
#define MIN(T0, T1, T2) T0 min_##T2(T1 min, T2 x, T2 y) \
{ \
*min = x > y ? y : x; \
}
MIN(void, int *, int)
MIN(void, double *, double)
#define min(T) min_##T
int main(int argc, const char *argv[])
{
int a = 100, b = 200;
printf("max_int = %d\n", max_int(a, b));
int min;
min_int(&min, a, b);
printf("min_int = %d\n", min);
double d1 = 3.14, d2 = 5.67;
printf("max_double = %lf\n", max_double(d1,d2));
double mind;
min(double)(&mind, d1, d2);
short s1 = 20, s2 = 6;
//max_short(s1,s2)
printf("max_short = %d\n", max(short)(s1,s2));
return 0;
}
// 結果:
max_int = 200
min_int = 100
max_double = 5.670000
max_short = 20
#include <stdio.h>
// 巨集定義的最後一個表達是就是巨集定義的返回值
#define MAX(T, a, b) ({T tmp,retVal; if (a > b) tmp = a; else tmp = b; retVal = tmp;})
#define MIN(a, b) ({a>b ? b : a;})
int main(int argc, const char *argv[])
{
printf("max = %d\n", MAX(int, 100,200));
printf("min = %d\n", MIN(100,200));
return 0;
}
5> C++ 模板
#include <iostream>
using namespace std;
// 交換兩個變數的值
template <typename A>
void Swap(A& a, A& b){
A tmp;
tmp = a;
a = b;
b = tmp;
}
int main(int argc, const char *argv[])
{
int x = 100, y = 200;
Swap<int>(x, y);
cout << "x = " << x << ", y = " << y << endl;
string str1 = "zhou";
string str2 = "kai";
Swap<string> (str1, str2);
cout << str1 << " " << str2 << endl;
return 0;
}
// 結果:
x = 200, y = 100
kai zhou
#include <iostream>
using namespace std;
template <typename A>
class Shape{
public:
Shape(A x, A y):m_x(x),m_y(y){}
~Shape(void){}
void Painter(void){
cout << "座標" << m_x << ":" << m_y << endl;
}
private:
A m_x;
A m_y;
};
int main(int argc, const char *argv[])
{
Shape <int> S1(10,20);
S1.Painter();
Shape <float> *S2 = new Shape<float>(11.1, 12.1);
S2->Painter();
Shape <double> S3 = Shape<double>(111.11,222.22);
Shape <double> &S4 = S3;
S4.Painter();
return 0;
}
//結果:
座標10:20
座標11.1:12.1
座標111.11:222.22
#include <iostream>
using namespace std;
template <typename A>
class Shape{
public:
Shape(A x, A y):m_x(x),m_y(y){}
virtual ~Shape(void){}
virtual void Painter(void){
cout << "座標" << m_x << ":" << m_y << endl;
}
private:
A m_x;
A m_y;
};
template <typename A, typename B>
class Rect:public Shape<A>{
public:
Rect(A x, A y, B w, B h):Shape<A>(x, y),m_w(w),m_h(h){}
~Rect(void){}
void Painter(void){
Shape<A>::Painter();
cout << "寬高:" << m_w<< ":" << m_h << endl;
}
private:
B m_w;
B m_h;
};
template <typename A, typename B>
class Circle:public Shape<A>{
public:
Circle(A x, A y, B r):Shape<A>(x,y),m_r(r){}
virtual ~Circle(){}
virtual void Painter(void){
Shape<A>::Painter();
cout << "半徑:" << m_r << endl;
}
private:
B m_r;
};
int main(int argc, const char *argv[])
{
Shape<int> *S1 = new Rect<int, float>(1,2,3.4,5.6);
S1->Painter();
Circle <int, double> C1(5,6,3.14);
Shape<int> &S2 = C1;
S2.Painter();
return 0;
}
//結果:
座標1:2
寬高:3.4:5.6
座標5:6
半徑:3.14
筆試題:編譯器編譯模板函式/模板類的實現機制?
採用是二次編譯或者延時編譯
當編譯器第一次“看到”模板函式定義時,由於模板函式的引數尚不明確,編譯器只做與型別無關的一般性檢查,如果檢查編譯無誤則生成模板的內部表現形式。
當編譯器第二次“看到”模板函式被呼叫時,再次使用模板函式呼叫時傳遞的型別實參,結合模板函式的內部表現形式,做與型別相關的語法檢查,如果無誤則生成具體的二次編譯的程式碼。
(及具體函式的程式碼)。
二、基於模板C++實現的標準模板庫
STL(Standed Template Library)
2.1、容器、迭代器、泛型演算法
容器:模板化的資料結構
泛型演算法:模組化的演算法
迭代器:為不同型別的容器,提供了
相同的資料訪問的介面函式。
2.2、容器 (模板類)
vector (向量)
list(列表)
2.3、迭代器
1> 根據迭代器的特性:單向迭代器和雙向迭代器
2> 根據迭代器的方向:正向迭代器和反向迭代器
3> 根據迭代器的運算:順序迭代器和隨機迭代器
4> 根據對目標的訪問:可寫迭代器和只讀迭代器
每一種容器都會以巢狀的形式提供至少四種迭代器
iterator - 正向可寫迭代器
const_iterator - 正向只讀迭代器
reverse_iterator - 反向可寫迭代器
const_reverse_iterator - 反向只讀迭代器
2.4、泛型演算法
排序,查詢,組合
三、vector(向量)
3.1、特點
連續的記憶體空間,通過下標訪問
動態記憶體管理
3.2、例項化物件
#include <vector>
using namespace std;
A.vector<元素型別> 向量物件; // 空向量
vector<int> vi;
cout << vi.size () << endl; // 0
cout << sizeof (vi) << endl; // 12
容器大小:元素個數,size()
容器物件大小:容器本身的位元組數,sizeof()
B.vector<元素型別> 向量物件 (初始大小); // 非空向量
vector<int> vi (3);
基本型別:初始化為"零"
類型別:用預設建構函式初始化
C.vector<元素型別> 向量物件 (初始大小, 初始值);
vector<int> vi (3, 5); // 5 5 5
vector<Student> vs (3, Student ("張飛", 22));
D.vector<元素型別> 向量物件 (起始迭代器, 終止迭代器);
int ai[5] = {10, 20, 30, 40, 50};
vector<int> v1 (ai, ai + 5); // 10 20 30 40 50
vector<int> v2 (&ai[0], &ai[5]); // 10 20 30 40 50
vector<int> v3 (ai+1, ai + 4); // 20 30 40
在STL中但凡用兩個迭代器表示一個元素範圍時,
其中的終止迭代器一定是該範圍中最後一個元素的下一個位置。
#include <iostream>
#include <vector>
using namespace std;
void print(vector<int> const& v){
cout << "物件大小:" << sizeof(v) << endl;
size_t size = v.size(); // 向量的容量
cout << "向量容量:" << size << endl;
cout << "變數向量中的元素:";
for(int i = 0; i < size; i++)
cout << v[i] << " ";
cout << endl;
}
int main(int argc, const char *argv[])
{
vector<int> v1; // 空向量
print(v1);
cout << "---------------" << endl;
vector<int> v2(3); // 定義向量物件,大小為3
print(v2);
cout << "---------------" << endl;
// 定義向量物件,大小為3,初始值為5
vector<int> v3(3,5);
print(v3);
cout << "---------------" << endl;
v3.reserve(5);
print(v3);
v3.push_back(6); // 插入元素6
print(v3);
v3.push_back(7); // 插入元素7
print(v3);
cout << "---------------" << endl;
int arr[5]={10,20,30,40,50};
vector<int> v4(arr,arr+5);
print(v4);
vector<int> v5(&arr[0], &arr[5]);
print(v5);
vector<int> v6(arr+2, arr+4);
print(v6);
return 0;
}
//結果:
物件大小:24
向量容量:0
變數向量中的元素:
---------------
物件大小:24
向量容量:3
變數向量中的元素:0 0 0
---------------
物件大小:24
向量容量:3
變數向量中的元素:5 5 5
---------------
物件大小:24
向量容量:3
變數向量中的元素:5 5 5
物件大小:24
向量容量:4
變數向量中的元素:5 5 5 6
物件大小:24
向量容量:5
變數向量中的元素:5 5 5 6 7
---------------
物件大小:24
向量容量:5
變數向量中的元素:10 20 30 40 50
物件大小:24
向量容量:5
變數向量中的元素:10 20 30 40 50
物件大小:24
向量容量:2
變數向量中的元素:30 40
四、迭代器
A.隨機迭代器:
在順序迭代器的基礎上又增加了如下功能:可以和整數做加減法運算;同類型的迭代器之間可以做大小比較運算和相減運算。
B.八個特徵迭代器
iterator begin (void)
返回一個迭代器,該迭代器引用向量的第一個元素
iterator end (void)
返回一個迭代器,該迭代器位於向量的最後一個元素之後
reverse_iterator rbegin (void)
返回引用向量中最後一個元素的迭代器
reverse_iterator rend (void)
返回位於向量中第一個元素之前的迭代器
const_iterator begin (void) const
返回一個迭代器,該迭代器引用向量的第一個元素
const_iterator end (void) const
返回一個const 迭代器,該迭代器位於向量的最後一個元素之後
const_reverse_iterator rbegin (void) const
返回引用向量中最後一個元素的迭代器
const_reverse_iterator rend (void) const
返回位於向量中第一個元素之前的迭代器
begin - 起始
end - 終止
不帶const - 可寫
帶const - 只讀
不帶r - 正向
帶r - 反向
對於正向迭代器而言,起始位置在首元素,終止位置在尾元素之後;
對於反向迭代器而言,起始位置在尾元素,終止位置在首元素之前。
任何可能導致容器結構發生變化的操作,比如資料元素的增刪,都可能引發其記憶體佈局的調整,
因此操作之前所獲得的迭代器就會因為這種調整而失效,安全起見最好重新定位這個迭代器以後再繼續使用。
#include <iostream>
#include <vector>
using namespace std;
void print(vector<int> const& v){
// 使用正向迭代器遍歷容器中的所有元素
for(vector<int>::const_iterator it = v.begin();
it != v.end(); ++it)
cout << *it << " ";
cout << endl;
}
void rprint(vector<int> const& v){
for(vector<int>::const_reverse_iterator
it = v.rbegin(); it != v.rend();
it++){
cout << *it << " ";
}
cout << endl;
}
int main(int argc, const char *argv[])
{
// 空向量
vector<int> v1;
for(int i = 1;i<=10;i++)
v1.push_back(i*10);
print(v1);
rprint(v1);
return 0;
}
//結果:
10 20 30 40 50 60 70 80 90 100
100 90 80 70 60 50 40 30 20 10
五、演算法
排序
IT sort (IT begin, IT end);
對[begin,end)區間內的元素做快速排序。
<algorithm>標頭檔案中的全域性函式sort()只能應用於帶有隨機迭代器的連續記憶體容器,
比如vector、deque。
列表的排序必須使用其成員函式sort()。
#include <iostream>
#include <algorithm> // 演算法相關的標頭檔案
#include <vector>
using namespace std;
template <typename T1>
void print(T1 begin, T1 end){
while(begin != end)
cout << *begin++ << " ";
cout << endl;
}
int main(int argc, const char *argv[])
{
vector<int> v1;
v1.push_back(33);
v1.push_back(57);
v1.push_back(78);
v1.push_back(25);
v1.push_back(123);
// 編譯器可以完成隱式型別的推導
print (v1.begin(), v1.end());
// 升序排序
sort(v1.begin(), v1.end());
print (v1.begin(), v1.end());
return 0;
}
//結果:
33 57 78 25 123
25 33 57 78 123
六、列表(list)
6.1、特點
雙向連結串列
在記憶體中是不連續的
6.2、例項化
list() 宣告一個空列表;
list(n) 宣告一個有n個元素的列表,每個元素都是由其預設建構函式T()構造出來的
list(n,val) 宣告一個由n個元素的列表,每個元素都是由其複製建構函式T(val)得來的
list(n,val) 宣告一個和上面一樣的列表
list(first,last) 宣告一個列表,其元素的初始值來源於由區間所指定的序列中的元素
6.3、唯一化
void unique (void);
僅對容器中連續的重複出現的元素做唯一化,而不是對整個容器中的元素做唯一化。
10 10 20 20 10 20 30 20 20 10 10
| unique()
V
10 20 10 20 30 20 10
6.4、排序
void sort (void); // 用元素型別的"<"運算子比較大小
<algorithm>標頭檔案中的全域性函式sort()只能應用於帶有隨機迭代器的連續記憶體容器,
比如vector、deque。列表的排序必須使用其成員函式sort()。
6.5、拆分:呼叫列表.splice (目標迭代器, 引數列表, ...);
從引數列表中剪切出全部或者部分元素,插入到呼叫列表中目標迭代器之前。
void splice (IT pos, list& lst); // 剪下全部
void splice (IT pos, list& lst, IT del); // 剪下一個
void splice (IT pos, list& lst, IT begin, IT end); //剪下區間
6.6合併:將兩個有序列表合併為一個,仍然保持有序。
void merge (list& lst); // <
參見:04list.cpp
#include <iostream>
#include <algorithm>
#include <list>
using namespace std;
template <typename T1>
void print(T1 begin, T1 end)
{
while(begin != end)
cout << *begin++ << " " ;
cout << endl;
}
int main(int argc, const char *argv[])
{
int arr[10] = {10,10,20,20,40,50,50,30,10,10};
list<int> l1(arr, arr+10);
// 連續重複的元素唯一化
l1.unique();
print(l1.begin(), l1.end());
// 呼叫list中的成員函式sort進行排序
l1.sort();
print(l1.begin(), l1.end());
l1.unique();
print(l1.begin(), l1.end());
cout << "拆分" << endl;
list <int> l2;
l2.push_back(111);
l2.push_back(555);
l2.push_back(333);
l2.push_back(444);
l2.push_back(222);
list<int>::iterator pos = l1.begin();
/*
l1.splice(pos,l2); 剪下全部
*/
/*
// 剪下一個元素
list<int>::iterator del = l2.begin();
l1.splice(pos, l2, ++del);
*/
print(l1.begin(), l1.end());
cout << "合併" << endl;
l1.merge(l2);
l1.sort();
print(l1.begin(), l1.end());
return 0;
}
//結果:
10 20 40 50 30 10
10 10 20 30 40 50
10 20 30 40 50
拆分
10 20 30 40 50
合併
10 20 30 40 50 111 222 333 444 555