C++primer(第五版)第十六章(模板与泛型编程)

news2024/9/27 19:19:47

16.1定义模板

关于模板,第一个要提的点就是,这个字念mu模板而不是mo(一开始打字就发现拼音错了,突然觉得自己要会小学深造一下).

模板就是将一个特定场合使用的东西变成可以在多个场合使用的东西.

16.1.1函数模板

template<typename T>
bool bijiao(T &a,T &b){
    return a==b;
}

上例是一个用于比较的函数,可以将T看成是int或是string之类的,总之T是代表一种类型,但是在我们编写代码的时候我们不知道这个T到底是什么类型,因此使用T来表示任意类型.如果我们需要比较两个int或是string,则可以

bijiao(1,2); //比较两个整数
bijiao("hello","world"); //比较两个字符串

如果没有模板,那么我们需要编写两个重载的函数:

bool bijiao(int &a,int &b){ return a==b;}
bool bijiao(string &a,string &b){ return a==b;}

并且如果后续有其他类型也需要使用该函数,则还需要再次编写.但如果使用了模板,则只需要编写一次即可.

template表示接下来的东西是一个模板.template也不一定要单独一行,你愿意的话也可以写成下面这样:

template<typename T> bool bijiao(T &a,T &b){
    return a==b;
}

template后接尖括号<>,是模板参数列表,里面可以用逗号分隔写入多个模板参数.里面是typename T,其中关键字typename是固定写法,也可以写成class,当我们需要通知编译器一个名字表示类型时则必须使用typename,因此最好还是都写成typename.typename后面的T表示为未知类型的名称,不一定要写成T,可以随便给它起名字,但是以下我都用T来表示模板参数的名字.

当我们调用模板函数时,编译器会用过我们传入的参数类型来推断T是什么,以此来实例化出来一个函数.

若是传入的参数为自定义类型,则需要保证该类型支持模板函数内的操作,例如:

class people {
public:
	int age;
	string name;
	people(int a,string n):age(a),name(n) {};
	bool operator== (const people &p) { //需要支持==运算符
		return  this->age ==p.age;
	}
};
template<typename T> bool bijiao(T &a,T &b){
	return a == b;
}
int main(void) {
	people p1=people(12, "a");
	people p2=people(13, "b");
	int a = 0; int b = 1;
	cout << bijiao(a, b) << endl;
	cout << bijiao(p1,p2) << endl;
	return 0;
}

不过模板应当尽量减少对实参类型的需求.

 使用模板函数时,通常编译器会在三个阶段报告错误.

编译模板:在编写阶段,编译器仅检查拼写错误,漏写分号等语法错误.

使用模板:在编写使用模板的代码时,会检查实参列表与模板参数列表的类型是否匹配.模板参数列表中,一个T(多个模板参数需要不同的名字用于区分)只能表示为一个类型,例如在上面的bijiao函数中传入两个类型不一致的实参则会发生错误.

模板实例化:在编译的时候,若是发现实参的类型(大多情况都和自定义类型有关)无法满足模板函数内部的操作时,则会报错.所以这种比较隐蔽的错误,在编写的时候IDE是不会有提示的.

 16.1.2类模板

有函数模板,自然就有类模板,但是和函数模板不一样的是,类模板在使用时需要指定模板参数的类型.例如:

vector<int> v;
deque<int> q;
stack<int> s;

类模板的名字不是类型,而给类模板的模板参数指定了类型的才是类型.所以严格来说,vector并不是一个类型,而vector<int>才算一个类型.

当我们使用STL(标准模板库)里的容器时,需要指定类型,就是因为它们是模板.

编写类模板时和函数模板差不多一致,都是在开头加上template和模板参数列表.类模板参数列表里也可以有多个模板参数,参考pair.

template<typename T>
class test{
    //具体内容
}

默认情况下, 对于一个实例化了的类模板,其成员只有在使用时才被实例化.                                        若类模板有友元,若友元不是模板,则友元可以访问改类的所有实例.若友元也是模板,则类模板可以指定友元的权限(访问本类的所有实例或是特定实例)

