【C++ 学习 ⑦】- 模板初阶(函数模板和类模板)

news2025/1/12 23:07:18

目录

一、前言

二、函数模板

2.1 - 基本概念和原理

2.2 - 定义格式

2.3 - 实例化详解

2.3.1 - 隐式实例化

2.3.2 - 显示实例化

2.4 - 模板参数的匹配原则

三、类模板

3.1 - 定义格式

3.2 - 实例化


参考资料

  1. C++函数模板(模板函数)详解 (biancheng.net)。

  2. C++函数模板5分钟入门教程 (biancheng.net)。

  3. C++类模板5分钟入门教程 (biancheng.net)。


一、前言

问题:如何实现一个通用的交换函数?

// 交换两个 int 类型变量的值
void Swap(int& x, int& y)
{
    int tmp = x;
    x = y;
    y = tmp;
}
​
// 交换两个 double 类型变量的值
void Swap(double& x, double& y)
{
    double tmp = x;
    x = y;
    y = tmp;
}
​
// 交换两个 char 类型变量的值
void Swap(char& x, char& y)
{
    char tmp = x;
    x = y;
    y = tmp;
}
​
// ... ...

虽然我们可以重载很多个函数,以满足交换不同类型变量的需求,但是这种方法不太高明

  1. 重载的函数除了形参 xy 以及函数体中临时变量 tmp 的数据类型不同,其他的代码都一样。

  2. 代码的可维护性也比较低,一个出错可能所有的重载均出错。

那么能否只写一遍 Swap 函数,就能用来交换各种类型变量的值呢?因此 "模板" 的概念就应运而生了

众所周知,有了 "模子" 后,用 "模子" 来批量制造陶瓷、塑料、金属制品就变得容易了。程序设计语言中的模板就是用来批量生成功能和形式都几乎相同的代码的。有了模板,编译器就能在需要的时候,根据模板自动生成程序的代码。从同一个模板自动生成的代码,形式几乎是一样的

模板是泛型编程的基础,泛型编程即以一种独立于任何特定类型的方式编写代码


二、函数模板

2.1 - 基本概念和原理

所谓函数模板(Function Template),实际上是建立一个通用函数,它所用到的数据类型(包括形参类型、局部变量类型、返回值类型)可以不具体指定,而是用一个虚拟的类型来代替(实际上是用一个标识符来占位)。编译的时候,编译器推导实参的数据类型,根据实参的数据类型和函数模板,生成特定的函数定义

在函数模板中,数据的值(value)和类型(type)都被参数化了

生成函数定义的过程被称为实例化

2.2 - 定义格式

template<typename T1. typename T2, ...>
返回值类型 函数名(参数列表)
{
    函数体
}

注意

  1. T1、T2、... 是类型参数(也可以说是虚拟的类型,或者说是类型占位符),类型参数的命名规则跟其他标识符的命名规则一样,不过使用 T、T1、T2、Type 等已经成为了一种习惯

  2. 其中 typename 关键字也可以用 class 关键字替换,它们没有任何区别

    C++ 早期对模板的支持并不严谨,没有引入新的关键字,而是用 class 来指明类型参数,但是 class 关键字本来已经用在类的定义中了,这样做显得不太友好,所以后来 C++ 又引入了一个新的关键字 typename,专门用来定义类型参数。不过至今仍然有很多代码在使用 class 关键字,包括 C++ 标准库、一些开源程序等。

#include <iostream>
using namespace std;
​
// template<typename T>
// 或者
template<class T>
void Swap(T& x, T& y)
{
    T tmp = x;
    x = y;
    y = tmp;
}
​
int main()
{
    int a = 10, b = 20;
    Swap(a, b);
    cout << a << " " << b << endl;  // 20 10
    
    double c = 1.1, d = 2.2;
    Swap(c, d);
    cout << c << " " << d << endl;  // 2.2 1.1
​
    char e = 'm', f = 'n';
    Swap(e, f);
    cout << e << " " << f << endl;  // n m
    return 0;
}

编译器会根据 Swap 模板自动生成多个 Swap 函数,用来交换不同类型变量的值。

函数模板中定义的类型参数不仅可以用在函数定义中,还可以用在函数声明中

#include <iostream>
using namespace std;
​
template<typename T>
void Swap(T& x, T& y);
​
int main()
{
    int a = 10, b = 20;
    Swap(a, b);
    cout << a << " " << b << endl;  // 20 10
    return 0;
}
​
template<typename T>
void Swap(T& x, T& y)
{
    T tmp = x;
    x = y;
    y = tmp;
}

2.3 - 实例化详解

