【C++程序员的自我修炼】初识模板

云收天彩色

木叶落秋声


目录

函数模板

函数模板的实现 

函数模板的实例化

模板参数的匹配原则

 参数模板推不出来的情况

类模板

类模板的定义格式

类模板的实例化

契子 ✨ 

我们在学 C语言 的时候应该都写过交换两个数的函数 swap

当时我们只是写了 int 类型,那么如果是 char、double类型呢?是不是像这样:写多个 swap 函数

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>代码的可维护性比较低,一个出错可能所有的重载均出错

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

就像以下的杯子一样,只要确定好具体的模型,就可以填充自己想要的颜色

答案是有的,接下来我将带大家了解模板的基本操作:


函数模板

概念:

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

格式:

template<typename T1, typename T2,......,typename Tn>

注意:typename 是用来定义模板参数关键字,也可以使用 class

template<class T1, class T2,......,class Tn>

函数模板的实现 

举个栗子~ 

#include<iostream>
using namespace std;template<typename T>
void Swap(T& a, T& b)
{T tmp = a;a = b;b = tmp;
}int main()
{int a = 1, b = 2;cout << "a = " << a << " " << "b = " << b << endl;Swap(a, b);cout << "a = " << a << " " << "b = " << b << endl;double c = 1.1, d = 2.2;cout << "c = " << c << " " << "d = " << d << endl;Swap(c, d);cout << "c = " << c << " " << "d = " << d << endl;return 0;
}

我们发现不管是 int 类型还是 double 类型都可以往里套,不仅如此 指针 类型也可以调用

	int a = 1, b = 2;int* pa = &a;int* pb = &b;

库里面的 swap

 ⭐我们发现库里面的 swap 函数的底层代码就是用模板!!!

就像我们古代的活字印刷术,名人的手稿诗集就是模板,通过模板我们可以印刷出各种类型

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

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

函数模板的实例化

用不同类型的参数使用函数模板时 ,称为函数模板的 实例化
模板参数实例化分为: 隐式实例化和显式实例
隐式实例化:让编译器根据实参推演模板参数的实际类型
显式实例化:在函数名后的<>中指定模板参数的实际类型

先举个栗子~

#include<iostream>
using namespace std;template<typename T>
T Add(const T& n, const T& m)
{return n + m;
}int main()
{int a = 1, b = 2;double c = 1.1, d = 2.2;Add(a, b);Add(c, d);return 0;
}

以上的代码很简单~我们传了同类型的两个参数 int类型的a和bdouble类型的c和d

像这种能自动推演出 T 的类型的就是隐式实例化


那么我们能不能传不同类型的两个参数呢比如说:a和c

我们发现是不行的,原因如下:

在编译期间,当编译器看到该实例化时,需要推演其实参类型 通过  实参a将T推演为int  类型,通过  实参c将T推演为double  类型,但模板参数列表中只有一个T, 编译器无法确定此处到底该将T确定为 int 或者 double 类型而报错

 

那有没有什么解决办法呢?

方法一:用户自己来强制转化

Add(a, (int)c);
Add((double)a, c)

因为 double类型c 强转成 int类型 ,精度会丢~

方法二:显示实例化

Add<int>(a, c)
Add<double>(a, c)

显示实例化就是现将数据进行隐式类型转化成自己指定类型(不让编译器自动推演参数)

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

以上的:

<1> c 就是隐式类型转化成 double

<2>c 就是隐式转换成 int 

方法三:利用 auto 自动推导 

template<typename T1, typename T2>
auto Add(const T1& n, const T2& m)
{return n + m;
}

以上代码如果返回值是整数就会自动推导成 int ,是小数则自动推导成 double

这样精度就不会丢失~

模板参数的匹配原则

<1>一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函
<2> 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模 板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板
#include<iostream>
using namespace std;int Add(int left, int right)
{cout << "Add(int left, int right)" << endl;return left + right;
}template<class T>
T Add(T left, T right)
{cout << "T Add(T left, T right)" << endl;return left + right;
}int main()
{Add(1, 2); Add<int>(1, 2); return 0;
}

 

简单来讲就是模板和用模板创造的函数 Add 可以同时存在,如果数据类型刚好和 Add 匹配,那么就可以直接用(不调用模板),如果不匹配可以隐式类型转换就可以将就用没有就用模板,当然如果我们想用模板的话,我们可以显示实例化