以下演示模板为友元,模板为非友元则和普通定义友元一致就不再演示.

template<typename T> class test;    //这里两行是提前声明例子中用到的模板类和模板函数
template<typename T> void call(test<T>& t);    //因为在实现的时候它们会相互包含,因此需要提前声明

template<typename T>
void call(test<T>& t) {    //模板函数,用于打印出test<T>类型对象的值.
	cout << t.val << endl;
}

template<typename T>
class test {
friend void call<T>(test<T> &t);  //若留此行,则表示call这个函数是所有test实例的友元
friend void call<int>(test<int> &t);  //若留此行,则表示call仅为test<int>的友元
public:
	test(T v):val(v) {};
private:
	T val;
};

int main(void) {
	test<int> t1(1);
	test<char> t2('a');
	call(t1);
	call(t2);
	return 0;
}

16.1.3模板参数

模板参数的名字可以随意起,但通常使用T.若多个模板在同一个作用域下,则应当避免模板参数的名字起冲突.

声明模板时应当包含模板参数列表,可以参考上面的代码.

模板中通常包含模板,所以在含有模板的文件中,应当在文件开头就对模板进行声明,同样参考上面的代码.

早期的C++标准只允许类模板有默认实参.但现在,函数模板也可以拥有默认实参.

无论何时使用类模板,都需要在类模板名字后面加上模板参数列表,若是所有参数都有默认实参,并且我们需要使用所有的默认实参时,模板参数列表可为空,即只写上<>空的尖括号. 

如下例子中的t2:

 16.1.4成员模板

一个类(无论是不是模板)的成员都可以是模板.被称为成员模板,但成员模板不能是虚函数.

使用时同样可以指定模板实参指定的类型.例如

16.1.5控制实例化

当模板被使用时才会被实例化,若是编写大项目(有多个文件),其中每个文件都实例化了某个模板的某个示例,则会造成资源浪费.在新标准中可以通过显示实例化来避免这种开销.

在模板声明的开头加上关键字extern则为显示实例化.编译器遇到extern模板声明时则不会在本文件中生成实例化代码.则必须在程序的其他位置有一个非extern的声明(定义).

简而言之,在多文件程序中,只需要留一个模板定义,其他地方声明模板都可以在开头加上extern.

16.2模板实参推断

16.2.1类型转换与模板类型参数

使用模板函数时,如果函数参数不是模板参数,则会对实参进行正常的类型转换.若是形参类型使用了模板类型参数,则大多情况下不会对实参进行类型转换,而是生成一个新的模板实例,例如:

 可以看到没有使用模板的函数会将传入的实参类型转变为形参的类型,而使用了模板的函数则会生成新的模板实例.

将实参传递给带模板类型的函数形参时,能够自动应用的类型转换只有const转换以及数组或函数到指针的转换.若是不理解这段话,可以参考下面书中给出的示例:

 16.2.2函数模板显式实参

使用函数模板时同样可以和使用类模板一样进行显式实参(指定模板参数类型),不一样的是类模板强制要求显式实参,而函数模板可以根据需求选择是否显示实参.

 还是上面的例子,在使用函数模板时显式实参,则可以达到图中效果.

显式模板实参按照从左到右的顺序与模板参数列表进行匹配:

//声明一个函数模板
template<typename T1,typename T2>
void test(T1 a,T2 b);

//使用函数模板并显式部分模板实参
test<int>(1.0,1); 
//此时T1被显式指定为了int,所以即使传入的是1.0,在函数内仍被转换为int.
//而T2通过传入的实参1而被推断为int.

但如果模板参数列表无法全部被推断出具体类型,则必须通过显示实参来指定模板参数类型,例如函数返回值类型为模板参数,此时无法通过传入的实参来推断出,则必须显式指定模板参数类型.

16.2.3尾置返回类型与类型转换

如上小节所说,如果返回值类型使用了模板参数,则我们无法通过推断实参来判断返回值类型,则必须显式指定实参类型.但也有例外,如果返回值类型与形参类型一致,我们就可以通过尾置返回值类型和decltype(获取变量的类型,忘记的可以回顾一下前几章或者百度)配合,例如:

