3 - 函数模板,成员函数模板
本文主要介绍了独立函数的模板和类中成员函数的模板该如何编写。
函数模板
可以为独立函数编写模板,在现代 C++ 中,这是一个 一直普遍使用 的技术。
定义
和类模板类似:
namespace mystd { // 模拟for-each算法 template<typename iter_type, typename func_type> void for_each(iter_type first, iter_type last, func_type func) { for (auto iter = first; iter != last; ++iter) { func(*iter); } } }
在名称空间 mystd
中,编写了一个类似于 for-each
语法的函数模板。该函数模板可以对元素从头到尾遍历,并完成自定义函数 func()
的工作。
需要注意的是:函数模板不是函数,它必须经过实例化才能生成实际的函数定义。
实例化
可通过两种方式实例化这个函数:一种是通过尖括号显式地指定类型;另一种是忽略类型,让编译器根据里边的参数自行推导(编译期完成):
std::vector<int> iVec{ 1, 2, 3, 4, 5 }; // 显式指定类型 mystd::for_each<std::vector<int>::iterator, void(*)(int&)>(iVec.begin(), iVec.end(), [](int& elem) { ++elem; }); // 忽略类型,让编译器自行推导 mystd::for_each(iVec.begin(), iVec.end(), [](int& elem) { ++elem; });
我们定义了一个 vector<int>
,使用 for_each
函数模板,前两个参数是迭代器的开始和末尾,最后一个参数则是自定义的 lambda
表达式,可以让容器里的元素自增 1。
重载
template<typename T> void test(T) { std::puts("template"); } void test(int) { std::puts("int"); } test(1); // 匹配到test(int) test(1.2); // 匹配到模板 test("1"); // 匹配到模板
如上述代码所示,函数模板和非模板函数间可以重载。通常优先选择非模板的函数,具体规则详见重载决议。
成员函数模板
C++ 允许模板化类中的单个方法,可以在类模板中进行,也能在非模板化的类中进行。成员函数模板对赋值运算符和拷贝构造函数非常有用。
需要注意的是:不能用方法模板编写虚方法和析构函数。
定义
template<typename T> class MyVector { public: template<typename T2> void outPut(const T2& elem); }; // 类外定义 template<typename T> template<typename T2> void MyVector<T>::outPut(const T2& elem) { std::cout << elem << std::endl; }
首先定义了类模板 MyVector
,里边有个成员函数模板 outPut()
。
使用
mystd::MyVector<std::string> myVec; myVec.outPut(233);
可以正确输出 233,因为该成员函数主要看的是 T2 类型,而不是 T 类型。
用法
赋值运算符和拷贝构造函数
假设有一类模板 Test,它的拷贝构造函数和赋值运算符如下:
Test (const Test& src); Test& operator=(const Test& rhs);
假设 T 为 double
,那么上边的代码就会生成 Test<double>
专属的,此时参数不能为 Test<int>
,即使 int
可以强转为 double
。
现在成员函数模板便派上用场,只需 再增加模板化的赋值运算符和拷贝构造函数即可:
template<typename E> Test (const Test<E>& src); template<typename E> Test<T>& operator=(const Test<E>& rhs);
现在就能接收 Test<int>
类型的参数了。注意不要删除原来的拷贝构造函数和赋值运算符,因为如果 E 和 T 类型相同,编译器还是会调用原来的。并且,赋值运算符接收 E 类型,但返回 T 类型。