c++之说_12|模板

关于模板,至少我们要先了解几个概念

一:函数模板

二:类模板

三:模板特化

四:形参参数包

模板覆盖的东西太多  我目前也不了解太多

函数模板 语法

template<typename 类型名,typename 类型名,typename ...多参包类型名> 
//内部的typename可写多个 有时我们可能会看到 这里会写 class 意思大概是差不多的返回值 函数名(){};
如template<typename arg, typename ...args>
void set(arg s, args... d)
{val = s;base::set(d...);
};

这里我们可以看到  template<typename arg, typename ...args>

模板形参类型 有 arg  和  args 形参包 

arg  自然就代表可接受一个参数类型,

而args 形参包也叫变参参数包 可接受多个参数

当然也有要求

一:

模板形参必须可推导,显示实例化也属于可推导

二:

有显示实例化当然也有隐式实例化

但隐式实例化有个要求

函数参数中必须可推导模板形参

template<typename arg, typename ...args>
void set(arg s, args... d)
{val = s;
};

模板形参在函数参数中直接用到了

可如此调用  这叫隐式实例化
set(10,'g',50,100); //arg = int   args = {char,int,int}这叫显示实例化
set<int,char,int,int>(10,'g',50,100); //arg = int   args = {char,int,int}

你会说 我们要是在函数参数中没一一使用到 模板形参怎么办?

template<typename Obj,typename arg, typename ...args>
void set(arg s, args... d)
{Obj c(s,d...);
};

自然就要显示实例化了  注意必须可推导

set<obj_2>(10, 100);
// Obj = obj_2   agr = int  args ={int}

看就显示实例化了没用到的 Obj 模板形参  我们人看上去也是可推导的 

编译器是人写的所以逻辑也是有些符合我们的思维的

函数特化有两种

偏特化(部分特化)

全特化

说实在的我也不太懂  我只能把我懂的部分 说一下

首先我们先看看 函数模板的偏特化 与 全特化

//主模版
template<typename Obj,typename arg, typename ...args>
void set(arg s, args... d)
{Obj c(s,d...);
};//特化版本   args* 形参包中 全为 指针类型
template<typename Obj, typename arg, typename ...args>
void set(arg* s, args*... d)
{Obj c(d..., s);
}//特化版本  Ret(*s)(a...)  非成员函数的函数指针
template<typename Obj, typename Ret,typename ...a,typename ...args >
void set(Ret(*s)(a...), args* ... d)
{Obj c((*d)..., s());
}//全特化   
template<>
void set<obj_2,float,float>(float v, float d)
{obj_2 c(d, v);
}//全特化
template<>
void set<obj_2>(float v, float d,char k)
{obj_2 c(d, v);
}

这里有个叫主模版的函数模板   用特化就必须得需要主模版  什么叫主模板?

我的理解中是  函数模板 最基本的哪个

 比如上文

template<typename Obj,typename arg, typename ...args>
void set(arg s, args... d)
{
    Obj c(s,d...);
};

这个模板可以容纳下面几个特化出来的函数参数的样子 

比如指针类型  非成员函数的函数指针类型  成员函数的函数指针类型 

也就是说它更加全面

你说都更加全面了 我为什么还有特化

这是为了  处理不同的情况嘛   就和函数重载时  处理不同情况一样

比如


int geti()
{return 50;
}int b = 10, b2 = 100;
set<obj_2>(&b,&b2);
/*
调用的特化版本是
template<typename Obj, typename arg, typename ...args>
void set(arg* s, args*... d)
{Obj c(d..., s);
}
*/set<obj_2>(&geti, &b2);
/*
调用的特化版本是
template<typename Obj, typename Ret,typename ...a,typename ...args >
void set(Ret(*s)(a...), args* ... d)
{Obj c((*d)..., s());
}*/set<obj_1>(0.5f, 5.3f);
/*
调用主模板版本
template<typename Obj,typename arg, typename ...args>
void set(arg s, args... d)
{Obj c(s,d...);
};*/set<obj_2>(0.5f, 5.3f,'p');
/*
调用的特化版本是
template<>
void set<obj_2>(float v, float d,char k)
{obj_2 c(d, v);
}*/set<obj_2>(0.f);
/*
调用主模版
template<typename Obj,typename arg, typename ...args>
void set(arg s, args... d)
{Obj c(s,d...);
};*/