template<typename T1,tyename T2>
auto fun(T1 a,T2 b) -> decltype(a){
    return a+b;
}
//当然,上述例子完全可以写成下面这样,上例只是一种启发
template<typename T1,tyename T2>
T1 fun(T1 a,T2 b){
    return a+b;
}

当我们编写函数模板时,我们无法知道后面使用该函数模板的人会传入什么奇奇怪怪的类型,但是办法总比困难多,我们可以使用标准库的类型转换模板(需要include<type_traits>)来获取元素类型,然后针对不同类型进行不同操作.

 可以结合下面的表和上面书中给出的例子参考一下(因为俺不太懂).例子中的typename是为了告诉编译器type为一个类型.

16.2.4函数指针和实参推断

若使用函数模板初始化函数指针或是为函数指针赋值,则编译器使用指针的类型来推断模板实参,若是不能从函数指针类型确定模板实参,则会报错.当参数是一个函数模板实例的地址时,程序上下文必须满足对每个模板实参,能够唯一确定其类型或值.

16.2.5模板实参推断和引用

若函数参数是模板类型参数的一个普通引用(类似于T&),则只能传递给它左值.但如果函数参数是类似 const T& 的话,则没有限制,可以传递左值也可以传递右值.当函数参数形如 T&& ,那也是可以传递右值的.

若是发生引用嵌套(引用的引用),可以对引用进行折叠.

16.2.6理解std::move

不要理解,去感受.

本小节简单剖析了标准库提供的std::move,感兴趣的小伙伴可以自行去看原书.

16.2.7转发

如果函数内部使用了同样的形参调用了其他函数,那么可以称作转发(不单指函数模板),例如:

template<typename T,typename T1>
void fun(T a,T1 b){
    fun1(a,b);
}

 在转发的同时,我们需要保证转发使用的参数类型与接收到的形参类型一致.

如果一个函数参数是指向模板类型参数的右值引用(T&&),则对应的实参的const属性和左/右值属性将得到保持,因此函数模板若是需要转发,则形参接收应当使用右值引用.

除此之外,使用std::forward标准库函数可以用于保持原始参数的类型,因此完整的函数模板转发例子如下:

16.3重载与模板

函数模板可以被另一个函数模板或是非模板函数重载,只需要保证名字相同,且形参列表不同即可.

当有多个重载模板对一个调用提供同样好的匹配时,应选择最特例化的版本.

若是非函数模板与一个函数模板提供同样好的匹配则优先选择非模板版本.

在定义任何函数之前,记得声明所有重载的函数版本,这样编译器就不会由于没遇到你希望调用的函数而实例化一个并非是需要的版本.

16.4可变参数模板

参数包是可变数目的参数,共有两种参数包:模板参数包和函数参数包.

使用省略号来表示参数包,例如:

 如果想知道一个包里有多少参数,可以使用sizeof...

cout<<"包内有"<<sizeof...(args)<<"个参数"<<endl;//假设args为一个包

16.4.1编写可变参数函数模板

可变参数模板用于我们不知道所要处理的参数的数目也不知道其类型的情况.

16.4.2包扩展

这里的扩展意思就是将包拆开,获取包内单独的元素,操作方法就是在包的名字后面加上三个点...

 以下面的例子为例(是上一小节书中的例子)

其中Ts是模板参数包,b是函数参数包.第一次使用包扩展(拆包)是在第二个print函数的参数列表里,b的类型为Ts...即将模板参数包拆开.

第二次使用是在第二个print函数内,递归调用了print函数,而递归时只传入了拆开的函数参数包b(这里不算转发).因此每次调用都会使参数减少,直到参数只有一个时,函数参数包b表示为0个参数,此时调用的是第一个print函数(因为只有一个参数时,它匹配度更高),结束递归.

16.4.3转发参数包

可变参数模板+forward转发=

std::forward<Ts>(b)...  //其中Ts表示模板参数包,b表示函数参数包

16.5模板特例化

假设我们之前的模板是这样的:

template<typename T>
void fun(T a,T b);

