【第六节】C++中的模板

目录

前言

一、函数模板

二、模块特化

三、重载函数模板

四、类模板

五、总结


前言

        模板是C++编程中的一种强大工具,它允许程序员创建具有通用数据类型的函数库和类库。作为支持参数多态性的机制,模板极大地增强了代码的复用性和灵活性。

        在C++语言中,程序的结构主要由函数和类组成,而模板则提供了两种关键的形式:

A: 函数模板 - 这是一种定义,允许函数根据传递的参数类型自动适应,从而实现对不同数据类型的通用操作。

B: 类模板 - 类似于函数模板,类模板允许创建可以处理多种数据类型的类,使得类的实例化可以根据实际使用的数据类型进行定制。

        通过这两种模板形式,C++程序员能够设计出更加通用和可扩展的代码,有效提升软件开发的效率和质量。


一、函数模板

        考察两个swap()函数,一个交换整型数,一个交换浮点数。尽管函数的功能一样、仅处理数据类型不同,但对有强数据类型校验的C++语言,必须使用函数重载,分别编写代码。

void swap(int& a, int& b) {int temp = a;a = b;b = temp;
}void swap(float& a, float& b) {float temp = a;a = b;b = temp;
}

        这两个函数的参数个数、实现代码是相同的,只有形参的类型不同。如果将两函数中的int和float进行参数化,使用参数T替代,则交换任何一对数据类型的变量(包括类对象)可以定义为:

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

        有了上述定义,对任一类型T的两个变量或类对象(x1,x2),函数调用swap(x1.x2),编译器都能理解并执行函数模板所确定的功能。
        函数模板提供了具有处理相同功能的一类函数的抽象,它以任意类型T为参数、其定义形式如下:

template<参数化类型名表><返回类型〉<函数名>(<参数表>)
{
    //<函数体>
}

template<typename T_TYPE>int fun(T_TYPE NUmA, T_TYPE NumB)
{
    //……
}

        其中,template 是定义函数模板的关键字,<参数化类型名表>可以包含基本数据类型,也可以包含类类型如果是类类型,则需加关键字class,例如:
template <class T_TYPE>
        函数模板是对一组函数的描述,它不是一个实实在在的函数,表示每次它能单独处理在类型形式参数中说明的数据类型。编译器不会为其产生任何执行代码。
        当编译器发现有函数调用:函数名(实参数),当实参数与函数模板形参数相匹配时,则产生一个重载函数。该重载函数与函数模板实现功能相同(函数体定义相同),该重载函数称为模板函数(template function)。
        下面举例说明了函数模板的定义和使用方法:

#include <iostream>
using namespace std;template <typename T_TYPE>
T_TYPE my_max(T_TYPE NumA, T_TYPE NumB) {return NumA > NumB ? NumA : NumB;
}int main() {cout << my_max(1, 2) << endl; cout << my_max(1.2, 3.4) << endl; cout << my_max('B', 'A') << endl; system("pause"); return 0;
}

        函数模板是一种蓝图或定义,它本身并非一个具体的函数,而是通过使用泛型类型参数来描述一类函数的结构。当编译器遇到特定的函数调用时,它会根据函数模板生成一个具体的函数实例,即模板函数。这个模板函数是一个实际的、可执行的函数定义,包含了编译器生成的特定类型代码,能够直接在程序中执行。通过这种方式,函数模板提供了一种高效且灵活的方法来创建适用于多种数据类型的函数。

二、模块特化

        模板的特化,亦可视为一种特殊的实例化过程,它包括隐式实例化和显式实例化两种形式。通常,我们所说的显式实例化即指模板的特化。特化的目的在于应对模板在实例化过程中可能遇到的特殊情况,确保模板能够正确处理特定类型的数据。

模板的特化主要分为两种:普通特化和偏特化,它们各自具有不同的特点:

普通特化:针对模板中所有替换类型中的某一种类型进行专门的处理,以适应该类型的特殊需求。

偏特化:针对模板中所有替换类型中的某一种类型的某些特定方面进行专门的处理,以优化或修正该类型的行为。

        以一个比较大小的函数模板为例,该模板在设计时可能仅考虑了常规情况。然而,当参数为指针类型时,模板函数可能会错误地比较指针的地址而非指针所指向数据的大小。为了解决这一问题,我们需要对参数为指针类型的模板函数进行特化处理,确保其能够正确比较指针所指向数据的大小,而不是指针本身的地址。这种特化处理能够使模板更加灵活和精确地适应各种使用场景。

代码示例:

#include <iostream>// 普通特化
template<typename T1, typename T2>
void myFunction(T1 a, T2 b) {std::cout << "General template for T1=" << typeid(T1).name()<< ", T2=" << typeid(T2).name() << std::endl;
}// 偏特化
template<>
void myFunction<int, int>(int a, int b) {std::cout << "Specialized template for int and int" << std::endl;
}int main() {myFunction(3, 4);system("pause"); return 0;
}

三、重载函数模板

在C++中,函数重载的解析遵循以下规则:

  1. 查找参数完全匹配的函数: 编译器首先查找完全匹配调用函数的参数列表的函数。如果找到一个精确匹配的函数,那么编译器将使用这个函数。

  2. 查找函数模板: 如果没有找到精确匹配的函数,编译器会查找函数模板。如果找到一个函数模板,它会尝试用调用参数实例化这个模板,生成一个新的函数。

  3. 通过类型转换产生参数匹配的函数: 如果没有找到精确匹配的函数或函数模板,编译器会尝试进行类型转换,以找到一个参数匹配的函数。这可能包括标准类型转换(如算术转换、派生类向基类的转换等)以及用户定义的类型转换。

  4. 寻找可变参数函数: 如果没有找到精确匹配的函数、函数模板或通过类型转换得到的匹配函数,编译器会查找是否有可变参数函数(如printf函数)可以接受可变数量的参数。

  5. 寻找省略号匹配的函数: 如果没有找到其他匹配的函数,编译器会查找是否有函数的参数列表中包含省略号(...),这通常用于可变参数函数。

  6. 错误报告: 如果上述步骤都无法找到匹配的函数,编译器将报告错误,表明没有找到可以调用的函数。

示例代码:

#include <iostream>
using namespace std;// 普通函数
void print(int i) {std::cout << "int: " << i << std::endl;
}// 函数模板
template <typename T>
void print(T value) {std::cout << "Template: " << value << std::endl;
}// 通过类型转换产生参数匹配的函数
void print(double d) {std::cout << "double: " << d << std::endl;
}int main() {int a = 5;double b = 3.14;// 调用精确匹配的函数print(a); // 输出: int: 5// 调用函数模板实例化产生的函数print(b); // 输出: double: 3.14// 调用通过类型转换产生参数匹配的函数print(42); // 输出: int: 42,因为42可以转换为intreturn 0;
}

四、类模板

        在编程中,当我们需要定义一个一维数组或链表时,无论数组元素或链表结点是何种数据类型,它们的基本操作(如插入、删除、检索等)通常保持一致。例如,一个数组类可以处理任何类型的数据,而链表类也可以定义结点以适应不同的数据类型,但其基本操作流程不变。

        然而,如果对于每种不同的数据类型,我们都必须重新定义一个新的类,这将导致大量的代码重复和维护上的困难。为了解决这个问题,我们可以引入类模板的概念。通过类模板,我们可以使用一个通用参数T来代表数组元素或链表结点的数据类型。这样,一个类模板就可以适用于多种数据类型,而不需要为每种类型单独编写类定义。这种方法极大地提高了代码的复用性和灵活性,减少了重复工作,使得类的实现更加高效和易于管理。

类模板的定义格式为:
template〈模板参数表>
class <类名>
{
    //类体说明
}
其中 template 是关键字,<模板参数表>中可以有多个参数,其间用逗号分隔。

代码示例:

#include <iostream>
using namespace std;template <typename T>
class Array {
private:T* elements;size_t size;public:// 构造函数Array(size_t s) : size(s) {elements = new T[s];}// 析构函数~Array() {delete[] elements;}// 复制构造函数Array(const Array& other) : size(other.size) {elements = new T[size];for (size_t i = 0; i < size; ++i) {elements[i] = other.elements[i];}}// 赋值运算符重载Array& operator=(const Array& other) {if (this != &other) {delete[] elements;size = other.size;elements = new T[size];for (size_t i = 0; i < size; ++i) {elements[i] = other.elements[i];}}return *this;}// 获取数组大小size_t getSize() const {return size;}// 获取元素T& operator[](size_t index) {if (index >= size) {throw std::out_of_range("Index out of range");}return elements[index];}// 常量版本获取元素const T& operator[](size_t index) const {if (index >= size) {throw std::out_of_range("Index out of range");}return elements[index];}
};int main() {// 创建一个整型数组Array<int> intArray(5);for (size_t i = 0; i < intArray.getSize(); ++i) {intArray[i] = i * 2;}for (size_t i = 0; i < intArray.getSize(); ++i) {std::cout << intArray[i] << " ";}std::cout << std::endl;// 创建一个浮点型数组Array<float> floatArray(3);floatArray[0] = 3.14f;floatArray[1] = 2.71f;floatArray[2] = 1.61f;for (size_t i = 0; i < floatArray.getSize(); ++i) {std::cout << floatArray[i] << " ";}std::cout << std::endl;return 0;
}

        类模板是为具有相同特性的一组类,定义的一种模式,它说明单个类怎样建立,这与类型声明说明单个对象是怎样建立类似。用模板实参数生成的类称为模板类,模板类可以生成对象。