有人可能看到 有些特化怎么 模板形参比主模版还多

template<typename Obj, typename Ret,typename ...a,typename ...args >
void set(Ret(*s)(a...), args* ... d)
{Obj c((*d)..., s());
}

这就是模板特化的一部分特性  注意 特化版本的模板形参与主模版的模板形参并无瓜葛 

就算他们是一样的名字 

但是实则是有一定要求的

比如

//主模板
template<typename Obj, typename arg>
void set1(arg c) { Obj c{}; };//全特化
template<>
void set1<obj_2,float>(float c) { obj_2 bc{}; };//错误特化版本  
template<>
void set1<obj_2,int,char>(int c) {};
/*这里我们注意到了, set1<obj_2,int,char>  有三个模板实参 而 我们的主模板只需要 两个模板实参  这就是要求:  不能大于主模版要求的模板形参数目*///错误特化版本   与上述一样   
template<>
void set1<obj_2>(int c,char b) {};

和函数重载  很类似的规则

有人会问了 那你第一种怎么可写好多个模板实参?

注意主模版哦

template<typename Obj,typename arg, typename ...args>
void set(arg s, args... d)
{Obj c(s,d...);
};

一眼上去三个模板形参   但是我们最后是个模板形参包啊

不限个数的啊  超过两个的形参  统统进入形参包

这里可能有少年提出这样的写法

template<typename arg, typename ...args>
void set<obj_2>(arg s, args... d)
{obj_2 c(s,d...);
};

看着 嗯.....  我就特化处理  这个obj_2类型的 

不过可惜 这样写法是错误的

注意右边的 编译输出错误

好了函数模板的特化说完了

---------------------------------------------------

现在我们来看看类模板

      

上模板

//主模版
template<typename tp>
struct t1
{
};//特化版本
template<typename Ret,typename Clss,typename ...Args>
struct t1 <Ret(Clss::*)(Args...)>
{using F = Ret(Clss::*)(Args...);t1(F fptr):ptr(fptr) {};F ptr;
};int main()
{t1<decltype(&obj_2::gets)> b(&obj_2::gets);return 0;
}

和函数模板特化差不多

类模板可通过构造函数的参数推断模板形参

template<typename tp>
struct t1
{using type = tp;t1(tp p) :tpo(p) {};tp tpo;
};template<typename Ret,typename Clss,typename ...Args>
struct t1 <Ret(Clss::*)(Args...)>
{using F = Ret(Clss::*)(Args...);t1(F fptr):ptr(fptr) {};F ptr;
};int main()
{t1<decltype(&obj_2::gets)> b(&obj_2::gets);t1 b = t1(&obj_2::gets);//特化版本 struct t1 <Ret(Clss::*)(Args...)>// F = int(obj_2::*)(int,int,char);t1(obj_2()); // 推断的是主模版  tp = obj_2return 0;
}

类模板可以弥补我们之前函数模板的遗憾

template< typename tp>
struct t2<obj_2,tp>
{t2(tp p):op(p) {}tp op;obj_2 d;};

它可以这样特化

但是又有可惜的事情了


t1 b3 = t1(obj_2()); //ok
auto c =    t2<obj_2,decltype(b3)>(b3); //ok  template< typename tp> struct t2<obj_2,tp>auto c2 = t2<obj_2>(b3); // error  可惜不可以这样调用  至少我是能看出来 应该可以推导
template<typename obj,typename tp>
struct t2
{using type = tp;t2(obj* oj,tp p) :ptr(oj), tpo(p) {};obj* ptr;tp tpo;
};//main 中t1 b = t1(&obj_2::gets);
t1 b3 = t1(obj_2());
auto c3 = t2(&b, &b3);//可以

