C++ 初识模板

目录

0.前言

1.泛型编程

2.函数模板

2.1概念

2.2格式

2.3原理

2.4函数模板的实例化

2.4.1隐式实例化

2.4.2显式实例化

2.5模板参数的匹配原则

3.类模板

3.1类模板的定义格式

3.2类模板的实例化

4.结语


(图像由AI生成) 

0.前言

在 C++ 中,模板是一种强大的编程工具,可以实现泛型编程。泛型编程是一种编程范式,通过在代码中使用参数化类型,使得代码更加通用,可以应用于不同类型的数据。模板可以应用于函数和类,分别称为函数模板和类模板。下面,就让我们开始认识模板,走进泛型编程的世界吧!

1.泛型编程

泛型编程是一种编程范式,旨在通过使用参数化的类型来提高代码的复用性和通用性。这种范式允许程序员编写与类型无关的代码,适用于多种数据类型,从而提高代码效率和质量。

在没有泛型编程的情况下,当我们需要编写一个可以交换两个变量值的 Swap 函数时,很可能需要为每种数据类型编写一个具体的函数。例如,你可能需要为整型、双精度浮点型和字符型分别实现 Swap 函数:

void Swap(int& left, int& right) {
    int temp = left;
    left = right;
    right = temp;
}

void Swap(double& left, double& right) {
    double temp = left;
    left = right;
    right = temp;
}

void Swap(char& left, char& right) {
    char temp = left;
    left = right;
    right = temp;
}

这些函数的实现在逻辑上是相同的,唯一的区别在于操作的数据类型不同。这种方式显然不符合 "Don't Repeat Yourself" (DRY) 的原则,因为相同的逻辑被重复写了多次。

为了解决代码重复的问题,并使函数能够处理不同的数据类型,泛型编程被引入。在 C++ 中,这是通过模板来实现的。

泛型编程的核心思想是使用参数化类型,也就是说,在编写函数或类时不指定具体的类型,而是使用一个占位符(类型参数),这个占位符在函数或类被使用时才被替换为实际的类型。这样,上面的三个 Swap 函数可以被一个模板函数取代:

template<typename T>
void Swap(T& left, T& right) {
    T temp = left;
    left = right;
    right = temp;
}

这个模板函数可以用于任何数据类型,从基本的数据类型(如 int, double, char)到用户定义的类型。你只需要一次定义,就可以用于多种类型,而不需要为每一种类型重写代码。

下面,就让我们进一步了解函数模板的概念、格式等相关内容。

2.函数模板

2.1概念

函数模板是 C++ 提供的一种工具,用于实现泛型编程。通过函数模板,程序员可以定义一个通用的函数框架,让编译器根据实际传入的参数类型自动生成相应的函数实例。这种方法不仅能够处理不同的数据类型,还可以应对未来可能需要支持的新类型,极大地增强了代码的通用性和灵活性。

函数模板的基本思想是:在编写时不指定具体的数据类型,而是用一个或多个模板参数(模板类型参数)来代替具体类型。当函数被调用时,编译器根据传入的实参自动推导出模板参数的类型,并生成对应类型的函数实例。这个过程称为模板的实例化。

2.2格式

函数模板的定义遵循一定的语法格式。下面是一个基本的函数模板定义的格式:

template <typename T>
ReturnType FunctionName(ParameterType parameter) {
    // 函数体
}

在这个格式中,关键部分如下:

  • template <typename T>:这行代码告诉编译器以下定义是一个模板,其中 T 是一个模板参数,它代表一种数据类型。关键字 typename 可以被 class 替换,二者在这里意义相同,都表示 T 是一个类型占位符。

  • ReturnType:函数的返回类型,它可以是任意类型,包括模板类型参数 T

  • FunctionName:函数名。这是模板实例化后生成的每个函数的名称。

  • ParameterType parameter:参数列表,参数类型可以是模板类型参数 T,也可以是其他任何具体的类型。参数可以有多个,也可以有不同的类型。

一个实际的函数模板示例如下,它用于返回两个值中的较大者:

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

这个 Max 函数可以用于任何支持比较操作符 > 的数据类型。例如,你可以用它来比较两个整数、两个浮点数,甚至两个字符串。调用示例:

