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

简介: 【C++】你想要的——印刷模板儿

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后,直接不产生符号表,还存在什么地址吗?


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


总结:


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

目录
相关文章
|
1月前
|
存储 算法 C++
C++ STL 初探:打开标准模板库的大门
C++ STL 初探:打开标准模板库的大门
95 10
|
3月前
|
编译器 C++
【C++】——初识模板
【C++】——初识模板
【C++】——初识模板
|
1月前
|
编译器 程序员 C++
【C++打怪之路Lv7】-- 模板初阶
【C++打怪之路Lv7】-- 模板初阶
16 1
|
1月前
|
编译器 C语言 C++
C++入门6——模板(泛型编程、函数模板、类模板)
C++入门6——模板(泛型编程、函数模板、类模板)
41 0
C++入门6——模板(泛型编程、函数模板、类模板)
|
1月前
|
算法 编译器 C++
【C++篇】领略模板编程的进阶之美:参数巧思与编译的智慧
【C++篇】领略模板编程的进阶之美:参数巧思与编译的智慧
79 2
|
1月前
|
存储 编译器 C++
【C++篇】引领C++模板初体验:泛型编程的力量与妙用
【C++篇】引领C++模板初体验:泛型编程的力量与妙用
38 2
|
1月前
|
存储 算法 编译器
【C++】初识C++模板与STL
【C++】初识C++模板与STL
|
1月前
|
编译器 C++
【C++】模板进阶:深入解析模板特化
【C++】模板进阶:深入解析模板特化
|
2月前
|
存储 算法 程序员
C++ 11新特性之可变参数模板
C++ 11新特性之可变参数模板
55 0
|
3月前
|
并行计算 测试技术 开发工具
【简历模板】c/c++软件工程师
【简历模板】c/c++软件工程师
74 0