C++程序设计(七)—— 类模板与向量

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Alexshi5/article/details/82635117

一、类模板

        如果将类看作某些数据类型的框架,然后将这些数据类型从类中分离出来形成一个通用的数据类型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等长,可以大于也可以小于。当定义的向量比数组长时,数组长度以后的元素是不确定的,这是定义向量的又一种方式,而且是在定义向量的同时完成初始化,但不能使用这种方法去初始化一个已经声明或定义过的向量。

扫描二维码关注公众号,回复: 3396848 查看本文章

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();//删除向量中的所有对象

猜你喜欢

转载自blog.csdn.net/Alexshi5/article/details/82635117
今日推荐