C++开发基础——函数模板

news2024/9/23 9:32:49

一,函数模板

1.基础概念

模板编程是C++中泛型编程的基础。

一个模板可以是创建类或者函数的蓝图。

模板编程分两种,分别是算法抽象的模板、数据抽象的模板。算法抽象的模板以函数模板为主,数据抽象的模板以类模板为主。

基于函数模板生成的函数定义被称为模板的一个实例。

模板的定义以关键字template开始,后跟一个由尖括号"<>"括起来的模板参数列表。

2.函数模板的简单样例

函数模板的开头:template

定义模板参数的关键字:typename

模板参数样例:T1, T2

函数参数样例:a, b

template <typename T1, typename T2>
void func(T1 a, T2 b)
{
    //process code
}

补充:在C++98标准添加关键字typename之前,C++也可以使用关键字class来为函数模板创建模板参数列表。

代码样例:

template <class T>
void Swap(T &a, T &b)
{
    T temp;
    temp = a;
    a = b;
    b = temp;
}

3.函数模板的实例化

函数模板的实例化是指,编译器根据函数模板和具体的数据类型生成函数定义。

函数模板在实例化以后,模板参数会变成具体的数据类型,比如int, char等。

对于某一种具体的数据类型,比如int,无论以这个数据类型调用多少次函数模板,最后只生成一次该类型的模板实例。

所以,对于相同的数据类型,第一次调用函数模板的时候才会生成实例,后面再次调用的时候,都是直接使用该实例。

当编译器遇到一个函数模板的定义时,并不会马上生成相关代码,只有当我们将函数模板实例化成一个函数定义时,编译器才会生成代码。

代码样例:

a.函数模板

template <typename T>
T add(T num1, T num2) {
   return (num1 + num2);
}

b.函数模板的实例化

int result1 = add<int>(2, 3);
double result2 = add<double>(2.2, 3.3);

实例化过程的图示:

 在项目工程中,我们通常将类的定义放在头文件中,将类的成员函数的定义放在源文件中,将普通函数的声明放在头文件中,将普通函数的定义放在源文件中,但是函数模板的规则和它们不一样。

    为了让编译器为实例化后的函数模板生成代码,编译器需要同时知道函数模板的声明和定义,因此函数模板的定义也需要放在头文件中。

4.函数模板的引用传参

 对于以下函数模板:

template <typename T>
T larger(T a, T b)
{
    return a > b ? a : b;
}

该函数模板实例化以后生成的函数,需要按值传递的方式接收实参。

由于按值传送对象,会导致不必要地复制这些对象,因此,推荐使用const引用的方式定义模板参数。

template <typename T>
const T& larger(const T& a, const T& b)
{
    return a > b ? a : b;
}

5.函数模板的返回类型推断

对于无返回值的函数模板,可以把返回值类型写为void,比如最开始提到的:

template <typename T1, typename T2>
void func(T1 a, T2 b)

有的函数模板,返回值类型和参数一致,同为T,比如:

template <typename T>
T larger(T a, T b)

但是,当返回值类型和参数不一致时,得想办法让编译器可以推断返回值类型。

最简单的方式是使用auto关键字。

template <typename T1, typename T2>
auto larger(const T1& a, const T2& b)
{
    return a > b ? a : b;
}

但是,使用auto来推导函数的返回值类型时,会默认去掉引用和const限定符,因此,以上方式会导致返回值发生不必要的复制。

        因此,为了让返回值被const修饰,且采取引用的方式来传值,需要显式地加上"const &",以上代码可以改为: 

template <typename T1, typename T2>
const auto& larger(const T1& a, const T2& b)
{
    return a > b ? a : b;
}

还有一种更好的方式,C++11标准引入了decltype关键字,decltype相当于"const auto&",因为decltype在做类型推导时,不会去掉引用和const限定符。

但是decltype的用法不能像auto一样,直接放在函数名前面。

decltype用法分两种:

方式1.拖尾方式:decltype(返回值相关代码)

template <typename T1, typename T2>
auto larger(T1 a, T2 b) -> decltype(a > b ? a : b)
{
    return a > b ? a : b;
}

方式2.和auto关键字结合:decltype(auto)

template <typename T1, typename T2>
decltype(auto) larger(T1 a, T2 b)
{
    return a > b ? a : b;
}

第一种用法需要把返回值相关的代码逻辑重复写一遍,第二种用法更简洁。

