泛型编程基础知识详解
泛型编程基础知识详解
- 函数模板
- 参数的推断
- 指定模板类型
- 类型推断总结
- 函数模板特化
- 全特化
- 类模板
- 参数的推断
- 类模板的特化
- 全特化
- 成员函数全特化
- 成员函数模板
- 模板中友元
- 友元类
- 友元函数
- 可变参函数模板
- 可变参数(包)的展开
- 折叠表达式
- 一元左折
- 一元右折
- 二元左折
- 二元右折
- 可变参数表达式
- 可变参类模板
- 类型展开
- 模板类型展开
函数模板
模板的定义包含几个关键字的使用
使用
template
关键字开头类型模板参数
T
前面要是用typename
修饰。template <typename T> T add(T v_1, T v_2) {return v_1 + v_2; }
参数的推断
指定模板类型
编译器可以根据输入自行推断参数的数据类,但是要显示的输入否则无法推断,如下所示
template <typename T, typename U, typename R>
R add(T v_1, U v_2) {return v_1 + v_2;
}int main() {std::cout << add(1, 1.2) << std::endl;return 0;
}
上面的代码定义了,三个类型模板参数其中T 明确是 int
而U 明确是 double
,但是由于没有信息明确指出返回值R 是什么类型
,因此,在编译时编译器会报如下错误信息。
error C2672: “add”: 未找到匹配的重载函数
error C2783: “R add(T,U)”: 未能为“R”推导 模板 参数
这时可以使用<> 明确的指出每个模板参数的类型
,如下所示。
int main() {std::cout << add<int, double, double>(1, 1.2) << std::endl;return 0;
}
如果者写法比较麻烦可以优化代码,指定一部分模板参数
,让编译器推断另外一部分模板参数
,修改如下。
template <typename R, typename T, typename U>
R add(T v_1, U v_2) {return v_1 + v_2;
}int main() {std::cout << add<double>(1, 1.2) << std::endl;return 0;
}
上面的代码说明了<>
指定模板参数类型的规则,明确模板参数的类型,一旦从某个模板参数开始推断,后续的所有模板参数都需要让编译器推断,不能跳着指定,有点儿类似与函数写默认参数的规则。
除了指定模板类型参数的方法外,还可以使用返回类型后置的方法,如下所示:
template <typename T, typename U>
auto add(T v_1, U v_2)->decltype(v_1 + v_2) {return v_1 + v_2;
}
类型推断总结
根据实际类型推断
template <typename T> T add(T v_1, T v_2) {return v_1 + v_2; }int main() {std::cout << add<double>(1.2, 1.2) << std::endl;return 0; }
指定参数类型
template <typename R, typename T, typename U> R add(T v_1, U v_2) {return v_1 + v_2; }int main() {std::cout << add<double>(1, 1.2) << std::endl;return 0; }
函数模板特化
全特化
全特化就是将泛化函数模板中的所有模板参数都用具体的类型代替,构成一个特殊的函数模板。
#include <iostream>
/* 泛化 */
template <typename T, typename U>
void test(T & v_1, U & v_2) {std::cout << "template func" << std::endl;
}
/* 全特化 */
template<>
void test<int, double>(int & v_1, double & v_2) {std::cout << "over specialized func" << std::endl;
}
这种全特化的模板函数会被正常函数覆盖,如下所示。
#include <iostream>template <typename T, typename U>
void test(T & v_1, U & v_2) {std::cout << "template func" << std::endl;
}template<>
void test<int, double>(int & v_1, double & v_2) {std::cout << "over specialized func" << std::endl;
}void test(int & v_1, double & v_2) {std::cout << "normal func" << std::endl;
}int main() {int v_1 = 1; double v_2 = 1.2;test(v_1, v_2); // 输出 normal funcreturn 0;
}
类模板
类模板,也是产生类的模具,通过给定的模板参数,生成具体的类,也就是实例化一个特定的类。类模板声明和实现
都放在一个头文件中,因为实例化具体类的时候必须有类模板的全部信息。
#include <iostream>template <typename T>
class TempClass {public:TempClass(T & _val);
};template<typename T>
TempClass<T>::TempClass(T &_val) {std::cout << _val << std::endl;
}int main() {int v_1 = 1; double v_2 = 1.2;TempClass<int> t_1(v_1);// TempClass<int> t_2(v_2); 编译报错TempClass<double> t_2(v_2);return 0;
}
参数的推断
在C++11
中不能直接通过传入的参数类型推断出类模板的类型只能显示的声明,但是在C++17
中则可以直接像函数模板那样直接推断类型,如下所示。
#include <iostream>template <typename T>
class TempClass {public:TempClass(T & _val);
};template<typename T>
TempClass<T>::TempClass(T &_val) {std::cout << _val << std::endl;
}int main() {int v_1 = 1; double v_2 = 1.2;TempClass<double> t_2(v_2);TempClass t_1(v_1); // c++11 中编译报错return 0;
}
类模板的特化
全特化
类的泛化完整的写法如下,在每个函数的实现前加template<typename T>
模板关键字,或者直接在声明处进行代码编写。
#include <iostream>template <typename T>
class TempClass {public:void printTempClass();
};template<typename T>
void TempClass<T>::printTempClass() {std::cout << "template func" << std::endl;
}
int main() {TempClass<int> s;s.printTempClass();return 0;
}
泛化模板类与全特化模板类是完全独立的。
template <>
class TempClass<int> {public:TempClass();
};TempClass<int>::TempClass() {std::cout << "over specialized TempClass" << std::endl;
}int main() {TempClass<int> s;s.printTempClass(); // 报错return 0;
}
由于TempClass<int>
属于全特化的模板,可以看作是一个全新的类
,但是这个类模板中没有声明printTempClass()
函数,因此编译时出错。
如果在写全特化类模板时需要使用函数声明和实现分离的方式,则在函数实现前不用添加template<>
关键字,这是由于全特化类模板相当于一个普通类,因此,需要按照普通类的成员函数进行实现,代码如下。
template <>
class TempClass<int> {public:void printTempClass();
};
// template<> 不能写
void TempClass<int>::printTempClass() {std::cout << "over specialized func" << std::endl;
}
成员函数全特化
如果只是需要全特化类模板中的某个成员变量则在实现函数前添加template<>
关键字,如下所示。
#include <iostream>template <typename T>
class TempClass {public:void printTempClass();
};template<>
void TempClass<double>::printTempClass() {std::cout << "over specialized func double" << std::endl;
}template<>
void TempClass<int>::printTempClass() {std::cout << "over specialized func int" << std::endl;
}int main() {TempClass<double> temp_class_double;temp_class_double.printTempClass();TempClass<int> temp_class_int;temp_class_int.printTempClass();return 0;
}
成员函数模板
普通函数
和类模板
都可以定义自己的函数模板
,如下所示代码。
// 类模板
template <typename T>
class TempClass {public:template<class T2> // 定义自己的模板TempClass(T2 & _value);
};template<typename T> // 必需先写类模板标记
template<class T2>
TempClass<T>::TempClass(T2 &_value) {}
需要注意的是,如果是类模板的成员函数模板,在实现时需要先写类模板的标记关键字
。
// 普通类
class TempClass {public:template<class T2> // 定义自己的模板TempClass(T2 & _value);
};template<class T2>
TempClass::TempClass(T2 &_value) {}
模板中友元
友元类
// 前置声明
template <typename T> class TempClassFriend;template <typename T>
class TempClass {friend class TempClassFriend<int>; // 声明友元时特化友元类
private:int age_;
};template <typename T>
class TempClassFriend {public:void TestFriend() {TempClass<int> temp;temp.age_ = 10; // 正常调用}
};
template <typename T>
class TempClass {template <typename> friend class TempClassFriend; // 将类模板直接声明成友元类
private:int age_;
};template <typename U>
class TempClassFriend {public:void TestFriend() {TempClass<int> temp{};temp.age_ = 10; // 正常调用std::cout << temp.age_ << std::endl;}
};
template <typename T>
class TempClass {friend T; // 将需要声明为友元类的类模板变成模板类型参数
private:int age_;
};template <typename U>
class TempClassFriendA {public:void TestFriend() {TempClass<TempClassFriendA> temp{}; // 将类作为类型参数之一temp.age_ = 10; // 正常调用std::cout << temp.age_ << std::endl;}
};
友元函数
#include <iostream>template <typename U> void func(U _value); // 模板前置声明class TempClass {friend void func<int>(int); // 声明特化版本函数模板
private:int age_ = 10;
};template <typename U>
void func(U _value) {TempClass temp;U value = temp.age_ + _value;std::cout << value << std::endl;
}
#include <iostream>class TempClass {template <typename U> friend void func(U _value); // 定义声明函数模板为友元函数
private:int age_ = 10;
};template <typename U>
void func(U _value) {TempClass temp;U value = temp.age_ + _value;std::cout << value << std::endl;
}
可变参函数模板
可变参函数模板最基本写法如下:
#include <iostream>
#include <vector>template <typename... T>
void changeParam(T... args) {std::cout << sizeof...(args) << std::endl; // 推断参数数量
}
- 尖括号中的
typename
后跟三个点。 - 原模板形参
T称为可变参数类型
或称为包
,它包含了0到多个不同类型参数。
可变参数(包)的展开
包数量和类型是不固定的因此需要将包中的数据一次取出就需要将包进行参数包展开
,参数包的展开也是比较固定的方法,采用递归的方式进行展开
。
可变参函数模板的代码在编写时需要有两个函数作为辅助:
- 参数包展开函数。
- 同名的递归终止函数,该函数是一个普通函数,不是函数模板,且必须是空参。
// 可变参函数模板声明
template <typename T, typename... U>
void changeParam(T param, U... args) {std::cout << "param = " << param << std::endl;changeParam(args...);
}
// 递归终止函数
void changeParam() {std::cout << "package over." << std::endl;
}
int main() {changeParam(10, 2.6, "abc");return 0;
}
运行结果
param = 10
param = 2.6
param = abc
package over.
递归过程解析:
- 第一次执行
changeParam(10, 2.6, "abc")
时,T param = 10
而U... args = 2.6, "abc"
,然后进入第二次。 - 第二次执行属于递归过程,函数的入参有两个,当传入一个包作为参数时,编译器自动将包中的第一个参数分出来,因此,
T param = 2.6
而U... args = "abc"
,然后进入第三次。 - 第三次执行
T param = "abc"
而U... args已经没有参数
,进入第四次。 - 第四次执行时由于
args没有参数
,因此变为了空参,此时编译器会调用void changeParam()
终止函数。
是在c++17
标准中增加了一个编译期间if语句
,可以在编译时判断常量,用这种语法可以省掉递归终止函数
,写法如下:
template <typename T, typename... U>
void changeParam(T param, U... args) {std::cout << "param = " << param << std::endl;if constexpr(sizeof...(args) > 0) {changeParam(args...);} else {std::cout << "package over." << std::endl;}
}
折叠表达式
折叠表达式(Fold Expressions)
是C++17标准中新加入的,可以直接对可变长参数进行计算,示例代码如下:
template <typename ...T>
auto add(T ...args) {return (... + args);
}
int main() {std::cout << add(10, 20, 30) << std::endl;return 0;
}
一元左折
表示从左开始计算,计算顺序(([0] 运算符 [1]) 运算符 [2]) 运算符 ...... 运算符 [n]
。
template <typename ...T>
auto sub(T ...args) {return (... - args); // 点在左边
}
int main() {std::cout << sub(10, 20, 30) << std::endl; // (10 - 20) - 30 = -40return 0;
}
一元右折
表示从右开始计算,计算顺序0 ...... 运算符 ([n - 2] 运算符 ([n - 1] 运算符 [n]))
。
template <typename ...T>
auto sub(T ...args) {return (args - ...); // 点在右边
}
int main() {std::cout << sub(10, 20, 30) << std::endl; // 10 - (20 - 30) = -20,// 这里一定不能想象成 30 - 20 - 10return 0;
}
二元左折
在一元左折的基础上增加了一个初始值的设定,计算前先和这个初始值进行计算。
template <typename ...T>
auto sub(T ...args) {return (100 - ... - args); // 注意初始值和点之间也要有符号,符号必须一致
}
int main() {std::cout << sub(10, 20, 30) << std::endl; // (((100 - 10) - 20) - 30) = 40return 0;
}
二元右折
template <typename ...T>
auto sub(T ...args) {return (args - ... - 100); // 注意初始值和点之间也要有符号,符号必须一致
}
int main() {std::cout << sub(10, 20, 30) << std::endl; // (10 - (20 - (30 - 100))) = -80return 0;
}
可变参数表达式
让每个参数都自行计算,例如,可以配和折叠表达式让所有数值都扩大2倍然后在求和。
template <typename ...T>
auto sub(T ...args) {return (args + ...);
}template <typename ...T>
auto multiple(T ...args) {return sub(2 * args...); // 每个参数都扩大两倍
}int main() {std::cout << multiple(10, 20, 30) << std::endl; // 120return 0;
}
可变参类模板
类型展开
template <typename ...Args>
class testClass {public:testClass() {printf("Template, this = %p\n", this);}
};
template <typename T, typename ...Args>
class testClass<T, Args...>: private testClass<Args...>{public:testClass(): data_(0) {std::cout << "PartialSpecial, this = "<< this << ", sizeof... (args) = "<< sizeof... (args) << std::endl;}
private:T data_;
};
int main() {testClass<int, float, double> tc;return 0;
}
Template, this = 00000095200FF698
PartialSpecial, this = 00000095200FF698, sizeof... (args) = 0
PartialSpecial, this = 00000095200FF698, sizeof... (args) = 1
PartialSpecial, this = 00000095200FF698, sizeof... (args) = 2
结果分析:
- 当创建
testClass
对象的时候,有三个参数类型,和函数解包时一样,T得到一个类型T = int
,因此Args还有2个类型
,进入继承父类构造开始递归。 - 由于
Args还有2个类型
,因此,继承的是有2个类型参数的类模板,调用其构造函数后,T得到一个类型T = float
而Args还有1个类型
,再次递归构造其父类。 - 由于
Args还有1个类型
,因此,继承的是有1个类型参数的类模板,调用其构造函数后,T得到一个类型T = double
而Args还有0个类型
,再次递归构造其父类。 - 当构造无参的类模板构造函数时,就进入了
终止递归类模板(主模板)
,根据继承时的构造关系这个模板也是最先实例化的最终得到如上输出。
- 当创建
写法规范
不能出现一个以上的可变参数类型。
template <typename ...Args1, typename ...Args2> class testClass {};
有多个类型时将可变参数模板放在最后。
template <typename T, typename ...Args> class testClass {};
模板类型展开
如果类模板的传入参数都是容器,则需要用下列方法进行展开。
// [3] 递归展开终止函数
template <typename T, template<typename> typename ...Container>
class testClass {public:testClass() {printf("Template, this = %p\n", this);}
};
// [2] 类模板递归展开函数
template <typename T,template<typename> typename FirstContainer,template<typename> typename ...Container>
class testClass<T, FirstContainer, Container...>: private testClass<T, Container...> {public:testClass() {std::cout << "PartialSpecial, this = "<< this << ", sizeof... (Container) = "<< sizeof... (Container)<< std::endl;container_.push_back(10);}
private:FirstContainer<T> container_;
};
// [1] 要展开的类模板
template <typename T,template<typename> typename ...Container>
class myClass: private testClass<T, Container...> {public:myClass() {std::cout << "myClass, this = "<< this << ", T type is = "<< typeid(T).name() << ", container size = "<< sizeof... (Container)<< std::endl;}
};int main() {myClass<int, std::vector, std::list, std::deque> tc;return 0;
}
Template, this = 0000009262AFFD00
PartialSpecial, this = 0000009262AFFD00, sizeof... (Container) = 0
PartialSpecial, this = 0000009262AFFD00, sizeof... (Container) = 1
PartialSpecial, this = 0000009262AFFD00, sizeof... (Container) = 2
myClass, this = 0000009262AFFD00, T type is = int, container size = 3
泛型编程基础知识详解相关推荐
- R语言基础知识详解及概括
R语言基础知识详解及概括 目录 R语言基础知识详解及概括 R数据可视化示例 R语言进行数据创建
- R语言可视化绘图基础知识详解
R语言可视化绘图基础知识详解 图形参数:字体.坐标.颜色.标签等: 图像符号和线条: 文本属性: 图像尺寸及边界: 坐标轴.图例自定义等: 图像的组合: #install.packages(c(&qu ...
- 计算机网络相关知识 参考博客 子网掩码怎么理解 网关及网关的作用 路由器基础知识详解
子网掩码怎么理解 https://blog.csdn.net/farmwang/article/details/64132723 网关及网关的作用 https://blog.csdn.net/zhao ...
- RabbitMQ基础知识详解
RabbitMQ基础知识详解 2017年08月28日 20:42:57 dreamchasering 阅读数:41890 标签: RabbitMQ 什么是MQ? MQ全称为Message Queue, ...
- Android随机点名器,Excel基础知识-详解随机点名器
说道制作个案例纯粹意外,我多少有点选择恐惧症,为了不在"选择"上纠结,就自己小玩了一下,就用了程序做了个选择器,其实很简单,就是有小时候玩的"点兵点将",稍微变 ...
- Python基础知识详解 从入门到精通(八)魔法方法
目录 Python基础知识详解 从入门到精通(八)魔法方法 什么是魔法方法 基础魔法方法(较为常用) 比较操作符 算数运算符 反运算(类似于运算方法) 增量赋值运算 一元操作符 类型转换 上下文管理( ...
- 网络管理之基础知识详解
网络管理之基础知识详解 目录 3.1 网络的特征 3.2 拓扑结构 4.1 OSI简介 4.2 数据传输过程 4.3 分层作用 4.4 PDU 5.1 单播 5.2 多播 5.3 广播 5.4 三种通 ...
- 工业相机基础知识详解
工业相机基础知识详解 工业相机是机器视觉系统的一个最关键的组件.他的功能很简单,就是将被检测的物体拍摄下来,然后转换成电脑可以识别的图像,以便以后进行图像处理,从而完成检测任务.工业相机俗称工业摄像机 ...
- 小白入门!网络安全基础知识详解(附知识问答)
小白入门!网络安全基础知识详解(附知识问答) 一.引论 提到网络安全,一般人们将它看作是信息安全的一个分支,信息安全是更加广义的一个概念:防止对知识.事实.数据或能力非授权使用.误用.篡改或拒绝使用所 ...
- 【BLE】OTA基础知识详解
[BLE]OTA基础知识详解 一. 概念 1. 缩写 BIM Boot Image Manager , the software bootloader CRC cyclic redundancy ch ...
最新文章
- 深度学习实战—基于TensorFlow 2.0的人工智能开发应用
- 汉化 Hirens.BootCD 中的 XP 系统
- rails笔记 cache系统
- idea类前面有个j_idea中所有Java类标识变成一个J
- Linux静态路由相关
- ElasticSearch - 嵌套对象 nested
- ExtJS 4应用架构设计
- Qt VS中设置.ui文件的生成的.h的目录
- 防止重复提交表单的两种方法
- A.I. Wiki 人工智能
- 【图像去噪】基于matlab GUI均值+中值+高斯低通+多种小波变换图像去噪【含Matlab源码 856期】
- python 字体颜色_Python字体颜色设置
- 偏最小二乘法_实例讲解:简明扼要最小二乘法计算过程
- C++/Qt 计算24点
- SpringBoot使用爬虫(初级阶段)
- 微信小程序wx.onLocationChange的使用,并计算移动距离
- 快速构建企业级应用的开发平台
- uniapp 电商app签到功能实现
- java.exe点击无反应_win7系统双击JeR安装包没有任何反应的解决方法
- el-checkbox点击后面的内容不选中复选框
热门文章
- 最小巧最简单最安全的KMS模拟器vlmcsd,最新支持Win10 server2016和Office2016!
- 手机python30编程教程入门_Python新手入门教程_在手机上就能学习编程的软件
- html 苹果手机输入法,苹果手机搜狗输入法怎么计算字数?
- 运放搭建电压电流转换电路分析
- oracle创建数据库实例
- python--手柄遥控通讯
- 矩阵的Cholesky 分解
- RiceQuant开源项目Rqalpha运行结果文件result.pkl读取
- matlab中phantom函数,matlab官网上下的phantom3d不对
- Simulink代码生成基础体验教程