int i = Max(10, 5);           // 实例化一个比较 int 的 Max 函数
double d = Max(5.3, 10.6);    // 实例化一个比较 double 的 Max 函数
std::string s = Max(std::string("apple"), std::string("banana")); // 实例化一个比较 std::string 的 Max 函数

在这些例子中,编译器会自动推导模板参数 T 的具体类型,并生成相应的函数实例。这种自动类型推导和实例化机制使得函数模板非常强大且灵活,是 C++ 泛型编程的核心工具之一。

2.3原理

函数模板的原理涉及到几个关键的概念和编译时的行为,主要包括模板的定义、实例化和类型推导。这些机制共同工作,使得函数模板可以灵活地应用于各种数据类型,同时保持高效的执行速度。

当定义一个函数模板时,你实际上没有生成任何代码。函数模板本身更像是一个蓝图或者配方,指示编译器如何生成具体的函数实例。模板定义包含了所有必要的逻辑,但留给编译器在具体类型确定时才生成代码。

C++11 引入的自动类型推导进一步简化了模板的使用。在调用模板函数时,不必显式指定参数类型,编译器能根据传递的实参自动推导出模板参数的类型。这使得模板函数调用更加简洁,降低了模板使用的复杂性。

在编译时,对于每个通过显式或隐式实例化生成的模板函数,编译器会生成相应类型的函数实体。这意味着,如果你的模板函数被用于三种不同的类型,则编译器将生成三个不同的函数。每个生成的函数都是针对特定类型优化的,这避免了运行时类型检查或转换,从而提高执行效率。

由于模板函数的代码在编译时生成,编译器有机会对生成的代码进行优化,比如内联小型模板函数。内联可以消除函数调用的开销,但同时增加了编译后的代码量。这种权衡是性能优化中常见的考虑。

2.4函数模板的实例化

函数模板的实例化是指从模板生成具体函数代码的过程。这个过程可以是隐式的,由编译器自动根据函数调用的上下文完成;也可以是显式的,由程序员手动指定模板参数。理解这两种实例化方式对于高效地使用函数模板至关重要。

2.4.1隐式实例化

在隐式实例化中,编译器根据模板函数调用时提供的参数类型自动推导出模板参数的具体类型。编译器使用这些具体的类型来生成相应的函数版本。

template<typename T>
void print(T value) {
    std::cout << value << std::endl;
}

int main() {
    print(123);        // 隐式实例化为 print<int>(int)
    print(3.14);       // 隐式实例化为 print<double>(double)
    print("Hello");    // 隐式实例化为 print<const char*>(const char*)
}

在这个例子中,print 函数模板被三次调用,每次调用都基于传递给函数的实参类型(整数、浮点数和字符串字面值),编译器自动推导出模板参数 T 的类型,并生成对应的函数实例。

2.4.2显式实例化

显式实例化则是程序员直接指定模板参数的具体类型,告诉编译器生成特定类型的模板函数实例。这种方式使得生成的函数类型不依赖于调用时的参数类型,可以用于预先生成特定的模板实例或当模板参数不能被自动推导时使用。

template<typename T>
void print(T value) {
    std::cout << value << std::endl;
}

// 显式实例化定义
template void print<int>(int);
template void print<double>(double);
template void print<const char*>(const char*);

int main() {
    print(123);        // 使用预先实例化的 print<int>(int)
    print(3.14);       // 使用预先实例化的 print<double>(double)
    print("Hello");    // 使用预先实例化的 print<const char*>(const char*)
}

在这个例子中,print 函数的每种类型实例已经在全局范围内显式实例化。这意味着编译器在遇到这些函数调用时,不需要再进行类型推导和函数实例化,直接使用已经生成的实例。这可以用于优化编译时间,或者在模板的定义与实例化需要在不同编译单元中进行时使用。

2.5模板参数的匹配原则

函数模板的参数匹配是 C++ 中一个复杂但非常强大的特性,特别是当非模板函数和同名的函数模板同时存在时。这里,我们将探讨三个关键的规则,每个规则都将通过代码进行详细解释。

规则 1:非模板函数与同名模板函数的共存

在 C++ 中,一个非模板函数可以与一个同名的函数模板共存,且该模板可以被实例化为与非模板函数具有相同签名的函数。这种情况下,非模板函数被称为模板的显式专用化。

#include <iostream>

// 非模板函数
void print(int x) {
    std::cout << "Non-template function: " << x << std::endl;
}

