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

一,函数模板

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

matplotlib库简介及函数说明

目录 简介matplotlib.pyplot as plt 常用函数说明创建子图plt.subplots&#xff08;&#xff09;.plot&#xff08;&#xff09; 子图参数set_title&#xff08;&#xff09;axis2.legend()fig.autofmt_xdate() 简介 matplotlib 是一个用于创建二维图表和数据可视化的 Python …

【数据挖掘】实验3:常用的数据管理

实验3&#xff1a;常用的数据管理 一&#xff1a;实验目的与要求 1&#xff1a;熟悉和掌握常用的数据管理方法&#xff0c;包括变量重命名、缺失值分析、数据排序、随机抽样、字符串处理、文本分词。 二&#xff1a;实验内容 【创建新变量】 方法1&#xff1a; mydata <…

写一个五子棋小游戏

具体如下&#xff0c;直接来 目录 大致一看 导入模块和初始化 定义棋盘&#xff08;Checkerboard类&#xff09; 定义AI类 游戏主循环&#xff08;main函数&#xff09; 绘图和辅助函数 AI算法解析 完整代码 大致一看 导入模块和初始化 一开始导入了必要的模块&#x…

【边缘智能】Jetson板卡上安装QT5与OpenCV集成

学习《OpenCV应用开发&#xff1a;入门、进阶与工程化实践》一书 做真正的OpenCV开发者&#xff0c;从入门到入职&#xff0c;一步到位&#xff01; 安装QT5与QT Creator 如果只是简单的使用QT的GUI库&#xff0c;没有其它要求&#xff0c;其实特别容易&#xff0c;一行命令行…

【Unity每日一记】unity中的内置宏和条件编译(Unity内置脚本符号)

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;uni…

【数据结构和算法初阶(C语言)】二叉树的顺序结构--堆的实现/堆排序/topk问题详解---二叉树学习日记②

目录 ​编辑 1.二叉树的顺序结构及实现 1.1 二叉树的顺序结构 2 堆的概念及结构 3 堆的实现 3.1堆的代码定义 3.2堆插入数据 3.3打印堆数据 3.4堆的数据的删除 3.5获取根部数据 3.6判断堆是否为空 3.7 堆的销毁 4.建堆以及堆排序 4.1 升序建大堆&#xff0c;降序建小堆 4.2堆…

RPM与DNF的操作实践

这几课有三个目标&#xff1a; 第一步&#xff1a;先配置软件源 跳转到yum.repos.d目录&#xff0c;用vim创建一个openeuler_x84_64.repo文件。这个文件就是我们将会用到的软件源。 我们在里面添加这些东西&#xff0c;保存并退出即可。 然后&#xff0c;我们用yum list all就…

【CICD】Jenkins 常用操作手册

常见词汇 词汇 说明 Node 作为 Jenkins 环境的一部分并能够执行Pipeline或项目的机器&#xff0c;无论是 Master 还是Agent 都被认为是 Node。 Master 存储配置&#xff0c;加载插件以及为 Jenkins 呈现各种用户界面的主控节点 Agent 通常是一台主机或容器&#xff0c;连…

Hive:数据仓库利器

1. 简介 Hive是一个基于Hadoop的开源数据仓库工具&#xff0c;可以用来存储、查询和分析大规模数据。Hive使用SQL-like的HiveQL语言来查询数据&#xff0c;并将其结果存储在Hadoop的文件系统中。 2. 基本概念 介绍 Hive 的核心概念&#xff0c;例如表、分区、桶、HQL 等。 …

Chrome历史版本下载地址:Google Chrome Older Versions Download (Windows, Linux Mac)

最近升级到最新版本Chrome后发现页面居然显示错乱,是在无语, 打算退回原来的版本, 又发现官方只提供最新的版本下载, 为了解决这个问题所有收集了Chrome历史版本的下载地址分享给大家. Google Chrome Windows version 32-bit VersionSizeDate104.0.5112.10279.68 MB2022-05-30…

TT-100K数据集,YOLO格式

