C++: 模板初阶

文章目录

  • 一. 泛型编程
  • 二. 函数模板
    • 函数模板的原理
    • 函数模板的实例化
      • 隐式实例化: 让编译器根据实参推演模板参数的实际类型
      • 显示实例化: 在函数名后的<>中制定模板参数的世纪类型
    • 模板参数的匹配原则
  • 三. 类模板
    • 类模板的定义格式
    • 类模板的实例化

一. 泛型编程

如何实现一个通用的交换函数呢? 参数是不同类型的数据.

void Swap(int& left, int& right)
{
  int temp = left;
  left = right;
  right = temp;
}

void Swap(double& left, double& right)
{
  double temp = left;
  left = right;
  right = temp;
}

void Swap(char& left, char& right)
{
  char temp = left;
  left = right;
  right = temp;
}

使用函数重载虽然可以实现, 但是仍然有不足:

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

如果需要能在用户提供的类型上使用此函数, 这种策略就失效了.


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

就像活字印刷术一样, 只要提供了字模, 就可以用不同的颜色来印出同样的字

在这里插入图片描述

C++提供了模板的语法, 给这个"模具"中填充不同的类型, 就可以生成具体类型的代码

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

在这里插入图片描述

二. 函数模板

针对上面 Swap 函数有多种类型参数的情况, 可以定义一个通用的函数模板(function template), 而不是为每个类型都定义一个新函数.

一个函数模板就是一个公式, 可用来生成针对特定类型的函数版本. Swap 的模板版本可能像下面这样:

template <typename T>     // 模板定义格式:  template<typename T1, typename T2, ... , typename Tn>
void Swap(T &left, T &right)
{
  T temp = left;
  left = right;
  right = temp;
}

模板定义以关键字 template 开始, 后面跟一个模板参数列表, 这是一个用都号分割的一个或多个模板参数的列表, 用小于号 < 和 大于号 > 包围起来.

在模板定义中, 模板参数列表不能为空.

模板参数列表的作用很像函数参数列表.
函数参数列表定义了形参对象, 模板参数列表定义了类型.

模板参数表示类或函数定义中用到的类型或值. 当使用模板的时候, 显示或隐式地制定模板实参, 将其绑定到模板参数上.

比如上述的 Swap 函数声明了两个名为 T 的类型参数, T 表示一个类型, T 的实际类型则在编译时根据传入的参数确定.

对于不同的参数类型, 最终调用的函数参数类型也不同

在这里插入图片描述


函数模板的原理

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

在这里插入图片描述

在编译器编译阶段, 对于函数模板的使用, 编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用.

例如: 当用 double 类型使用函数模板时, 编译器通过对实参类型的推演, 将 T 确定为 double 类型, 然后产生一份专门处理 double 类型的代码

注意 函数模板的模板参数类型可以写 typename, 也可以写 class (不可以写 struct).

函数模板的实例化

用不同类型的参数使用函数模板时, 称为函数模板的实例化.

在这里插入图片描述

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

template <typename T>
T Add(const T &left, const T &right)
{
  return left + right;
}

int main()
{
  int a1 = 10, b1 = 20;
  Add(a1, b1);    // 推演出模板参数类型为 int

  double a2 = 1.1, b2 = 2.2;
  Add(a2, b2);    // 推演出模板参数类型为 double

  Add(a1, b2);    // 推演失败

  return 0;
}

前两个可以通过编译, 函数模板类型相同, 编译器可以推演出函数模板的类型.

第三个传入了一个 int 类型和一个 double 类型, 编译器推演模板参数类型失败.

在这里插入图片描述

因为定义模板函数的时候, 规定了模板函数参数只有一个类型 T, 传入两个类型, 编译器不能确定应该是将 T 演绎成哪个类型.

在模板中, 编译器不会进行类型转换操作, 一旦转换出问题, 编译器就会背黑锅, 所以不会隐式转换.

有两种处理方式:

  1. 用户自己进行强化类型转换
  2. 使用显示实例化
Add(a1, (int)b2);   // 用户自己强制类型转换