五、总结

        模板是C++中一种安全且高效的代码重用机制。它采用参数化类型的方式,使得在创建对象或函数时,通过传递不同的实参类型,可以改变其行为。无论是函数模板还是类模板,经过实例化后,可以生成具有不同数据类型的模板函数和模板类。尽管这些模板处理的数据类型可能各异,但它们的功能和实现逻辑始终保持一致。每个模板类的实例都是一个真实的对象,可以像其他类的对象一样进行操作和使用。

        在C++中,一个显著的发展趋势是广泛采用标准模板库(STL),它已经成为许多编译器不可或缺的一部分。STL是一个基于模板的综合性类库,其中包含了诸如向量、链表和队列等数据结构,以及一些通用的排序和查找算法等。STL极大地减少了程序员需要重复编写的代码量,提高了编程的效率和代码的可维护性,为开发者提供了极大的便利。

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

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

相关文章

Pytorch线性回归

使用pytorch来重现线性模型的过程&#xff0c;构造神经网络module&#xff0c;构造损失函数loss&#xff0c;构造随机梯度下降的优化器sgd。 一 revise 首先确定我们的模型&#xff0c;我们希望完成的目标就是得到较小的loss&#xff0c;所以我们就需要一个标量值的loss。 那…

YOLOv10(2):网络结构及其检测模型代码部分阅读

YOLOv10&#xff08;1&#xff09;&#xff1a;初探&#xff0c;训练自己的数据-CSDN博客 目录 1. 写在前面 2. 局部模块 &#xff08;1&#xff09;SCDown &#xff08;2&#xff09;C2fCIB &#xff08;3&#xff09;PSA(partial self-attention) 3. 代码解读 &#x…

手把手教大家如何使用Kaggle平台的免费GPU资源跑深度学习模型

如果手头没有GPU资源是没法很好进行学习和实操各种深度学习模型的&#xff0c;所幸有一些平台提供了GPU资源供广大兴趣爱好者进行免费使用。 一、免费GPU资源的平台 1. Google Colab 地址&#xff1a;https://colab.research.google.com/ 简介&#xff1a;Google Colab&…

ssm_mysql_高校自习室预约系统(源码)

博主介绍&#xff1a;✌程序员徐师兄、8年大厂程序员经历。全网粉丝15w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

思科防火墙配置内网地址 配置PAT 思科防火墙还是不能访问外网?

环境: 思科设备 frp2100 问题描述: 思科防火墙配置内网地址 配置PAT 思科防火墙还是不能访问外网? 解决方案: 要配置思科ASA防火墙以便内网用户可以访问外网,通常会使用PAT(端口地址转换),这里是一个基本的配置流程示例。请注意,以下步骤假定你已具备必要的网络知…

kubernetes之etcd基本操作

etcd常用操作 拷贝etcd命令行工具查看etcd集群节点查看etcd集群监控状态查看etcd集群节点状态查看etcd集群内的所有key查看etcd集群内对应key的valueetcd备份etcd恢复 拷贝etcd命令行工具 rootmaster2:~# find / -iname "etcdctl" /var/lib/docker/overlay2/70f2d53…

在mongodb5.0.11版本中创建用户和授权

目录 处理原因&#xff1a; 1、设定mongodb的环境变量 2、登录mongodb 3、使用admin库 4、创建 mongouser用户 5、更新 mongouser 可使用的库 6、创建 mongouser角色授权 7、更新root账号也可以使用 mongouser角色 处理原因&#xff1a; 在MongoDB中&#xff0c;root角…

throw和throws的区别是什么?

在Java编程语言中&#xff0c;throw和throws都是与异常处理相关的关键字&#xff0c;但它们在使用方式和作用上有着明显的区别。下面将从技术难点、面试官关注点、回答吸引力和代码举例四个方面来详细解释它们之间的区别。 一、技术难点 throw&#xff1a;throw关键字用于在程…

IDEA中,MybatisPlus整合Spring项目的基础用法

