当前位置: 首页 > news >正文

初始C++模板

1.泛型编程

1.1什么事泛型编程

在学习C语言时,我们时常会有这样的烦恼:

在针对每一种不同的类型变量进行函数传参或者是运算处理时,我们总是编写不同的函数或者是进行不同的处理,才能达到目的,这时,我们就会想到,有没有什么操作能够让我们只写一种函数就能堆所有的百年来那个类型都进行处理,在C++中,就实现了泛型编程。

我们先来看看下面的代码:

这是C语言的交换函数:

void swapi(int* x, int* y)
{int tmp = *x;*x = *y;*y = tmp;
}
void swapd(double* x, double* y)
{double tmp = *x;*x = *y;*y = tmp;
}
int main()
{int i = 12;int j = 20;printf("整形, 交换前:i:%d, j:%d\n", i, j);swapi(&i, &j);printf("整形, 交换后:i:%d, j:%d\n", i, j);double in = 12.5;double jn = 32.6;printf("双精度浮点型, 交换前:i:%lf, j:%lf\n", in, jn);swapd(&in, &jn);printf("双精度浮点型, 交换后:i:%lf, j:%lf\n", in, jn);return 0;
}

 从上面的代码我们可以看出,在C语言中我们进行,相同的操作,但由于受限于变量的类类型,我们还是需要进行许多操作,这不由得让我们觉得过于冗余。

但是反泛型编程就能够很好的解决这样的问题:

我们来及看下面的代码:


using namespace std;template<class T>
void Swap(T& x, T& y)
{T tmp = x;x = y;y = tmp;
}
int main()
{int i = 12;int j = 20;printf("整形, 交换前:i:%d, j:%d\n", i, j);Swap(i, j);printf("整形, 交换后:i:%d, j:%d\n", i, j);double in = 12.5;double jn = 32.6;printf("双精度浮点型, 交换前:i:%lf, j:%lf\n", in, jn);Swap(in, jn);printf("双精度浮点型, 交换后:i:%lf, j:%lf\n", in, jn);return 0;
}

通过反省编程,我们就轻松的实现了,各类型的数据交换,避免了C语言中的一些无法避免的问题,是程序更加的见解,可读性也变得更高!

接下来,我们就来正式的了解一下什么是泛型编程。

2. 泛型模板

我们可以想象一下,在一个制作工艺中,我们通常可以通过相同的模板来实现将不同的类型的材料,塑造吃呢公同样的形状,在这里,我们就需要用到一种模具来实现这样的操作。

如果C++中,也存在这样的模板的话,通过在模板上填充不同的材料(类型变量),来获取不同才来哦的构建(即生成相应的代码),那将会节省许多的头发。巧的就是,前人早已将树种好,我们现在只需要再次乘凉就好。

所以综上:泛型编程就是:编写与类型无关代码,是代码复用的手段。模板是反省编程的基础。

3.函数模板

3.1函数模板的概念 

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

3.2函数模板的模式 

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

返回值的类型 函数名(参数列表){}

template<typename T>
void Swap( T& left, T& right)
{
T temp = left;
left = right;
right = temp;
}

注意:typename是用来定义函数模板的关键字,也可以使用class(切记:不能使用struct来代替class)

3.3函数模板实现的原理

函数 模板是一个蓝图,他本省并不是函数,是编译器使用方式产生具体类型的函工具。所以其实魔板就是将我们本应该重复做的事情交给了编译器来做,这样可以可以提高效率。

在编译器的编译阶段,对于函数模板,编译器需要根据传入的参数来推演出生成对应函数的类型,将T确定为double类型,然后产生一份专门处理double类型的代码 ,对于其他的类型也都是这样的。

3.4函数模板的实例化

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

隐式实例化和显示实例化

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

template<class T>
T Add(const T& left, const T& right)
{
return left + right;
} i
nt main()
{
int a1 = 10, a2 = 20;
double d1 = 10.0, d2 = 20.0;
Add(a1, a2);
Add(d1, d2);
/*
该语句不能通过编译,因为在编译期间,当编译器看到该实例化时,需要
通过实参a1将T推演为int,通过实参d1将T推演为double类型,但模板
一个T,
编译器无法确定此处到底该将T确定为int 或者 double类型而报错
注意:在模板中,编译器一般不会进行类型转换操作,因为一旦转化出问题,编译器就需要
背黑锅
Add(a1, d1);
*/
// 此时有两种处理方式:1. 用户自己来强制转化 2. 使用显式实例化
Add(a, (int)d);
return 0;
}

 2.显示实例化:在函数的使用时,在函数名的后面<>中指定模板参数的实际类型

i
nt main(void)
{
int a = 10;
double b = 20.0;
// 显式实例化
Add<int>(a, b);
return 0;
}

如果类型不匹配,编译器会尝试进行隐式类型转化,如果无法进行正确的转化,编译器就会报错。

3.5模板参数的匹配原则

1.一个非模板函数可以和一个同名的函数一起存在,而且这个函数在进行调用时。会有先调用已经存在的类型的函数,同时,模板函数还是可以经过实例化生成这个函数。

// 专门处理int的加法函数
int Add(int left, int right)
{
return left + right;
} /
/ 通用加法函数
template<class T>
T Add(T left, T right)
{
return left + right;
} v
oid Test()
{
Add(1, 2); // 与非模板函数匹配,编译器不需要特化
Add<int>(1, 2); // 调用编译器特化的Add版本
}

