1. 程式人生 > >C++程式設計(七)—— 類模板與向量

C++程式設計(七)—— 類模板與向量

一、類模板

        如果將類看作某些資料型別的框架,然後將這些資料型別從類中分離出來形成一個通用的資料型別T,為這個資料型別設計一個操作集,並且允許原來那些資料型別的類都能使用這個操作集,這將避免因為類的資料型別不同而產生的重複性設計。型別T通常被稱為類模板,在編譯時,由編譯器將類模板與某種特定的資料型別聯絡起來,就產生一個特定的類(模板類)。

1、類模板基礎知識

⑴ 類模板的成分和語法

        為了建立類模板,在類模板引數表之後應有類宣告,在類中可以像使用其他型別那樣使用模板型別。例如,可以把模板引數用做資料成員的型別、成員函式的型別或成員函式的引數型別等。示例如下:

template <typename T> //帶引數T的模板宣告,可用typename代替class
class TempTest{
private:
	T x,y; //型別為T的私有資料成員
public:
	TempTest(T a,T b):x(a),y(b){}
	T getX(){ //內聯成員函式,返回型別為T
		return x;
	}
	T getY(){
		return y;
	}
};

⑵ 類模板的物件

        類模板也稱引數化類,初始化類模板時,只要傳給它指定的資料型別,編譯器就用指定型別模板引數產生的相應的模板類。示例如下:

int main(){
	TempTest<int> obj(10,20);
	return 0;
}

⑶ 使用類模板計算4個數中的最大值 

//template <typename T,int size = 5> //這種方式可以傳遞程式中的引數值
template<typename T>
class Math2 {
private:
	T x, y, z , s;
	T max1(T a, T b) {
		return (a > b) ? a : b;
	}
public:
	Math2(T a, T b, T c ,T d);
	T max2(void);
};

template <typename T> //定義成員函式時必須再次宣告模板
Math2<T>::Math2(T a,T b,T c,T d):x(a),y(b),z(c),s(d){

}

template <typename T>
T Math2<T>::max2(void){
	return max1(max1(x,y),max1(z,s));
}
#include <iostream>
using namespace std;

#include "Example2.h"
void example2();
int main(){
	example2();
	return 0;
}

void example2(){
	Math2<int> m(10,20,15,8);
	int i = m.max2();
	cout << "這四個數中的最大值是:" << i << endl;//20
}

2、類模板的派生與繼承

        類模板也可以繼承,繼承的方法與普通的類一樣。宣告模板繼承之前,必須重新宣告類模板。模板類的基類和派生類都可以是模板(或非模板)類。

        下面是模板類繼承非模板類的示例:

#include <iostream>
using std::cout;

class Point{
private:
	int x,y;
public:
	Point(int a,int b):x(a),y(b){

	};
	void show(){
		cout << "x=" << x << ",y=" << y << "\n";
	}
};

template <typename T>
class Line:public Point{
private:
	T x2,y2;
public:
	Line(int a,int b,T c,T d):Point(a,b){
		x2 = c;
		y2 = d;
	}
	void show(){
		Point::show();
		cout << "x2=" << x2 << ",y2=" << y2 << "\n";
	}
};
#include "Example3.h"
void example3();
int main(){
	example3();
	return 0;
}

void example3(){
	Point a(10,20);
	a.show();
	//x=10,y=20  物件a是整數座標

	Line<int> l1(10,20,30,40);
	l1.show();
	//x=10,y=20
	//x2=30,y2=40 線段ab的兩個座標均是整數

	Line<double> l2(10,20,30.5,40.5);
	l2.show();
	//x=10,y=20
	//x2=30.5,y2=40.5 線段ab的兩個座標,第一個座標是整數,第二個座標是實數
}

          下面是模板類繼承模板類的示例:

#include <iostream>
using std::cout;

template <typename T>
class Point1{
private:
    T x,y;
public:
	Point1(T a,T b):x(a),y(b){

	};
	void show(){
		cout << "x=" << x << ",y=" << y << "\n";
	}
};

