1. 类模板
泛型思想可运用于函数即函数模板外,还可以运用于类即模板类,使得类的实现不关注数据元素的具体类型,而只关注类所需要实现的功能。模板类在实际中主要用于存储和组织数据元素,如数组类、链表类、栈类(后进先出)、队列类(先进先出)等,这些类的共同之处为:类中的数据组织方式和数据元素的具体类型无关。
模板类的语法如下:
template<typename T>
class TestCls
{
public:
T func(T a, T b)
{
//...
}
};
模板类的意义跟模板函数意义一样,在于以相同的方式处理不同的数据类型。与模板函数不同的是,模板类的使用只能显示指定具体类型,无法自动推导,需要使用具体类型“<类型>”定义对象:
TestCls<double> t1;
TestCls<std::string> t2;
double i = t1.func(4, 5);
std::string ss = t2.func("HelloWorld\n");
编译器对模板类的处理方式和模板函数相同:
a. 同是通过对函数模板的具体类型的调用产生不同的类;
b. 同是进行两次编译:在声明的地方对类模板代码本身进行编译,在使用的地方对参数类型替换后的代码再次进行编译。
在实际工程中,类模板是在头文件中定义,且类模板中的函数的实现不能分开实现在不同的文件中,类模板外部定义的成员函数需要加上<>声明。
来个简单的例子:
//13_class_template.h
#ifndef __CLASS_TEMPLATE_H__
#define __CLASS_TEMPLATE_H__
template <typename T>
class calculate
{
public:
T add(T a, T b);
T subtract(T a, T b);
T multiplication(T a, T b);
T divide(T a, T b);
};
template <typename T>
T calculate<T>::add(T a, T b)
{
return a + b;
}
template <typename T>
T calculate<T>::subtract(T a, T b)
{
return a - b;
}
template <typename T>
T calculate<T>::multiplication(T a, T b)
{
return a * b;
}
template <typename T>
T calculate<T>::divide(T a, T b)
{
return a / b;
}
#endif /* __CLASS_TEMPLATE_H__ */
//13_class_template.cpp
#include <stdio.h>
#include "13_class_template.h"
int main(void)
{
calculate<int> op;
printf("op.add(1, 2) = %d\n", op.add(1, 2));
printf("op.subtract(5, 2) = %d\n", op.subtract(5, 2));
printf("op.multiplication(12, 2) = %d\n", op.multiplication(12, 2));
printf("op.divide(8, 2) = %d\n", op.divide(8, 2));
return 0;
}
编译运行:
注意,calculate类的实现还是存在缺陷,如string类型的减法操作:
std::cout << "op.subtract(\"hello\", \"world\") = " << op.subtract("hello", "world") << std::endl;
对std::string类型的两个变量进行减法运算,编译器显然没有这种实现:
用操作符重载减号操作符的方式加以区分实现:
std::string operator- (std::string a, std::string b)
{
return "Error";
}
这样当T数据类型为std::string时,op.subtract(“hello”, “world”)的实现体为:
std::string s = "hello" - "world" ;
编译器就会根据上下文调用operator- (std::string a, std::string b):
2. 类模板的本质
(1) 和函数模板一样,类模板也可以定义任意多个不同的类型参数,如:
template <typename T1, typename T2>
class TestCls
{
public:
void add(T1 a, T2 b);
};
template <typename T1, typename T2>
void TestCls <T1, T2>::add(T1 a, T2 b)
{
printf("estCls <T1, T2>::add(T1 a, T2 b) = %d\n", a + b);
}
(2) 类模板也支持特化,以满足在某种特定类型下的类模板需要特定实现的需求,如:
//部分特化
template <typename T>
class TestCls <T, T>
{
public:
void add(T a, T b);
};
template <typename T>
void TestCls <T, T>::add(T a, T b)
{
printf("TestCls <T, T>::add(T a, T b) = %d\n", a + b);
}
这种称为部分特化:只用特定的规则约束类型参数,特化后的数据类型还是泛指类型T。在这里,原先是T1、T2类型,二者既可以是同一种类型,也可以是不同一种,特化后T1、T2只能是相同的一种类型T。当我们定义的对象的T1、T2类型是一样的时候,编译器会选择特化后的类模板:
int main(void)
{
TestCls<int, int> t; //编译器会选择特化后的类。
//注意<>内不能为空,编译器对类模板不能进行类型推导。
t.add(5, 5);
return 0;
}
编译运行:
类模板还支持完全特化,即类模板的类型参数完全指定,如:
//完全特化,完全特化后"<>"内为空
template <>
class TestCls<int, char> //直接指定T1、T2的类型
{
public:
void add(int a, char b)
{
printf("TestCls<int, char>::add(int a, char b)\n");
}
};
int main(void)
{
TestCls<int, char> t;
t.add(5, 5);
return 0;
}
编译运行:
注意,特化只是模板的分开实现,本质上还是同一个类模板,特化类模板在使用方式是与类模板是一样的,即必须显式的指定每一个类型参数(编译器不会自动推导)。
(3) 重新定义类还是完全特化类模板
重新定义一个类和完全特化类模板都可以实现在某种特定类型下实现某种特定的操作,但是重新定义类后就需要考虑调用哪一个类的问题:
//类模板
template <typename T1, typename T2>
class TestCls
{
public:
void add(T1 a, T2 b);
};
//部分特化
template <typename T>
class TestCls <T, T>
{
public:
void add(T a, T b)
{
printf("TestCls <T, T>::add(T a, T b) = %d\n", a + b);
}
};
//完全特化
template <>
class TestCls<int*, int*> //直接指定T1、T2的类型
{
public:
void add(int *a, int *b)
{
printf("TestCls<int*, int*>::add(int *a, char *b) = %d\n", *a + *b);
}
};
//重新定义类
class TestCls_INT
{
public:
void add(int* a, int* b)
{
printf("TestCls_INT::add(int* a, int* b) = %d\n", *a + *b);
}
};
int main(void)
{
int a = 8, b = 3;
TestCls_INT t1; //重新定义类需要考虑调用的是哪一个类
t1.add(&a, &b);
TestCls<int*, int*> t2;
t2.add(&a, &b);
TestCls<int, int> t3;
t3.add(a, b);
}
编译运行:
在前面讲到的函数模板,它也支持特化,但是注意,函数模板只支持完全特化,特化后的函数模板没有出现泛指数据类型,也就是template的语句为”template <>”。有关内容在记录在文章http://blog.csdn.net/qq_29344757/article/details/77394049中。