一、本文涉及的知识点【重点】 IDEA中使用MybatisPlus生成代码&#xff0c;并使用。 Spring整合了Mybatis框架后&#xff0c;开发变得方便了很多&#xff0c;然而&#xff0c;Mapper、Service和XML文件&#xff0c;在Spring开发中常常会重复地使用&#xff0c;每一次的创建、修…

关键主题汇编

用户使用双屏扩展桌面类软件时,所遇到的主要疑难问题汇编 https://exesoft.blog.csdn.net/article/details/43116867 知识竞赛现场管理系统安装配置及使用疑难问题汇编 https://exesoft.blog.csdn.net/article/details/53457491 关于知识竞赛现场管理系统内置的第三方答题平…

【杂记-浅谈XSS跨站脚本攻击】

一、什么是XSS&#xff1f; XSS&#xff0c;Cross-site Scripting&#xff0c;跨站脚本攻击&#xff0c;是一种典型的Web程序漏洞利用攻击&#xff0c;攻击者利用Web程序对用户输入检查不足的漏洞将可执行恶意脚本注入网站或Web应用&#xff0c;当用户访问网页时触发恶意脚本的…

App Web前端怎么开发:深入解析与开发实践

App Web前端怎么开发&#xff1a;深入解析与开发实践 在当今数字化时代&#xff0c;Web前端作为连接用户与应用程序的桥梁&#xff0c;其重要性不言而喻。对于想要涉足App Web前端开发的开发者而言&#xff0c;理解并掌握开发流程、技术栈以及最佳实践是至关重要的。本文将分四…

milvus向量数据库

Milvus 是一个开源的向量数据库&#xff0c;用于管理、搜索和分析高维向量数据。它特别适用于处理像图像、视频、音频和文本等非结构化数据。以下是关于 Milvus 向量数据库的详细介绍和入门教程。 ### Milvus 简介 Milvus 的主要特性包括&#xff1a; 1. **高性能**&#xf…

权限修饰符和代码块

一.权限修饰符 1.权限修饰符:是用来控制一个成员能够被访问的范围的。 2.可以修饰成员变量&#xff0c;方法&#xff0c;构造方法,内部类。 3.例子&#xff1a; public class Student {priviate String name;prviate int age;} 二.权限修饰符的分类 有四种作用范围大小…

自然语言处理中的RNN、LSTM、TextCNN和Transformer比较

引言 在自然语言处理&#xff08;NLP&#xff09;领域&#xff0c;理解和应用各种模型架构是必不可少的。本文将介绍几种常见的深度学习模型架构&#xff1a;RNN&#xff08;循环神经网络&#xff09;、LSTM&#xff08;长短期记忆网络&#xff09;、TextCNN&#xff08;文本卷…

ubuntu下搭建Supervisor

sudo apt update #安装 sudo apt install supervisor#启动 supervisord 服务&#xff1a; sudo systemctl start supervisor#关闭 supervisord 服务 sudo systemctl stop supervisor#重启 supervisord 服务 sudo systemctl restart supervisor#设置 supervisord 开机自启 sudo …

详解寄存器模型reg_model的auto_predict

什么是reg_model镜像值? DUT的配置寄存器的值是实际值&#xff0c;reg_model有镜像值、期望值的概念。 镜像值&#xff1a;存放我们认为此时DUT里寄存器的实际值。 期望值&#xff1a;存放我们期望DUT寄存器被赋予的值。 什么是auto predict&#xff1f; 那么怎么更新reg…

安卓ANR检测、分析、优化面面谈

前言 一个引发讨论的楔子&#xff0c;以下三种现象有什么区别&#xff1a; App停止运行App暂无响应App闪退 答案&#xff1a; 产生原因不同&#xff1a;停止运行是UNCheckExceptionError暂无响应是ANRDialog闪退是CheckExceptionError 本文讨论的主题是ANR的定义、分类、复现…

Debian常用命令详细介绍

1. apt-get update&#xff1a;更新软件源列表 apt-get update命令用于更新系统中可用软件包的包列表。在Linux和类Unix操作系统中&#xff0c;软件包管理器&#xff08;如APT&#xff09;维护着一个包含可用软件包信息的列表&#xff0c;通常保存在系统的软件源中。通过运行a…

Three.js 中文Typeface文件字体大全 | 如何利用Github获取中文Typeface文件

Three.js中文3D字体在线示例 TextGeometry 和 TextBufferGeometry 是用于生成3D文本的有效工具。 在使用这些工具时&#xff0c;我们需要指定一个包含字体信息的 JSON 文件&#xff0c;称为 typeface.json。 Github 搜索结果 通过在 GitHub 上搜索 TextBufferGeometry 的相…