对了  忘记说一件事了

形参包 我们通过特化给它拆开

//主模版
template<typename ...T>
struct Tuple_text {};//特化
template<>
struct Tuple_text<>
{void set() {};
};//特化
template<typename Ty1, typename ...Ty2>
struct Tuple_text<Ty1, Ty2...> :public Tuple_text<Ty2...> {Ty1 val;using base = Tuple_text<Ty2...>;Tuple_text() {}template<typename arg, typename ...args>Tuple_text(arg a, args... d) :val(a), base(d ...) {}template<typename ...arg>void set(arg... args) {};template<>void set<>() {};template<typename arg, typename ...args>void set(arg s, args... d){val = s;base::set(d...);};base& get(){return *this;}
};

这是今天学习到的  c++元组的类似做法

这里最关键的地方就是

template<>
struct Tuple_text<>
{void set() {};
};template<typename Ty1, typename ...Ty2>
struct Tuple_text<Ty1, Ty2...> :public Tuple_text<Ty2...>
{Ty1 val;
using base = Tuple_text<Ty2...>;Tuple_text() {}
template<typename arg, typename ...args>
Tuple_text(arg a, args... d) :val(a), base(d ...) {}}

没想到吧  我们的构造函数都能模板

这个有点复杂

我们展开看看   C++ Insights (cppinsights.io)  这个网站可以展开模板

#include <cstdio>//主模板
template<typename ... T>
struct Tuple_text
{
};//特化
template<>
struct Tuple_text<>
{inline void set(){}};//以下都为特化  实例化后其实也就是特化
template<>
struct Tuple_text<long> : public Tuple_text<>
{long val;using base = Tuple_text<>;inline Tuple_text();template<>inline Tuple_text<long>(long a): Tuple_text<>(), val{a}{}};template<>
struct Tuple_text<float, long> : public Tuple_text<long>
{float val;using base = Tuple_text<long>;inline Tuple_text();template<>inline Tuple_text<float, long>(float a, long __d1): Tuple_text<long>(__d1), val{a}{}};template<>
struct Tuple_text<char, float, long> : public Tuple_text<float, long>
{char val;using base = Tuple_text<float, long>;inline Tuple_text();template<>inline Tuple_text<char, float, long>(char a, float __d1, long __d2): Tuple_text<float, long>(__d1, __d2), val{a}{}};template<>
struct Tuple_text<int, char, float, long> : public Tuple_text<char, float, long>
{int val;using base = Tuple_text<char, float, long>;inline Tuple_text();template<>inline Tuple_text<int, char, float, long>(int a, char __d1, float __d2, long __d3): Tuple_text<char, float, long>(__d1, __d2, __d3), val{a}{}};int main()
{Tuple_text<int, char, float, long> c = Tuple_text<int, char, float, long>(100, 'o', 6.0F, 500L);return 0;
}

注意main 函数里

我们看到这是一系列的继承关系

我们去vs 看看内存布局

我们看到  Tuple_text<int, char, float, long> 类里面有所有的 val 

那我们应该怎么拿到呢?

Tuple_text<int, char, float, long> 的 val 很简单

但是 继承的 父类 Tuple_text<char, float, long> 的 val

怎么拿呢?

我们要是能转换为 父类对象就好了

using base = Tuple_text<Ty2...>;

base& get()
{return *this;
}

这样是不是就能拿到父类对象了?

Tuple_text<int, char, float, long>  : Tuple_text<char, float, long> :

Tuple_text<float, long> : Tuple_text<long> : Tuple_text<>

每一级的 base 都是本级继承的父类

c.val   c.get().val  c.get().get().val  c.get().get().get().val

Tuple_text<> 这个是我们自己特化的类

是一个空的 所以继承链到此终结

模板编程是面向编译器的

很强大 但是也很难以解读

模板的玩法不只这些  玩法很多很多 看大家积累了 我也需要积累

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

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

相关文章

【JavaScript学习路线——详细讲解】