🌤️举个栗子~你想吃蛋糕了,如果有你喜欢吃的蛋糕肯定是有现成的吃现成的,如果没有可以将就一下吃别的,若是一个蛋糕都没有,就要求店员有模板做

 参数模板推不出来的情况

 举个栗子~我们来看看以下代码,Func(1)是否推演T成功

#include<iostream>
using namespace std;template<class T>
T* Func(int a)
{T* p = (T*)operator new(sizeof(T));new(p)T(a);return p;
}int main()
{int* ret = Func(1);return 0;
}

因为我们之前 T 的推演是编译器通过对实参类型的推演的,而这里我们的参数并不是 T 

那怎么办呢?

这里我们的显示实例化起到很大作用

int* ret = Func<int>(1);

编译器推导不出来,我们就自己指定类型

类模板

类模板的定义格式

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

举个栗子~我们之前写过的栈

#include<iostream>
#include<assert.h>
#include<cstdlib>
using std::cout;
using StackDataUsing = int;
class Stack
{
public:void Init(size_t newcapacity = 4){StackDataUsing* newNode = new StackDataUsing[newcapacity];if (newNode == nullptr){perror("new");exit(-1);}data = newNode;capacity = newcapacity;top = 0;}void Destory(){delete data;data = nullptr;top = capacity = 0;}void push(StackDataUsing x){if (capacity == top){StackDataUsing* newNode = new StackDataUsing[2 * capacity];if (newNode == NULL){perror("new");exit(-1);}data = newNode;capacity *= 2;}data[top++] = x;}StackDataUsing Top(){return data[top - 1];}void Pop(){assert(top > 0);top--;}bool Empty(){return top == 0;}private:StackDataUsing* data;size_t capacity;size_t top;
};int main()
{Stack st1;  //intStack st2; //doublereturn 0;
}

 

我们之前想创建两个 double 、int  类型的栈还需要写两个类来进行,但是现在不必了

因为我们升级了~

#include<iostream>
#include<assert.h>
#include<cstdlib>
using std::cout;
template<class T>
class Stack
{
public:void Init(size_t newcapacity = 4){T* newNode = new T[newcapacity];if (newNode == nullptr){perror("new");exit(-1);}data = newNode;capacity = newcapacity;top = 0;}void Destory(){delete data;data = nullptr;top = capacity = 0;}void push(const T& x){if (capacity == top){T* newNode = new T[2 * capacity];if (newNode == NULL){perror("new");exit(-1);}data = newNode;capacity *= 2;}data[top++] = x;}T Top(){return data[top - 1];}void Pop(){assert(top > 0);top--;}bool Empty(){return top == 0;}private:T* data;size_t capacity;size_t top;
};

我们只要将原来 StackDataUsing 的位置置为 T 即可,T 就是对应的数据类型

类模板的实例化

类模板实例化与函数模板实例化不同, 类模板实例化需要在类模板名字后跟 <> ,然后将实例化的类型放在 <> 中即可,类模板名字不是真正的类,而实例化的结果才是真正的类
Stack<int> st1;    //int
Stack<double> st2; //double

我们的模板是写给编译器的,编译器通过实例化帮我们创建对应的类

注意 Stack<int> Stack<double>是两个不同的类 

总结:

我们在函数模板通常是通过参数进行 T 类型的推导,而类模板这是直接显示实例化

还有一个小点需要注意~如果我们要做声明与定义怎么办?

template<class T>
class Stack
{
public:void push(const T& x);// ... 
private:T* data;size_t capacity;size_t top;
};template<class T>
void Stack<T>::push(const T& x)
{if (capacity == top){T* newNode = new T[2 * capacity];if (newNode == NULL){perror("new");exit(-1);}data = newNode;capacity *= 2;}data[top++] = x;
}

声明与定义分离必须模板化


 

冉冉星河远

          共与乘舟还

先介绍到这里啦~

有不对的地方请指出💞

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【JAVA】实现只有一个窗口弹出的底层逻辑——单身模式

目录 背景说明 代码实现 手写笔记 背景说明 有的时候&#xff0c;当你点击一个选项时会弹出来多个窗口&#xff0c;而有的时候只会弹出一个。 实际上&#xff0c;弹出多个窗口就是创建了多个相同的对象&#xff0c;而只弹出一个就是我们今天即将分享的单身模式——一个类只产生…