2.对于非模板函数和对应同名的函数模板, 如果其他条件相同,在调用时会有优先调用非模板函数而不会从该模板中产生一个实例。如果模板可以产生一个更好的实力函数,那么,编译器还是会考虑从模板重中生成。

// 专门处理int的加法函数
int Add(int left, int right)
{
return left + right;
}// 通用加法函数
template<class T1, class T2>
T1 Add(T1 left, T2 right)
{
return left + right;
}void Test()
{
Add(1, 2); // 与非函数模板类型完全匹配,不需要函数模板实例化
Add(1, 2.0); // 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的
Add函数
}

3.模板函数不允许自动类型转换,但是普通函数可以进行这样的操作!

4.类模板

4.1类模板的定义方式 

template<class T1, class T2, ..., class Tn>
class 类模板名
{
// 类内成员定义
};
#include<iostream>
using namespace std;
// 类模版
template<typename T>
class Stack
{ p
ublic:
Stack(size_t capacity = 4)
{
_array = new T[capacity];
_capacity = capacity;
_size = 0;
}void Push(const T& data);
private:
T* _array;
size_t _capacity;
size_t _size;
};
// 模版不建议声明和定义分离到两个文件.h 和.cpp会出现链接错误,具体原因后面会讲
template<class T>
void Stack<T>::Push(const T& data)
{
// 扩容
_array[_size] = data;
++_size;
} 
int main()
{
Stack<int> st1; // int
Stack<double> st2; // double
return 0;
}

 4.2类模板的实例化

类模板的实例化与函数的模板实例化有所不同,类模板实例化需要在类模板名字后面跟<>,然后就爱那个实力化的类型放在<>中即可,类模板的名字不是真正的类,而是实例化后的结果才是真正的类!!!


/ Stack是类名,Stack<int>才是类型
Stack<int> st1; // int
Stack<double> st2; // double

好,今天的内容就到这里,咱们下期再见,拜拜!! 

相关文章:

  • Solidity智能合约中的异常处理(error、require 和 assert)
  • 【CAM350】使用总结 <一>{ 光绘Gerber 对齐 }
  • 【JavaScript】尾递归优化
  • BaoStock 的安装
  • 内网攻击思路流程与信息收集
  • 【Element-UI】实现el-drawer抽屉的左右拖拽宽度
  • Docker torchserve workflow部署流程
  • 数据文件(0)
  • excel快速入门(二)
  • Windows C++:MoveFile、MoveFileEx、MoveFileWithProgress、CopyFile、CopyFileEx。
  • S32K312 RTD 4.0.0 版本 OCU 例程配置流程说明
  • Linux centerOS 服务器搭建NTP服务
  • Android Glide(一):源码分析,内存缓存和磁盘缓存的分析,实现流程以及生命周期
  • SCAU学习笔记 - 面向对象程序设计课后习题
  • hive如何删除分区
  • 「面试题」如何实现一个圣杯布局?
  • Angular Elements 及其运作原理
  • canvas 高仿 Apple Watch 表盘
  • co.js - 让异步代码同步化
  • JavaScript 无符号位移运算符 三个大于号 的使用方法
  • Netty+SpringBoot+FastDFS+Html5实现聊天App(六)
  • ng6--错误信息小结(持续更新)
  • Puppeteer:浏览器控制器
  • 每个JavaScript开发人员应阅读的书【1】 - JavaScript: The Good Parts
  • 突破自己的技术思维
  • Python 之网络式编程
  • 关于Android全面屏虚拟导航栏的适配总结
  • # windows 运行框输入mrt提示错误:Windows 找不到文件‘mrt‘。请确定文件名是否正确后,再试一次
  • #大学#套接字
  • $.proxy和$.extend
  • (done) NLP “bag-of-words“ 方法 (带有二元分类和多元分类两个例子)词袋模型、BoW
  • (java版)排序算法----【冒泡,选择,插入,希尔,快速排序,归并排序,基数排序】超详细~~
  • (Spark3.2.0)Spark SQL 初探: 使用大数据分析2000万KF数据
  • (附源码)springboot 基于HTML5的个人网页的网站设计与实现 毕业设计 031623
  • (附源码)springboot课程在线考试系统 毕业设计 655127
  • (含答案)C++笔试题你可以答对多少?
  • (算法)区间调度问题
  • (游戏设计草稿) 《外卖员模拟器》 (3D 科幻 角色扮演 开放世界 AI VR)
  • (转) ns2/nam与nam实现相关的文件
  • (转)chrome浏览器收藏夹(书签)的导出与导入
  • ***通过什么方式***网吧
  • .Net 6.0 Windows平台如何判断当前电脑是否联网
  • .NET6 开发一个检查某些状态持续多长时间的类
  • .Net高阶异常处理第二篇~~ dump进阶之MiniDumpWriter
  • @JoinTable会自动删除关联表的数据
  • @NestedConfigurationProperty 注解用法
  • @Tag和@Operation标签失效问题。SpringDoc 2.2.0(OpenApi 3)和Spring Boot 3.1.1集成
  • [AIGC] 广度优先搜索(Breadth-First Search,BFS)详解
  • [Docker]六.Docker自动部署nodejs以及golang项目
  • [Firefly-Linux] RK3568 pca9555芯片驱动详解
  • [IE编程] 如何获得IE版本号
  • [IM] [Webhook] Webhook实现IM平台机器人
  • [J2ME]url请求返回参数非法(java.lang.illegalArgument)
  • [leetcode]Search a 2D Matrix @ Python
  • [loj6039]「雅礼集训 2017 Day5」珠宝 dp+决策单调性+分治