模板特例化就是:

template<>
void fun(int a,int b);

将模板参数列表清空,然后在原本模板参数的位置写上自己需要的特例,但是要记得和模板匹配,例如本例中模板参数只有一个T,并且形参a和b的类型都是T.所以实例化要保证形参a和b的类型一致.

特例化的本质是实例化一个模板,而不是重载,因此特例化不影响函数匹配(不用担心重复声明).

我们可以部分特例化类模板,但是不能部分特例化函数模板.

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

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

相关文章

了解 IPNS:我们可以使用 4EVERLAND IPNS Manager 做什么?

关键词&#xff1a;4EVERLAND、IPNS、4EVERLAND IPNS 管理器、去中心化内容 IPNS 是星际名称系统的缩写&#xff0c;是一个允许我们在 IPFS&#xff08;星际文件系统&#xff09;内创建可变指针以及名称和地址内容的系统。IPFS 使用内容标识符 (CID) 作为唯一文件标识符&#…

Photoshop制作Alphas(没有显卡好痛苦,两个MAYA交替弄)

做个选区&#xff0c;选白色 擦除背景 在把背景填充上白色

【PyGIS】GDAL及Rasterio多线程转换NC格式文件为TIFF

汇总 【GIS】使用cdsapi下载ERA5和ERA5_land逐小时数据 NC格式介绍 说明 NC文件读取使用netCDF4,NC文件转换为TIF使用rasterio或者GDAL。 一些细节: 格点数据转换为TIFF文件时候,计算六参数时候,应该要考虑,格点数据存储的坐标属于栅格中心点的位置,转换为TIFF时候,…

Java----使用eureka进行注册连接(微服务简单实现)

当采用微服务架构时&#xff0c;各个业务流程被逐一分解&#xff0c;虽说是分解&#xff0c;但还是要进行连接的&#xff0c;最简单的就是使用http请求&#xff0c;将他们联系起来&#xff0c;通过给容器注入restTemplate&#xff0c;然后使用内置的方法进行请求&#xff0c;但…

基于Javaweb实现ATM机系统开发实战(四)用户修改删除功能实现

我们点一下修改&#xff0c;发现页面进行了跳转&#xff0c;跳转到了/toUpdate&#xff0c;并传递了用户的卡号。 我们可以先查看一下用户列表展示界面的前端代码&#xff1a;userlist.jsp&#xff0c;可以看到前端代码中做了跳转的动作&#xff0c;我们需要在后端中完成相应的…

VectorCAST单元测试参数配置

一、打开 VectorCAST 通常情况下&#xff0c;技术人员会配置一个脚本文件&#xff08;.bat、.cmd&#xff09;&#xff0c;用户可以通过这个脚本文件来启动 VectorCAST。使用脚本文件启动 VectorCAST&#xff0c;可以在启动时设置好编译器相关的环境变量&#xff0c;方便 Vecto…

企业毛利高,进项抵扣少,增值税高,怎么办?

企业毛利高&#xff0c;进项抵扣少&#xff0c;增值税高&#xff0c;怎么办&#xff1f; 《税筹顾问》专注于园区招商、企业税务筹划&#xff0c;合理合规助力企业节税&#xff01; 金税四期的出现&#xff0c;让很多企业都陷入了税负重的不利局面。当然了在此环境之下&#x…

回溯法总结

文章目录 回溯法如何理解回溯法 回溯算法模板框架如下&#xff1a;树枝去重树层去重回溯法去重什么时候去重&#xff1f;树层去重数组used[i-1]&#xff1a;回溯函数的参数startIndex&#xff1a;回溯函数的参数用Set的对象uset&#xff1a;局部变量例题 其它细节对于组合问题&…

测试开发知识图谱

目录 前言&#xff1a; 1 测试方法与理论 2 Shell脚本相关 3 数据库相关 4 git 代码管理 5 Python 编程语言与测试框架 6 Web 自动化测试 7 移动端 app 自动化测试 8 常用开源测试平台 9 客户端专项测试 10 服务端接口测试 11 服务端接口自动化测试 12 服务端性能…

