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();//刪除向量中的所有物件