6.20模板

c/c++/jave 静态语言强类型语言编译型语言,想要在运行以前就能够找出错误。

python是一个弱类型语言,在运行的时候再确定变量的类型。

背景需求:想要在强类型严格的要求下希望能够更加灵活。

有三种方式可以实现:宏定义(不便检错),函数重载,模板

模板:在使用时,通用类型可被具体的类型,如 int、double 甚至是用户自定义的类型来代替。模板引入一种全新的编程思维方式,称为“泛型编程”或“通用编程”。

//希望将类型参数化
//使用class关键字或typename关键字都可以
template <class T>
T add(T x, T y)
{
 return x + y;
}

int main(void){
	cout << add(1,2) << endl;
    cout << add(1.2,3.4) << endl;
    string s1 = "xixi";
    string s2 = "haha";
    cout << add(s1, s2) << endl;
 return 0;
}

将类型参数化

函数模板相较于我们以前使用的函数重载不用定义出各种类型的函数,再调用时可以实例化对应的函数。

函数模板在调用的时候,就可以知道参数的类型。

模板发生的时机是在编译时

模板本质上就是一个代码生成器,它的作用就是让编译器根据实际调用来生成代码。

函数模板 --》 生成相应的模板函数 --》编译 ---》链接 --》可执行文件

相比于普通的函数就是多了一步实例化出生成相应的模板函数

模板的定义

将类型也参数化

模板可以分为两类,一个是函数模版,另外一个是类模板

模板参数是一个更大的概念,包含了类型参数和非类型参数。

在具体的调用的时候根据函数模板可以实例化出模板函数,也可以由类模板实例化出模板类。

上述的模板实例化的时候是一种隐式实例化,下述的模板的实例化是一种显示实例化,模板参数一定会转化为指定的类型。

template <class T>
T add(T t1,T t2)
{ return t1 + t2; }

void test0(){
    int i1 = 3, i2 = 4;
    cout << "add(i1,i2): " << add<int>(i1,i2) << endl;  //显式实例化
}

函数模板的重载

背景需求:因为可能出现同为形参的两个参数的类型也是不同的。就像是上面add(int,double)

【注意】以下在工作中不会出现,出现歧义

template <class T> //模板一
T add(T t1,T t2)
{ 
return t1 + t2;
}


template <class T1, class T2>  //模板二
T1 add(T1 t1, T2 t2)
{
return t1 + t2;
}

这样的返回的类型是T1类型还是有问题。

add<int> (1, 1.2) 这个显示实例化也只是将第一个参数实例化为指定的类型,第二个参数类型不变

区分下列哪些情况调用哪个模板函数

【注意】尽量不要定义可以给同一个函数可以使用多个模板的写法,如果要使用的话也不要进行显示定义,这样很难分析究竟使用了哪一个模板。

double x = 9.1;
int y = 10;
cout << add<int,int>(x,y) << endl; //模板二  (1)
cout << add<int>(x,y) << endl; //模板一   (2)
//第(2)次调用时,指定了返回类型和第一个参数类型为int,那么x会经历一次类型转换变成int型,而y本身就是int,可以匹配模板一;
cout << add<int>(y,x) << endl; //模板二   (3)
//第(3)次调用时,同样指定了返回类型和第一个参数类型为int,y本身就是int,x是double
//类型,匹配模板二,可以不需要进行任何类型转换,所以优先匹配模板二。
//因为如果要是没有模板二的话就只能走模板一再继续对于参数二继续进行转换

在一个模块中定义多个通用模板的写法应该谨慎使用(尽量避免)如果实在需要也尽量使用隐式实例化的方式进行调用,编译器编译器会选择参数类型最匹配的模板(通常是参数类型需要更少转换的模板)。

【了解】下列的情况,不要这样使用

template <class T1, class T2>  
T1 add(T1 t1, T2 t2)
{
	cout << "模板一" << endl;
	return t1 + t2;
} 

template <class T1, class T2>  
T1 add(T2 t2, T1 t1)
{
	cout << "模板二" << endl;
	return t1 + t2;
}

int a = 10;
double b = 1.2;
cout << add(a,b) << endl; //error
//冲突不能知道走那一条路
cout << add<int>(a,b) << endl; //模板一
cout << add<double>(a,b) << endl;  //模板二
//template <class T1, class T2>  根据这个来所以说在模板二这种情况还是让T1是double类型