显示实例化: 在函数名后的<>中制定模板参数的世纪类型

int main()
{
  int a1 = 10;
  double b = 20.0;

  Add<int> (a1, b2);  // 显示实例化

  return 0;
}

如果类型不匹配, 编译器会进行隐式类型转换, 如果无法转换成功, 编译器会报错.


如果函数参数没有模板参数, 那么就无法使用隐式实例化, 只能进行显示实例化.

// 申请一个T类型十个元素的数组并返回
template <typename T>
T *f()
{
  T *p = new T[10];
  return p;
}

int main()
{
  int *p1 = f<int>();
  double *p2 = f<double>();

  return 0;
}

模板参数的匹配原则

  1. 一个非模板函数和一个同名的函数模板同时存在, 而且该函数模板可以被实例化为这个非模板函数
// 专门处理 int 类型的加法函数
int Add(const int &left, const int &right)
{
  cout << "(int) Add" << endl;
  return left + right;
}

// 通用加法函数
template <typename T>
T Add(const T &left, const T &right)
{
  cout << "(template) Add" << endl;
  return left + right;
}

int main()
{
  Add(1, 2);          // 有现成的优先用现成的
  Add<int>(1, 2);     // 如果指定显示实例化, 用函数模板
}

在这里插入图片描述


  1. 对于非模板函数和同名函数, 如果其他条件爱你都相同, 在调用时会优先调用非模板函数而不会从该模板产生一个实例. 如果模板可以产生一个具有更好匹配的函数, 那么将选择模板.
// 专门处理 int 类型的加法函数
int Add(const int &left, const int &right)
{
  cout << "(int) Add" << endl;
  return left + right;
}

// 通用加法函数
template <typename T1, typename T2>
T1 Add(const T1 &left, const T2 &right)
{
  cout << "(template) Add" << endl;
  return left + right;
}

int main()
{
  Add(1, 2);          // 与非函数模板完全匹配, 不需要函数模板实例化
  Add(1, 2.0);        // 有更合适的不会进行隐式类型转换调用非函数模板, 而会直接调用函数模板实例化的实例
}

在这里插入图片描述


  1. 函数模板不允许自动类型转换, 而普通函数可以进行自动类型转换
// 专门处理 int 类型的加法函数
int Add(const int &left, const int &right)
{
  cout << "(int) Add" << endl;
  return left + right;
}

// 通用加法函数
template <typename T>
T Add(const T &left, const T &right)
{
  cout << "(template) Add" << endl;
  return left + right;
}

int main()
{
  Add(1, 2.0); // 函数模板不能进行自动类型转换, 只能调用普通函数, 传参的时候进行自动类型转化
}

在这里插入图片描述


总结

  1. 优先调用现有的普通函数
  2. 没有函数模板, 普通函数参数可以自动类型转换的, 使用普通函数.
  3. 可以通过函数模板实例化更合适的函数, 哪怕普通函数可以自动类型转换, 也用函数模板.

三. 类模板

以前写数据结构的时候, 通常会用 typedef 重命名数据结构内数据的类型.
例如 Stack 中用 typedef int STDataType, 制定 Stack 中的数据类型是 int 类型的.

但是, 这样只能保证一次只能用 int, 如果需要用到 double 的, 需要再重新写一份.

有了类模板就能很好的解决这个问题.

类模板的定义格式

template <class T1, class T2,...,class T3>
class 类模板名
{
    // 类内成员定义
};

例如: Stack

template <class T>
class Stack
{
public:
  Stack(size_t capacity = 10)
      : _array(new T[capacity])
      , _capacity(capacity)
      , _top(0)
  {}

  ~Stack();
  
  void push(const T &data);
  //...
private:
  T *_array;
  int _capacity;
  int _top;
};

// 类外定义成员函数必须要加模板参数列表
template <class T>
Stack<T>::~Stack()
{
  delete[](_array);
  _capacity = _top = 0;
}

类外定义成员函数, 必须要加上模板参数列表声明, 并且指定类域时必须加上模板参数列表.

