关于我们

质量为本、客户为根、勇于拼搏、务实创新

< 返回新闻公共列表

【C++】你想要的——印刷模板儿(下)

发布时间:2023-06-29 15:00:32

3.模板函数和自定义函数

当模板函数和自己实现的函数是否可以同时存在时?

//专门处理int的加法函数 int Add(int left, int right) {  return left + right; }  // 通用加法函数 templateT Add(T left, T right) {  return left + right; }  int main() {  int a = 1, b = 2;  Add(a, b);   Add(a, b);   return 0; }

   

当自己写的函数和模板函数同时存在时,二者不会冲突,在之前我们讲过他们的函数名修饰规则是不同的。


同时存在,且调用时,首先会调用自己写的函数。因为模板函数相当于一个半成品,他需要推演实例化才会生成具体的函数,所以当然先使用自己实现的。


如果一定要使用模板函数的话,就需要显示实例化:Add(a,b);


这就叫泛型编程,与具体的类型无关!


2.类模板

类模板与函数模板不同的是:类模板统一显式实例化,不需要推演,或者说没有推演的时机,而函数模板实参传递形参时,就会发生推演实例化。


格式:

templateclass 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() {  // 显示实例化  Stackst1; // double  st1.Push(1.1);   Stackst2; // int  st2.Push(1);  // s1,s2是同一个类模板实例化出来的,但是模板参数不同,他们就是不同类型  return 0; }

   

可能有人会问:s1=s2;  会不会发生隐式类型转换呢?当然不会,隐式类型转换只有在类型相近才会发生。

接下来创建一个数组类模板:

namespace mj {  template class array  {  public:  inline T& operator[](size_t i) //这里引用做返回值的目的是除了减少拷贝构造,还有可以修改返回值,直接可以修改数组里的值  {  assert(i < N); //严格控制越界访问的情况  return _a[i];  }  private:  T _a[N];  }; }  int main() {  mj::arraya1;  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中定义没有实例化!!


办法一:


  你没有实例化,我给你补上:在定义后面加一个实例化

templateStack::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;

   

但是就会有另一个问题,我如果使用的时候,创建不同类型,那模板实例化就要有不同类型,那就要一直补实例化,总不肯用一个补一个吧。


方法二:


那就是模板的编译不分离:(不要将定义和声明一个到.cpp,一个到.h)


当放在一个文件中时,在编译时,.h 文件展开后,定义和声明都在test.cpp中,那直接就会完成模板实例化,就有了函数地址,不需要再去链接了。


链接:只有声明没有定义才会到处去找定义。


那有人就会问,加inline可以吗?


inline当然不可以,加了inline后,直接不产生符号表,还存在什么地址吗?


直接放类中也不行,当数据量大的时候,都挤到一推,代码阅读性很差,会傻傻搞不清!


总结:


初阶模板,概念,分类,要注意的地方,这对后续的学习都是非常重要的,慢慢铺垫,厚积薄发!!


/template/Home/leiyu/PC/Static