【C++】一文教你认识模板
创始人
2025-05-28 05:22:56
0

目录

  • 1.泛型编程
  • 2.函数模板
    • 2.1函数模板的原理
    • 2.2函数模板的实例化
    • 2.3模板参数的匹配原则
  • 3.类模板
    • 3.1类模板的定义格式
    • 3.2类模板的实例化

1.泛型编程

我们先来看一个swap函数

void Swap(int& a,int& b)
{int c = a;a = b;b = c;
}void Swap(double& a, double& b)
{double c = a;a = b;b = c;
}void Swap(char& a, char& b)
{char c = a;a = b;b = c;
}

像这么一个简单的Swap函数,因为参数的类型不同,我们就要针对不同的类型来创建多个Swap函数,这样的情况虽然是以函数重载可以实现,但有以下两点不好的地方:

  1. 重载的函数仅仅是类型不同,代码复用率比较低,只要有新类型出现时,就需要用户自己增加对应的函数
  2. 代码的可维护性比较低,一个出错,可能所有的重载均出错。

那我们能不能告诉编译器一个模子,让编译器根据不同的类型利用该模子来生成代码 呢?

古人似乎也面对过这样的问题,一个人写出了一篇照耀古今的文章,想要将其传播出去,但又受限于科技的发展,只能照人手抄,这就限制了知识的传播,直到活字印刷的出现,这一现象发生革命性的改变

在这里插入图片描述

以框为模板,只要我们替换里面的模块,就能印刷出不同的文章,不得不佩服古人的智慧。

如果C++中,也能存在这样一个模具,那将会节省我们很多时间,推迟掉发的速度。刚好C++中就有这样的方法。

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

在C++中,模板分为函数模板类模板

2.函数模板

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

函数模板格式如下:

template

返回值类型 函数模板名(T1 参数名, …, Tn 参数名){}

注意:

  1. typename是用来定义模板参数的关键字,也可以使用class,但不能用struct。
  2. template设定模板参数与下面紧挨着的函数统称为函数模板,对应的模板参数不能在其他函数内使用,想要用只能继续创建函数模板。
  3. 模板参数设置几个就要用几个,不能有剩余的,编译器会报错,如下:
template
T Add(const T& a, const T& b)
{return a + b;
}int main()
{int a1 = 1,a2 = 2;Add(a1,a2);return 0;
}

在这里插入图片描述

template
void Swap(T& a,T& b)
{T c = a;a = b;b = c;
}int main()
{int a = 1;int b = 2;Swap(a, b);cout << a << " " << b << endl;return 0;
}

在这里插入图片描述

2.1函数模板的原理

函数模板是一个模板,不是函数,是编译器用使用方式产生特定具体类型函数的模具。所以其实模板就是将本来应该我们做的重复的事情交给了编译器。

我们编写如下代码查看调用函数模板是否地址相同(在汇编环境下查看,若是直接调试,调用的都是函数模板):

template
void Swap(T& a,T& b)
{T c = a;a = b;b = c;
}int main()
{int a = 1, b = 2;Swap(a, b);double c = 1.0, d = 2.0;Swap(c, d);return 0;
}

在这里插入图片描述

如上图,两次调用的函数的地址不同,

而如果是下面相同类型的两次函数的调用,则地址相同:

template
void Swap(T& a,T& b)
{T c = a;a = b;b = c;
}int main()
{int a = 1, b = 2;Swap(a, b);int c = 3, d = 4;Swap(c, d);return 0;
}

在这里插入图片描述

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

在这里插入图片描述

拓展:

Swap函数是经常使用的函数,在库内也就定义好了,我们想要使用可以直接调用库内的swap函数即可

在这里插入图片描述

#include
using namespace std;int main()
{int a = 1, b = 2;swap(a, b);cout << a << " " << b << endl;return 0;
}

在这里插入图片描述

2.2函数模板的实例化

用不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数实例化有分为:隐式实例化和显示实例化。

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

    template
    T Add(const T& a, const T& b)
    {return a + b;
    }int main()
    {int a1 = 1, a2 = 2;cout << Add(a1, a2) << endl;double b1 = 1.1, b2 = 2.2;cout << Add(b1, b2) << endl;return 0;
    }
    

    在这里插入图片描述

    若是执行下面的语句,则会报错

    	Add(a1, b1);
    

    因为在编译期间,当编译器看到该实例化时,需要推演其实参类型,通过实参a1将T推演为int,通过实参b1将T推演为double类型,但模板参数列表中只有一个T,编译器无法确定T到底是什么类型,因此报错。

    注意: 在模板中编译器不会进行自动类型转换,因为编译器毕竟是编译器它没有那么智能,不会揣摩程序员的心思,知道是转int还是double所以直接不转。

    想要解决这个问题有三种方法:

    1. 用户自己强制类型转换

      	Add(a1, (int)b1);//结果为int类型的值Add((double)a1, b1);//结果为double类型的值
      
    2. 在创造一个模板参数,使用两个模板参数接收两个参数

      template
      T1 Add(const T1& a, const T2& b)
      {return a + b;
      }int main()
      {int a1 = 1;double b1 = 1.1;cout << Add(a1, b1) << endl;return 0;
      }
      

      缺陷:但需要返回的是什么类型仍需要程序员自己挑动。

    3. 使用显示实例化

      	Add(a1,b1);//将参数全部置为int类型Add(a1,b1);//将参数全部置为double类型
      
  2. 显式实例化:在函数名后的<>中指定模板参数的实际类型。

    int main()
    {int a1 = 1;double b1 = 1.1;cout << Add(a1, b1) << endl;cout << Add(a1, b1) << endl;return 0;
    }
    

    在这里插入图片描述

    如果类型不匹配,编译器会尝试按照<>内的类型进行隐式类型转换,如果无法转换成功,编译器会报错。