同时, 类模板成员函数的声明与定义必须放在同一个文件中, 否则编译器会链接失败.

类模板的实例化

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

Stack<int> s1;
Stack<double> s2;

普通类的类名即是类型, 而类模板的类名不是类型, 类名<数据类型> 才是整个类的类型.

Stack<int>Stack<double> 不是同一个类, 他们只是同一类模板显示实例化生成的不同类.

本章完.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/150410.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Python练习

Python 练习一练习二练习三 练习一 实现代码&#xff1a; def merge(list1, list2):merged_list []i 0j 0while i < len(list1) and j < len(list2):if list1[i] < list2[j]:merged_list.append(list1[i])i 1else:merged_list.append(list2[j])j 1merged_list …

为什么程序员一定要写单元测试?

大家好&#xff0c;我是鱼皮&#xff0c;很多初学编程的同学都会认为 “程序员的工作只有开发新功能&#xff0c;功能做完了就完事儿”。但其实不然&#xff0c;保证程序的正常运行、提高程序的稳定性和质量也是程序员的核心工作。 之前给大家分享过企业项目的完整开发流程&am…

分享一个在线免费制作和视频合成gif的网站

一、打开网站 在线制作高清gif动图-视频转gif表情工具-图片合成软件-gif.cn_GIF中文网 如图 二、可以选择gif合成&#xff0c;也就是把多张图片合成gif 效果图&#xff0c;我用了三张图片。 三、可以选择视频转gif。 效果图 四、完

RT-DETR算法优化改进:Backbone改进 | LSKNet:遥感旋转目标检测新SOTA | ICCV 2023

💡💡💡本文独家改进:LSKNet 助力RT-DETR ,替换backbone,Large Selective Kernel Network (LSKNet),可以动态地调整其大空间感受野,以更好地建模遥感场景中各种物体的测距的场景。 推荐指数:五星 RT-DETR魔术师专栏介绍: https://blog.csdn.net/m0_63774211/cat…

Git学习(黑马程序员)

基本配置 在用户目录下创建文件.bashrc 1打开Git Bash 2 在目录下输入指令&#xff1a; touch ~/.bashrc在文件中写入内容&#xff1a; #用于输出git提交日志 alias git-loggit log --prettyoneline --all --graph --abbrev-commit #用于输出当前目录所有文件及基本信息 al…

百度搜索智能化算力调控分配方法

作者 | 泰来 导读 随着近年深度学习技术的发展&#xff0c;搜索算法复杂度不断上升&#xff0c;算力供给需求出现了爆发式的增长。伴随着AI技术逐步走到深水区&#xff0c;算法红利在逐步消失&#xff0c;边际效应日益显著&#xff0c;算力效能的提升尤为重要&#xff0c;同时随…

【接口自动化测试】Postman(一) 介绍和安装

一.Postman介绍 Postman是一款非常流行的接口调试工具&#xff0c;它使用简单&#xff0c;而且功能也很强大。不仅测试人员会使用&#xff0c;开发人员也会 经常使用。 主要特点 1. 简单易用的图形用户界面 2. 可以保存接口请求的历史记录 3. 使用测试集Collections可以更…

rocketmq5.X 单机搭建 虚拟机搭建rocketmq5.1.4 搭建最新版本mq rocketmq5.1.4版本单体搭建 rocketmq(一)

1. 官网下载地址&#xff1a; 下载 | RocketMQ 2. 配置环境&#xff1a; 我是在/etc/profile.d 新建了一个rocketmq_env.sh 配置了jdk, maven, 以及mq. mq文件下载的 配置完之后&#xff0c;刷新环境source /etc/profile 3. 配置rocket mq 的jvm配置&#xff0c;就是两个启…

OpenAI:我们暂停了ChatGPT Plus新用户注册

今天中午&#xff0c;OpenAI 首席执行官 Sam Altman 在 X 平台发文说&#xff0c;将暂停 ChatGPT Plus 新用户注册。 we are pausing new ChatGPT Plus sign-ups for a bit > :( the surge in usage post devday has exceeded our c> apacity and we want to make sure e…