函数模板与函数模板重载的条件

1.两个模板名字相同

2.参数顺序不同也可重载

3.模板参数的个数不同重载

template <class T2, class T1>
T1 add(T2 t2, T1 t1)
{
return t1 + t2;
}

template <class T1, class T2, class T3>
T1 add(T1 t1, T2 t2, T3 t3)
{
return t1 + t2 + t3;
}

模板函数与普通函数的重载

这种情况使用普通函数的效率更高会使用普通函数

//函数模板与普通函数重载
template <class T1, class T2>
T1 add(T1 t1, T2 t2)
{
return t1 + t2;
}

short add(short s1, short s2){
cout << "add(short,short)" << endl;
return s1 + s2;
}

void test1(){
short s1 = 1, s2 = 2;
cout << add(s1,s2) << endl;   //调用普通函数
}

头文件和实现文件形式

声明和实现可以分离 

//函数模板的声明
template <class T>
T add(T t1, T t2);


void test1(){ 
    int i1 = 1, i2 = 2;
	cout << add(i1,i2) << endl;
}

//函数模板的实现
template <class T>
T add(T t1, T t2)
{
    return t1 + t2;
}

1. 但是注意当在不同的文件中定义的时候,如果是在头文件中写出实现是可以的实现的。

2. 但是如果是在实现文件中写出,需要将模板实例化为两个double函数相加

3. 也可以在头文件中再加一个引用,这样就知道它的实现了

对模板的使用,必须要拿到模板的全部实现,如果只有一部分,那么推导也只能推导出一部分,无法满足需求。

换句话说,就是模板的使用过程中,其实没有了头文件和实现文件的区别,在头文件中也需要获取模板的完整代码,不能只有一部分。

【最终】现在的解决办法是C++的标准库都是由模板开发的,所以经过标准委员会的商讨,将这些头文件取消了后缀名,与C的头文件形成了区分;这些实现文件的后缀名设为了tcc

实现

//temple.cc
#include "add"
#include <iostream>
using namespace std;

int main()
{
    cout << add<int>(1,1.2) << endl;
    return 0;
}

template <class T1, class T2>
T1 add(T1 a, T2 b);
#include "add.tcc"
template <class T1, class T2>
T1 add(T1 a, T2 b){
    return a+b;
}

这种情况下就不用再进行联合编译,因为实际上就相当于将实现文件也定义头文件中,是c++定义的一种特殊的书写方式。

模板的特化

背景需求:通用模板不能使用的时候可以使用特化模板。 

就像是上面的例子不能实现char*类型的+,可以采用的方法有可以指定为<string>这样就会进行类型转换。

//特化模板
//这里就是告诉编译器这里是一个模板
template <>
const char * add<const char *>(const char * p1,const char * p2){
    //先开空间
    char * ptmp = new char[strlen(p1) + strlen(p2) + 1]();
    strcpy(ptmp,p1);
    strcat(ptmp,p2);
    return ptmp;
}

void test0(){
    //通用模板无法应对如下的调用
    const char * p = add<const char *>("hello",",world");
    cout << p << endl;
    delete [] p;
}

【注意】

1.如果没有相对应的通用模板(函数名参数类型),特化模板在能够定义

2. 再使用特化模板的时候最好写成上述的形式,不是使用隐式的方式

使用模板的规则

1. 一个函数可以调用多个模板均可实现的时候谨慎使用

2. 尽量使用隐式转换,不然不知道调用的是哪一个模板

3. 尽量不要指定模板转换的类型,也会造成不知道是哪一个函数

4. 使用特化模板的时候,使用固定的格式声明。

 模板的参数类型

1.类型参数

class T类型可以被推导

2.非类型参数 

整型数据不能是浮点型数据char/short/int/long/size_t,不能是float/double

因为是在编译的阶段确定的

template <class T,int kBase>
T multiply(T x, T y){
	return x * y * kBase;
}

void test0(){
int i1 = 3,i2 = 4;
//此时想要进行隐式实例化就不允许了,因为kBase无法推导
cout << multiply(i1,i2) << endl;  //error
cout << multiply<int,10>(i1,i2) << endl;   //ok
}

当然可以有默认值,类型化和非类型化参数都可以。

template <class T = int,int kBase = 10>
T multiply(T x, T y){
	return x * y * kBase;
}