// 函数模板
template<typename T>
void print(T x) {
    std::cout << "Template function: " << x << std::endl;
}

int main() {
    print(10);   // 调用非模板函数
    print<>(10); // 强制使用模板实例化
}

在这个例子中,print(10) 会调用非模板函数,因为它提供了一个精确的匹配。而 print<>(10) 通过空的尖括号 <> 强制使用模板,即使存在一个非模板的完全匹配。

规则 2:非模板函数的优先级

如果存在同名的非模板函数和函数模板,且它们都可以处理相同的调用,非模板函数通常会被优先选择,因为它提供了一个更为具体的匹配。但是,如果函数模板能够提供一个更好的匹配,则会选择模板函数。

#include <iostream>

void process(double x) {
    std::cout << "Non-template function: " << x << std::endl;
}

template<typename T>
void process(T x) {
    std::cout << "Template function: " << x << std::endl;
}

int main() {
    process(10.5); // 调用非模板函数
    process(10);   // 调用模板函数,因为模板提供了更好的匹配(int 匹配)
}

这里,process(10.5) 直接调用非模板函数,因为它精确匹配 double 类型。但对于 process(10),尽管非模板函数可行(通过隐式转换),编译器选择了模板函数,因为模板提供了对 int 类型的直接匹配,没有需要类型转换。

规则 3:模板函数与类型转换

模板函数通常不允许隐式的类型转换(除了基本的类型提升如 intdouble),而非模板函数可以进行更广泛的自动类型转换。

#include <iostream>

void display(long x) {
    std::cout << "Non-template function: " << x << std::endl;
}

template<typename T>
void display(T x) {
    std::cout << "Template function: " << x << std::endl;
}

int main() {
    display(10);    // 调用非模板函数,因为 int 到 long 的隐式转换
    display('a');   // 调用模板函数,字符到 long 的转换不被允许自动应用于模板
}

在这个例子中,display(10) 调用了非模板函数,尽管 10int 类型,但可以自动转换为 long 类型。对于 display('a'),虽然 charlong 的转换在非模板函数中也是可行的,但由于 charT (这里是 char)的模板实例化提供了直接匹配,所以选择了模板函数。

3.类模板

类模板是 C++ 泛型编程的另一个核心特性,允许程序员编写一个框架来生成可以操作任意类型数据的类。类似于函数模板,类模板提供了代码的复用性与类型的灵活性。

3.1类模板的定义格式

类模板的定义与函数模板类似,通过在类定义前加上 template 关键字和一系列模板参数来声明。这些参数可以在类内的成员函数、成员变量和嵌套类型中使用。

基本语法

template <typename T>
class ClassName {
public:
    T memberVariable;

    void memberFunction(T param) {
        // 函数体
    }
};

示例代码

template <typename T>
class Box {
public:
    T width, height, depth;

    // 构造函数
    Box(T w, T h, T d) : width(w), height(h), depth(d) {}

    // 成员函数,计算体积
    T volume() const {
        return width * height * depth;
    }
};

在这个例子中,Box 类模板可以用于创建存储任何类型的 widthheightdepth 的盒子,并计算其体积。由于使用了模板参数 T,这个类可以用于 intdouble 或其他任何支持乘法运算的数据类型。

3.2类模板的实例化

类模板的实例化是在编译时根据指定的具体类型参数创建类的过程。实例化的结果是一个具体的类,具有与模板参数相对应的数据类型。

隐式实例化: 当你创建一个模板类的对象并提供具体类型时,编译器会自动为你生成该类型的类实例。

Box<int> intBox(10, 20, 30);
std::cout << "Volume of intBox: " << intBox.volume() << std::endl;

显式实例化: 你也可以显式地告诉编译器为特定类型生成模板类的实例,这常用于减少编译时间,特别是当模板在多个文件中使用时。

template class Box<double>;  // 显式实例化

int main() {
    Box<double> doubleBox(10.5, 20.5, 30.5);
    std::cout << "Volume of doubleBox: " << doubleBox.volume() << std::endl;
}

在显式实例化中,template class Box<double>; 指示编译器为 double 类型生成 Box 类模板的全部成员定义。之后,在其他代码中创建 Box<double> 的对象时,不需要再次实例化。

4.结语