6.模板参数可以指定默认值

可以用具体的数据类型为模板参数指定默认值。

例如:当函数经常使用int类型的参数时,指定模板参数的默认值为int。

template <typename T1=int, typename T2>
void func(T1 a, T2 b)

7.非类型的模板参数

模板参数分两种:

1.类型模板参数

2.非类型模板参数

由尖括号"<>"括起来的模板参数列表中,除了可以包含类型模板参数,还可以包含非类型模板参数。

以上提到的"typename T1, typename T2"中的"T1, T2"都属于类型模板参数,而"int n,  float m"中的"n, m"都属于类型模板参数非类型模板参数

类型模板参数经过实例化会变成具体类型。

非类型模板参数经过实例化会变成具体的值。

代码样例: 

应用场景:比较不同长度的字符串字面常量。

函数模板定义了两个非类型模板参数,参数N表示第一个数组的长度,参数M表示第二个数组的长度。

数组采用const和引用的方式传参。

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

非类型模板参数可以使用的数据类型: 

整型,如int、long等
枚举类型
对象类型的引用或指针
函数的引用或指针
类成员的指针

当模板参数列表中,同时有类型模板参数和非类型模板参数时,建议将非类型模板参数写在类型模板参数的前面。

代码样例:

template <int lower, int upper, typename T>
bool is_in_range(const T& value)
{
    return (value <= upper) && (value >= lower);
}

完整代码样例:

求任意数据类型,任意大小的数组的平均值。

#include <iostream>
template <typename T, int N>
T average(const T(&array)[N])
{
       T sum{};
       int i;
       for (i = 0; i < N; ++i)
       {
              sum += array[i];
       }
       return sum / N;
}
int main()
{
       double array_1[2]{ 1.1, 2.1 };
       std::cout << average(array_1) << std::endl;
       float array_2[]{ 1.0, 2.0, 3.0, 4.0 };
       std::cout << average(array_2) << std::endl;
       int array_3[] = { 1, 2, 3, 4 };
       std::cout << average(array_3) << std::endl;
       return 0;
}

运行结果:

1.6
2.5
2

8.inline/constexpr修饰的函数模板

和具体函数一样,函数模板可以用inline或constexpr修饰。

inline或constexpr在修饰时放在模板参数列表之后,返回值类型之前。

代码样例:

template <typename T>
inline T min(const T&, const T&);

9.函数模板的重载

函数模板的重载有两种方式:

方式1.用同名函数重载函数模板

方式2.用另一个函数模板重载已有模板

重载的代码样例:

template <typename T>
T larger(const T data[], size_t count)
{
    T result {data[0]};
    for (size_t i {1}; i < count; ++i)
    {
        if (data[i] > result)
            result = data[i];
    }
    return result;
}

template <typename T>
T larger(const std::vector<T>& data)
{
    T result {data[0]};
    for (auto& value : data)
    {
        if (value > result)
            result = value;
    }
    return result;
}

二,函数模板的特例 

1.基础概念

函数模板的特例是由原始的函数模板具体化而来的,因此,函数模板的特例也被称为函数模板的具体化(explicit specialization)

函数模板的特例的定义必须放在函数模板的声明和定义之后。

当编译器找到与函数调用匹配的具体化定义时,将直接使用该函数模板的特例,而不再实例化函数模板。

函数模板的特例也以关键字template开头,但要省略参数,所以template后面的尖括号是空的。

函数模板的特例的定义需要传递具体的参数类型。

当函数模板的某个实例,需要被定义一种不同于原始函数模板的行为,就可以使用函数模板的特例去定义。

空的尖括号“<>”表示编译器不需要做类型推导。

函数模板特例的简单样例:

template <>
void func(int a, double b)
{
    //process code
}

2.代码样例

给定函数模板 larger(T1 a, T2 b)

template <typename T1, typename T2>
decltype(auto) larger(T1 a, T2 b)
{
    return a > b ? a : b;
}

由于该函数模板不适用于指针数据类型,因此,定义以下函数模板的特例。

函数模板的特例,在代码逻辑中相比原始的函数模板多了解引用操作。

template <>
int* larger<int*>(int* a, int* b)
{
    return *a > *b ? a : b;
    //解引用操作是为了让两个指针比较指向的数值而不是地址
}

普通函数,函数模板,函数模板特例的代码形式

//function
void Swap(int& a, int& b);

//template prototype
template <typename T>
void Swap(T& a, T& b);

