初识C++ · 模板初阶

目录

1 泛型编程

2 函数模板

3 类模板


1 泛型编程

模板是泛型编程的基础,泛型我们碰到过多次了,比如malloc函数返回的就是泛型指针,需要我们强转。

既然是泛型编程,也就是说我们可以通过一个样例来解决类似的问题,比如:

void swap(int& a,int& b)
{int tmp = a;a = b;b = tmp;
}

就交换问题来说,我们可以交换整型,可以交换浮点型,但是我们可不可以在一个文件里面实现同时交换两种类型呢?实际上对于C语言来说是不可以的,因为C语言没有模板的概念。

可能会说用typedef ,但是用了也只能解决交换其他类型的原因,不能实现同时交换两种不同的类型,也可能说用auto,但是auto不能作为函数参数使用。

这里就需要用到模板了,模板使用到了两个关键字,一个是template ,一个是typename,template是模板的英文名,typename是类型名的英文,还是很好理解的。

使用如下:

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

2 函数模板

相关的关键字只有两个,这里来谈一下使用的问题,具体使用如下:

template <typename T>
//模板
void Swap(T& a,T& b)
{T tmp = a;a = b;b = tmp;
}
int main()
{int a = 1, b = 2;Swap(a, b);double c = 1.1, d = 2.2;Swap(c, d);return 0;
}

可能看起来比较抽象?

模板实现的原理是模板实例化,背后靠的都是编译器,编译器会自动推演需要的类型,所以这里会有一个问题:两次交换调用的是同一个函数吗?

当然不是的,这里可以借助汇编看一下:

汇编层面可以看到调用的函数的地址不一样,所以调用的函数不是一个函数。

模板其实是编译器在负重前面,我们使用模板的时候,编译器自动推演出我们需要的函数,

那么这里就涉及效率问题了,某种意义上来说模板的调用效率确实没有直接调用来的快,但是省事儿,我们不用再去多写那么多行代码了,毕竟函数重载也要多写代码的

基本的使用我们了解了,以后我们使用交换函数也不用自己去实现了,因为库里面有,可以直接进行使用:

现在有一些问题:

使用模板的时候,我们想要交换类型不同的怎么办?

如果我们尝试直接交换a c的话,就会报错,即没有函数模板是可以使用的

为了方便演示使用加法函数:

T Add(T x,T y)
{return x + y;
}

解决方法1->强制转换:

int main()
{int a = 1;double b = 2.5;Add(a,(int)b);return 0;
}

但是这种处理方法丢失了原本我们期待的结果,它造成了精度的丢失。

解决方法2->显式实例化:

int main()
{int a = 1;double b = 2.5;double ret = Add<double>(a,b);cout << ret;return 0;
}
int main()
{int a = 1;double b = 2.5;cout << Add<int,double>(a, b) << endl;return 0;
}

 显式实例化可以让编译器不去自动推演参数类型。

我们知道最后的结果是double类型的,所以显式调用double类型的加法函数,调用的写法是函数后面加个<>,里面写要显式调用的类型,但是呢,都差点意思。

解决方法3->多个模板混用 + auto:

template <typename T1,typename T2>
auto Add(const T1& x,const T2& y)
{return x + y;
}
int main()
{int a = 1;double b = 2.5;cout << Add(a, b) << endl;return 0;
}

既然是多参数,那么我们使用不同的模板就行,但是返回值的话,就用auto即可,这里可以说auto很妙,不会存在丢失精度的问题,也不用显式实例化,也不用强转什么的。

但是有些情况,是必须要显式实例化的:

T* Func(int a)
{T* p = (T*)operator new(sizeof(T));new(p)T(a);return p;
}

返回的是一个指针,但是函数返回什么类型呢?如果没有显式实例化的话是没有办法返回的。

这里就必须要显式实例化了:

int main()
{int a = 1;Func<int>(a);return 0;
}

再补充一个小点,typename可以用class进行代替,二者完全等价的。

template <typename T>
template <class T>

两种写法均可。

这里在引入一个点,叫匹配原则,有合适的优先选择合适的:

//整型的加法函数
int Add(int x,int y)
{cout << "int Add(int x,int y)" << endl;return x + y;
}
//通用加法函数
template <typename T1,typename T2>
auto Add(const T1& a,const T2& b)
{cout << "auto Add(const T1& a,const T2& b)" << endl;return a + b;
}
//一般加法
template<typename T>
T Add(const T& left, const T& right)
{cout << "T Add(const T& left, const T& right)" << endl;return left + right;
}

这里提供了三个函数可以实现加法,一个是类型完全匹配的,一个是通用的加法函数,一个是需要编译器需要自己推演的:

int main()
{cout << Add(1, 3) << endl;cout << Add(1, 3.3) << endl;cout << Add(1.1, 3.1) << endl;return 0;
}

那么这里的调用,就会:

第一个,因为完全匹配非模板的函数,所以优先调用;