通过本文的介绍,我们可以看到,C++ 模板是一种强大的工具,使得编程变得更为灵活和通用。无论是函数模板还是类模板,它们都提供了实现泛型编程的基础,允许代码以类型无关的方式被重用和适应不同的数据需求。这不仅增加了代码的复用性和可维护性,还提高了程序的效率和可靠性。掌握C++模板是每个C++程序员提升编程技能的关键步骤,也是深入理解现代C++设计和实现的基础。

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

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

相关文章

密码学 | 承诺:常见的承诺方案

&#x1f951;原文&#xff1a;密码学原语如何应用&#xff1f;解析密码学承诺的妙用 - 知乎 1 简介 密码学承诺 涉及 承诺方、验证方 两个参与方&#xff0c;以及以下两个阶段&#xff1a; 承诺阶段&#xff1a;承诺方选择一个敏感数据 v v v&#xff0c;为它计算出相应…

【团体程序设计天梯赛】L2-052 吉利矩阵

思路&#xff1a; 直接回溯枚举每一个位置填的数&#xff0c;二维肯定是不方便的&#xff0c;我们转成一维&#xff0c;下标x从0到n*n-1。二维数组下标从0到n-1&#xff0c;在一维中下标为x的点在二维中对应行是x/n&#xff0c;列是x%n。 每个数最小能填的是0&#xff0c;最大…

总结线程池

目录 导言&#xff1a; 正文&#xff1a; 1.概念 2.线程池的组成和基本原理 3.使用ThreadPoolExecutor创建线程池 4.使用Executors 创建常见的线程池 总结&#xff1a; 导言&#xff1a; 虽然创建销毁线程比创建销毁进程更轻量&#xff0c; 但是在频繁创建销毁线程的时候…

深度学习transformer架构详细详解

一、transformer的贡献 transformer架构的贡献&#xff1a;该架构只使用自注意力机制&#xff0c;没有使用RNN或卷积网络。且可以实现并行计算&#xff0c;加快模型训练速度。 &#xff08;将所有的循环层全部换成&#xff1a;multi-headed self-attention&#xff09; 二、t…

JavaScript运算符(赋值、自增自减、比较、逻辑、展开、优先级)、分支语句(if、三元表达式、switch)、循环结构(while、for)、断点调试

目录 1. 运算符1.1 赋值运算符1.2 自增和自减运算符1.3 比较运算符1.4 逻辑运算符1.5 展开运算符1.6 运算符优先级 2. 分支语句2.1 if2.2 三元表达式2.3 switch 3. 循环结构3.1 while循环3.2 for循环 4. 断点调试 1. 运算符 1.1 赋值运算符 -*/% 1.2 自增和自减运算符 前置…

(C++) 树状数组

目录 一、介绍 二、一维树状数组 2.1 区间长度 2.2 前驱和后继 2.3 查询前缀和 2.4 点更新 三、一维数组的实现 3.1 区间长度函数 3.2 前缀和 3.3 插入/更新 3.4 封装成类 一、介绍 树状数组&#xff08;Binary Indexed Tree&#xff0c;BIT&#xff09;&#xff0c;又称为 …

ActiveMQ 如果数据处理出现异常会怎么样

我们有一个 Spring 的客户端&#xff0c;在处理消息的时候因为程序的原因出现消息处理异常。 对这种情况&#xff0c;ActiveMQ 会把出现异常的消息放在 DLQ 队列中进行持久化。 因此&#xff0c;在 ActiveMQ 消息处理队列中需要持续关注 DLQ 队列&#xff0c; DLQ 的队列都是无…

线段树汇总

线段树是一种二叉搜索树&#xff0c;与区间树相似&#xff0c;它将一个区间划分成一些单元区间&#xff0c;每个单元区间对应线段树中的一个叶结点。 使用线段树可以快速的查找某一个节点在若干条线段中出现的次数&#xff0c;时间复杂度为O(logN)。而未优化的空间复杂度为2N&a…

最新版的GPT-4.5-Turbo有多强

OpenAI再次用实力证明了&#xff0c;GPT依然是AI世界最强的玩家&#xff01;在最新的AI基准测试中&#xff0c;OpenAI几天前刚刚发布的GPT-4-Turbo-2024-04-09版本&#xff0c;大幅超越了Claude3 Opus&#xff0c;重新夺回了全球第一的AI王座&#xff1a; 值得一提的是&#xf…