template <typename T>
class Line1:public Point1<T>{
private:
	T x2,y2;
public:
	Line1(T a,T b,T c,T d):Point1<T>(a,b){
		x2 = c;
		y2 = d;
	}
	void show(){
		Point1<T>::show();
		cout << "x2=" << x2 << ",y2=" << y2 << "\n";
	}
};
#include "Example4.h"
void example4();
int main(){
	example4();
	return 0;
}

void example4(){
	Point1<int> a(10,20);
	a.show();
	//x=10,y=20

	Line1<int> l1(10,20,30,40);
	l1.show();
	//x=10,y=20
	//x2=30,y2=40 全部都是整數

	Line1<double> l2(10.5,20.5,30.5,40.5);
	l2.show();
	//x=10.5,y=20.5
	//x2=30.5,y2=40.5 全部都是實數
}

 二、向量與泛型演算法

        在陣列生存期內,陣列的大小是不會改變的。向量是一維陣列的類版本,它與陣列相似,其中的元素項是連續儲存的,但它和陣列不同的是:向量中儲存元素的多少可以在執行中根據需要動態的增長或縮小。向量是類模板,具有成員函式。

1、定義向量列表

       向量類模板定義在標頭檔案vector中,它提供4種建構函式,用來定義由各元素組成的列表,用length表示長度,用type表示資料型別,物件名為name。如下:

vector <type> name;//定義type的向量空表
vector <type> name(length);//定義具有length個type的向量,元素初始化為0
vector <type> name(length,a);//定義具有length個type的向量,元素初始化為a
vector <type> name1(name);//使用已定義的向量name構造向量name1

       使用示例如下:

#include <algorithm>
#include <vector>
void example5();
int main(){
	example5();
	return 0;
}

void example5(){
	vector <int> v1;cout << v1.size() << endl;//0
	vector <int> v2(20);cout << v2.size() << endl;//20
	vector <int> v3(20,1);cout << v3.size() << endl;//20
	vector <char> v4(10,'t');cout << v4.size() << endl;//10
	v1 = v3;cout << v1.size() <<endl;//20
	for(int i=0;i<v4.size();i++){
		cout << v4[i] << " " ;//t t t t t t t t t t
	}
	cout << endl;

	//定義一個數組
	int arr1[10] = {1,2,3,4,5,6,7,8,9,10};
	//初始化向量
	vector <int> v5(arr1,arr1+10);
	for(int j=0;j<v5.size();j++){
		cout << v5[j] << " ";
	}
}

         向量定義的賦值運算子是“=”,允許同類型的向量列表相互賦值,而不管它們的長度如何。向量可以改變賦值目標的大小,使它的元素數目與賦值源的元素數目相同。

        不能使用列表初始化向量,但可以先初始化一個數組,然後把陣列的內容複製給向量。在上例中,arr1代表陣列的起始地址,arr1+10代表結束標誌位,向量v5的長度為10,結束標誌是向量自動產生的,所以v5不需要與arr1等長,可以大於也可以小於。當定義的向量比陣列長時,陣列長度以後的元素是不確定的,這是定義向量的又一種方式,而且是在定義向量的同時完成初始化,但不能使用這種方法去初始化一個已經宣告或定義過的向量。

2、泛型指標

        與操作物件的資料型別相互獨立的演算法被稱為泛型演算法。也就是說,泛型演算法提供了很多操作向量的行為,而這些演算法和想要操作的元素型別無關。下面是向量a與泛型指標的示意圖:

         向量具有指示第一元素的標記begin和指示結束的標記end,也就是標識要進行操作的元素空間。如果begin不等於end,演算法便會首先作用於begin所指的元素,並將begin前進一個位置,然後作用於當前begin所指的元素,如此繼續前進,直到begin等於end為止。因為end是最後一個元素的下一個位置,所以元素存在的範圍是半開區間[begin,end)。

        在向量中,泛型指標是在底層指標的行為之上提供一層抽象化機制,取代程式原來的指標直接操作方式。假設用T表示向量的引數化資料型別,iterator在STL裡面是一種通用指標,它在向量中的作用相當於T *。用iterator宣告向量的正向泛型指標的一般形式如下:

