1. 程式人生 > 實用技巧 >C++04模板與標準模板庫

C++04模板與標準模板庫

目錄

一、模板 (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