【C++泛型学习笔记】函数模板

news2024/9/21 20:50:23

提到C++的程序设计方法,最先想到的便是两种:面向过程和面向对象编程。但是当我们去阅读一些优秀的C++库源码时(比如CGAL),就会直接被其的泛型编程劝退。泛型编程也是C++程序设计方法中的一种,不同于上述两种设计方法,其最突出的特点是提高代码复用性和减少代码冗余,这也是大型开源项目使用泛型的原因之一。而我学习泛型编程的目的在于,能够看懂CGAL等库的源码和具备高复用性代码编写能力,并且为学习STL、Boost等库打下基础。

学习参考书籍:王健伟《C++新经典:模板与泛型编程》

函数模板

基本范例

#include <iostream>
using namespace std;

// 函数重载
int Sub(int tv1,int tv2)
{
	return tv1 - tv2;
}

double Sub(double tv1, double tv2)
{
	return tv1 - tv2;
}

// 函数模板
template<typename T>
T Sub(T tv1, T tv2)
{
	return tv1 - tv2;
}

int main()
{
	int subv1 = Sub(3, 5);
	cout << "subv1 = " << subv1 << endl;
	double subv2 = Sub(4.7, 2.1);
	cout << "subv2 = " << subv2 << endl;
}

对于C++这种强类型语言,我们创建一个Sub函数用于返回两数相减结果,如果希望该函数不仅支持整数相减还支持浮点数相减,那么我们会通过重载函数的方式,写两个不同参数类型的同名Sub函数。这时,如果我们定义一个模板函数Sub,便可以减少一半的代码量。通过定义一个通用的模板函数,避免为每个类型都定义一个不同的函数。

函数模板的定义范例如下:

template<typename T>
T Sub(T tv1, T tv2)
{
	return tv1 - tv2;
}
  • T:称为模板参数,确切讲是一个类型模板参数,代表的是一个类型。“T”也可以换成其他标识符。

  • 模板的定义以关键字template开头。

  • 模板参数前面用typename进行修饰。

    也可以使用class代替typename对模板参数进行修饰。一般人们习惯用typename表明模板实参可以是任一类型,而class表明模板实参必须是一个类类型。

  • 模板参数及其修饰符都用一对**尖括号<>**括起来。

  • 虽然模板参数不限制类型,但是需要注意的是传入参数类型必须合法,即对函数中进行的操作是合法的,不然在编译阶段编译器将会判断出来,并报错。

模板实例化

我们会疑惑,既然没有声明函数参数的类型,那么程序是怎么计算出来的呢?其实在编译过程中,若我们调用了某个函数模板,那么编译器会对这个函数模板进行实例化,用具体的“类型”代替“类型模板参数”的过程叫做实例化(也称代码生成器)。

所以,我们可以认为虽然我们没有完成对函数参数进行类型声明,但是编译器却很智能的根据我们调用函数输入的参数类型自动对模板进行了实例化。为了证实这个想法,我们通过在Developer Command Prompt for VS中使用dumpbin工具将编译生成的.obj文件转换成.txt便于我们分析编译结果。

在这里插入图片描述

注意需要用管理员权限打开命令行,并且进入到.obj所在文件夹下执行如上语句。

在生成的.txt文件中,我们可以找到如下字段:

在这里插入图片描述

如上则是Sub函数模板实例化的结果,实例化后的函数名分别为Sub<int>和Sub<double>。该函数名由三部分组成:模板名、一对尖括号<>和括号间一个具体类型。

模板参数推断

前面讲到,编译器能根据传入参数类型自动推断模板参数类型。但是如果出现下面这种情况。

template<typename T,typename U,typename V>
V Sub(T tv1, U tv2)
{
	return tv1 - tv2;
}

int main()
{
	double subv1 = Sub(3, 5.1);
	cout << "subv1 = " << subv1 << endl;
}