void test0(){
	int i1 = 1.2,i2 = 1.2;
	cout << multiply<int,100>(i1,i2) << endl; //100 
	cout << multiply<int>(i1,i2) << endl;     //10
	cout << multiply(i1,i2) << endl;          //14.4
}

根据上述的三个例子

优先级:指定的类型 > 推导出的类型 > 类型的默认参数

但是那再什么时候或使用到默认值情况,是在既没有指定,又不能推导的时候,使用默认类型。

往往是在单独设置返回类型的时候

template <class T1 = double,class T2 = double,class kBase = 10>
T1 multiply(T2 t1, T2 t2){
return t1 * t2 * kBase;
}

cout << multiply(1.2,1.2) << endl; //ok

成员函数模板

class Point
{
public:
	Point(double x,double y)
	: _x(x)
	, _y(y)
	{}

	template <class T>
	T add(T t1)
	{
		return _x + _y + t1;
	}
private:
	double _x;
	double _y;
};

void test0(){
Point pt(1.5,3.8);
cout << pt.add(8.8) << endl;
}

普通成员函数指针也是有this指针的。

同理static成员函数,就没有this指针,只能访问静态数据成员。

成员函数模板 不能设为 virtual,成员函数模板起效在编译阶段,虚函数实现是在运行时候。

类模板

类模板定义的是一类类

存放任意元素类型的类

template <class T = int, int kCapacity = 10>
class Stack
{
public:
    Stack()
    : _top(-1)
    , _data(new T[kCapacity]())
    {
        cout << "Stack()" << endl;
    }
    ~Stack(){
        if(_data){
            delete [] _data;
            _data = nullptr;
        }
        cout << "~Stack()" << endl;
    }
    bool empty() const;
    bool full() const;
    void push(const T &);
    void pop();
    T top();
private:
    int _top;
    T * _data;
};
int main(){
    Stack<> st;

}

这个时候已经有默认值,但是也是需要加上<>

可变参数模板

对参数进行了高度泛化,它能表示0到任意个数、任意类型的参数。

例如:printf函数的参数个数可能有很多个,用...表示,参数的个数、类型、顺序可以随意,可以写0到任意多个参数。

printf函数的参数个数可能有很多个,用...表示,参数的个数、类型、顺序可以随意,可以写0到任意多个参数。

template <class ...Args>  
void func(Args ...args);

//普通函数模板做对比
template <class T1,class T2>
void func(T1 t1, T2 t2);

利用可变参数模板输出参数包中参数的个数

template <class ...Args>//Args 模板参数包
void display(Args ...args)//args 函数参数包
{
    //输出模板参数包中类型参数个数
    cout << "sizeof...(Args) = " << sizeof...(Args) << endl;
    //输出函数参数包中参数的个数
    cout << "sizeof...(args) = " << sizeof...(args) << endl;
}

void test0(){
    display();
    display(1,"hello",3.3,true);
}

当类型相同的时候,也并不会减少类型个数,还是和参数个数相同,因为最多有这些个类型,这样就不会有可能导致类型的缺少。

//递归的出口
void print(){
	cout << endl;
}

//重新定义一个可变参数模板,至少得有一个参数
//不断递归到最后就没有相应的函数可以调用,因为没有可以适用于无参的,所以
//定义一个优先级比较高的无参的函数来实现
template <class T,class ...Args>
void print(T x, Args ...args)
//有多少个参数个数有多少个类型个数与之对应
{
	cout << x << " ";
	print(args...);  //省略号在参数包右边
//这样就会继续继续解包
}

设置不同的出口,如果最后的是int,就会从int直接输出,因为只剩一个参数且是int类型

void print(){
    cout << endl;
}

void print(int x){
    cout << x << endl;
}

template <class T,class... Args>
void print(T x, Args... args)
{
    cout << x << " ";
    print(args...);
}

print(1,"hello",3.6,true,100);

想要获取所有类型的元素的出口

【注意】1. 递归的出口最好使用普通函数作为递归出口(因为普通函数优先级更高,不会出现函数重载情况)

2. 这个地方也可以两个两个或者三个参数一块解包,但是需要准备只有一个参数或者无参数的出口。

获取参数的类型(了解)

void printType(){
    cout << endl;
}

//重新定义一个可变参数模板,至少得有一个参数
template <class T,class... Args>
void printType(T x, Args... args)
{
    cout << typeid(x).name() << " ";
    printType(args...);
}