2.3.1 - 隐式实例化

让编译器根据实参类型推演模板参数的实际类型

#include <iostream>
using namespace std;
​
template<typename T>
T Add(const T& x, const T& y)
{
    return x + y;
}
​
int main()
{
    int a = 10, b = 20;
    cout << Add(a, b) << endl;  // 30
​
    double c = 1.1, d = 2.2;
    cout << Add(c, d) << endl;  // 3.3
    return 0;
}

注意

cout << Add(a, c) << endl;  
// error:"T Add(const T &,const T &)": 模板 参数 "T" 不明确

上面的语句不能通过编译,是因为在编译期间,编译器通过实参 a 将 T 推演为 int 类型,通过实参 c 将 T 推演为 double 类型,而模板参数列表中只有一个 T,编译器无法确定此处到底该将 T 确定为 int 还是 double 类型而报错。

在模板中,编译器一般不会进行类型转换操作,因为一旦转换出问题,编译器就需要 "背黑锅"。

解决方案一

cout << Add((double)a, c) << endl;  // 11.1
// 或者
cout << Add(a, (int)c) << endl;  // 11
// 以 (int)c 为例,c 强转类型转换后传参的过程大概可以分为以下几个步骤:
// 1. 在另外的地方找一个内存构造一个临时变量 tmp
// 2. 将 c 的整数部分赋值给 tmp
// 3. 用 tmp 传参
// 4. 销毁 tmp
// 因为临时变量具有常性,所以 Add 函数的形参 x 和 y 必须用常引用

解决方案二

#include <iostream>
using namespace std;
​
template<typename T1, typename T2>
T1 Add(const T1& x, const T2& y)
{
    return x + y;
}
​
int main()
{
    int a = 10;
    double c = 1.1;
    cout << Add(a, c) << endl;  // 11
    // 若 Add 函数的返回值类型为 T2,则输出的结果为 11.1
    return 0;
}

解决方案三:显示实例化

2.3.2 - 显示实例化

在函数名后的 <> 中指定模板参数的实际类型

cout << Add<double>(a, c) << endl;  // 11.1
cout << Add<int>(a, c) << endl;  // 11

如果类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功编译器就会报错。

更常见的使用场景

#include <iostream>
using namespace std;
​
template<typename T>
T* Alloc(int n)
{
    return new T[n];
}
​
int main()
{
    int* arr = Alloc<int>(5);
    for (int i = 0; i < 5; ++i)
    {
        arr[i] = i;
    }
    for (int i = 0; i < 5; ++i)
    {
        cout << arr[i] << " ";
    }
    cout << endl;
    // 0 1 2 3 4
    delete[] arr;
    return 0;
}

2.4 - 模板参数的匹配原则

  1. 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数

    #include <iostream>
    using namespace std;
    ​
    void Swap(int& x, int& y)
    {
        int tmp = x;
        x = y;
        y = tmp;
    }
    ​
    template<typename T>
    void Swap(T& x, T& y)
    {
        T tmp = x;
        x = y;
        y = tmp;
    }
    ​
    int main()
    {
        int a = 10, b = 20;
        Swap(a, b);  // 与模板函数匹配,编译器不需要特化
        cout << a << " " << b << endl;  // 10 20
    ​
        Swap<int>(a, b);  // 调用编译器特化的 Swap 版本
        cout << a << " " << b << endl;  // 20 10
        return 0;
    }

  2. 对于非模板函数和同名的函数模板,如果其他条件都相同,在调用时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数,那么将选择模板

    #include <iostream>
    using namespace std;
    ​
    int Add(int x, int y)
    {
        return x + y;
    }
    ​
    template<typename T1, typename T2>
    T1 Add(const T1& x, const T2& y)
    {
        return x + y;
    }
    ​
    int main()
    {
        int a = 10, b = 20;
        cout << Add(a, b) << endl;  // 30
        // 与非模板函数完全匹配,不需要函数模板实例化
        
        double c = 1.1;
        cout << Add(a, c) << endl;  // 11
        // 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的 Add 函数
        return 0;
    }


三、类模板

3.1 - 定义格式

C++ 除了支持函数模板,还支持类模板(Class Template)。函数模板中定义的类型参数可以用在函数声明和函数定义中,类模板中定义的类型参数也可以用在类声明和类实现中。类模板的目的同样是将数据的类型参数化

声明类模板的格式为

template<class T1, class T2, ...>
class 类名
{
    // TODO;
};

例如

template<class T>
class Stack
{
public:
    Stack(int default_capacity = 5)
        : _data(new T[default_capacity]), _top(0), _capacity(default_capacity)
    {}
    ~Stack();
private:
    T* _data;
    int _top;
    int _capacity;
};

