3.模板函数和自定义函数
当模板函数和自己实现的函数是否可以同时存在时?
//专门处理int的加法函数 int Add(int left, int right) { return left + right; } // 通用加法函数 template<class T> T Add(T left, T right) { return left + right; } int main() { int a = 1, b = 2; Add(a, b); Add<int>(a, b); return 0; }
当自己写的函数和模板函数同时存在时,二者不会冲突,在之前我们讲过他们的函数名修饰规则是不同的。
同时存在,且调用时,首先会调用自己写的函数。因为模板函数相当于一个半成品,他需要推演实例化才会生成具体的函数,所以当然先使用自己实现的。
如果一定要使用模板函数的话,就需要显示实例化:Add(a,b);
这就叫泛型编程,与具体的类型无关!
2.类模板
类模板与函数模板不同的是:类模板统一显式实例化,不需要推演,或者说没有推演的时机,而函数模板实参传递形参时,就会发生推演实例化。
格式:
template<typename T> class Stack { public: Stack(int capacity = 4) { _a = (T*)malloc(sizeof(T)*capacity); if (_a == nullptr) { perror("malloc fail"); exit(-1); } _top = 0; _capacity = capacity; } ...... private: T* _a; int _top; int _capacity; }; int main() { // 显示实例化 Stack<double> st1; // double st1.Push(1.1); Stack<int> st2; // int st2.Push(1); // s1,s2是同一个类模板实例化出来的,但是模板参数不同,他们就是不同类型 return 0; }
可能有人会问:s1=s2; 会不会发生隐式类型转换呢?当然不会,隐式类型转换只有在类型相近才会发生。
接下来创建一个数组类模板:
namespace mj { template<class T> class array { public: inline T& operator[](size_t i) //这里引用做返回值的目的是除了减少拷贝构造,还有可以修改返回值,直接可以修改数组里的值 { assert(i < N); //严格控制越界访问的情况 return _a[i]; } private: T _a[N]; }; } int main() { mj::array<int> a1; for (size_t i = 0; i < N; ++i) { //相当于: a1.operator[](i)= i; a1[i] = i; } for (size_t i = 0; i < N; ++i) { // a1.operator[](i) cout << a1[i] << " "; } cout << endl; for (size_t i = 0; i < N; ++i) { a1[i]++; } for (size_t i = 0; i < N; ++i) { cout << a1[i] << " "; } cout << endl; return 0; }
我们可以发现,类对象居然也可以使用数组那一套了??当然是取决于运算符重载。
他与普通数组最大的区别是:
1. 普通数组对于数组越界的这种情况,只能随机的抽查!而我们自己实现的类模板可以严格的控制越界访问这种情况!别说越界修改,越界访问都不行!
2.效率上因为[]是运算符重载,使用就会调用函数开辟栈帧,但是若定义到类中,并且加inline,就对于效率来说,那真是完美!
3.模板不支持分离编译
我们在实现数据结构的时候,是不是会经常去分几个文件去实现不同模块的功能?
(个人习惯).h文件中,我们写声明;.cpp文件中,我们写定义;test.cpp中,我们测试使用
但今天学习的模板就不能这么做了!!具体不能怎么做,我们上代码:
如果这样写的话,他就会报链接错误(就是在使用时找不到定义)
我们知道,在预处理阶段,就会将.h头文件展开,test.cpp中只有声明,在调用函数时,就会去找他的地址(call stack()),那么在编译的时候,编译器允许只有声明没有函数,相当于你可以先给他一个承诺,兑不兑现后面再说。
但在链接的时候,test.cpp中,却不能找到它的地址,这是为什么??这就是模板和其他的区别!
链接错误原因:
.cpp中的定义,不是实例化模板,他只是一个模板,没有任何实例化成任何类型。所以你在使用类模板的时候,压根就找不到它的定义,当然也找不到地址了,这不就链接错误了吗?
看上图:stack st; 显示实例化,但是.h中只有声明,test.cpp用的地方实例化了,但是定义的地方stack.cpp却没有实例化,只是一个模板。
用的地方在实例化,但是有声明,没有定义;
定义的地方没有实例化。
解决方法:
那转来转去就是一个问题:stack.cpp中定义没有实例化!!
办法一:
你没有实例化,我给你补上:在定义后面加一个实例化
template<class T> Stack<T>::Stack(int capacity = 4) { 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 Stack<int>;
但是就会有另一个问题,我如果使用的时候,创建不同类型,那模板实例化就要有不同类型,那就要一直补实例化,总不肯用一个补一个吧。
方法二:
那就是模板的编译不分离:(不要将定义和声明一个到.cpp,一个到.h)
当放在一个文件中时,在编译时,.h 文件展开后,定义和声明都在test.cpp中,那直接就会完成模板实例化,就有了函数地址,不需要再去链接了。
链接:只有声明没有定义才会到处去找定义。
那有人就会问,加inline可以吗?
inline当然不可以,加了inline后,直接不产生符号表,还存在什么地址吗?
直接放类中也不行,当数据量大的时候,都挤到一推,代码阅读性很差,会傻傻搞不清!
总结:
初阶模板,概念,分类,要注意的地方,这对后续的学习都是非常重要的,慢慢铺垫,厚积薄发!!