printType(1,"hello",3.6,true,100);

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

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

相关文章

各场景ChatGPT指令Promp提示词大全

一、Promp指令使用指南 直接复制Promp指令提示词使用 可以前往已经添加好Prompt预设的AI系统测试使用&#xff08;可自定义添加使用&#xff09; 支持GPTs应用Prompt自定义预设应用 SparkAi SparkAi创作系统是一款基于ChatGPT和Midjourney开发的智能问答和绘画系统&#xff…

Vue2中为啥不用 Object.defineProperty 实现响应式数组 ? 不能监听到数组变化吗?

Vue2.0 对于数据响应式的实现上是有一些局限性的&#xff0c;比如&#xff1a; 无法检测数组和对象的新增&#xff1b; 无法检测通过索引改变数组的操作&#xff1b; 针对以上问题&#xff0c;我们一般都会把锅甩给 Object.defineProperty。所以&#xff0c;在Vue 3.0 中&am…

uniapp 小程序 堆叠轮播图 左滑 右滑 自动翻页 点击停止自动翻页

uniapp 小程序 堆叠轮播图 左滑 右滑 自动翻页 点击停止自动翻页 超过指定时间未点击滑动 则继续开始滚动 直接上代码 componentSwiper.vue 需要注意页面切换时清除计时器 <template><view><view class"swiperPanel" touchstart"startMove"…

STM32烧写hex及bin文件的五种方法

一.STVP 1.概述 STVP是ST早期的一款下载编程工具&#xff0c;支持早期的ST早期的芯片&#xff08;比如ST7系列&#xff09;&#xff0c;也支持STM8、 STM32。 该工具虽然相对ST-LINK utility、STM32CubeProg比较老&#xff0c;但该工具官方在2017年还进行了维护&#xff0c;现…

基于web的摩托车销售系统的设计与实现-计算机毕业设计源码031706

摘 要 信息化社会内需要与之针对性的信息获取途径&#xff0c;但是途径的扩展基本上为人们所努力的方向&#xff0c;由于站在的角度存在偏差&#xff0c;人们经常能够获得不同类型信息&#xff0c;这也是技术最为难以攻克的课题。针对摩托车销售系统等问题&#xff0c;对摩托车…

<sa8650>QCX Usecase 使用详解— Spectra Studio工程建立

<sa8650>QCX Usecase 使用详解— Spectra Studio工程建立 一 前言二 建立usecase工程2.1 前提2.2 创建usecase工程3.2 查看usecase2三 总结一 前言 目前高通平台在camera模块中,我们会使用到usecase这么一个功能模块;本文主要讲解sa8650平台中,通过Spectra Studio 可视化…

[C/C++][VsCode]使用VsCode在Linux上开发和Vscode在线调试

目录 0. 前言1. win10上搭建环境Linux环境2.编写makefile3.怎么在线调试结语 0. 前言 在开发中&#xff0c;可以一边开发一边调试&#xff0c;这样可以大大的减少bug&#xff1b;但是正常来说一个大点的项目&#xff0c;是不太可能单步调试的&#xff0c;因为一般都是用make或…

无人机螺旋桨理论教学培训课程

本文档为一份详细的关于TYTO机器人公司提供的电机和螺旋桨理论及其实验操作的指南。指南首先概述了材料、实验目标以及实验的介绍部分&#xff0c;随后详细阐述了理论问题、实验步骤和附录内容。实验目的在于通过实际测试来测量和理解不同螺旋桨参数对无人机性能的影响&#xf…

手把手教你使用kimi创建流程图【实践篇】

学境思源&#xff0c;一键生成论文初稿&#xff1a; AcademicIdeas - 学境思源AI论文写作 引言 在昨日的文章中&#xff0c;我们介绍了如何使用Kimi生成论文中的流程图。今天&#xff0c;我们将更进一步&#xff0c;通过实践案例来展示Kimi在生成流程图方面的应用。这不仅将加…

回归洛伦兹变换

现在再回到洛伦兹变换&#xff0c; 将其写成上角标表示惯性系的形式&#xff08;注意不是幂次&#xff09;&#xff0c; 并且认为洛伦兹变换中的两个方程的比例常数&#xff0c; 并不仅仅是因为虚数单位数量巨大导致的“误判”&#xff0c;虽然这也是说得通的。因为我们已经看到…

