目录
前言
本文为之前工作中写了一些技术分享文档之一,主要是谈谈C++泛型编程,颗粒度略细,主要从模板的角度去谈。本文由于是较早写的笔记,因此可能引用了一些博文,如有发现,请跟我这边说,我这边会在文末补充引用。
泛型编程
-
实现方式:在C++中,主要用函数模板和类模板
-
作用:可以使编写的代码被很多不同的类型所共享,大大提高了代码的重用性
为什么需要泛型编程
给个经典例子
编写一个工具函数:实现两个数的转换
void Swap(int& a, int& b)
{
int c = a;
a = b;
b = c;
}
void Swap(double& a, double& b)
{
double c = a;
a = b;
b = c;
}
以上使用重载的方式,带来什么问题呢?
-
是否符合需求?万一,我还需要一个float类型呢?
-
如果需求有变,a或者b其中一个不能为0呢?就重复修改三个函数
-
以上只是一个简单的函数,如果更复杂的话,重复劳动,容易出错,而且还带来很大的维护和调试工作量
以下为使用模板来写(typename与class作用完全一样,一般使用typename会多一些):
template <typename T>
void Swap(T& a, T& b)
{
T c = a;
a = b;
b = c;
}
函数模板
常用于实现工具函数
例子
写一个简单的数组打印
template < typename T >
void arrPrint(T a[], int len)
{
for(int i=0; i<len; i++)
{
cout << a[i] << ", ";
}
cout << endl;
}
调用
int a[5] = {5, 3, 2, 4, 1};
arrPrint<int>(a, 5);
或者说一个常用的获取数组长度的函数模板
template<class T>
int arrLength(T& arr)
{
return sizeof(arr) / sizeof(arr[0]);
}
为什么函数模板能够执行不同的类型参数
实际上,编译器对函数模板进行了两次编译
-
第一次编译时,首先去检查函数模板本身有没有语法错误
-
第二次编译时,会去找调用函数模板的代码,然后通过代码的真正参数,来生成真正的函数。
-
所以函数模板,其实只是一个模具,当我们调用它时,编译器就会给我们生成真正的函数。
换句话说,是根据我们的调用来生成函数,这个函数叫模板函数。
怎么验证?
直接使用函数指针来打印一下指向的地址即可,或者,查看编译后的汇编代码也可以
特别说明
-
函数模板可以像普通函数一样被重载
-
函数模板不接受隐式转换
-
当有函数模板,以及普通重载函数时,编译器会优先考虑普通函数
-
可以通过空模板实参列表来限定编译器只匹配函数模板
template<typename T>
void example(T& x, T& y)
{
std::cout << "模板函数" << std::endl;
}
template<typename T1, typename T2>
void example(T1& x, T2& y)
{
std::cout << "重载模板函数" << std::endl;
}
void example(int &x, char& y)
{
std::cout << "普通函数" << std::endl;
}
调用
int a = 0, b = 0;
char c = 0;
//调用重载模板函数
example<int>(a, b);
example<int, char>(a, c);
// 函数模板不接受隐式转换
example<int>(a, b); //报错,不提供隐式转换
//优先考虑普通函数
example(a, c);
//如果想执行模板函数
example<>(a, c);
类模板
类模板通常应用于数据结构方面,使得类的实现不在关注数据元素的具体类型,而只关注需要实现的功能 比如: 数组类,链表类,Queue类,Stack类等
类模板中的函数声明和实现都必须在同一个文件,有时为了区分,一般文件名后缀为.hpp
例子
template <typename T1, typename T2>
class Example
{
public:
T1 key;
T2 value;
Example(T1 k, T2 v) :key(k), value(v) { };
void Func(T1 t)
{
std::cout << "普通成员函数" << std::endl;
}
template <class T3>
void Func(T3 t)
{
std::cout << "成员函数模板" << std::endl;
}
};
调用:
std::string str = "hello";
int a = 10;
Example<std::string, int> example(str, a);
example.Func(a); //调用普通成员函数
example.Func<>(a); //调用成员函数模板
example.Func(str); //调用成员函数模板
为什么类模板能够执行不同的类型参数
和函数模板类似
类模板特化
类似于函数重载,存在多个相同的类名,但是模板类型都不一致
-
完全特化:表示显示指定类型参数,模板声明只需写成template<>,并在类名右侧指定参数
-
部分特化:表示通过特定规则约束类型参数,模板声明和类似,并在类名右侧指定参数
template <typename T1, typename T2>
class Example
{
public:
Example(T1 a, T2 b)
{
std::cout << "普通类模板" << std::endl;
}
};
template <>
class Example<int, int>
{
public:
Example(int a, int b)
{
std::cout << "完全特化" << std::endl;
}
};
template <typename T>
class Example< T&, T&>
{
public:
Example(T& a, T& b)
{
std::cout << "部分特化" << std::endl;
}
};
调用:
int a = 3, b = 4;
float c = 4.5;
Example<int, float> ex1(a, c);
Example<int, int> ex2(a, b);
Example<int &, int &> ex3(a, b);