模板(初阶)

简介: 模板(初阶)

一、泛型编程


如何实现一个通用的Swap函数


void Swap(int& x, int& y)
{
  int tmp = x;
  x = y;
  y = tmp;
}
void Swap(double& x, double& y)
{
  double tmp = x;
  x = y;
  y = tmp;
}
int main()
{
  int a = 10;
  int b = 20;
  printf("交换前:a=%d , b=%d\n", a, b);
  Swap(a, b);
  printf("交换后:a=%d , b=%d\n", a, b);
  double c = 12.5;
  double d = 24.2;
  printf("交换前:a=%.2lf , b=%.2lf\n", c, d);
  Swap(c, d);
  printf("交换后:a=%.2lf , b=%.2lf\n", c, d);
  return 0;
}


像上面那样使用函数重载虽然可以不知不觉地实现不同的参数类型达到一样的交换效果,但是可以看到,Swap函数除了参数类型不同,其他的运算逻辑都是一样的。


函数重载有两个不好的地方:一是代码的复用性低,只要有新的类型出现就要再重载一个函数,代码冗余太严重。二是维护成本较高,一个重载函数出错就会导致所有的重载函数都出错,修改成本太大。


那么我们能否给编译器一个模子,让编译器根据不同的类型利用该模子来生成一份对应的代码呢?基于这样的问题,C++就出现了模板,之后又推出了泛型编程。


泛型编程:即编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础。


二、函数模板


2.1 函数模板的概念


函数模板代表了一个函数家族,该函数模板与类型无关,在调用函数时被实例化,根据实参类型产生特定类型版本的函数。


2.2 函数模板的格式


template<class T1,class T2,class T3…>


返回值 函数名(参数列表){函数体}


例如Swap函数:


template <class T>
void Swap(T& x, T& y)
{
  T tmp = x;
  x = y;
  y = tmp;
}
int main()
{
  int a = 10;
  int b = 20;
  printf("交换前:a=%d , b=%d\n", a, b);
  Swap(a, b);
  printf("交换后:a=%d , b=%d\n", a, b);
  double c = 12.5;
  double d = 24.2;
  printf("交换前:a=%.2lf , b=%.2lf\n", c, d);
  Swap(c, d);
  printf("交换后:a=%.2lf , b=%.2lf\n", c, d);
  return 0;
}



由上图可以看出,写了一个Swap函数模板之后,无论需要交换什么类型的数据,都不用手动再写同样逻辑的代码,而是编译器会自动生成一份对应的代码完成数据的交换,


2.3 函数模板的原理


函数模板本身并不是一个函数,而是一个模具,是编译器使用特定方式产生具体类型函数的模具。就好比我们工厂在制造月饼的时候所用的月饼模,它本身并不是月饼,只不过把材料放到这个模具之后就能形成月饼的形状。换句话说其实模板就是将本来应该我们做的重复的事情交给了编译器做。



在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。比如:当用int类型使用函数模板时,编译器通过对实参类型的推演,将T确定为int类型,然后产生一份专门处理int类型的代码,对于其他类型也是如此。


2.4 函数模板的实例化


用不同类型的参数使用函数模板生成对应的函数,称为函数模板的实例化。模板参数实例化分为:隐式实例化和显式实例化。


1、隐式实例化:让编译器根据实参推导出模板参数的实际类型。


2、显式实例化:在函数名后的<>中指定模板参数的实际类型。


template <class T>
T Add(const T& x,const T& y)
{
  return x + y;
}
int main()
{
  隐式实例化,通过传实参a、b给Add函数模板,自动生成一份
  int类型的Add函数代码
  int a = 10;
  int b = 20;
  int sum1 = Add(a, b);
  cout << sum1 << endl;
  隐式实例化,通过传实参c、d给Add函数模板,自动生成一份
  double类型的Add函数代码
  double c = 12.5;
  double d = 24.2;
  double sum2 = Add(c, d);
  cout << sum2 << endl;
  //Add(a, d);
  //这样写是错误的,因为该函数模板只有一个模板参数T,而a的类型是
  //int,d的类型是double;编译器无法确定这里的T应该推导为int还是double从而报错
  //在模板中,编译器不会进行类型转换
  //这时解决方法有两个:
  //1、强制类型转换 
  int sum3 = Add(a, (int)d);
  cout << sum3 << endl;
  //2、显式实例化
  //如果类型不匹配,编译器会尝试进行隐式类型转换,如果无法成功
  // 转换编译器将会报错。
  //例如这里的c是double类型,编译器会把它隐式转换成int类型再
  //利用模板实例化出int类型的Add函数,再调用
  int sum4 = Add<int>(a, c);
  cout << sum4 << endl;
  return 0;
}


2.5 模板参数的匹配原则


  1. 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数。





  1. 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会用模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择用模板特化出来一个更匹配的版本。




  1. 函数模板不支持自动类型转换,但普通函数支持进行自动类型转换。