模板参数有三个,分别是传入类型T、U和返回类型V,运行报错:error C2783: "V Sub(T,U)": 无法推导 "V" 的 模板 参数。虽然T、U的类型能够像之前那样根据传入参数类型推断出来,但是这里完全没有告诉任何返回值的类型,所以会报错。解决方法如下:

  • 手动指定类型。
double subv1 = Sub<int,double,double>(3, 5.1);

这里可以看到,我们为了指定第三个模板参数V的类型,将前两个都指定了。因为规定我们可以在调用时通过尖括号指定一部分模板参数,另一部分则可以由编译器去推断。规定语法为:一旦某一位置模板参数需要编译器推断,那么之后的所有参数都应该由编译器推断。故我们可以将需要推断的模板参数置后,需指定的参数置前,就得到如下写法:

template<typename V,typename T,typename U>
V Sub(T tv1, U tv2)
{
	return tv1 - tv2;
}

int main()
{
	double subv1 = Sub<double>(3, 5.1);
	cout << "subv1 = " << subv1 << endl;
}

再者,还可以通过返回类型后置语法进行改写:

template<typename T,typename U>
auto Sub(T tv1, U tv2) -> decltype(tv1 + tv2)
{
	return tv1 - tv2;
}

int main()
{
	double subv1 = Sub(3, 5.1);
	cout << "subv1 = " << subv1 << endl;
}

上述代码中,使用auto结合decltype完成返回类型推断。当然也能去掉 -> decltype(tv1 + tv2),使用auto关键字对类型进行自动推断。

空模板参数列表:<>,在调用函数时,在函数名后加上<>,可以明确所调用的对象为函数模板,而非同名的普通函数。

重载

函数模板重载概念:函数模板名字相同,但参数数量和参数类型不同。对于重载函数模板和同名普通函数,编译器会选择最合适的一个进行使用。当同名普通函数和函数模板都比较合适时,优先选择普通函数;在两个重载函数模板之间选择形参数量和类型最接近函数调用输入实参的一个。

泛化、全特化和偏特化

通常我们写的函数模板(如上面的示例)都是泛化的函数模板。而特化版本是从函数模板的泛化版本中抽出的一个子集

全特化:把泛化版本中所有的模板参数都用具体类型替代。

// 函数模板(泛化版本)
template<typename T,typename U>
auto Sub(T tv1, U tv2)
{
	printf("泛化版本");
	return tv1 - tv2;
}

// 全特化版本
template<> // 全特化<>中为空,所有模板参数都用具体类型替代
auto Sub<int, double>(int tv1, double tv2)
{
	printf("全特化版本");
	return tv1 - tv2;
}

// 偏特化版本(局部特化)-模板参数数量上的偏特化
template<typename T>
auto Sub(T tv1, int tv2)
{
	printf("偏特化版本");
	return tv1 - tv2;
}

int main()
{
	int a = 3, b = 5;			// 调用偏特化版本
	//double a = 3.0, b = 5.0;	// 调用泛化版本
	//int a = 3; double b = 5.0;	// 调用全特化版本
	cout << Sub(a, b) << endl;
}

函数调用顺序:同名普通函数>模板特化版本>模板泛化版本

默认参数

函数模板中的类型模板参数可以设置默认值。这样就可以不用指定形参类型。

template<typename V = double, typename T, typename U>
V Sub(T tv1, U tv2)
{
	return tv1 - tv2;
}

int main()
{
	cout << "subv1 = " << Sub(3, 5.1) << endl;
}

非类型模板参数

除了类型模板参数以外,函数模板还可以有非类型的普通模板参数。

template<int val, typename V = double, typename T, typename U>
V Sub(T tv1, U tv2)
{
	return tv1 - tv2 - val;
}

int main()
{
	cout << "subv1 = " << Sub<10>(3, 5.1) << endl;
}

同样,非类型模板参数val也能设置默认值。

Tips:

  • 非类型模板参数的值一般为常量
  • 并非任何类型的参数都能作为非类型模板参数。int类型可以,float、double或类类型不可以。