上面的代码仅仅是类的声明,我们还需要在类外定义成员函数。在类外定义成员函数时仍然需要带上模板头,格式为

template<class T>
Stack<T>::~Stack()
{
    if (_data)
        delete[] _data;
    _top = _capacity = 0;
}

注意:除了 template 关键字后面要指明类型参数,类名 Stack 后面也要带上类型参数,只是不加 classtypename 关键字了

3.2 - 实例化

与函数模板不同的是,类模板实例化需要在类模板名字后面跟 <>,然后将实例化的类型放在 <> 中,即类模板必须显示实例化,因为编译器不能根据给定的数据推演出数据类型

类模板名字不是真正的类,而实例化的结果才是真正的类

Stack<int> st1;
Stack<double> st2;
Stack<int>* p1 = new Stack<int>(10);
Stack<double>* p2 = new Stack<double>(15);

对于普通类,类名和类型是一样的;对于模板类,类名和类型不一样,在上面的例子中,类名是 Stack,类型则是 Stack<int>/ Stack<double>

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

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

相关文章

chatgpt赋能Python-python_for_line_in_f

Python for Line in F: 一种高效的文件读写方法 对于Python编程工程师来说&#xff0c;文件的读写操作是一项必备技能。但是&#xff0c;如果你还在使用传统的逐行读取方法&#xff0c;那么你可能需要了解一种更加高效的文件读写方法&#xff1a;Python for Line in F。 什么…

使用亚马逊云科技Amazon VPC Lattice简化服务间的连接、安全和监控

在亚马逊云科技re:Invent 2022中&#xff0c;亚马逊云科技介绍了Amazon VPC Lattice预览版&#xff0c;这是Amazon Virtual Private Cloud&#xff08;Amazon VPC&#xff09;的一项新功能&#xff0c;可通过一致的方式连接、保护和监控服务之间的通信。借助Amazon VPC Lattice…

十八、map和set

文章目录 一、关联式容器&#xff08;一&#xff09;序列式容器&#xff1a;&#xff08;二&#xff09;关联式容器&#xff1a; 二、树形结构与哈希结构&#xff08;一&#xff09;树型结构&#xff08;二&#xff09;哈希结构 三、键值对四、set五、multiset六、map&#xff…

有没有一种支持对象建模、数据库建模和低代码能力的工具,用来解放程序员生产力呢?

1. 简介 作为一个程序员&#xff0c;很多时候在面对项目开发工期短、任务重、功能复杂、压力大&#xff0c;同时还得迎合领导或者甲方的要求提供研发设计文档时&#xff0c;往往很苦恼&#xff0c;因为随着软件项目的迭代&#xff0c;很难保持输出与代码一致的数据模型和架构模…

Configuring MySQL for a SSD based SAN

Configuring MySQL for a SSD based SAN (Doc ID 2673662.1)正在上传…重新上传取消To Bottom In this Document APPLIES TO: MySQL Server - Version 5.6 and later Information in this document applies to any platform. GOAL Considering a Virtualized environment wit…

chatgpt赋能Python-python_ip归属地

Python IP归属地查询 在网络安全领域&#xff0c;IP地址归属地查询是一项非常重要的任务。很多时候我们需要知道某个IP地址的归属地以解决一些安全问题。Python语言在这方面也发挥了巨大的作用&#xff0c;有各种成熟的IP地址归属地查询库。在本文中&#xff0c;我们将介绍如何…

CMOS摄像头驱动分析-i2c驱动

