C++模板基础1——定义函数模板

函数模板定义格式

模板函数定义格式如下:

template <typename T>
返回类型 函数名(参数列表) {// 函数体
}

其中,template<typename T>是模板声明,用于定义模板参数 T。可以使用不同的关键字代替 typename,例如 class

返回类型是函数返回的数据类型,可以是基本数据类型、自定义数据类型或者 void

函数名是函数的名称,可以根据需要命名。

参数列表是函数的参数,可以是零个或多个参数,参数之间用逗号分隔。每个参数都可以有自己的数据类型和名称。 

函数体是函数的具体实现。在模板函数中,可以使用模板参数 T 来代表不同的数据类型。

例如,定义一个模板函数来计算数组的平均值:

template <typename T>
T average(T arr[], int size) {T sum = 0;for (int i = 0; i < size; i++) {sum += arr[i];}return sum / size;
}

在调用模板函数时,需要指定模板参数的具体类型。例如,调用模板函数计算整型数组的平均值:

int arr[] = {1, 2, 3, 4, 5};
int size = sizeof(arr) / sizeof(arr[0]);
int avg = average<int>(arr, size);

实例化函数模板

我们先定义一个函数模板

template <typename T>
int compare(const T& vl, const T& v2)
{if (v1 < v2) return -1;if (v2 < v1) return 1;return 0;
}

当我们调用一个函数模板时,编译器(通常)用函数实参来为我们推断模板实参,即,当我们调用compare时,编译器使用实参的类型来确定绑定到模板参数工的类型。

例如,在下面的调用中:

cout << compare(1, 0) << endl; // T为int

实参类型是int。编译器会推断出模板实参为int,并将它绑定到模板参数T。

编译器用推断出的模板参数来为我们实例化一个特定版本的函数。

当编译器实例化一个模板时,它使用实际的模板实参代替对应的模板参数来创建出模板的一个新“实例”。

例如,给定下面的调用:

// 实例化出 int compare(const int&, const int&)
cout << compare(1, 0) << endl;// T为int//实例化出 int compare (const vector<int>&, const vector<int>&)
vector<int>vec1{1, 2,3},vec2{4, 5, 6};
cout << compare(vec1, vec2) << endl; // T为vector<int>

编译器会实例化出两个不同版本的 compare。

对于第一个调用,编译器会编写并编译一个compare版本,其中T被替换为int:

int compare (const int &vl, const int &v2)
{
if (v1 < v2) return -1;
if (v2 < v1) return 1;
return 0;
}

对于第二个调用,编译器会生成另一个compare版本,其中T被替换为vector<int>。

这些编译器生成的版本通常被称为模板的实例(instantiation)。

模板类型参数 

我们的compare函数有一个模板类型参数。

一般来说,我们可以将类型参数看作类型说明符,就像内置类型或类类型说明符一样使用。

特别是,类型参数可以用来指定返回类型或函数的参数类型,以及在函数体内用于变量声明或类型转换:

//正确:返回类型和参数类型相同
template <typename T> T foo (T* p)
{
T tmp = *p; // tmp的类型将是指针p指向的类型
//...
return tmp;
}

类型参数前必须使用关键字class或typename:

// 错误:U之前必须加上cIasa 或typename
template <typename T,U>
T calc(const T&, const U&);


在模板参数列表中,这两个关键字的含义相同,可以互换使用。一个模板参数列表中可以同时使用这两个关键字:

// 正确:在模板参数列表中,typename和class 没有什么不同
template <typename T, class U> calc (const T&, const U&);

看起来用关键字typename来指定模板类型参数比用class更为直观。

毕竟,我们可以用内置(非类)类型作为模板类型实参。

而且,typename更清楚地指出随后的名字是一个类型名。但是,typename是在模板已经广泛使用之后才引入C++语言的,某些程序员仍然只用class。

非类型模板参数

除了定义类型参数,还可以在模板中定义非类型参数。