一些奇怪但合法的语法

  • template<typename, int>
    auto Add2()
    {
    	return 100;
    }
    

    模板参数未用到,可以省略标识符。

  • template<typename T, typename int val>
    auto Add2()
    {
    	return 100;
    }
    

    第一个typename修饰类型模板参数T,第二个typename表示其后修饰的int是一个类型,虽然多余但是合法。

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

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

相关文章

一文教你从Linux内核角度探秘JDK NIO文件读写本质(下)

接上文一文教你从Linux内核角度探秘JDK NIO文件读写本质&#xff08;上&#xff09; 10. JDK NIO 对普通文件的写入 FileChannel fileChannel new RandomAccessFile(new File("file-read-write.txt"), "rw").getChannel();ByteBuffer heapByteBuffer B…

LRU和FIFO页面置换算法模拟实战

Introduction 本文将介绍如何使用LRU和FIFO实现页面置换的模拟&#xff08;Python实现&#xff09;&#xff0c;并使用缺页率进行算法的评价。 Requirement 先附上具体的要求: 【实验目的】 &#xff08;1&#xff09;了解内存分页管理策略 &#xff08;2&#xff09;掌握…

[附源码]JAVA毕业设计昆明市人民医院血库管理系统(系统+LW)

[附源码]JAVA毕业设计昆明市人民医院血库管理系统&#xff08;系统LW&#xff09; 目运行 环境项配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。…

SSM框架-SpringMVC(一)

目录 1 SpringMVC简介 1.1 什么是mvc 1.2 什么是SpringMVC 1.3 SpringMVC的特点 2 入门案例 2.1 开发环境 2.2 创建maven工程 2.3 配置web.xml 2.4 创建请求控制器 2.5 创建SpringMVC配置文件 2.6 测试HelloWorld 2.7 优化配置 3 RequestMapping注解 3.1 RequestM…

3. 递归

3.1 递归 假设你在祖母的阁楼中翻箱倒柜&#xff0c;发现了一个上锁的神秘手提箱。 祖母告诉你&#xff0c;钥匙很可能在下面这个盒子里。 这个盒子里有盒子&#xff0c;而盒子里的盒子又有盒子。钥匙就在某个盒子中。 为找到钥匙&#xff0c;你将使用什么算法? 第一种&…

c++ word 不依赖软件操作

1、Duck 可以提取word的内容与表格&#xff0c;新建工程直接复制源码就可以得到库 使用实例如下所示&#xff1a; using namespace duckx; duckx::Document doc("file.docx"); doc.open(); //获取当前word中所有的表格 for (auto p doc.tabl…

计算机毕业论文java毕业设计选题源代码基于SSM的会议室预约系统

&#x1f496;&#x1f496;更多项目资源&#xff0c;最下方联系我们✨✨✨✨✨✨ 目录 Java项目介绍 资料获取 Java项目介绍 《SSM会议室预约系统》该项目主要解决了会议室预约日常工作中的一些问题&#xff0c;采用技术的技术是jsp springmvcspringmybatis cssjs等。 项目…

ARM 汇编写启动代码之设置栈和调用C语言

一、C语言运行时需要和栈的意义 “C语言运行时&#xff08;runtime&#xff09;”需要一定的条件&#xff0c;这些条件由汇编来提供。C语言运行时主要是需要栈。 C语言与栈的关系&#xff1a;C语言中的局部变量都是用栈来实现的。如果我们汇编部分没有给 C 部分预先设置合理合…

PAT甲级考试知识点总结和一些看法

0 引言 本人今年PAT甲级考了95分&#xff0c;平时力扣也有再刷&#xff08;大概有360题&#xff09;&#xff0c;感觉PAT主要还是面向考研党的&#xff0c;里面的题目其实难度是小于力扣的&#xff0c;但这种难度的题目浙大去年考研机试居然有20%的0分我其实不是很理解。 PAT…

【计算机网络】计算机网络复习总结 ------ 物理层