CMOS摄像头驱动分析-i2c驱动 文章目录 CMOS摄像头驱动分析-i2c驱动设备树内容module_i2c_driver宏分析ov2640_i2c_driverov2640_probe 设备树内容 ov2640: camera0x30 {compatible "ovti,ov2640";reg <0x30>;status "okay";pinctrl-names "…

三、Mybatis从入门到入土

一、什么是Mybatis 1&#xff09;MyBatis 是一款优秀的持久层&#xff08;DAO层&#xff09;框架 2&#xff09;MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集的过程 3&#xff09;MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息&#xff0c;将…

程序员做爬虫有哪些痛点

很多网站会设置反爬虫机制&#xff0c;如验证码、IP封禁、请求频率限制等&#xff0c;这些机制会增加爬虫的难度。 程序员做爬虫时&#xff0c;可能会遇到以下难点&#xff1a; 1、反爬虫机制 许多网站会设置反爬虫机制&#xff0c;如验证码、IP封禁、请求频率限制等&#x…

ICV: ADAS SoC市场规模将在2024年迎来较大突破

随着先进驾驶辅助系统&#xff08;ADAS&#xff09;的出现和对于自动驾驶的追求&#xff0c;汽车行业正在经历快速转型。这些技术进步的核心是ADAS SoC&#xff0c;它是实现多个功能集成于单一平台的关键组件。ADAS SoC已经成为智能汽车的重要驱动因素&#xff0c;彻底改变了安…

Linkage Mapper Toolkit中的 Barrier Mapper 功能解析(含实际案例分析)

✅创作者:陈书予 🎉个人主页:陈书予的个人主页 🍁陈书予的个人社区,欢迎你的加入: 陈书予的社区 🌟专栏地址: Linkage Mapper解密数字世界链接 文章目录 🎉引言🌟博客介绍🌟Linkage Mapper 工具和 ArcGIS 简介🎉Barrier Mapper 和 ArcGIS 的集成</

python内置模块之logging

文章目录 1 开始使用 logging 1.1 第一个程序1.2 日志级别1.3 输出格式 2 输出日志到文件 2.1 使用 basicConfig 配置文件路径2.2 logging 模块化设计2.3 自动分割日志文件 最近因为一个小需求&#xff0c;需要保存日志到文件中。因为平时调试都只是用print&#xff0c;当不…

Cesium教程(五):加载自定义地形数据

目录 1、准备数据 2、转化数据 3、发布数据 4、加载数据 5、数据下载地址 1、准备数据 可以从NASA网站免费下载30m空间分辨率高程模型数据 在地图上选择区域之后&#xff0c;点击左侧对应区域下载即可&#xff1a; 也可以在地理空间数据云上下载所需要的地区数据地理空间数…

Zeebe系列(1)-- Zeebe官方管理台使用

Zeebe管理台创建 注册zeebe账号&#xff0c;可以获得30天的免费试用期。可以在官方提供的管理台页面进行zeebe集群的创建等操作。 Accounts | Sign Up - Camunda Zeebe管理台是进行流程管理的界面&#xff0c;主要包括5个组件。 Console Console&#xff1a;Zeebe的控制台&…

Java高并发核心编程—内置锁原理篇

注&#xff1a;本笔记是阅读《Java高并发核心编程卷2》整理的笔记&#xff01; 导致并发修改的原因 例如Java中的i等指令并非是原子操作&#xff0c;而是三条指令的集合&#xff1a;“内存取值”、“寄存器增加1”、“存值到内存” 。 因此&#xff0c;如果是多线程并发使用C…

PHP操作宝塔面板Api,宝塔服务器搭建,API接口使用教程

最近两个月都在写Bty项目&#xff0c;所以收集了很多很多宝塔常用到的一些Api接口&#xff0c;官方文档虽然写了一点&#xff0c;但是始终是不怎么全的&#xff0c;下面我们来看看宝塔面板的接口如何抓取 接口抓取 1、登录宝塔面板 2、找到自己想要的功能 3、f12打开审查元素&a…

韵达转债上市价格预测

韵达转债 基本信息 转债名称&#xff1a;韵达转债&#xff0c;评级&#xff1a;AA&#xff0c;发行规模&#xff1a;24.5亿元。 正股名称&#xff1a;韵达股份&#xff0c;今日收盘价&#xff1a;12.23元&#xff0c;转股价格&#xff1a;12.15元。 当前转股价值 转债面值 / 转…

vue关于静态路由和动态路由:

这篇文章写得超详细&#xff01;&#xff01;&#xff01; &#x1f449;vue实现动态路由一步到位_vue动态路由怎么实现_ds_surk的博客-CSDN博客 目录 静态路由的配置&#xff1a; 步骤&#xff1a; 动态路由的配置&#xff1a; 步骤&#xff1a; 代码实现&#xff1a; …

网易云音乐开发--search模块基本功能实现(除历史记录模块)

search头部搭建 老样子搭建一个search搜索页面 还有一块没有实现&#xff0c;那就是让输入框默认的文本变换颜色 微信小程序: input输入框placeholder样式的修改_微信小程序placeholder样式_酷伊奥的博客-CSDN博客 百度搜索了一下&#xff0c;找到了这个大佬的解决方案。很nic…

ICV:中国的数字经济与5G市场研究报告

近日&#xff0c;专注于前沿科技领域的国际咨询机构ICV发布了《中国的数字经济与5G市场研究报告》。报告指出&#xff0c;随着5G商用的发展&#xff0c;5G对经济社会的影响逐步显现&#xff0c;其影响突出体现在对数字产业发展的带动上。随着5G应用的不断创新与扩散&#xff0c…