有了前面模板函数的铺垫,我们再来看模板类。对于模板类,大家了解基本用法即可,特别是模板类的使用方法,因为后续介绍的标准模板库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容器会大量使用这样的语法形式定义容器变量。