第二个,因为更匹配通用的加法函数,需要编译器自己推演的力度没有那么大,所以调用两个模板的参数。

第三个,完全需要编译器自己推理,那就只能它了。

所以函数模板的调用也要看谁更匹配,优先使用更匹配的,编译器也想休息~


3 类模板

类模板其实后面用的最多的,现在先做个了解,比如stack,我们要实现两种栈,一个是用来存储int数据,一个是用来存储数据double的,就需要用到模板,不然解决不了一个文件存在两个类型栈的情况:

template<typename T>
class Stack
{
public:Stack(size_t capacity = 4){_array = (T*)malloc(sizeof(T) * capacity);if (nullptr == _array){perror("malloc申请空间失败");return;}_capacity = capacity;_size = 0;}void Push(const T& data);
private:T* _array;size_t _capacity;size_t _size;
};

有了模板之后,我们实现相同的就很容易了,这里如果里面的函数定义和声明分离的话,如果不是同一个.h文件的话,造成的效果是很难想象的,会造成编译的时间大幅度的增加。

分离之后的写法:

template<typename T>
class Stack
{
public:Stack(size_t capacity = 4){_array = (T*)malloc(sizeof(T) * capacity);if (nullptr == _array){perror("malloc申请空间失败");return;}_capacity = capacity;_size = 0;}void Push(const T& data);
private:T* _array;size_t _capacity;size_t _size;
};
template<class T>
void Stack<T>::Push(const T& data)
{;
}

创建不同的Stack就显式实例化就可以了:

int main()
{stack<int> p1;stack<double> p2;return 0;
}

模板初阶还是好理解的,后面介绍高阶的。


感谢阅读!

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

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

相关文章

【linux】进程概念|task_struct|getpid|getppid

目录 ​编辑 1.进程的概念 进程的基本概念 进程与程序的主要区别 进程的特征 进程的状态 描述进程—PCB task_struct中的内容 查看进程 1.创建一个进程&#xff0c;运行以下代码 通过系统调用获取进程标示符 getpid()以及getppid() 1.进程的概念 进程的基本概念…

rust容器、迭代器

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家&#xff1a;点击跳转 目录 一&#xff0c;std容器 1&#xff0c;Vec&#xff08;向量、栈&#xff09; 2&#xff0c;VecDeque&#xff08;队列、双端队…

C++类和对象(基础篇)

前言&#xff1a; 其实任何东西&#xff0c;只要你想学&#xff0c;没人能挡得住你&#xff0c;而且其实学的也很快。那么本篇开始学习类和对象&#xff08;C的&#xff0c;由于作者有Java基础&#xff0c;可能有些东西过得很快&#xff09;。 struct在C中的含义&#xff1a; …

开机弹窗找不到OpenCL.dll是怎么回事,哪种修复方法更推荐

当用户在操作电脑过程中遇到系统提示“OpenCL.dll丢失”时&#xff0c;这究竟是怎么一回事呢&#xff1f;OpenCL.dll&#xff0c;作为Open Computing Language&#xff08;开放计算语言&#xff09;的重要动态链接库文件&#xff0c;它在图形处理器&#xff08;GPU&#xff09;…

爬虫:爬取豆瓣电影

文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 前言 上篇我们将到如何利用xpath的规则&#xff0c;那么这一次&#xff0c;我们将通过案例来告诉读者如何使用Xpath来定位到我们需要的数据&#xff0c;就算你不懂H5代码是怎么个嵌套或者十分复…

my-room-in-3d中的电脑,电视,桌面光带发光原理

1. my-room-in-3d中的电脑&#xff0c;电视&#xff0c;桌面光带发光原理 最近在github中&#xff0c;看到了这样的一个项目&#xff1b; 项目地址 我看到的时候&#xff0c;蛮好奇他这个光带时怎么做的。 最后发现&#xff0c;他是通过&#xff0c;加载一个 lightMap.jpg这个…

C++语法|如何写出高效的C++代码(一)|对象使用过程中背后调用了哪些方法(构造和析构过程)?

文章目录 再探拷贝构造函数和重载复制运算符实例化新对象和赋值操作强转为类类型指针和引用时临时对象的构造和析构过程 考考你问题答案 再探拷贝构造函数和重载复制运算符 实例化新对象和赋值操作 首先我们写一个类&#xff0c;实现它的拷贝构造并重载赋值运算符。 class T…

数值计算方法——大题题型总结

目录 一、绝对误差限、相对误差限 1.1 例题 1.2 解题套路 1.3 题解 二、敛散性、收敛速度 2.1 例题 2.2 解题套路 2.3 题解 三、牛顿迭代法 3.1 例题 3.2 解题套路 3.3 题解 四、割线法 4.1 例题 4.2 解题套路 ​4.3 题解 五、列主元素消去法 5.1 例题 5.…

python爬虫实战

