②显式实例化:在函数名后的<>中指定模板参数的实际类型
template<typename Ad> Ad Add(const Ad& a, const Ad& b) { return a + b; } int main() { int a1 = 10, a2 = 20; double d1 = 6.4, d2 = 4.8; Add<int>(a1, d1);//我们帮助编译器明确类型 return 0; }
我们帮助模板明确推演类型。
Ⅴ模板参数的匹配原则
①一个非模板函数可以和一个同名的函数模板同时存在
//通用加法函数 template<typename Ad> Ad Add(const Ad& a, const Ad& b) { return a + b; } //专门处理int的加法函数 int Add(const int& a, const int& b) { return a + b; } int main() { int a1 = 10, a2 = 20; double d1 = 6.4, d2 = 4.8; Add<int>(a1, a2);//我们帮助编译器明确类型 return 0; }
我想在这里问一个问题,我们调用int类型的加法函数时,编译器会自己类型推演int还是直接用现成的int加法函数呢?
编译器也会偷懒,如果这里有现成的,编译器是不会自己推演化实现的!!!
🖊对于非模板函数和同名函数模板,如果其他条件都相同,在调用时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数,那么将选择模板。
那这里还有一个问题,我们难道不能实现不同类型的加法吗?其实是可以的,我们想一下函数模板类型是可以定义多个的:
template<typename T1,typename T2,......,typename Tn>
我们给两个函数模板类型就好了。
三、类模板
Ⅰ类模板的定义格式
template<class T1,class T2,...class Tn> class 类模板名 { //类成员定义 };
类模板实例化只能显式实例化,需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。
// Vector类名,Vector<int>才是类型 Vector<int> s1; Vector<double> s2;
s1和s2是同一个类模板实例化出来的,但是模板参数(一个是int,一个是double)不同,那么s1和s2就是不同的类型。
①为什么要有类模板?
typedef int T; //template<typename T> class Stack { public: void Init() { _array = (T*)malloc(sizeof(T) * 3); if (NULL == _array) { perror("malloc申请空间失败!!!"); return; } _capacity = 3; _size = 0; } void Destroy() { if (_array) { free(_array); _array = NULL; _capacity = 0; _size = 0; } } private: void CheckCapacity() { if (_size == _capacity) { int newcapacity = _capacity * 2; T* temp = (T*)realloc(_array, newcapacity * sizeof(T)); if (temp == NULL) { perror("realloc申请空间失败!!!"); return; } _array = temp; _capacity = newcapacity; } } private: T* _array; int _capacity; int _size; };
比如说一个栈,我们想要数据类型是int,就typedef为int,我们想要数据类型为double,就typedef为double。好像类模板没有啥用处,我们typedef也可以做到不同的数据类型处理。但是typedef解决不了什么问题?比如说:我同时需要两个栈,第一个栈数据类型为double,第二个数据类型为int。那么我们这时typedef为哪种类型呢?如果typedef两个,那么类也需要拷贝两份,显然比较不合适。所以typedef它解决的是可维护性,不是所谓的泛型编程。
②类模板不支持分离编译
我们之前写类的时候,为了代码的可读性和可维护性常常定义和声明分离。那么我们在写类模板的时候是否可以呢?
/*Stack.hpp*/ #pragma once #include<iostream> using namespace std; template<typename T> class Stack { public: Stack(int capacity = 4); ~Stack(); void Push(const T& x); private: T* _a; int _top; int _capacity; }; /*Stack.cpp*/ #include "Stack.hpp" template<class T> Stack<T>::Stack(int capacity) { cout << "Stack(int capacity = )" << capacity << endl; _a = (T*)malloc(sizeof(T)*capacity); if (_a == nullptr) { perror("malloc fail"); exit(-1); } _top = 0; _capacity = capacity; } template<class T> Stack<T>::~Stack() { cout << "~Stack()" << endl; free(_a); _a = nullptr; _top = _capacity = 0; } template<class T> void Stack<T>::Push(const T& x) { // .... // 扩容 _a[_top++] = x; } /*test.cpp*/ #include"Stack.hpp" int main() { Stack<int> st1; st1.Push(1); return 0; }
🖊这里需要注意,类模板中函数放在类外进行定义时,需要加模板参数列表。
我们就写了Stack类的定义和声明分离,然后我们可以运行测试一下就会发现:
它会报链接错误,为什么会报链接错误呢?我不是包了头文件吗?这是因为,头文件在函数实现里面展开,也就是定义的地方展开,但是它没有实例化!也就是它没有收到main函数传的推演类型,所以函数实现的文件里面还是模板,没有生成函数放进符号表。所以会链接错误。
能解决吗?可以!我们需要在函数文件里告诉它我们想让它推演的类型!
/*Stack.cpp*/ . . . . // 显示实例化 template class Stack<int>;
这样就解决了,但是我这只是传一个类型int,如果我还想推演实例化double类型还需要再写这样一个到函数文件。所以,很不爽。
③声明和定义都放在.h或者.hpp文件
.hpp文件是专供模板类的文件,它是.cpp和.h的结合。
#pragma once #include<iostream> using namespace std; template<typename T> class Stack { public: Stack(int capacity = 4); ~Stack(); void Push(const T& x); private: T* _a; int _top; int _capacity; }; template<class T> Stack<T>::Stack(int capacity) { cout << "Stack(int capacity = )" << capacity << endl; _a = (T*)malloc(sizeof(T)*capacity); if (_a == nullptr) { perror("malloc fail"); exit(-1); } _top = 0; _capacity = capacity; } template<class T> Stack<T>::~Stack() { cout << "~Stack()" << endl; free(_a); _a = nullptr; _top = _capacity = 0; } template<class T> void Stack<T>::Push(const T& x) { // .... // 扩容 _a[_top++] = x; }