JavaScript学习路线 1. 介绍2. JavaScript基础3. HTML/CSS与JavaScript结合4. 进阶JavaScript5. 前端框架与库6. API与HTTP通信7. 移动端和PWA8. Node.js和后端开发9. 测试和部署10. 全栈项目实践11. 持续学习和社区参与 1. 介绍 学习JavaScript的路线可以分为几个主要阶段&am…

【水文】计算斐波那契数列的第n项

#include <stdio.h> // 函数声明 int fibonacci(int n); int main() { // 输入正整数n int n; printf("请输入一个正整数 n&#xff1a;"); scanf("%d", &n); // 调用函数计算斐波那契数列的第n项并输出结果 int result …

CopyOnWriteArrayList底层原理全面解析【建议收藏】

简介 CopyOnWriteArrayList是Java中的一个线程安全的集合类&#xff0c;是ArrayList线程安全版本&#xff0c;主要通过Copy-On-Write&#xff08;写时复制&#xff0c;简称COW&#xff09;机制来保证线程安全。 Copy-On-Write机制核心思想&#xff1a;向一个数组中添加数据时…

LabVIEW动平衡测试与振动分析系统

LabVIEW动平衡测试与振动分析系统 介绍了利用LabVIEW软件和虚拟仪器技术开发一个动平衡测试与振动分析系统。该系统旨在提高旋转机械设备的测试精度和可靠性&#xff0c;通过精确测量和分析设备的振动数据&#xff0c;以识别和校正不平衡问题&#xff0c;从而保证机械设备的高…

Leetcode 322 零钱兑换

题意理解&#xff1a; 给你一个整数数组 coins &#xff0c;表示不同面额的硬币&#xff1b;以及一个整数 amount &#xff0c;表示总金额。 计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额&#xff0c;返回 -1 。 你可以认为每种硬币…

Springboot集成jasypt实现配置文件加密

Jasypt它提供了单密钥对称加密和非对称加密两种加密方式。 单密钥对称加密&#xff1a;一个密钥加盐&#xff0c;可以同时用作内容的加密和解密依据&#xff1b; 非对称加密&#xff1a;使用公钥和私钥两个密钥&#xff0c;才可以对内容加密和解密&#xff1b; 我们以单密钥对称…

前端 reduce()用法总结

定义 reduce()方法对数组中的每个元素执行一个由您提供的reducer函数(升序执行)&#xff0c;将其结果汇总为单个返回值。语法为&#xff1a; array.reduce(function(accumulator, currentValue, currentIndex, arr), initialValue); /*accumulator: 必需。累计器currentValu…

5 步轻松上手,教你从 0 到 1 落地 Jmeter 接口自动化脚本!

Jmeter是进行接口测试的一款非常主流的工具&#xff0c;但绝大部分测试工程师&#xff0c;对于Jmeter接口测试脚本整理都是一知半解的。今天这篇文章&#xff0c;就以一个金融项目中接口为例&#xff0c;通过简单5步&#xff0c;教大家如何0代码编写Jmeter接口自动化脚本&#…

CPU和GPU有什么区别,玩游戏哪个更重要?

大家好&#xff01;今天我们要聊的话题是CPU和GPU&#xff0c;它们在电脑中扮演着重要的角色&#xff0c;虽然看起来只是两个简单的缩写&#xff0c;但它们的功能和影响是截然不同的&#xff01; 那么&#xff0c;究竟CPU和GPU有什么区别呢&#xff1f;在玩游戏时&#xff0c;…

Linux 系统开启网络服务

首先&#xff0c;大家新装的linux系统可能都没有安装vim工具&#xff0c;所以打开文件的方式是 vi /etc/sysconfig/network-scripts/ifcfg-ens33在这个界面把onboot改为yes&#xff0c;我这里是设置完的。然后通过下面语句重新启动服务就可以了。 service network restartcen…

2024.2.7日总结(小程序开发4)