TT-100K数据集YOLO格式&#xff0c;分为train、val和test&#xff0c;其中train中共有6793张图片&#xff0c;val中共有1949张图片&#xff0c;test中共有996张图片。数据集只保留包含图片数超过100的类别。共计46类。

uniapp微信小程序随机生成canvas-id报错?

uniapp微信小程序随机生成canvas-id报错&#xff1f; 文章目录 uniapp微信小程序随机生成canvas-id报错&#xff1f;效果图遇到问题解决 场景&#xff1a; 子组件&#xff0c;在 mounted 绘制 canvas&#xff1b;App、H5端正常显示&#xff0c;微信小程序报错&#xff1b; 效…

信息系统项目管理师019:存储和数据库(2信息技术发展—2.1信息技术及其发展—2.1.3存储和数据库)

文章目录 2.1.3 存储和数据库1.存储技术2.数据结构模型3.常用数据库类型4.数据仓库 记忆要点总结 2.1.3 存储和数据库 1.存储技术 存储分类根据服务器类型分为&#xff1a;封闭系统的存储和开放系统的存储。封闭系统主要指大型机等服务器。开放系统指基于包括麒麟、欧拉、UNIX…

MacBook远程桌面Windows使用Microsoft Remote Desktop for Mac_亲测使用

MacBook远程桌面Windows使用Microsoft Remote Desktop for Mac_亲测使用 像Windows上有自带的远程桌面连接软件.MacBook没有自带的远程连接Windows桌面的工具,需要安装软件来实现. 像远程桌面控制软件一般有 TeamViewer、向日葵远程控制, ToDesk, Microsoft Remote Desktop f…

【ZooKeeper3、Watcher机制

本文基于 Apache ZooKeeper Release 3.7.0 版本书写 作于 2022年5月15日 17:22:11 转载请声明 演示前的ZooKeeper目录状态&#xff0c;只有zookeeper默认目录&#xff1a; 在客户端直接输入 --help 命令&#xff0c;可以看到以下文字&#xff1a; 可以看到 addWatch 命令&am…

视频桥接芯片#LT8912B适用于MIPIDSI转HDMI+LVDS应用方案,提供技术支持。

1. 概述 Lontium LT8912B MIPI DSI 转 LVDS 和 HDMI 桥接器采用单通道 MIPI D-PHY 接收器前端配置&#xff0c;每通道 4 个数据通道&#xff0c;每个数据通道以 1.5Gbps 的速度运行&#xff0c;最大输入带宽高达 6Gbps。 对于屏幕应用&#xff0c;该桥接器可解码 MIPI DSI 18bp…

【QED】斐波那契游戏

文章目录 题目思路代码复杂度分析时间复杂度空间复杂度 总结 题目 题目链接&#x1f517; 斐波那契数列指的是这样一个数列&#xff1a;1&#xff0c;1&#xff0c;2&#xff0c;3&#xff0c;5&#xff0c;8&#xff0c;13&#xff0c;21&#xff0c;34&#xff0c;55&#x…

Docker部署TeamCity来完成内部CI、CD流程

使用TeamCity来完成内部CI、CD流程 本篇教程主要讲解基于容器服务搭建TeamCity服务&#xff0c;并且完成内部项目的CI流程配置。至于完整的DevOps&#xff0c;我们后续独立探讨。 一个简单的CI、CD流程 以下分享一个简单的CI、CD流程&#xff08;仅供参考&#xff09;&#…

C++进阶之路---手撕“红黑树”

顾得泉&#xff1a;个人主页 个人专栏&#xff1a;《Linux操作系统》 《C从入门到精通》 《LeedCode刷题》 键盘敲烂&#xff0c;年薪百万&#xff01; 一、红黑树的概念与性质 1.概念 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个结点上增加一个存储位表示结点…

大数据开发-数据仓库简介

文章目录 什么是数据仓库数据仓库基础知识数据仓库的建模方式数据仓库分层数据仓库的命名规范典型数仓系统架构 什么是数据仓库 数据仓库(Data Warehouse)是一个面向主题的、集成的、稳定的且随时间变化的数据集合&#xff0c;用于支持管理人员的决策 面向主题&#xff1a;类…