三、类模板


3.1 类模板的定义格式


下面是一个简单的作为示例的Stack类:


//注意:Stack不是具体的类,是编译器根据被实例化的类型生成具体类的模具
template <class T>
class Stack
{
public:
  Stack(int capacity = 10)
    :_a(new T[capacity])
    ,_capacity(capacity)
    ,_top(0)
  {}
  ~Stack()
  {
    if (_a)
    {
      delete[] _a;
      _a = nullptr;
    }
    _capacity = _top = 0;
  }
  //Push函数类中声明,类外定义
  void Push(const T& x);
private:
  T* _a;
  int _capacity;
  int _top;
};
// 注意:类模板中函数放在类外面定义时,需要加模板参数列表
//并且这个模板参数列表只在这个Push函数内有效,即每定义一个
//成员函数,都需要写一遍模板参数列表(谨记)
template <class T>
void Stack<T>::Push(const T& x)
{
  _a[_top] = x;
  _top++;
}
int main()
{
  //实例化成存放int类型数据的栈
  Stack<int> st1;
  st1.Push(1);
  st1.Push(2);
  st1.Push(3);
  st1.Push(4);
  //实例化成存放double类型的栈
  Stack<double> st2;
  st2.Push(1.2);
  return 0;
}


3.2 类模板的实例化


类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型(int,double,char或者自定义类型等)放在<>中即可,类模板名字不是真正的类,相当于一张图纸,而实例化的结果才是真正的类,实例化得到的具体的类才能用来定义对象。


下面的Stack是类名,Stack<int>和Stack<double>才是类型
即Stack是类模板,Stack<int>才是实例化出来的具体的类
Stack<int> st1;
Stack<double> st2;
相关文章
|
机器学习/深度学习 PyTorch 算法框架/工具
训练误差与泛化误差的说明
训练误差与泛化误差的说明
538 0
在mac OSX中安装启动zookeeper(采用brew安装方式)
项目需要,所以,在mac OSX中安装了一下zookeeper。
975 0
|
SQL Java 数据库连接
SQL SELECT语句的基本用法
SQL SELECT语句的基本用法
|
8月前
|
Java 调度 开发者
Java线程池ExecutorService学习和使用
通过学习和使用Java中的 `ExecutorService`,可以显著提升并发编程的效率和代码的可维护性。合理配置线程池参数,结合实际应用场景,可以实现高效、可靠的并发处理。希望本文提供的示例和思路能够帮助开发者深入理解并应用 `ExecutorService`,实现更高效的并发程序。
197 10
|
机器学习/深度学习 PyTorch 算法框架/工具
神经网络中的分位数回归和分位数损失
在使用机器学习构建预测模型时,我们不只是想知道“预测值(点预测)”,而是想知道“预测值落在某个范围内的可能性有多大(区间预测)”。例如当需要进行需求预测时,如果只储备最可能的需求预测量,那么缺货的概率非常的大。但是如果库存处于预测的第95个百分位数(需求有95%的可能性小于或等于该值),那么缺货数量会减少到大约20分之1。
823 2
|
存储 数据挖掘 Linux
探索Linux命令rpm2cpio:解析RPM包内容的利器
`rpm2cpio`是Linux下用于从RPM包中提取内容的工具,它将`.rpm`转换为CPIO归档。无需安装,可直接访问包内文件,适合数据分析。命令简单,常与`cpio`结合使用,如`rpm2cpio package.rpm | cpio -idmv`解压文件。示例包括提取特定文件和列出包内所有文件。注意权限、路径和文件完整性,使用前备份数据,并查阅文档以优化使用。
|
SQL 关系型数据库 MySQL
如何在 MySQL 或 MariaDB 中导入和导出数据库
如何在 MySQL 或 MariaDB 中导入和导出数据库
1032 0
|
人工智能 运维 自然语言处理
对话蚂蚁李建国:当前AI写代码相当于L2.5,实现L3后替代50%人类编程
超70%代码问题,单纯靠基座大模型是解决不了的;未来3-5年,人类50%编程工作可以被替代,有些环节甚至完全自动化。蚂蚁集团代码大模型CodeFuse负责人李建国说道。当下,AI代码生成领域正在野蛮式生长,巨头涌入,AI员工频频上线企业;首个AI程序员Devin被曝造假…… 面对风起云涌的代码生成变革,李建国给出了这样一个明确论断。
262 1
|
机器学习/深度学习 数据可视化 Python
数据分享|Python用偏最小二乘回归Partial Least Squares,PLS分析桃子近红外光谱数据可视化
数据分享|Python用偏最小二乘回归Partial Least Squares,PLS分析桃子近红外光谱数据可视化
|
机器学习/深度学习 人工智能 图计算
【视觉AIGC识别】误差特征、人脸伪造检测、其他类型假图检测
【视觉AIGC识别】误差特征、人脸伪造检测、其他类型假图检测
635 0