页面导航 页面导航是页面之间的相互跳转&#xff1a; <a>链接location.href 小程序中实现页面导航的两种方式&#xff1a; 声明式导航 在页面上声明一个<navigator>导航组件通过点击<navigator>组件实现页面跳转 编程式导航 调用小程序的导航API&…

飞天使-k8s知识点15-kubernetes散装知识点4-CNI网络插件与kubectl

文章目录 CNI 网络插件安装任意节点运行kubectlAPI的版本区别与废弃API查询 CNI 网络插件安装 这里将以 Calico 为例&#xff0c;提供在 Kubernetes 1.20.6 版本上安装 CNI 插件的步骤。请注意&#xff0c;具体的步骤可能会因 CNI 插件的类型和你的特定环境而略有不同。设置 Ku…

BaseMapper中提供的方法(17种CRUD)

BaseMapper封装的17种增删改查方法 MybatisPlus框架中mapper层继承了BaseMapper接口&#xff0c;该接口中封装了常用的增删改查方法&#xff0c;共有17种&#xff0c;以下是方法的详情介绍 首先需要明确的括号内的一些对象定义 泛型T&#xff1a;实体类类型Param注解&#x…

JavaScript 设计模式之单例模式

单例模式 常规单例 单例模式我们在日常使用中还是非常多的&#xff0c;比如常见的 jQuery&#xff0c;prototype&#xff0c;vue等都是属于单例模式&#xff0c;我们在使用 new Vue 的时候&#xff0c;返回的也会是同一个实例的&#xff0c;简单实现 // 方式一 let Car func…

mybatis-plus循环处理多个条件的 or 查询

我们一般用 mybatis-plus 的提供的 api 接口处理 List、Set 作为条件查询的时候&#xff0c;都会使用 in&#xff0c;例如 &#xff08;Student 类省略 没啥好些的&#xff09;&#xff1a; LambdaQueryWrapper<Student> queryWrapper new QueryWrapper<Student>…

iPhone解锁 AnyMP4 iPhone Unlocker

AnyMP4 iPhone Unlocker是一款功能强大的iPhone解锁软件&#xff0c;旨在帮助用户轻松解决iPhone密码忘记、设备锁定等问题。无论是屏幕密码、指纹解锁还是Face ID&#xff0c;该软件都能提供有效的解决方案。 这款软件支持多种iPhone型号&#xff0c;包括最新的iPhone 14系列…

2.3_9 吸烟者问题

2.3_9 吸烟者问题 问题描述 问题分析 假设一个系统有三个抽烟者进程和一个供应者进程。每个抽烟者不停地卷烟并抽掉它&#xff0c;但是要卷起并抽掉一支烟&#xff0c;抽烟者需要有三种材料&#xff1a;烟草、纸和胶水。三个抽烟者中&#xff0c;第一个拥有烟草、第二个拥有纸…

泛娱乐社交出海洞察,Flat Ads解锁海外增长新思路

摘要:解读泛娱乐社交应用出海现状与趋势,解锁“掘金”泛娱乐社交出海赛道新思路。 根据全球舆情监测机构 Meltwater 和社交媒体机构We are Social最新发布数据显示,全球社交媒体活跃用户数量已突破50亿,约占世界人口总数62.5%。庞大的用户数量意味着广阔的增量空间,目前,随着全…

使用HCPpipelines分割皮层

前段时间阅读了一篇文献,文章的做法我比较感兴趣,所以打算学习一下文献的做法。文章的最开始一部分是使用HCPpipelines对T1和T2像进行皮层分割,调用的是freesurfer6。https://github.com/Washington-University/HCPpipelines 一、工作环境准备 1.安装好FSL,版本在6.0.2以上…

Linux环境下配置HTTP代理服务器教程

大家好&#xff0c;我是你们可爱的Linux小助手&#xff01;今天&#xff0c;我将带你们一起探索如何在Linux环境下配置一个HTTP代理服务器。请注意&#xff0c;这不是一次火箭科学的实验&#xff0c;而是一次简单而有趣的冒险。 首先&#xff0c;我们需要明确什么是HTTP代理服…