Python 爬虫从入门到入狱之路一

实际上爬虫一共就四个主要步骤&#xff1a; 明确目标 (要知道你准备在哪个范围或者网站去搜索)爬 (将所有的网站的内容全部爬下来)取 (去掉对我们没用处的数据)处理数据&#xff08;按照我们想要的方式存储和使用&#xff09; 我们在之前写的爬虫程序中&#xff0c;都只是获取…

通讯:单片机串口和电脑通讯

目录 1.串口输出数据到电脑 硬件部分 串口输出数据到电脑的软件软件部分&#xff1a; 相关问题&#xff1a; 2.单片机串口--485--485转USB--电脑 串口&#xff0c;芯片&#xff0c;转换器&#xff0c;设备之间的通讯的接线&#xff0c;都是要TX--RX, RX--TX 交叉连接。 单…

基于springboot+Vue高校宿舍管理系统的设计与实现【附源码】

本科毕业设计&#xff08;论文&#xff09; 基于springbootVue高校宿舍管理系统的设计与实现 目录 摘要 2 第一章 绪论 2 1.1 开发背景 2 1.2 开发意义 2 第二章 系统分析 3 2.1 系统的需求分析 3 2.2 系统开发设计思想 3 2.3系统开发步骤 3 2.4 系统的主要技术 4 2.4.1 B/S系…

70年,800个,全球AI大模型数据可视化;750名工程师透露的AI真相;GenAI将取代初级程序员?NO!出海美国的创始人必读手册 | ShowMeAI日报

&#x1f440;日报&周刊合集 | &#x1f3a1;ShowMeAI官网 | &#x1f9e1; 点赞关注评论拜托啦&#xff01; 1. Anthropic 发布 Claude Artifacts&#xff0c;大模型从「聊天」正式迈入「工作流」 上周&#xff0c;Anthropic 公司发布了最新的大模型 Claude 3.5 Sonnet&am…

2024年【山东省安全员B证】最新解析及山东省安全员B证操作证考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年【山东省安全员B证】最新解析及山东省安全员B证操作证考试&#xff0c;包含山东省安全员B证最新解析答案和解析及山东省安全员B证操作证考试练习。安全生产模拟考试一点通结合国家山东省安全员B证考试最新大纲及…

AI助手的超级工具箱:Phidata框架实战指南

目录 引言一、Phidata概述二、Phidata的安装与快速入门1、安装Phidata2、环境配置3、快速入门3.1 可以搜索网页的助手3.2 可以查询财务数据的助手 三、Phidata的高级应用1、可以编写和运行Python代码的助手2、可以使用 SQL 分析数据的助手3、可生成 Pydantic 模型的助手4、具有…

探索Uptime命令:Linux系统管理员的必备工具

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 &#x1f38f;&#xff1a;你只管努力&#xff0c;剩下的交给时间 &#x1f3e0; &#xff1a;小破站 探索Uptime命令&#xff1a;Linux系统管理员的必备工具 前言基本用法语法输出示例输出字段解释系…

【RF Transceiver】ADRV9040 8T8R

具有DFE、400MHz iBW射频收发器的8T8R SoC 特性 8个差分发送器&#xff08;Tx&#xff09; 8个差分接收器&#xff08;Rx&#xff09; 2个观察接收器&#xff08;ORx&#xff09; 单频段和多频段&#xff08;N x 2T2R/4T4R&#xff09;能力 可调范围1内4个波段轮廓 调谐范围&a…

记录一个笔误引发的bug导致生产环境报错,但是本地环境,测试环境运行正常

记录一个笔误引发的bug导致生产环境报错&#xff0c;但是本地环境&#xff0c;测试环境运行正常 因为headers请求头过长导致报错 在feign外调其他系统时候&#xff0c;是重新封装headers 问题在于 MultiValueMap 属于静态变量。这里讲userAgent的内容传递过去。是不断累加的…

Stable Diffusion【进阶篇】:真人漫改之迪士尼风格定制

大家好&#xff0c;我是极客菌 关于真人漫改是一个应用比较多的图片定制方向&#xff0c;本文以及后面的章节我们结合一些具体的大模型或者LORA来更深入的实践一下。 一. 迪士尼风格 在SD的大模型中&#xff0c;实现迪士尼或者皮卡斯风格的图片&#xff0c;首推 Disney Pix…