一个非类型参数表示一个值而非一个类型。

我们通过一个特定的类型名而非关键字class或typename 来指定非类型参数。

当一个模板被实例化时,非类型参数被一个用户提供的或编译器推断出的值所代替。这些值必须是常量表达式,从而允许编译器在编译时实例化模板。

例如,我们可以编写一个compare版本处理字符串字面常量。这种字面常量是constchar的数组。由于不能拷贝一个数组,所以我们将自己的参数定义为数组的引用。

由于我们希望能比较不同长度的字符串字面常量,因此为模板定义了两个非类型的参数。第一个模板参数表示第一个数组的长度,第二个参数表示第二个数组的长度:

template<unsigned N, unsigned M>
int compare(const char(&pl)[N], const char(&p2)[M])
{return strcmp(pl, p2);
}

当我们调用这个版本的compare时:

compare("hi", "mom");

编译器会使用字面常量的大小来代替N和M,从而实例化模板。记住,编译器会在一个字符串字面常量的末尾插入一个空字符作为终结符,因此编译器会实例化出如下版本:
int compare (const char (&pl)[3], const char (&p2)[4])

  • 一个非类型参数可以是一个整型,或者是一个指向对象或函数类型的指针或(左值)引用。
  • 绑定到非类型整型参数的实参必须是一个常量表达式。
  • 绑定到指针或引用非类型参数的实参必须具有静态的生存期。
  • 我们不能用一个普通(非static)局部变量或动态对象作为指针或引用非类型模板参数的实参。
  • 指针参数也可以用nullptr或一个值为0的常量表达式来实例化。
  • 在模板定义内,模板非类型参数是一个常量值。在需要常量表达式的地方,可以使非类型参数,例如数组大小。

非类型模板参数的模板实参必须是常量表达式 

inline和constexpr的函数模板

函数模板可以声明为inline或constexpr的,如同非模板函数一样。

inline或constexpr说明符放在模板参数列表之后,返回类型之前;

//正确;inline说明符跟在模板参数列表之后
template <typename T> inline T min(const T&, const T&);
//错误:inline说明符的位置不正确
inline template <typename T> T min(const T&, const T&);

编写类型无关的代码

我们最初的compare函数虽然简单,但它说明了编写泛型代码的两个重要原则:

  • 模板中的函数参数是const的引用。
  • 函数体中的条件判断仅使用<比较运算。

通过将函数参数设定为const的引用,我们保证了函数可以用于不能拷贝的类型。

大多数类型,包括内置类型和我们已经用过的标准库类型(除unique_ptr和IO类型之外),都是允许拷贝的。

但是,不允许拷贝的类类型也是存在的。

通过将参数设定为const的引用,保证了这些类型可以用我们的compare函数来处理。

而且,如果 compare用于处理大对象,这种设计策略还能使函数运行得更快。

你可能认为既使用<运算符又使用>运算符来进行比较操作会更为自然:

//期望的比较操作
if (v1 < v2) return -1;
if (v1 > v2) return 1;
return 0;

但是,如果编写代码时只使用<运算符,我们就降低了 compare 函数对要处理的类型的要求。

这些类型必须支持<,但不必同时支持>。

实际上,如果我们真的关心类型无关和可移植性,可能需要用less来定义我们的函数:

// 即使用于指针也正确的compare版本;
template <typename T> int compare(const T 6vl, const T &v2)
{if (less<T>()(v1, v2)) return -1;
if (less<T>()(v2, v1)) return 1;
return 0;
}

原始版本存在的问题是,如果用户调用它比较两个指针,且两个指针未指向相同的数组,则代码的行为是未定义的(据查阅资料,less<T>的默认实现用的就是<,所以这其实并未起到让这种比较有一个良好定义的作用)。

模板程序应该尽量减少对实参类型的要求。


模板编译

当编译器遇到一个模板定义时,它并不生成代码。