import requests import json yesinput(输入页数&#xff1a;) yesint(yes)headers {"accept": "application/json, text/plain, */*","accept-language": "zh-CN,zh;q0.9","content-type": "application/json",…

JAVA基础之jsp标准标签

jsp动作标签实现实例化一个实体类 <jsp:useBean id"标识符" class"java类名" scope"作用范围"> 传统的java方式实例化一个实体类 Users user new Users(); <%%> id: 对象名 * class:类 创建对象时,完全限定名(包名…

Linux基础之yum和vim

目录 一、软件包管理器yum 1.1 软件包的概念 1.2 软件包的查看 1.3 软件包的安装和删除 二、Linux编辑器之vim 2.1 vim的基本概念 2.2 正常模式&#xff08;命令模式&#xff09; 2.3 底行模式 2.4 输入模式 2.5 替换模式 2.6 视图模式 2.7 总结 一、软件包管理器yu…

嵌入式Linux学习第四天启动方式学习

嵌入式Linux学习第四天 今天学习I.MX6U 启动方式详解。I.MX6U有多种启动方式&#xff0c;可以从 SD/EMMC、NAND Flash、QSPI Flash等启动。 启动方式选择 BOOT 的处理过程是发生在 I.MX6U 芯片上电以后&#xff0c;芯片会根据 BOOT_MODE[1:0]的设置来选择 BOOT 方式。BOOT_M…

Spring - 9 ( 10000 字 Spring 入门级教程 )

一&#xff1a; MyBatis XML 配置文件 Mybatis 的开发有两种方式&#xff1a; 注解XML 我们已经学习了注解的方式, 接下来我们学习 XML 的方式 MyBatis XML 的方式需要以下两步: 配置数据库连接字符串和 MyBatis写持久层代码 1.1 配置连接字符串和 MyBatis 此步骤需要进…

STC8增强型单片机开发 【第一个程序 - 点亮第一盏灯】

目录 一、创建项目 1. 创建一个新的项目 ​编辑 2. 配置开发板信息 ​编辑 3. 取消汇编配置 4. 项目结构 二、编码实现 1. 项目准备 2. 代码实现 点灯&#xff1a; 熄灯&#xff1a; 3. 编译烧录运行 配置编译输出 保存和编译代码 ​编辑 烧录 一、创建项目 1. …

OceanBase 如何实现多层面的资源隔离

OceanBase的资源隔离涵盖了多个方面&#xff0c;如物理机器间的隔离、不同租户之间的隔离、同一租户内的隔离&#xff0c;以及针对大型查询请求的隔离等。在实际应用OceanBase的过程中&#xff0c;我们经常会遇到这些操作场景或产生相关需求。这篇文章针对这些内容进行了简要的…

阿里云SLB监听虚拟服务器组时,既有部署在k8s容器里的应用,又有部署在ecs机器上的应用,k8s应用无法连接部署在ecs机器上的应用

一、背景 阿里云SLB可以添加多个监听端口&#xff0c;包括http和tcp&#xff0c;但是当被添加的后端应用&#xff0c;既有部署在k8s里&#xff0c;也有部署在ecs机器里。同一个slb下&#xff0c;这种混合方式的监听&#xff0c;会导致部署在k8s应用中的应用无法连接后者&#…

Spring扩展点(一)Bean生命周期扩展点

Bean生命周期扩展点 影响多个Bean的实例化InstantiationAwareBeanPostProcessorBeanPostProcessor 影响单个Bean的实例化纯粹的生命周期回调函数InitializingBean&#xff08;BeanPostProcessor 的before和after之间调用&#xff09;DisposableBean Aware接口在生命周期实例化过…

内存卡突然罢工?数据恢复有高招!

内存卡作为我们日常生活中常见的存储设备&#xff0c;广泛应用于手机、相机等设备中。然而&#xff0c;有时我们会遇到内存卡损坏打不开的情况&#xff0c;这时该如何应对呢&#xff1f;本文将为您详细解析内存卡损坏的原因&#xff0c;并提供有效的数据恢复方案&#xff0c;帮…

FPGA第二篇,FPGA与CPU GPU APU DSP NPU TPU 之间的关系与区别

简介&#xff1a;首先&#xff0c;FPGA与CPU GPU APU NPU TPU DSP这些不同类型的处理器&#xff0c;可以被统称为"处理器"或者"加速器"。它们在计算机硬件系统中承担着核心的计算和处理任务&#xff0c;可以说是系统的"大脑"和"加速引擎&qu…

如何进行Go语言的性能测试和调优?

文章目录 开篇一、性能测试1. 使用标准库中的testing包2. 使用第三方工具 二、性能调优1. 优化算法和数据结构2. 减少不必要的内存分配和垃圾回收3. 并发和并行 结尾 开篇 Go语言以其出色的性能和简洁的语法受到了广大开发者的喜爱。然而&#xff0c;在实际开发中&#xff0c;…