文章目录
-
函数模板用于设计程序代码相同而所处里的数据类型不同的通用函数。与此类似,C++也支持用类模板来设计结构和成员函数完全相同,但所处理的数据类型不同的通用类
-
比如,对于堆栈类而言,可能存在整数栈、双精度数栈、字符栈等多种不同数据类型的栈,每个栈类除了所处理的数据类型不同之外,类的结构和成员函数完全相同,可为了在非模板的类设计中实现这些栈,不得不重复编写各个栈类的相同代码,例如初始化栈、入栈、出栈等操作。为了解决该问题,C++中用类模板来设计这样的类簇最方便,一个类模板就能够实例化生成所有需要的栈类。
-
类模板也称为类属类,它可以接收类型作为参数,设计出与具体类型无关的通用类。在设计类模板时,可以使其中的某些数据成员,成员函数的参数或返回值与具体类型无关
类模板的定义
- 类模板与函数模板的定义形式相似,如下所示:
cpp
template <typename T1, typename T2, ....>
class 类名{
... ...
};
- 实例:设计一个栈的类模板Stack,在模板中使用类型参数T表示栈中存放的数据, 用非类型参数MAXSIZE代表栈的大小
cpp
#include <iostream>
using namespace std;
template<typename T, int MAXSIZE>
class Stack{
private:
T elements[MAXSIZE];
int top; //栈顶
public:
Stack():top(0){
}
void push(T e);
T pop();
bool empty(){
return top == 0;
}
bool full(){
return top==MAXSIZE;
}
};
template<typename T, int MAXSIZE>
void Stack<T, MAXSIZE>::push(T e){
if(full()){
cout << "stack is full, can not push" << endl;
}
elements[top++] = e;
}
template<typename T, int MAXSIZE>
T Stack<T, MAXSIZE>:: pop(){
if(top == 0){
cout << "stack is empty, can not pop" << endl;
return 0;
}
top--;
return elements[top] ;
}
#if 0
int main(void){
Stack<int, 10> iStack;
return 0;
}
#endif
类模板实例化
- 类模板的实例化包括模板实例化和成员函数实例化
- 当用类模板定义对象时,将引起类模板的实例化。在实例化类模板时,如果模板参数是类型参数,则必须为它指定具体的类型;如果模板参数是非类型参数,则必须为它指定一个常量值。如对前面的Stack类模板而言,下面是它的一条实例化语句:
cpp
Stack<int, 10> istack;
- 编译器实例化Stack的方法是:将Stack模板声明中的所有的类型参数T替换为int, 将所有的非类型参数MAXSIZE替换为10, 这样就用Stack模板生成了一个int类型的模板类
- 为了区别于普通类,暂且将该类记作Stack<int, 10>, 即在类模板名后面的一对<>中写上模板参数。该类的代码如下:
cpp
class Stack{
private:
int elements[10];
int top; //栈顶
public:
Stack():top(0){
}
void push(int e);
int pop();
bool empty(){
return top == 0;
}
bool full(){
return top==MAXSIZE;
}
};
- 最后c++用这个模板类定义一个对象istack
- 注意:在上面的实例化过程中,并不会实例化类模板的成员函数,也就是说,在用类模板定义对象时并不会生成类成员函数的代码
- 类模板成员函数的实例化发生在该成员函数被调用时,这就意味着只有哪些被调用的成员函数才会被实例化。或者说,只有当成员函数被调用了,编译器才会为它生成真正的的代码
cpp
#include <iostream>
using namespace std;
template<typename T, int MAXSIZE>
class Stack{
private:
T elements[MAXSIZE];
int top; //栈顶
public:
Stack():top(0){
}
void push(T e);
T pop();
bool empty(){
return top == 0;
}
bool full(){
return top==MAXSIZE;
}
};
template<typename T, int MAXSIZE>
void Stack<T, MAXSIZE>::push(T e){
if(full()){
cout << "stack is full, can not push" << endl;
}
elements[top++] = e;
}
template<typename T, int MAXSIZE>
T Stack<T, MAXSIZE>:: pop(){
if(top == 0){
cout << "stack is empty, can not pop" << endl;
return 0;
}
top--;
return elements[top] ;
}
int main(void){
Stack<int, 10> iStack;
iStack.push(12);
return 0;
}
- 可以将pop函数的类外定义删除掉,然后再编译运行程序,可以发现程序通用能够正确地执行
- 与普通类的对象一样,类模板的对象或引用也可以作为函数的参数,只不过这类函数通常是模板函数,且其调用实参常常是该类模板的模板类对象
cpp
#include <iostream>
using namespace std;
template<typename T, int MAXSIZE>
class Stack{
private:
T elements[MAXSIZE];
int top; //栈顶
public:
Stack():top(0){
}
void push(T e);
T pop();
bool empty(){
return top == 0;
}
bool full(){
return top == MAXSIZE;
}
};
template<typename T, int MAXSIZE>
void Stack<T, MAXSIZE>::push(T e){
if(full()){
cout << "stack is full, can not push" << endl;
}
elements[top++] = e;
}
template<typename T, int MAXSIZE>
T Stack<T, MAXSIZE>::pop(){
top--;
return elements[top];
}
template<typename T>
void display(Stack<T, 10> &s){
while(!s.empty()){
cout << s.pop() << endl;
}
}
int main(void){
Stack<int, 10> iStack;
iStack.push(12);
iStack.push(1);
iStack.push(2);
iStack.push(123);
iStack.push(12111);
display(iStack);
return 0;
}
类模板特化
- 类模板代表了一种通用程序设计的方法,它表示了无限类集合,可以实例化生成基于任何类型的模板类。在通常情况下,由类模板生成的模板类都能够正常地工作,但也有有类模板生成的模板类代码对某些数据类型不适用的情况
- 如:设计一个通用数组类,它能够直接存取数组元素,并能够输出数组中的最小值
cpp
#include <iostream>
#include <cstdlib>
using namespace std;
template<typename T>
class Arr{
private:
T *arr;
int size;
public:
Arr(int size = 10){
this->size = size;
//arr = new T(size);
arr = (T *)malloc(sizeof(T) * size);
}
~Arr(){
free(arr);
}
T &operator[](int i);
T Min();
};
template<typename T>
T& Arr<T>::operator[](int i){
if(i<0 || i > size -1){
cout << "\n 数组下标越界" << endl;
}
return arr[i];
}
template<typename T>
T Arr<T>::Min(){
T tmp;
tmp = arr[0];
for(int i=1; i<size; i++){
if(tmp > arr[i])
tmp = arr[i];
}
return tmp;
}
int main(void){
Arr<int> a(5);
for(int i=0; i<5; i++)
a[i] = i+100;
cout << a.Min() << endl;
Arr<char *> b(5);
b[0] = (char *)"faa";
b[1] = (char *)"bbb";
b[2] = (char *)"ccc";
b[3] = (char *)"ddd";
b[4] = (char *)"eee";
cout << b.Min() << endl;
return 0;
}
- 显然Arr类模板并不完全适用于生成char *类型的模板类,因为Arr类模板的Min成员函数并不适用于字符指针类型的计算大小
- 解决上述问题的方法就是类模板的特化,即用与该模板相同的名字为某种数据类型专门重新一个模板类。特化类模板时,可以随意增减和改写模板原有的成员,成员函数的改写也不受任何限制,可以与原来的成员函数变得完全不同
- 类模板有两种特化方式,一种是特化整个类模板,另一种是特化个别成员函数。前者是为某种类型单独建立一个类,后者则只针对特化的数据类型提供个别成员函数的实现代码,特化后的成员函数不再是一个模板函数,而是针对特点类型的普通函数
- 与函数模板特化方式相同,类模板成员函数的特化也以template<>开头。形式如下:
cpp
template <>
返回类型 类模板名<特化的数据类型>:: 特化成员函数名(参数表){
... ...
}
- Arr类模板对char *类型来说, 除了Min成员函数不适用外,其余成员都可以,则针对char * 类型重新编写Min成员函数,即特例Arr类模板的Min成员函数就能够解决字符串求大小的问题。特化的Min函数:
cpp
template<>
char * Arr<char *>::Min(){
char *tmp;
tmp = arr[0];
for(int i=1; i<size; i++){
if(strcmp(tmp ,arr[i])>0)
tmp = arr[i];
}
return tmp;
}
- 为了某种数据类型特化整个类模板也要以template<>开头,形式如下所示:
cpp
template <> class 类模板名<特化数据类型>{
... ...
};
- 上例中,也可以为char *提供整个模板的特化,如下所示:
cpp
#include <iostream>
#include <cstdlib>
#include <cstring>
using namespace std;
template<typename T>
class Arr{
private:
T *arr;
int size;
public:
Arr(int size = 10){
this->size = size;
//arr = new T(size);
arr = (T *)malloc(sizeof(T) * size);
}
~Arr(){
free(arr);
}
T &operator[](int i);
T Min();
};
template<typename T>
T& Arr<T>::operator[](int i){
if(i<0 || i > size -1){
cout << "\n 数组下标越界" << endl;
}
return arr[i];
}
template<typename T>
T Arr<T>::Min(){
T tmp;
tmp = arr[0];
for(int i=1; i<size; i++){
if(tmp > arr[i])
tmp = arr[i];
}
return tmp;
}
// 特化
template<>
class Arr<char *>{
private:
char **arr;
int size;
public:
Arr(int size = 10){
this->size = size;
//arr = new T(size);
arr = (char **)malloc(sizeof(char *) * size);
}
~Arr(){
free(arr);
}
char * &operator[](int i){
if(i<0 || i > size -1){
cout << "\n 数组下标越界" << endl;
}
return arr[i];
}
char * Min(){
char * tmp;
tmp = arr[0];
for(int i=1; i<size; i++){
if(strcmp(tmp, arr[i]) > 0)
tmp = arr[i];
}
return tmp;
}
};
int main(void){
Arr<int> a(5);
for(int i=0; i<5; i++)
a[i] = i+100;
cout << a.Min() << endl;
Arr<char *> b(5);
b[0] = (char *)"faa";
b[1] = (char *)"bbb";
b[2] = (char *)"ccc";
b[3] = (char *)"ddd";
b[4] = (char *)"eee";
cout << b.Min() << endl;
return 0;
}