1. 模板的一些使用的细节
我们直接来看一下场景:
#include <iostream> #include <vector> #include <list> using namespace std; template<class Container> void Print(const Container& v) { Container::const_iterator it = v.begin(); while (it != v.end()) { cout << *it << " "; it++; } cout << endl; } void test1() { vector<int> v{ 1, 2, 3, 4 }; list<int> lt{ 1, 2, 3, 4 }; Print(lt); } int main() { test1(); return 0; }
这段代码我们是实现了一个泛型,让这个 Print 拥有了遍历各种容器的能力,
但是这段代码是会被编译器报错的:
编译器这里告诉给我们很清楚了,叫我们在 const_iterator 类型前面加个 typename
template<typename Container> void Print(const Container& v) { typename Container::const_iterator it = v.begin(); while (it != v.end()) { cout << *it << " "; it++; } cout << endl; }
加上之后,确实是能跑过了:
那这是为什么呢?
因为编译器不知道你这个模板参数::const_iterator 究竟是一个类型还是一个对象(类型),
这里我可以举一个例子:
class A { public: static int const_iterator; };
如果有这样一个类,可以用 A::const_iterator,
那么编译器有怎么知道该怎么实例化呢?
所以你需要加上一个 typename 告诉编译器,你是一个类型,
这样编译器就让你过了。
但其实还有一种更方便的写法:
template<typename Container> void Print(const Container& v) { auto it = v.begin(); while (it != v.end()) { cout << *it << " "; it++; } cout << endl; }
是的,欢迎来到 auto 宣传片~,auto 一定是一个类型,所以编译器就不会报错了。
其实我们在学习优先级队列的时候就遇到过这样的场景:
他下面的这一段就是 typename 的应用场景,
当我们用到模板参数来取内嵌类型的时候,都要用 typename 提醒一下编译器我们是一个类型。
2. 非类型模板参数
我们直接来看场景,
比如说我们想要实现一个静态的栈:
让栈1 的容量是 10 而栈2 的容量是 100 ,显然做不到。
#define N 10 template<class T> class stack { private: T _a[N]; int _top; }; void test2() { stack<int> st1; stack<int> st2; }
这个时候我们可以用非类型模板参数来实现:(是的我们以前的叫做类型模板参数)
template<class T, size_t N> class stack { private: T _a[N]; int _top; }; void test2() { stack<int, 10> st1; stack<int, 100> st2; }
这样就可以通过模板来控制这个值了。
3. 模板的特化
我们直接来看一个场景:
template<class T> int Com(T a, T b) { return a > b; } // 模板的特化 template<> int Com<int*>(int* a, int* b) { return *a > *b; } void test3() { int a = 1, b = 0; cout << Com(a, b) << endl; cout << Com(&a, &b) << endl; }
如果我们想对一种特殊的情况进行不同的操作的时候,
我们可以使用模板特化的技术,因为模板会根据最匹配的一种来进行实例化。
但是,其实这种情况我们重载一个 T* 类型的函数似乎也能够做到,
那我们再来看一个例子:
template<class T1, class T2> class Date { public: Date() { cout << "Date<T1, T2>" << endl; } private: T1 _d1; T2 _d2; }; void test4() { Date<int, int> d1; Date<int, double> d2;
输出:
假如我想让用<int, double> 这个类模板的时候使用不同的逻辑,
我们就可以使用模板特化:
template<class T1, class T2> class Date { public: Date() { cout << "Date<T1, T2>" << endl; } private: }; template<> class Date<int, double> { public: Date() { cout << "Date<int, double>" << endl; } private: }; void test4() { Date<int, int> d1; Date<int, double> d2; }
输出:
我们可以看到传不同的模板参数,就可以走不同的逻辑。
我们上面的操作是全特化,其实我们可以部分特化,也就是偏特化:
template<class T1, class T2> class Date { public: Date() { cout << "Date<T1, T2>" << endl; } private: }; template<class T1> class Date<T1, double> { public: Date() { cout << "Date<int, double>" << endl; } private: }; void test4() { Date<int, int> d1; Date<int, double> d2; }
这样的操作也是支持的。
偏特化其实还支持一种操作,就是对某些类型进行限制:
template<class T1, class T2> class Date { public: Date() { cout << "Date<T1, T2>" << endl; } private: }; template<class T1> class Date<T1, double> { public: Date() { cout << "Date<int, double>" << endl; } private: }; template<class T1, class T2> class Date<T1*, T2*> { public: Date() { cout << "Date<T1*, T2*>" << endl; } private: }; void test4() { Date<int, int> d1; Date<int, double> d2; Date<int*, double*> d3; }
输出:
就像这种情况。
4. 模板的分离编译
模板是不支持分离编译的。
来看这个例子:
Stack.h 声明:
Stack.cpp 定义
test.cpp 调用:
出现链接错误:
实际上,模板还没实例化,导致编译器链接的时候找不到定义。
那有没有什么方式可以解决呢?
可以在这里进行显示实例化操作,这样就可以支持了。
但是这种方式也不好用,如果别人再传一个 double 类型,
又得写一个显示实例化 double 的操作,还是很麻烦的。
所以如果使用模板的话比较建议的是如果有很大的函数,
可以在同一个文件里面声明和定义分离(一个在类内一个在类外)
写在最后:
以上就是本篇文章的内容了,感谢你的阅读。
如果感到有所收获的话可以给博主点一个赞哦。
如果文章内容有遗漏或者错误的地方欢迎私信博主或者在评论区指出~