NOIP学习小站
西安交通大学附属中学航天学校

模板类

有了前面模板函数的铺垫,我们再来看模板类。对于模板类,大家了解基本用法即可,特别是模板类的使用方法,因为后续介绍的标准模板库STL中的容器都是基于模板类来实现的。

首先我们来看一个普通的类:

#include<iostream>
using namespace std; 
#define ARR_MAX 10000
class MyList{
    private:
    int arr[ARR_MAX];
    int len;
    public:
    MyList(){
        this->len = 0;
    }
    //末尾追加元素 
    void append(int one){
        this->arr[this->len] = one;
        this->len++;
    }
    //输出所有元素 
    void print(){
        for(int i=0;i<this->len;i++)
			cout<<this->arr[i]<<" ";
        cout<<endl;
    }
    //找到最大元素 
    int max(){
        if(this->len==0)
			cout<<"ERROR"<<endl;
        int ret = this->arr[0];
        for(int i=1;i<this->len;i++)
            if(ret<this->arr[i])
				ret = this->arr[i];
        return ret;
    }
};
int main()
{   
    MyList ml;
    for(int i=1;i<=10;i++){
        ml.append(i);
        cout<<ml.max()<<endl;
    }
    ml.print();
    return 0; 
}

MyList就像一个功能简单的“队列”,可以使用append成员函数向队列末尾添加新的int元素,可以使用max成员函数找出队列中的最大值,还可以使用print成员函数输出队列中的所有元素。队列的所有元素实际存储在成员变量int arr[ARR_MAX]数组中,还有一个成员变量len用来记录队列长度。

但是这个”队列“能处理的基本元素是int类型的数据,如果要处理double类型、string类型的元素,我们会想到再定义对应的类:

#include<iostream>
using namespace std; 
#define ARR_MAX 10000
class MyList{
    private:
    double arr[ARR_MAX];
    int len;
    public:
    MyList(){
        len = 0;
    }
    //末尾追加元素(参数是double) 
    void append(double one){
        arr[len] = one;
        len++;
    }
    //输出所有元素 
    void print(){
        for(int i=0;i<len;i++)
			cout<<arr[i]<<" ";
        cout<<endl;
    }
    //找到最大元素(返回值是double) 
    double max(){
        if(len==0)
			cout<<"ERROR"<<endl;
        double ret = arr[0];
        for(int i=1;i<len;i++)
            if(ret<arr[i])
				ret = arr[i];
        return ret;
    }
};
int main()
{   
    MyList ml;
    for(int i=1;i<=10;i++){
        ml.append(1.0/i);
        cout<<ml.max()<<endl;
    }
    ml.print();
    return 0; 
}
#include<iostream>
using namespace std; 
#define ARR_MAX 10000
class MyList{
    private:
    string arr[ARR_MAX];
    int len;
    public:
    MyList(){
        len = 0;
    }
    //末尾追加元素(参数是string) 
    void append(string one){
        arr[len] = one;
        len++;
    }
    //输出所有元素 
    void print(){
        for(int i=0;i<len;i++)
			cout<<arr[i]<<" ";
        cout<<endl;
    }
    //找到最大元素(返回值是string) 
    string max(){
        if(len==0)
			cout<<"ERROR"<<endl;
        string ret = arr[0];
        for(int i=1;i<len;i++)
            if(ret<arr[i])
				ret = arr[i];
        return ret;
    }
};
int main()
{ 
    MyList ml;
    for(int i=1;i<=10;i++){
    	string s; cin>>s;
        ml.append(s);
        cout<<ml.max()<<endl;
    }
    ml.print();
    return 0; 
}

有了前面模板函数的思路,可知这样的做法不够简洁,其实这里可以使用模板类来处理基本元素是int、double、string,甚至是前面提到的Circle类的情况:

#include<iostream>
using namespace std; 
#define ARR_MAX 10000
//和模板函数一样,这里指明模板类中会使用的通用数据类型T 
template<typename T>
class MyList{
    private:
    T arr[ARR_MAX];		//数组元素是T类型 
    int len;
    public:
    MyList(){
        len = 0;
    }
    //末尾追加元素(参数是T类型) 
    void append(T one){
        arr[len] = one;
        len++;
    }
    //输出所有元素 
    void print(){
        for(int i=0;i<len;i++)
			cout<<arr[i]<<" ";
        cout<<endl;
    }
    //找到最大元素(返回值是T类型) 
    T max(){
        if(len==0)
			cout<<"ERROR"<<endl;
        T ret = arr[0];
        for(int i=1;i<len;i++)
            if(ret<arr[i])
				ret = arr[i];
        return ret;
    }
};
int main()
{   
	//注意模板类变量的定义方法
	//需要显示地指定通用类型T的实际类型 
	MyList<int> ml1;
    for(int i=1;i<=10;i++){
        ml1.append(i);
        cout<<ml1.max()<<endl;
    }
    ml1.print();
    
    MyList<double> ml2;
    for(int i=1;i<=10;i++){
        ml2.append(1.0/i);
        cout<<ml2.max()<<endl;
    }
    ml2.print();
    
    MyList<string> ml3;
    for(int i=1;i<=10;i++){
        string s; cin>>s;
        ml3.append(s);
        cout<<ml3.max()<<endl;
    }
    ml3.print();
    return 0; 
}

我们可以进一步改造前面提到的Circle类,重载<运算符并重载ostream运算符(用于cout输出),这样模板类MyList也能处理Circle类型的元素:

#include<iostream>
using namespace std; 
#define ARR_MAX 10000
#define PI 3.14159

class Circle{
	public:
	double r;
	Circle(){
		r = 0;
	}
	Circle(double r){
		this->r = r;
	}
	double length() const{
		return 2*PI*r;
	}
	double area() const{
		return PI*r*r;
	}
}; 
//重载<运算符
bool operator <(const Circle &a,const Circle &b){
	return a.r < b.r;
}

//重载ostream运算符,可以直接用cout输出Circle变量
//输出圆的半径、周长、面积信息(三元组形式) 
ostream& operator<<(ostream &os,const Circle &c){
    os<<"("<<c.r<<" "<<c.length()<<" "<<c.area()<<")";
    return os;
}

//和模板函数一样,这里指明模板类中会使用的通用数据类型T  
template<typename T>
class MyList{
    private:
    T arr[ARR_MAX];		//数组元素是T类型 
    int len;
    public:
    MyList(){
        len = 0;
    }
    //末尾追加元素(参数是T类型) 
    void append(T one){
        arr[len] = one;
        len++;
    }
    //输出所有元素 
    void print(){
        for(int i=0;i<len;i++)
			cout<<arr[i]<<" ";
        cout<<endl;
    }
    //找到最大元素(返回值是T类型) 
    T max(){
        if(len==0)
			cout<<"ERROR"<<endl;
        T ret = arr[0];
        for(int i=1;i<len;i++)
            if(ret<arr[i])
				ret = arr[i];
        return ret;
    }
};
int main()
{   
	MyList<Circle> ml;
	for(int i=1;i<=10;i++){
		ml.append(Circle(1.0*i));
		cout<<ml.max()<<endl;
	} 
	ml.print();
	return 0; 
}

和模板函数相似,模板类不是实际的类,而是编译器用于生成一个或多个类的 "模具"。在编写模板类时,不必指定实际数据类型,而是使用类型名称(上面的typename T)来指定通用数据类型。当编译器遇到对模板类的定义时,它将检查定义语句处的数据类型,并对照模板类的实现代码生成与数据类型相对应的实际类代码。

综上所述,对于模板类,需要通过:类名<实际数据类型> 变量名;的方式来定义模板类变量。例如前面示例代码中出现的:

MyList<int> ml1;
MyList<double> ml2;
MyList<string> ml3;
MyList<Circle> ml;

记住这样的语法形式,后面介绍的STL容器会大量使用这样的语法形式定义容器变量。