//template explicit specialization
template <>
void Swap<int>(int& a, int& b);

3.编译时的匹配优先级

当某个具体的数据类型可以同时匹配上普通函数,函数模板,函数模板的特例时,普通函数的调用优先于函数模板特例,函数模板特例的调用优先于原始函数模板。

三,参考阅读

《C++17入门经典》

《C++ primer》

《深入理解C++11》

https://www.programiz.com/cpp-programming/function-template

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

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

相关文章

合成孔径雷达(SAR)RD算法点目标成像与分析Matlab仿真

文章目录 一、概述二、仿真思路1.概述2.高分3号简介与基本参数 三、回波生成1.卫星运行速度计算2.几何3.信号参数与时间轴生成(1)信号参数(2)时间轴生成 4.点目标回波生成(1)点目标坐标设置(2)回波生成 四、低斜视角处理1.距离压缩2.方位向傅里叶变换3.距离徙动校正4.方位压缩5…

转录因子/组蛋白修饰靶基因数据库:Cistrome DB使用教程

最近有小伙伴经常询问怎么预测转录因子的下游靶基因&#xff0c;以及预测一些组蛋白修饰影响的靶基因信息。今天就给大家介绍一下Cistrome数据浏览器&#xff08;Cistrome Data Browser&#xff09;。 Cistrome DB是来自人类和小鼠的ChIP-seq、ATAC-seq和DNase-seq数据的资源&…

内网穿透利器 n2n 搭建指南

1. n2n 简介 上文实验分析了 FRP 和 Zerotier 的利弊&#xff0c;本文再介绍另一种内网穿透方案&#xff0c;n2n。 n2n 是 C/S 架构的内网穿透服务&#xff0c;不同于 FRP 的 反向代理&#xff0c;它的原理是类似 Zerotier 的先打孔&#xff0c;打孔失败再尝试转发。关于打孔本…

深入理解Netty以及为什么项目中要使用?(二)Reactor模型

Reactor模型 了解了NIO多路复用后&#xff0c;就有必要再和大家说一下Reactor多路复用高性能I/O设计模式&#xff0c;Reactor本质上就是基于NIO多路复用机制提出的一个高性能IO设计模式&#xff0c;它的核心思想是把响应IO事件和业务处理进行分离&#xff0c;通过一个或者多个…

BUUCTF-Ezsql1

1.打开靶机 打开第一个链接 2.万能密码 使用万能密码&#xff1a;a or 1 # 密码为随意 第二个用kali打开 3.ssh连接靶机 ssh ctf284490d0-7600-4c65-9160-5ced02f45633.node5.buuoj.cn -p 28191 由题可知密码为123456 4.找到并修改index.php文件 找到index.php文件 #内容如…

常见排序及查找算法

内容引用自&#xff1a; 【数据结构和算法】十大经典排序算法&#xff08;动图演示&#xff09; 算法复杂度 1、冒泡排序 1.1、动图演示 遍历列表数据&#xff0c;共遍历length(列表)次&#xff0c;每一次的遍历都要从左到右进行两两比对&#xff0c;左边比右边小&#xff0…

机器人路径规划:基于Bug算法的机器人路径规划(提供Python代码)

一、Bug算法简介 Bug 算法是一种基于追踪障碍物的路径规划算法&#xff0c;它模拟了一种昆虫寻找巢穴的行为&#xff0c;因此得名Bug算法。Bug算法的基本思路是&#xff1a;当机器人遇到障碍物时&#xff0c;他会沿着障碍物的边缘行走&#xff0c;直到到达目标点。该算法可以分…

代码随想录算法训练营第二十五天|216.组合总和III,17.电话号码的字母组合

216.组合总和III 题目 找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数&#xff0c;并且每种组合中不存在重复的数字。 说明&#xff1a; 所有数字都是正整数。 解集不能包含重复的组合。 示例 1: 输入: k 3, n 7 输出: [[1,2,4]] 示例 2: 输入…

深入了解JVM底层原理

一、JVM内存结构 1、方法区&#xff1a;存储编译后的类、常量等&#xff08;.class字节码文件&#xff09; 2、堆内存&#xff1a;存储对象 3、程序计数器&#xff1a;存储当前执行的指令地址&#xff08;计算机处理器&#xff08;CPU&#xff09;正在执行的下一条指令在内存…

Java后端八股----JVM篇