计算机网络 内容管理物理层 physical layer相关概念术语信息数据 data信号 signal码元 code cell 【波特率B --- 信号v】比特率R ---- 数据v基带信号 baseband带通&#xff08;频带&#xff09;信号单工 simplex 半双工 全双工失真理想信道奈奎斯特定理 &#xff08;理想&#…

[附源码]Python计算机毕业设计Django求职招聘网站

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

室内温度控制仿真(Simulink+PLC)

本篇博客将会和大家一起一步步解读Simulink自带的仿真模型(Thermal Model of a House),之后再讨论PLC控制系统控制室内环境温度的一些经验方法。温度控制的大部分控制方法都是采用PID控制,有关PLC的PID控制相关内容可以参看专栏的其它文章,链接如下: 博途PLC 1200/1500P…

CN_计算机网络性能指标@信道利用率@信道吞吐率

文章目录性能指标带宽(Bandwidth)&#x1f388;时延(Dely)发送时延&#x1f388;传播时延处理时延排队时延时延带宽积往返时延(Round-Trip Time,RTT)吞吐量(Throughput)速率(Speed)带宽(Bandwidth)信道利用率补充利用率信道利用率发送周期发送时间(传输时间)信道利用率计算&…

(附源码)springboot《升学日》日本大学信息及院校推荐网站 毕业设计 251949

基于springboot《升学日》日本大学信息及院校推荐网站 摘 要 随着科学技术的飞速发展&#xff0c;各行各业都在努力与现代先进技术接轨&#xff0c;通过科技手段提高自身的优势&#xff1b;对于《升学日》日本大学信息及院校推荐网站当然也不能排除在外&#xff0c;随着网络…

面向对象中的继承

面向对象中的继承 封装 低耦合&#xff0c;高内聚 多态 重载&重写 重载 其实这是后台的知识&#xff0c;这么做的原因是&#xff1a;涉及到服务器的承压能力。减轻并发数 重写 子类重写父类中的方法 怎么理解面向对象&#xff1f; 一切皆对象……学院派的答法&#xff0c;尽…

Some App Tech Support 一些应用技术支持

Some App Tech Support 一些应用技术支持 Getting Support: mail: qiudi7323gmail.com or leave comment below. 获得支持&#xff1a; 邮件&#xff1a;qiudi7323gmail.com 或者在下面留下评论。

深入理解Nginx线程池【内附原理讲解以及源码分析】

文章目录&#x1f680;前言❓什么是并发编程⭐多进程和多线程并发编程的比较&#x1f34e;线程池⭐线程池组成⭐线程池的核心组件⭐源码分享⭐线程池关键结构体刨析⭐线程池关键函数刨析&#x1f330;总结&#x1f680;前言 因为前段时间项目需要所以阅读分析了Nginx线程池源码…

[每周一更]-(第23期):Docker 逻辑图及常用命令汇总

Docker是一种轻量级的虚拟化技术&#xff0c;同时是一个开源的应用容器运行环境搭建平台&#xff0c;可以让开发者以便捷方式打包应用到一个可移植的容器中&#xff0c;然后安装至任何运行Linux或Windows等系统的服务器上。相较于传统虚拟机&#xff0c;Docker容器提供轻量化的…

认识哈希表

作者&#xff1a;~小明学编程 文章专栏&#xff1a;Java数据结构 格言&#xff1a;目之所及皆为回忆&#xff0c;心之所想皆为过往 目录 为什么我们需要哈希表&#xff1f; 哈希表的原理 什么是哈希值 冲突 负载因子 解决冲突 闭散列 开散列/哈希桶 代码实现 不考虑…

我们为什么需要调用InitCommonControls?

很多第一次使用外壳通用控件 (Shell common controls) 的新手碰到的问题之一是&#xff1a;他们经常忘记调用 InitCommonControls 。 但是如果你有机会查看这个函数的反汇编代码&#xff0c;则你会发现&#xff1a;这个函数实际上不做任何事情&#xff0c;就像另外一个函数 Flu…