只有当我们实例化出模板的一个特定版本时,编译器才会生成代码。当我们使用(而不是定义)模板时,编译器才生成代码,

这一特性影响了我们如何组织代码以及错误何时被检测到,

通常,当我们调用一个函数时,编译器只需要掌握函数的声明。类似的,当我们使用一个类类型的对象时,类定义必须是可用的,但成员函数的定义不必已经出现。因此,我们将类定义和函数声明放在头文件中,而普通函数和类的成员函数的定义放在源文件中
模板则不同:为了生成一个实例化版本,编译器需要掌握函数模板或类模板成员函数的定义。因此,与非模板代码不同,模板的头文件通常既包括声明也包括定义

函数模板和类模板成员函数的定义通常放在头文件中。

关键概念:模板和头文件
模板包含两种名字:

  • 那些不依赖于模板参数的名字
  • 那些依赖于模板参数的名字

当使用模板时,所有不依赖于模板参数的名字都必须是可见的,这是由模板的提供者来保证的。而且,模板的提供者必须保证,当模板被实例化时,模板的定义,包括类模板的成员的定义,也必须是可见的。

用来实例化模板的所有函数、类型以及与类型关联的运算符的声明都必须是可见的,这是由模板的用户来保证的。

通过组织良好的程序结构,恰当使用头文件,这些要求都很容易满足。模板的设计者应该提供一个头文件,包含模板定义以及在类模板或成员定义中用到的所有名字的声明。模板的用户必须包含模板的头文件,以及用来实例化模板的任何类型的头文件。

大多数编译错误在实例化期间报告

模板直到实例化时才会生成代码,这一特性影响了我们何时才会获知模板内代码的编译错误。通常,编译器会在三个阶段报告错误。

  1. 第一个阶段是编译模板本身时。在这个阶段,编译器通常不会发现很多错误。编译器可以检查语法错误,例如忘记分号或者变量名拼错等,但也就这么多了。
  2. 第二个阶段是编译器遇到模板使用时。在此阶段,编译器仍然没有很多可检查的。对于函数模板调用,编译器通常会检查实参数目是否正确。它还能检查参数类型是否匹配。对于类模板,编译器可以检查用户是否提供了正确数目的模板实参,但也仅限于此了。
  3. 第三个阶段是模板实例化时,只有这个阶段才能发现类型相关的错误。 依赖于编译器如何管理实例化,这类错误可能在链接时才报告。

当我们编写模板时,代码不能是针对特定类型的,但模板代码通常对其所使用的类型有一些假设。
例如,我们最初的compare函数中的代码就假定实参类型定义了<运算符。
 

if (v1 <v2) return -1;// 要求类型T的对象支持<操作
if (v2 <v1) return 1;//要求类型T的对象支持<操作return 0; //返回int;不依赖于

当编译器处理此模板时,它不能验证 if 语句中的条件是否合法。

如果传递给 compare的实参定义了<运算符,则代码就是正确的,否则就是错误的。例如,
 

Sales_data datal, data2;
cout << compare(datal,data2) << endl;// 错误:Sales_data未定义<

此调用实例化了 compare 的一个版本,将T替换为 Sales_data。

if 条件试图对Sales_data 对象使用<运算符,但Sales data并未定义此运算符。此实例化生成了一个无法编译通过的函数版本。但是,这样的错误直至编译器在类型Sales_data 上实例化compare时才会被发现。

保证传递给模板的实参支持模板所要求的操作,以及这些操作在模板中能正确WARNING 工作,是调用者的责任。

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

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

相关文章

腾讯云4核8G服务器最多能承载多少用户在线?谁知道?

腾讯云4核8G服务器价格&#xff1a;轻量4核8G12M优惠价格646元15个月、CVM S5服务器4核8G配置1437元买1年送3个月。腾讯云4核8G服务器支持多少人同时在线&#xff1f;支持30个并发数&#xff0c;可容纳日均1万IP人数访问。腾讯云百科txybk.com整理4核8G服务器支持多少人同时在线…