上图中线程1&#xff0c;2如果资源被抢占了&#xff0c;则程序计数器记录一下执行的行号&#xff0c;等到资源就绪后会从记录的行号继续向后执行。 Java8把静态变量以及常量放到了线程的本地内存原空间中(避免放在堆中不可控)。 &#x1f446;图中第二种情况不太容易出现…

【Chapter1】操作系统概述,计算机操作系统教程,第四版,左万利,王英

文章目录 一、操作系统的基本概念1.1操作系统的层次结构1.2操作系统的运行视图1.3操作系统的概念(定义)1.4操作系统的功能和目标1.4.1操作系统的功能和目标——作为系统资源的管理者1.4.2操作系统的功能和目标——向上层提供方便易用的服务1.4.2.1GUI&#xff1a;图形化用户接口…

CPU vs. GPU :本质差异是?

他们的目的都是做并行计算的&#xff0c;但并行计算可分为时间上的并行和空间上的并行。所以我觉得本质差异是&#xff1a; CPU 时间并行GPU 空间并行 这样就容易理解他们的工作方式&#xff1a; 对CPU来说&#xff0c;不同的核心可以执行不同的机器指令但GPU则不同&#xff…

长安链开源社区发布2023年度长安链优秀应用案例

1月27日结束的“长安链发布三周年庆暨生态年会”上&#xff0c;在国家区块链技术创新中心的指导下&#xff0c;长安链开源社区联合长安链生态联盟正式发布2023年度长安链行业示范案例、领域精品案例及特色创新案例。 本次评选面向2023年度应用长安链上线并取得应用成效的案例&…

SSA优化最近邻分类预测(matlab代码)

SSA-最近邻分类预测matlab代码 麻雀搜索算法(Sparrow Search Algorithm, SSA)是一种新型的群智能优化算法&#xff0c;在2020年提出&#xff0c;主要是受麻雀的觅食行为和反捕食行为的启发。 数据为Excel分类数据集数据。 数据集划分为训练集、验证集、测试集,比例为8&#…

intelliJ配置Android环境 与 文件目录解释

实验目的与要求&#xff1a; 目的&#xff1a;掌握面向Android编程的开发环境搭建。学习、掌握Android程序编写基本步骤&#xff0c;例如&#xff0c;Android Studio平台编写简单的一个 HelloWorld程序&#xff0c;掌握编译、运行等基本步骤和操作。 内容要求&#xff1a; 搭建…

杂货铺 | vscode配置C/C++环境(亲测极简ver)

文章目录 &#x1f4da;Step1&#xff1a;下载安装VSCode&#x1f4da;Step2&#xff1a;下载安装g&#x1f4da;Step3&#xff1a;编辑环境变量&#x1f4da;Step4&#xff1a;安装vscode插件&#x1f4da;Step5&#xff1a;建好文件夹⭐️&#x1f4da;Step6&#xff1a;开始…

【Node.js从基础到高级运用】十三、NodeJS中间件高级应用

在现代web开发中&#xff0c;Node.js因其高效和灵活性而备受青睐。其中&#xff0c;中间件的概念是构建高效Node.js应用的关键。在这篇博客文章中&#xff0c;我们将深入探讨Node.js中间件的高级应用&#xff0c;包括创建自定义中间件、使用第三方中间件等。我们将从基础讲起&a…

qt vs 编程 字符编码 程序从源码到编译到显示过程中存在的字符编码及隐藏的字符编码转换

理解字符编码&#xff0c;请参考&#xff1a;unicode ucs2 utf16 utf8 ansi GBK GB2312 CSDN博客 了解windows字符显示必须了解locale概念 参考&#xff1a;揭密 Windows 上的各种 locale - 知乎 汉字&#xff08;或者说多字节字符&#xff09;的存放需求&#xff0c;是计算…

【CSP试题回顾】202309-2-坐标变换(其二)

CSP-202309-2-坐标变换&#xff08;其二&#xff09; 解题代码 #include <iostream> #include <vector> #include <cmath> #include <iomanip> using namespace std;struct MyOpt {double k, theta; }; int n, m, opt, s, e; double para, x, y;int …

Java项目:62 基于ssm的校园驿站管理系统+jsp

作者主页&#xff1a;舒克日记 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 管理员管理快递仓库信息&#xff0c;管理待发货信息&#xff0c;管理已收快递&#xff0c;管理物流以及留言信息&#xff0c;管理员工和用户资料。 员…