UI自动化测试框架的搭建(详解)

前言 今天给大家分享一个seleniumtestngmavenant的UI自动化&#xff0c;可以用于功能测试&#xff0c;也可按复杂的业务流程编写测试用例&#xff0c;今天此篇文章不过多讲解如何实现CI/CD&#xff0c;只讲解自己能独立搭建UI框架&#xff0c;需要阅读者有一定的java语言基础&…

Redis概述

Redis是一款NoSql(非关系型)数据库&#xff0c;实现了主从同步。 使用场景&#xff1a; 对数据高并发的读写。 海量数据的读写。 对数据的可扩展性的。 NoSql数据库举例&#xff1a; Memcache&#xff1a;数据都在内存中&#xff0c;但是数据不持久化&#xff0c;而且只支…

巷议:跌落尘埃与风光无限

近几来制造业的退潮是不争的事实&#xff0c;其中以老资格直辖市天津尤为突出。曾记否&#xff0c;想当年韩国的三星集团是天津最强的外企&#xff0c;但是从2015年开始便撤离了&#xff0c;给天津经济带来了重创。 而天津的汽车产业&#xff0c;也日渐变得软弱。其中那曾经小…

JavaScript语法、语句、数据类型

一、JavaScript语法&#xff1a; 1、JavaScript字面量&#xff1a; JavaScript中的固定值称为字面量。数字字面量可以是整数、小数或者科学计数&#xff08;e&#xff09;,如3.1415926、1008、123e5等&#xff1b;字符串字面量可以使用单引号或者双引号&#xff0c;如“corli…

用于部署汽车AI项目的全面自动化数据流程

如何创建、优化和扩展汽车 AI 的数据流程 想到汽车行业的人工智能 (AI) 时&#xff0c;脑海中可能会立即浮现未来的道路上遍布自动驾驶汽车的情景。虽然这一切尚未实现&#xff0c;但汽车行业已在 AI 方面取得诸多进步&#xff0c;不仅安全性提高&#xff0c;车内体验也得到改…

数据库sql语句设置外键

当我们需要在数据库表之间建立关联关系时&#xff0c;可以使用外键&#xff08;Foreign Key&#xff09;来实现。在 SQL 中&#xff0c;外键可以用来保持数据的完整性&#xff0c;并帮助我们更有效地管理数据。以下是设置外键的步骤&#xff1a; 1.在创建表时&#xff0c;需要…

系统之家重装Win10系统教程图解

系统之家官网给用户们提供了不同品牌系统的下载&#xff0c;帮助更多的用户完成Win10系统的重新安装&#xff0c;从而解决自己Win10系统所遇到的问题。如果有用户不清楚详细的重装系统步骤&#xff0c;那么可以参考下面小编分享借助系统之家装机大师软件重装Win10系统教程图解介…

企业微信H5开发遇到的坑

企业微信官方推荐wx.agentConfig引用<script src"https://open.work.weixin.qq.com/wwopen/js/jwxwork-1.0.0.js"></script>是没有效果的 必须引用以下代码才有效果&#xff0c;这也是我看了社区的回答才有所收获&#xff0c;是一个坑 且VUE引用在线的…

Page分页records有数据,但是total=0,解决办法

Page分页records有数据&#xff0c;但是total0&#xff0c;解决办法 问题&#xff1a;程序运行起来后&#xff0c;后端接收前端传来的搜索请求信息正常&#xff0c;但无法在前端正确反馈信息&#xff0c;通过在后端排查发现total一直等于零&#xff0c;但数据库中有数据&#x…

需求工程咨询和实施服务

服务概述 多年来经纬恒润在汽车电子产品开发与量产、工程服务、研发流程体系建设方面积累了大量的实际研发经验&#xff0c;并为国内外主流OEM和核心供应商提供了相应的量产产品和研发服务&#xff0c;覆盖车身和舒适域、智能驾驶、智能网联、智能座舱、底盘控制、新能源及动力…

正则提取记录

使用正则 https?:\/\/([^\/\s])/