RabbitMQ Tutorial

参考API : Overview (RabbitMQ Java Client 5.20.0 API) 参考文档: RabbitMQ: One broker to queue them all | RabbitMQ 目录 结构 Hello World consumer producer 创建连接API解析 创建连接工厂 生产者生产消息 消费者消费消息 队列声明 工作队列Work Queues 公平…

Day81:服务攻防-开发框架安全SpringBootStruts2LaravelThinkPHPCVE复现

目录 PHP-框架安全-Thinkphp&Laravel Laravel CVE-2021-3129 RCE Thinkphp 版本3.X RCE-6.X RCE 版本6.X lang RCE J2EE-框架安全-SpringBoot&Struts2 Struct2 旧漏洞(CVE-2016-0785等) struts2 代码执行 &#xff08;CVE-2020-17530&#xff09;s2-061 Str…

LeetCode-437. 路径总和 III【树 深度优先搜索 二叉树】

LeetCode-437. 路径总和 III【树 深度优先搜索 二叉树】 题目描述&#xff1a;解题思路一&#xff1a;深度优先搜索解题思路二&#xff1a;0解题思路三&#xff1a;0 题目描述&#xff1a; 给定一个二叉树的根节点 root &#xff0c;和一个整数 targetSum &#xff0c;求该二叉…

刷题之Leetcode35题(超级详细)

35.搜索插入位置 力扣题目链接(opens new window)https://leetcode.cn/problems/search-insert-position/ 给定一个排序数组和一个目标值&#xff0c;在数组中找到目标值&#xff0c;并返回其索引。如果目标值不存在于数组中&#xff0c;返回它将会被按顺序插入的位置。 你可…

基于SSM实现的移动OA办公系统

系统介绍 基于SSM实现的移动OA办公系统设计了管理员、团队负责人、普通员工、部门负责人、人事部经理等几种用户角色 系统实现了如下功能&#xff1a; 管理员管理&#xff1a;用户管理、角色管理、权限管理、团队管理等功能 客户管理&#xff1a;客户管理、客户类型管理、状…

C语言笔试题之求解X的平方根

求解X的平方根 一、实例要求 1、给定一个非负整数 x &#xff0c;计算并返回 x 的算术平方根 &#xff1b;2、由于返回类型是整数&#xff0c;结果只保留整数部分 &#xff0c;小数部分将被舍去&#xff1b;3、不允许使用任何内置指数函数、运算符&#xff1b; 二、实例分析…

python作业

1.找出10000以内能被5或6整除&#xff0c;但不能被两者同时整除的数(函数) 2.写一个方法&#xff0c;计算列表所有偶数下标元素的和(注意返回值) 3.根据完整的路径从路径中分离文件路径、文件名及扩展名。 4.根据标点符号对字符串进行分行 5.去掉字符串数组中每个字符串的空格 …

江协STM32:定时器定时中断和定时器定时闹钟

定时器中断 新建文件 按这个图来编写程序 第一步&#xff1a;RCC开启时钟&#xff0c;定时器到基准时钟和整个外设到工作时钟就会同时打开 第二步&#xff1a;选择时基单元的时钟源&#xff0c;对于定时中断选择内部时钟源 第三步&#xff1a;配置时基单元&#xff0c;ARR,P…

Golang Channel底层实现原理

1、本文讨论Channel的底层实现原理 首先&#xff0c;我们看Channel的结构体 简要介绍管道结构体中&#xff0c;几个关键字段 在Golang中&#xff0c;管道是分为有缓冲区的管道和无缓冲区的管道。 这里简单提一下&#xff0c;缓冲区大小为1的管道和无缓冲区的管道的区别&…

维基百科推广方法及注意事项解析-华媒舍