【机器学习】重塑汽车设计与制造:实例与代码探索

机器学习重塑汽车设计与制造 一、机器学习在汽车设计中的应用二、机器学习在智能制造与生产中的应用 在数字化浪潮的推动下&#xff0c;机器学习技术正逐步成为汽车行业的创新引擎。从概念设计到智能制造&#xff0c;机器学习正以其独特的优势助力汽车产业的革新与发展。本文将…

实现基于RAG的QA应用程序

实现基于RAG的Q&A应用程序 LLM 支持的最强大的应用程序之一是复杂的 问答 &#xff08;Q&A&#xff09; 聊天机器人。这些应用程序可以 回答有关特定来源信息的问题。这些应用程序 使用一种称为检索增强生成 &#xff08;RAG&#xff09; 的技术。 什么是检索增强生成…

Golang | Leetcode Golang题解之第43题字符串相乘

题目&#xff1a; 题解&#xff1a; func multiply(num1 string, num2 string) string {if num1 "0" || num2 "0" {return "0"}m, n : len(num1), len(num2)ansArr : make([]int, m n)for i : m - 1; i > 0; i-- {x : int(num1[i]) - 0fo…

设计模式之访问者模式(上)

访问者模式 1&#xff09;概述 1.概念 访问者模式包含访问者和被访问元素两个主要组成部分。 处方单中的各种药品信息就是被访问的元素&#xff0c;而划价人员和药房工作人员就是访问者&#xff0c;被访问的元素通常具有不同的类型&#xff0c;且不同的访问者可以对它们进行…

上位机图像处理和嵌入式模块部署(树莓派4b处理类muduo网络编程)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 既然是linux编程&#xff0c;那么自然少不了网络编程。在linux平台上面&#xff0c;有很多的网络编程库可以选择&#xff0c;大的有boost、qt&…

免费PNG素材网站推荐:设计效率倍增!

一、即时设计 新一代协同设计工具即时设计&#xff0c;内置丰富社区资源&#xff0c;可以在此获得设计前线的各类PNG图像&#xff0c;以及矢量图标&#xff0c;包括毛玻璃、3D混搭、全息投影、单色、平面化等&#xff0c;都是符合目前市场的主流风格。通过最近更新、作品、资源…

影响钕铁硼磁钢性能的因素及方法

钕铁硼永磁材料自问世以来&#xff0c;就以其优越的磁性能而备受关注&#xff0c;被称为“磁王“&#xff0c;在市场需求的不断地增长下&#xff0c;钕铁硼生产工艺及磁体性能也不断发展和提升。我们一般用剩磁、矫顽力和最大磁能积这几个指标来衡量磁性材料的磁性能。 剩磁 B…

【C++】:类和对象(上)

目录 一&#xff0c;面向过程和面向对象初步认识二&#xff0c;类的引入三&#xff0c;类的定义3.1 **类的说明**3.2 **类的访问限定符**3.3 **类的两种实现方式**3.4 **成员变量的命名规则 --- 加下划线** 四&#xff0c;类的作用域4.1 **类域的说明**4.2 **类域与命名空间域的…

分析经过j2k压缩的dicom文件经验分享

最近碰到一个问题&#xff0c;在网上搜到是用JPEG 2000压缩的DICOM文件 JPEG 2000对应的transfer syntax UID为 1.2.840.10008.1.2.4.91 参考:https://dicom.nema.org/medical/dicom/current/output/chtml/part18/sect_8.7.3.html 该文件是用专业德国老牌开发库DCMTK生成的 (…

虚拟机VMware安装与Ubuntu

1.虚拟机安装 链接&#xff1a;百度网盘 请输入提取码 提取码&#xff1a;2fr6 CG54H-D8D0H-H8DHY-C6X7X-N2KG6 2.Ubuntu下载 Download Ubuntu Desktop | Ubuntu 3.设置 如后续要下一些软件越大越好

Diffusion Model原理剖析

目录 前言1. DDPM演算法初览2. 图像生成模型共同目标3. VAE: Lower bound of l o g P ( x ) logP(x) logP(x)4. Diffusion Model背后的数学原理5. 为什么需要Sample?6. Diffusion Model的应用7. Diffusion Model成功的关键总结参考 前言 接着上篇文章 图像生成模型浅析&#…