5.Eureka原理分析

消费者如何获取服务提供者具体信息&#xff1f; 1.服务提供者启动时向Eureka注册自己的信息。 2.Eureka保存这些信息。 3.消费者根据服务名称向Eureka拉取提供者信息。 如果有多个服务的提供者&#xff0c;消费者该如何选择&#xff1f; 1.服务消费者利用负载均衡算法&…

ardunio中自定义的库文件

1、Arduino的扩展库都是放在 libraries目录下的。完整路径为&#xff1a;C:\Users\41861\AppData\Local\Arduino15\libraries 所以我们需要在这个目录下创建一个文件夹&#xff0c;比如上面的例子是esp32上led灯控制程序&#xff0c;于是我创建了 m_led文件夹&#xff08;前面加…

web前端(简洁版)

0. 开发环境 && 安装插件 这里我使用的是vscode开发环境 Auto Rename Tag是语法自动补齐view-in-browser是快速在浏览器中打开live server实时网页刷新 1. HTML 文件基本结构 <html><head><title>第一个页面</title></head><body&g…

存储过程的查询

Oracle从入门到总裁:​​​​​​https://blog.csdn.net/weixin_67859959/article/details/135209645 在实际使用中&#xff0c;经常会需要查询数据库中已有的存储过程或者某一个存储过程的内容&#xff0c; 下面就介绍-下如何查询存储过程。 这需要使用到数据字典 user_sou…

关系型数据库的相关概念

表、记录、字段 表 一个实体集相当于一个表记录 一个实体相当于一个记录&#xff0c;在表中表表现为一行数据字段 一个字段相当于数据库表中的列 表的关联关系 一对一(一对一的表可以合并成一张表)一对多多对多 必须创建第三张表&#xff0c;该表通常称为联接表&#xff0c…

实现联系人前后端界面,实现分页查询04.15

实现联系人前后端界面&#xff0c;实现分页查询项目包-CSDN博客 项目结构 数据库中建立两个表&#xff1a; 完整的后端目录 建立联系人People表&#xff0c;分组Type表&#xff0c;实现对应实体类 根据需求在mapper中写对应的sql语句 查询所有&#xff0c;删除&#xff0c;添…

从国九条的颁布简单看待未来的因子轮动

上周4月12日《关于加强监管防范风险推动资本市场高质量发展的若干意见》又称国九条出台后&#xff0c;除了本周五中东局势对大盘的影响&#xff0c;本周一波三折的行情很大程度上都是围绕着国九条展开的。一个很有意思的现象是前两次国九条发布后&#xff0c;市场都诞生了波澜壮…

文件系统和软硬链接

文章目录 文件系统磁盘磁盘逻辑抽象inode 软硬链接软链接硬链接 文件系统 文件分为打开的文件和没有被打开的文件&#xff0c;而只有打开的文件是在内存的&#xff0c;也就是我们之前讲的&#xff0c;然而大部分文件都不是被打开的(当前不需要被访问的)&#xff0c;它们都在磁…

Leetcode算法训练日记 | day33

专题九 贪心算法 一、跳跃游戏 1.题目 Leetcode&#xff1a;第 55 题 给你一个非负整数数组 nums &#xff0c;你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。 判断你是否能够到达最后一个下标&#xff0c;如果可以&#xff0c;返回 …

成为程序员后我明白了什么?分享11条经验助你成功

文章目录 前言自我分析做什么挑战路线图总结 前言 入行15载&#xff0c;从一线大头兵&#xff0c;到负责技术的架构师&#xff0c;再到技术和管理一把抓的团队负责人&#xff0c;我从中总结了软件工程师成功入门的11个步骤&#xff0c;分享给想入行&#xff0c;或刚入行的同学们…

TCP/IP协议—MQTT

TCP/IP协议—MQTT MQTT协议MQTT协议特点MQTT通信流程MQTT协议概念 MQTT报文固定报头可变报头有效载荷 MQTT协议 消息队列遥测传输&#xff08;Message Queuing Telemetry Transport&#xff0c;MQTT&#xff09;是一个基于客户端-服务器的消息发布/订阅传输协议。它的设计思想…