vector <type> :: iterator 泛型指標名;
vector <char> :: iterator p;

 注意:上例中指標定義時使用的是p,而不是* p,因為vector <char> :: iterator相當於char *,如果使用* p,則表示是指標的指標。在這裡泛型指標是使用類來實現的。

        逆向泛型指標的宣告方式如下:

vector <type> :: reverse_iterator 泛型指標名;
vector <int> :: reverse_iterator p;

         下面演示先使用正向泛型指標來回遍歷一次輸出向量中的元素;然後,換一行使用逆向指標來回輸出。示例如下:

void example6();
int main() {
	example6();
	return 0;
}

void example6() {
	const int size = 5;
	//先定義一個數組
	int arr[size] = { 10, 20, 30, 40, 50 };
	//再使用陣列初始化向量
	vector<int> v1(arr, arr + size);
	//宣告正向泛型指標
	typedef vector<int>::iterator iterator;
	//將向量的首元素地址賦給指標
	iterator p1 = v1.begin();
	//迴圈正向輸出向量中的元素
	for (; p1 < v1.end(); p1++) {
		cout << *p1 << " ";
	}
	//迴圈逆向輸出向量中的元素
	for (--p1; p1 >= v1.begin(); p1--) {
		cout << *p1 << " ";
	}
	//10 20 30 40 50 50 40 30 20 10
	cout << endl;

	//宣告逆向指標
	typedef vector<int> :: reverse_iterator reverse_iterator;
	//將向量中的尾元素的地址賦給該指標
	reverse_iterator p2 = v1.rbegin();
	//使用逆向指標正向輸出向量中的元素
	for(;p2<v1.rend();p2++){
		cout << * p2 << " ";
	}
	//使用逆向指標逆向輸出向量中的元素
	for(--p2;p2>=v1.rbegin();p2--){
		cout << * p2 << " ";
	}
	//50 40 30 20 10 10 20 30 40 50
}

3、向量的資料型別

        向量除了可以使用基本資料型別外,還可以使用構造型別,只要符合構成法則即可。示例如下:

void example7();
#include <complex>
int main() {
	example7();
	return 0;
}

struct st{
	int a,b;
}a[] = {{1,2},{3,4}};
void example7(){
	//定義複數類的陣列並初始化
	complex<int> num[] = {complex<int>(10,20),complex<int>(15,25)};
	//使用複數類的指標作為向量的資料型別
	vector<complex<int> *> v1(2);
	v1[0] = &num[0];
	v1[1] = &num[1];
	for(int i = 0;i<2;i++){
		cout << "複數" << (i+1) <<"實部是:" << v1[i]->real() << ",複數"<< (i+1) <<"虛部是:" << v1[i]->imag() << endl;
	}
	//複數1實部是:10,複數1虛部是:20
	//複數2實部是:15,複數2虛部是:25

	//使用結構指標作為向量的資料型別
	vector<st *> v2(2);
	v2[0] = &a[0];
	v2[1] = &a[1];
	for(int i = 0;i<2;i++){
		cout << v2[i]->a << v2[i]->b;
	}
	//1234
}

4、向量最基本的操作方法

⑴ 訪問向量容量資訊的方法

size();//返回當前向量中已經存放的物件的個數
max_size();//返回向量可以容納最多物件的個數,一般是作業系統的定址空間所能容納的物件的個數
capacity();//返回無需再次分配記憶體就能容納的物件個數,它的初始值為程式設計師最初申請的元素個數,當存放空間已滿,又增加一個元素時,它在原來的基礎上自動翻倍擴充空間,以便存放更多的元素
empty();//當前向量為空時,返回true值

⑵ 訪問向量中物件的方法

front();//返回向量中的第一個物件
back();//返回向量中的最後一個物件
operator[](size_type,n);//返回向量中的第n+1個物件(下標為n的向量元素)

⑶ 在向量中插入物件的方法

push_back(const T&);//向向量尾部插入一個物件
insert(iterator it,const T&);//向it所指的向量位置前插入一個物件
insert(iterator it,size_type n,const T&X);//向it所指向量位置前插入n個值為X的物件

⑷ 在向量中刪除物件的方法

pop_back(const T&);//刪除向量中的最後一個物件
erase(iterator it);//刪除it所指向的容器物件
clear();//刪除向量中的所有物件