1. 维基百科 维基百科是一个自由而开放的在线百科全书&#xff0c;由志愿者共同创建和编辑。它是全球最大的百科全书&#xff0c;包含了广泛的主题和知识。作为一个公共平台&#xff0c;维基百科是广告和宣传的禁区&#xff0c;但它可以是一个有效的推广工具&#xff0c;帮助您…

ENSP华为防火墙WEB登录操作指南

ENSP华为防火墙WEB登录操作指南 华为防火墙登录WEB 1、华为防火墙配置&#xff1a;&#xff08;需要在互联接口下放通https和ping&#xff09; int g0/0/0 service-manage https permit service-manage ping permit 2、电脑需要配置虚拟网卡 3、虚拟网卡与云和防火墙配置的IP地…

【学习心得】Numpy学习指南或复习手册

本文是自己在学习Numpy过后总是遗忘的很快&#xff0c;反思后发现主要是两个原因&#xff1a; numpy的知识点很多&#xff0c;很杂乱。练习不足&#xff0c;学习过后一段时间不敲代码就会忘记。 针对这两个问题&#xff0c;我写了这篇文章。希望将numpy的知识点织成一张网&…

PLC通过Modbus转Profine网关接温度传感器方案

Modbus转Profinet网关用于实现Modbus协议和Profinet协议之间的数据转换和传输。Modbus转Profinet网关接温度传感器的方案主要涉及将Modbus协议的温度传感器数据转换为Profinet协议&#xff0c;以便与工业自动化系统中的其他设备进行通信和数据交换。 以下是实现此方案的基本步骤…

[StartingPoint][Tier0]Mongod

Task 1 How many TCP ports are open on the machine? (机器上打开了多少个 TCP 端口&#xff1f;) Example: $ sudo nmap -sS -T4 10.129.222.112 -p 27017,22 2 Task 2 Which service is running on port 27017 of the remote host? (哪个服务正在远程主机的端口 270…

设计模式总结-面向对象设计原则

面向对象设计原则 面向对象设计原则简介单一职责原则单一职责原则定义单一职责原则分析单一职责原则实例 开闭原则开闭原则定义开闭原则分析开闭原则实例 里氏代换原则里氏代换原则定义里氏代换原则分析 依赖倒转原则依赖倒转原则定义依赖倒转原则分析依赖倒转原则实例 接口隔离…

向量旋转操作之分段递归交换

开篇 这是对于之前一维向量左旋操作问题的最后一个解法&#xff0c;也是关于这个问题的最后一篇文章。在之前的文章中&#xff0c;我们分别用求逆法、取模置换法对该问题进行了解答&#xff0c;今天&#xff0c;使用的是分段递归的方式。 问题概要 将一个n元一维向量向左旋转i个…

探索数据库-------MYSQL故障排除与优化

目录 mysql逻辑架构图 一、MySQL 数据库故障 1.1 MySQL 单实例故障排查 1.1.1故障现象 1 1.1.2故障现象 2 1.1.3故障现象 3 1.1.4故障现象 4 1.1.5故障现象 5 1.1.6故障现象 6 1.1.7故障现象 7 1.1.8故障现象 8 1.2MySQL 主从故障排查 1.2.1故障现象 1 1.2.2故障…

电感与磁珠的区别以及在EMC的作用

电感与磁珠的区别以及在EMC的作用 电感的定义和特性电感的频率特性噪声对策方法电感的直流叠加饱和绕组型电感的特性 电感的定义和特性 电感是能够把电能转化为磁能而存储起来的元器件。电感器具有一定的电感&#xff0c;它只阻碍电流的变化。电感器又称扼流器、自电抗器、动态…

NPW(监控片的)的要点精讲

半导体的生产过程已经历经数十年的发展&#xff0c;其中主要有两个大的发展趋势&#xff0c;第一&#xff0c;晶圆尺寸越做越大&#xff0c;到目前已有超过70%的产能是12寸晶圆&#xff0c;不过18寸晶圆产业链推进缓慢&#xff1b;第二&#xff0c;电子器件的关键尺寸越做越小&…