国产CAN收发器XL1050可替代NXP的TJA1050T,性能参数基本一致

CAN收发器是CAN控制器和物理总线之间的接口&#xff0c;在工控等需要CAN通信的应用场合是必要的&#xff0c;工程师通常采用NXP、TI等品牌的&#xff0c;TJA1050T是常见型号之一。XL1050是信路达一款CAN收发器&#xff0c;本文讨论信路达的XL1050 替代NXP的TJA1050T的可行性。 …

前后端分离,前端代理设置

1. 没有vue.config.js的配置方法 1.1 在config目录下的index.js里面加入如下所示代码 proxyTable: {"/api": { // 不能写成^/apitarget: "http://localhost:50000", // 只写域名即可ws: true,changeOrigin: true, // 允许跨域pathRewrite: { // 重写&…

leetcode:除自身以外数组的乘积

除自身以外数组的乘积 medium 给你一个整数数组 nums&#xff0c;返回 数组 answer &#xff0c;其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请**不要使用除法&a…

抖音seo矩阵系统源码搭建技术+二开开源代码定制部署

抖音SEO源码是指将抖音平台上的视频资源进行筛选、排序等操作&#xff0c;进而提升其在搜索排名中的权重&#xff0c;从而让更多的用户能够发现并观看到这些视频资源。而抖音SEO矩阵系统源码则是指通过建立一个分析系统&#xff0c;分析抖音中的用户、视频、标签等数据&#xf…

【数据分类】基于蜣螂优化算法优化支持向量机的数据分类方法 DBO-SVM分类算法【Matlab代码#47】

文章目录 【可更换其他群智能算法&#xff0c;获取资源请见文章第6节&#xff1a;资源获取】1. 蜣螂优化算法&#xff08;DBO&#xff09;2. 支持向量机&#xff08;SVM&#xff09;3. DBO-SVM分类模型4. 部分代码展示5. 仿真结果展示6. 资源获取 【可更换其他群智能算法&#…

Java Leetcode 动态规划 91. 解码方法

未优化版本代码展示&#xff1a; class Solution {public int numDecodings(String s) {char[]charss.toCharArray();int lengths.length();//创建dp数组int[]dpnew int[length];//初始化if(chars[0]!0){dp[0]1;}//特殊情况处理if(length1){return dp[0];}if(chars[1]!0){dp[1…

Postman高级应用——变量、流程控制、调试、公共函数、外部数据文件

目录 流程控制 调试 公共函数 外部数据文件 总结&#xff1a; Postman 提供了四种类型的变量 环境变量&#xff08;Environment Variable&#xff09; 不同的环境&#xff0c;使用不同的环境变量&#xff0c;例如&#xff1a;测试过程中经常会用到 测试环境&#xff0c;外…

PHP5.4以下解决json_encode中文UNICODE转码问题

PHP5.4以下解决json_encode中文UNICODE转码问题 把汉字先urlencode 然后再使用json_encode&#xff0c; json_encode之后 再次使用urldecode来解码&#xff0c; 这样编码出来的json数组中的 汉字就不会出现unicode编码了。 $params[importList][recipientAddress] urlencode(&…

Spring WebFlux使用函数式编程模型构建异步非阻塞服务

1 前言 上文引入了 Spring 框架中专门用于构建响应式 Web 服务的 WebFlux 框架&#xff0c;同时我也给出了两种创建 RESTful 风格 HTTP 端点实现方法中的一种&#xff0c;即注解编程模型。 本文介绍另一种实现方法——如何使用函数式编程模型创建响应式 RESTful 服务&#xf…

解决子元素设置margin-top使父元素也跟着向下移动的问题

先看代码&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdevi…

rpc、gRPC快速入门,python调用,protobuf协议

什么是rpc?grpc又是什么&#xff1f; 什么是RPC 远程过程调用协议RPC (Remote Procedure Call Protocol) RPC是指远程过程调用&#xff0c;也就是说两台服务器A&#xff0c;B&#xff0c;一个应用部署在A服务器上&#xff0c;想要调用B服务器上应用提供的函数/方法&#xff…