2.3模板参数的匹配原则

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

    template
    T Add(const T& a)
    {return a + b;
    }int Add(const int& a, const int& b)
    {return a + b;
    }int main()
    {Add(1, 2);Add(1, 2);return 0;
    }
    

    如上面的Add(1,2),调用的是非模板函数,因为传到就是int型的参数,调用非模板函数直接就可以使用,而调用模板函数还需要实例化,编译器会选择调用更省时省力的函数。

    Add(1,2),因为显示实例化的存在只会去调用函数模板,将其实例化为与非模板函数相同的函数之后执行函数。

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

    template
    T1 Add(const T1& a, const T2& b)
    {return a + b;
    }int Add(const int& a, const int& b)
    {return a + b;
    }int main()
    {Add(1, 2);//与非模板函数匹配,编译器不需要特化Add(1, 2.0);//调用函数模板return 0;
    }
    

    Add(1,2)与非模板函数完全吻合,调用非模板函数,省时省力。

    Add(1,2.0)类型不同,只能通过模板实例化出相同类型的函数,调用模板。

  3. 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换。

    int Add(const int& a, const int& b)
    {return a + b;
    }int main()
    {cout << Add(1, 2.0) << endl;return 0;
    }
    

    在这里插入图片描述

3.类模板

3.1类模板的定义格式

template
class 类模板名
{//类内成员定义
};

使用类模板编写如下代码

template
class Stack
{
public:Stack(int capacity = 4){_a = new T[capacity];_top = 0;_capacity = capacity;}//声明和定义分离~Stack();private:T* _a;int _capacity;int _top;
};//在类外定义
template
Stack::~Stack();
{delete[] _a;_top = _capacity = 0;
}

注意:

  1. 类模板内的函数在类内声明,类外定义的写法要注意,与正常类不同。
  2. 类模板如果声明和定义分离,需要在一个文件内完成,不能拆分为两个文件,比如:声明放在.h文件,定义放在.c文件这是不允许的。

3.2类模板的实例化

类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板的名字不是真正的类实例化的结构才是真正的类。

使用上面编写的类模板

template
class Stack
{
public:Stack(int capacity = 4){_a = new T[capacity];_top = 0;_capacity = capacity;}~Stack(){delete[] _a;_top = _capacity = 0;}
private:T* _a;int _capacity;int _top;
};int main()
{Stack st1;Stack st2;return 0;
}
  • Stack不是类名,不是真正的类,Stack<类型>才是类

相关内容

热门资讯

linux入门---制作进度条 了解缓冲区 我们首先来看看下面的操作: 我们首先创建了一个文件并在这个文件里面添加了...
C++ 机房预约系统(六):学... 8、 学生模块 8.1 学生子菜单、登录和注销 实现步骤: 在Student.cpp的...
JAVA多线程知识整理 Java多线程基础 线程的创建和启动 继承Thread类来创建并启动 自定义Thread类的子类&#...
【洛谷 P1090】[NOIP... [NOIP2004 提高组] 合并果子 / [USACO06NOV] Fence Repair G ...
国民技术LPUART介绍 低功耗通用异步接收器(LPUART) 简介 低功耗通用异步收发器...
城乡供水一体化平台-助力乡村振... 城乡供水一体化管理系统建设方案 城乡供水一体化管理系统是运用云计算、大数据等信息化手段࿰...
程序的循环结构和random库...   第三个参数就是步长     引入文件时记得指明字符格式,否则读入不了 ...
中国版ChatGPT在哪些方面... 目录 一、中国巨大的市场需求 二、中国企业加速创新 三、中国的人工智能发展 四、企业愿景的推进 五、...
报名开启 | 共赴一场 Flu... 2023 年 1 月 25 日,Flutter Forward 大会在肯尼亚首都内罗毕...
汇编00-MASM 和 Vis... Qt源码解析 索引 汇编逆向--- MASM 和 Visual Studio入门 前提知识ÿ...
【简陋Web应用3】实现人脸比... 文章目录🍉 前情提要🌷 效果演示🥝 实现过程1. u...
前缀和与对数器与二分法 1. 前缀和 假设有一个数组,我们想大量频繁的去访问L到R这个区间的和,...
windows安装JDK步骤 一、 下载JDK安装包 下载地址:https://www.oracle.com/jav...
分治法实现合并排序(归并排序)... 🎊【数据结构与算法】专题正在持续更新中,各种数据结构的创建原理与运用✨...
在linux上安装配置node... 目录前言1,关于nodejs2,配置环境变量3,总结 前言...
Linux学习之端口、网络协议... 端口:设备与外界通讯交流的出口 网络协议:   网络协议是指计算机通信网...
Linux内核进程管理并发同步... 并发同步并发 是指在某一时间段内能够处理多个任务的能力,而 并行 是指同一时间能够处理...
opencv学习-HOG LO... 目录1. HOG(Histogram of Oriented Gradients,方向梯度直方图)1...
EEG微状态的功能意义 导读大脑的瞬时全局功能状态反映在其电场结构上。聚类分析方法一致地提取了四种头表面脑电场结构ÿ...
【Unity 手写PBR】Bu... 写在前面 前期积累: GAMES101作业7提高-实现微表面模型你需要了解的知识 【技...