c++的泛型编程与模板
“泛型编程”:一种不考虑具体数据类型的编程方式
1.用泛型编程实现类模板
假如现在有个需求,我们要实现一些类,主要用于存储和组织数据结构(如链表类、堆类等),关注其实现的裸机,而不是数据结构中元素的具体类型
- 使用泛型编程,即实例化类时,向其指定数据类型。这样实现的类十分的精简高效,但其实现本质是根据参数不同定义了不同的类,可以认为类模板是一种语法糖
- 类模板无法像函数模板那样自动推导类型,只能显示指定类型
template //类模板一般在头文件里定义,template关键字告诉编译器开始泛型编程,
< typename T1, typename T2, int N>//typename关键字用于声明泛指类型
class Operator
{
public:
T1 add(T2 a, N)//模板的预处理阶段可以传入参数,只能传递常量,有点类似于宏定义,N就是一个参数
};
template //template关键字告诉编译器开始泛型编程
< typename T1, typename T2, int N>//如果成员函数在外面定义,则也要加template和typename
T1 Operator<T1, T2, int N>::add(T2 a, N)
{
return static_cast<T1>(a + b);
}
int main()
{
Operator<int> op1; //实例化类时,必须显示指定类型
op1.add(1, 2); //调用类中的成员函数
return 0;
}
- 类模板是可以特化的,所谓特化,就是对类模板的某一种情况进行定义,当满足该情况时就会优先调用这个特化模板。有点类似于重载,但重载是差异化的,特化是特殊化的
template//原模板
< typename T1, typename T2 >
class Test
{
public:
void add(T1 a, T2 b)
{
cout << "void add(T1 a, T2 b)" << endl;
}
};
template
< typename T >
class Test < T, T > // 当 Test 类模板的两个类型参数完全相同时,使用这个实现
{
public:
void add(T a, T b)//特化的模板可重写原模板的函数
{
cout << "void add(T a, T b)" << endl;
}
void print()//也可以新增原模板的函数
{
}
};
template
< >
class Test < void*, void* > //完全特化,当参数都为void*时优先调用
{
public:
void add(void* a, void* b)
{
cout << "void add(void* a, void* b)" << endl;
}
};
int main()
{
Test<int, float> t1;//调用原模板
Test<long, long> t2;//调用部分特化的模板
Test<void*, void*> t3;//调用完全特化的模板
return 0;
}
2.用泛型编程实现函数模板
假设现在有个需求,需要我们实现一个Add接口,该接口可以对两个类型(int、double、string等都有可能)的变量进行相加,并将结果强制类型转换为特定类型再回
- 那么问题来了,这个函数对于参数的类型很讲究,但是参数类型我们事先是不知道的,那么只能对所有情况进行函数重载。但是这些重载函数仅仅是参数不同,内部逻辑是完全相同的,十分冗余
- 可以使用“泛型编程”,即类型也作为参数传入。这样实现的Add函数十分的精简高效,但其实现本质仍是函数重载,可以认为函数模板是函数重载的语法糖
template //template关键字告诉编译器开始泛型编程
< typename T1, typename T2, typename T3>//typename关键字用于声明泛指类型
T1 Add(T2 a, T3 b)
{
return static_cast<T1>(a + b);
}
- 此外,函数模板还可以进行“完全特化”,与类模板不同,函数模板不能进行“部分特化”。所谓特化,就是对函数模板的某一种情况进行定义,当满足该情况时就会优先调用这个特化模板。有点类似于重载,但重载是差异化的,特化是特殊化的
template//Add函数模板的一个完全特化
< >
char Add(int a, float b)
{
cout << "123" << endl;//特化的函数模板可以和原模板不一样
return static_cast<char>(a + b);
}
- 函数模板有两种使用方式,自动推导和显式指明,下面以swap为例
- 若使用自动推导方法,则无法推导出返回值的类型
- 若使用显式指明,则指明的类型按照之前
template < typename T1, typename T2, typename T3>
中的顺序一一对应,工程中一般将第一个类型作为返回值类型
int a = 1;
float b = 0;
char c = Add(b, a);//调用原模板,自动推导参数类型,但是无法推导返回值的类型
char d = Add<char, int, float>(a, b);//调用完全特化模板,显式指明类型为T1为char,T2为int,T3为float,
char d = Add<char>(a, b);//当然,也可以偷个懒,只指明返回值类型为char,反正参数类型可以自动推导....
3.普通函数与函数模板的冲突
函数模板也能被重载,不过只根据参数的个数不同来重载罢了,并且工程中,一般只是特化函数模板,并不会去重载函数模板。那么当一个普通的函数及其重载函数,遇到一个同名的函数模板及其特化时,编译器优先调用谁?
- 当参数匹配时,调用优先级:普通函数及其重载函数>函数特化模板>函数模板
- 调用时可以使用空的模板实参列表,如
Add<>(a, b)
来指定编译器调用函数模板