深入探索C++模板:从基础到高级应用

目录

一、 泛型编程

1.1 为什么需要泛型编程?

二、模板

2.1 概念

2.2 函数模板

2.2.1 概念

2.2.2 语法

2.2.3 示例

 2.2.4 模板实例化

隐式实例化

显示实例化

2.2.5 模板参数的匹配原则

2.3 类模板

2.3.1 概念

2.3.2 语法

2.3.3 示例

2.3.4 注意事项

2.3.5 解释注意事项

1. 成员函数定义:

2. 模板参数推导:

3. 模板特化:

三、模板分文件编写(特殊)

一、 泛型编程

当谈到泛型编程时,我们通常指的是一种在编程语言中编写通用代码,以便在不同的数据类型上都能有效工作的方法。这使得代码可以更加灵活、通用和重用。在C++中,泛型编程主要通过模板来实现,允许我们创建可以适用于多种数据类型的通用代码。

1.1 为什么需要泛型编程?

在编写代码时,我们经常需要处理各种不同类型的数据。如果每次都为每种数据类型编写专门的代码,将会导致代码冗余并降低效率。泛型编程的目标是通过创建通用代码来解决这个问题,这些代码可以适用于不同的数据类型,而无需重复编写。

举个例子:

我们要实现一个通用的交换数据的函数,在C语言中我们只能通过不同名的函数实现,很麻烦,C++中我们可以通过函数重载实现,但是也有弊端:重载的函数仅仅是类型不同,代码复用率比较低,只要有新类型出现时,就需要手动添加对应的函数实现,同时代码的可维护性也随之降低!

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;
}//其他类型
.........

那么是否有其他方法呢?我只要提供我需要的类型,编译器会自动实现对应版本的函数!

C++模板正是解决此问题的利器。

二、模板

2.1 概念

C++模板是一种用于实现泛型编程的重要特性,允许您编写通用的代码,可以适用于多种数据类型。模板在C++中被广泛使用,特别是在标准库中,用于创建通用的容器、算法和数据结构。

C++中的模板有两种主要类型:函数模板类模板

2.2 函数模板

2.2.1 概念

函数模板是C++中用于创建通用函数的一种机制,它允许您编写一个通用的函数,可以用于多种数据类型而不必针对每种数据类型编写单独的函数。函数模板在C++中是实现泛型编程的重要工具之一。

2.2.2 语法

函数模板的语法很简单,它由模板头和函数体组成

 其中,template <typename T> 声明了一个模板,T 是类型参数的占位符。在函数参数和返回类型中使用 T,编译器会根据参数类型自动确定实际的数据类型。(typename是用来定义模板参数关键字,也可以使用class(切记:不能使用struct代替class))。

2.2.3 示例

可以创建可以用于不同数据类型的通用函数,例如最大值函数:

 2.2.4 模板实例化

函数模板实例化是指在使用函数模板时,编译器根据传递的实际参数类型,生成特定类型的函数实现代码的过程。这个过程在编译阶段进行,以确保为特定类型的参数生成正确的代码。

模板参数实例化分为:隐式实例化和显式实例化

隐式实例化

指的是在代码中调用函数模板时,编译器会根据传递的参数类型隐式地生成特定数据类型的函数实现。这是函数模板常见的实例化方式。

template <typename T>
T Max(T a, T b) {return a > b ? a : b;
}int main() {int result = Max(5, 10); // 隐式实例化为 int Max(int a, int b)return 0;
}

假设我们调用 Max(5, 10),编译器会执行以下步骤来进行函数模板实例化:

  1. 编译器看到调用 Max(5, 10),需要实例化函数模板 Max
  2. 编译器分析参数 510,确定它们的类型为 int
  3. 编译器将模板参数 T 替换为 int,生成如下的特定实现:

 那么我这样调用呢?

int a = 2;
double b = 3.0;Max(a, b);

问题本质

该语句不能通过编译,当编译器遇到多个不同类型的实参时,需要确定一个适合的模板参数类型。然而,在某些情况下,可能出现无法明确确定模板参数类型的情况,因为模板参数列表中只有一个模板参数,但实际实参可能是不同的类型。

类型推断和类型转换

在模板实例化过程中,编译器通常不会进行隐式的类型转换,因为这可能导致不明确的结果。例如,在您的例子中,编译器不知道应该将模板参数 T 推断为 int 还是 double,因此无法进行正确的实例化。

注意:在模板中,编译器一般不会进行类型转换操作

此时有两种常用处理方式:

1. 用户强制转化

2. 显式实例化

强制转换

Max(a, (int)b);//或者Max((double)a, b);
显示实例化

在函数名后的<>中指定模板参数的实际类型,可以显式告知编译器实例化的类型,以避免模板参数推断的问题

Max<int>(a, b);//或者Max<double>(a, b);

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

2.2.5 模板参数的匹配原则

当存在非模板函数和同名函数模板时,编译器在进行函数调用时会根据匹配规则选择合适的函数。具体情况如下:

  1. 优先选择非模板函数:

    如果存在一个与实际参数类型完全匹配的非模板函数,那么编译器会优先选择调用这个非模板函数,因为它在类型匹配上更为特化。
  2. 模板参数匹配:

    如果不存在与实际参数类型完全匹配的非模板函数,编译器会尝试进行模板参数匹配,查找最匹配的函数模板。
  3. 更好的匹配:

    如果模板函数能够生成更好匹配的实例,编译器会选择调用这个模板函数。例如,如果模板参数可以通过隐式类型转换更好地匹配实际参数,那么模板将被选中。

我们来看一看例子

#include <iostream>// 通用输出函数模板
template<class T>
void Print(T value) {std::cout << value << std::endl;
}// 重载的输出函数,专门处理 const char* 类型
void Print(const char* value) {std::cout << "String: " << value << std::endl;
}int main() {Print(42);          // 调用通用函数模板 Print<T>Print("Hello");     // 调用重载函数 Print(const char*)Print(3.14);        // 调用通用函数模板 Print<T>Print<const char*>("Hello");    //显示调用通用函数模板 Print<T>return 0;
}
  1. template<class T> void Print(T value) 是一个通用的输出函数模板,用于输出不同类型的数据。

  2. void Print(const char* value) 是一个专门处理 const char* 类型的输出函数重载。

  3. main 函数中,调用 Print(42) 会匹配到通用函数模板,因为类型匹配。

  4. 调用 Print("Hello") 会匹配到重载函数,因为 const char* 更为特化。

  5. 调用 Print(3.14) 仍会匹配到通用函数模板。

  6. 显示调用模板,Print<const char*>("Hello")显然会调用函数模板。

再来一个例子

#include <iostream>// 专门处理 int 的加法函数
int Add(int left, int right) {return left + right;
}// 通用加法函数模板
template<class T1, class T2>
T1 Add(T1 left, T2 right) {return left + right;
}void Test() {Add(1, 2);       // 调用非函数模板,与 int Add(int left, int right) 匹配Add(1, 2.0);     // 调用函数模板,生成更匹配的版本
}
  1. int Add(int left, int right) 是一个专门处理 int 类型的加法函数。

  2. template<class T1, class T2> T1 Add(T1 left, T2 right) 是一个通用的加法函数模板,可以适用于不同类型的数据。

  3. 在调用 Add(1, 2) 时,编译器会选择调用非函数模板,因为它与实际参数类型完全匹配。

  4. 在调用 Add(1, 2.0) 时,编译器会选择调用函数模板。虽然非函数模板也可以匹配,但是函数模板可以生成更匹配的版本,编译器会根据实际参数生成更匹配的 Add 函数。

2.3 类模板

2.3.1 概念

当涉及到类模板时,就是在创建一个可以根据不同的数据类型生成不同类的模板。类模板允许您编写通用的类定义,以便适用于多种数据类型,而不必为每种类型编写单独的代码。

2.3.2 语法

类模板的语法类似于函数模板,但是应用于类的定义。以下是一个简单的类模板示例:

template <class T>
class MyContainer {
private:T value;public:MyContainer(T val) : value(val) {}T GetValue() {return value;}
};

2.3.3 示例

int main() {MyContainer<int> intContainer(42);MyContainer<double> doubleContainer(3.14);std::cout << intContainer.GetValue() << std::endl;    // 输出: 42std::cout << doubleContainer.GetValue() << std::endl; // 输出: 3.14return 0;
}

2.3.4 注意事项

  1. 成员函数定义: 类模板的成员函数通常也需要在模板类内定义,否则需要在定义外使用 template 关键字进行模板声明和实现。

  2. 模板参数推导: 在实例化类模板时,编译器可以自动推导模板参数的类型,也可以使用显式指定的方式进行实例化。

  3. 模板特化: 类模板也可以进行特化,针对特定类型提供自定义实现,类似于函数模板的模板特化。

  4. 实例化时代码生成: 类模板实例化时,编译器会根据实际的类型参数生成相应的类定义,从而创建特定类型的类。

2.3.5 解释注意事项

1. 成员函数定义:

在类模板中,成员函数可以在模板类内部定义,也可以在类外部使用 template 关键字进行模板声明和实现。

template <class T>
class MyContainer {
private:T value;public:MyContainer(T val) : value(val) {}T GetValue() {return value;}
};// 在类外部定义成员函数模板
template <class T>
T MyContainer<T>::GetValue() {return value * 2;
}

2. 模板参数推导:

编译器在实例化类模板时可以自动推导模板参数的类型,也可以使用显式指定的方式进行实例化。

int main() {MyContainer intContainer(42);MyContainer<double> doubleContainer(3.14);std::cout << intContainer.GetValue() << std::endl;    // 输出: 42std::cout << doubleContainer.GetValue() << std::endl; // 输出: 3.14return 0;
}

3. 模板特化:

类模板也可以进行特化,为特定类型提供自定义实现,类似于函数模板的模板特化。

// 类模板定义
template <class T>
class MyContainer {
private:T value;public:MyContainer(T val) : value(val) {}T GetValue() {return value;}
};// 类模板的特化版本
template <>
class MyContainer<int> {
private:int value;public:MyContainer(int val) : value(val) {}int GetValue() {return value * 2; // 自定义的实现}
};

三、模板分文件编写(特殊)

与平时代码分文件编写不同,模板的分文件编写涉及到一些特殊的注意事项和方法,以确保模板的正确实例化和链接。我将为您提供一个简单的示例,演示如何将模板分为头文件和源文件。

示例:

假设我们有一个类模板 MyTemplate,其中包含成员函数,我们希望将其分为头文件和源文件。

MyTemplate.h(头文件):

#ifndef MYTEMPLATE_H
#define MYTEMPLATE_Htemplate <typename T>
class MyTemplate {
public:MyTemplate(T value);void PrintValue();private:T data;
};#endif

MyTemplate.cpp(源文件):

#include <iostream>
#include "MyTemplate.h"template <typename T>
MyTemplate<T>::MyTemplate(T value) : data(value) {}template <typename T>
void MyTemplate<T>::PrintValue() {std::cout << "Value: " << data << std::endl;
}// 显式实例化模板
template class MyTemplate<int>;
template class MyTemplate<double>;

main.cpp(主文件):

#include "MyTemplate.h"int main() {MyTemplate<int> intObj(42);MyTemplate<double> doubleObj(3.14);intObj.PrintValue();doubleObj.PrintValue();return 0;
}

在源文件 MyTemplate.cpp 中,我们提供了类模板的实现,包括构造函数和成员函数的定义。我们还在源文件中使用了显式实例化template class MyTemplate<int>;template class MyTemplate<double>;),以确保在编译期间生成特定类型的模板实例。

最后,在主文件 main.cpp 中,我们只需要包含头文件 MyTemplate.h 并使用类模板。编译时,编译器会将类模板的实现部分从 MyTemplate.cpp 中提取出来并进行实例化。

虽然这种分文件编写模板的方式需要一些额外的步骤,但它确实可以使代码更有组织性和可维护性。

进一步解释

在源文件 MyTemplate.cpp 中使用显式实例化是为了确保编译器在编译期间生成特定类型的模板实例化代码。虽然函数模板的定义通常放在头文件中,但由于 C++ 的分离编译模型,模板的实现必须位于同一个编译单元中,以便编译器能够在需要时进行实例化。

在类模板的实现中,由于模板参数可能是多种不同的类型,编译器不会自动为每种类型生成实例化代码,除非在需要的地方显式使用模板。这就是使用显式实例化的原因:您告诉编译器为特定的类型生成实例化代码,以确保在链接时能够找到正确的模板实现。

函数分文件编写亦是如此原理!!!

函数模板初阶到此结束!笔者会继续更新进阶模板教程!!

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

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

相关文章

Html页面连线工具

在项目中遇了一个需要连线配置的功能。该功能引用了 bootstrap、layui 、svg-line等插件 下载链接 lixhttps://download.csdn.net/download/dongyan3595/88168121

用Python获取链家二手房房源数据,做可视化图分析数据

前言 数据采集的步骤是固定: 发送请求, 模拟浏览器对于url地址发送请求获取数据, 获取网页数据内容 --> 请求那个链接地址, 返回服务器响应数据解析数据, 提取我们需要的数据内容保存数据, 保存本地文件 所需模块 win R 输入cmd 输入安装命令 pip install 模块名 (如果你…

机器学习竞赛、CV/NLP 竞赛入门没人带解决办法

我的第一个AI竞赛是在Datawhale夏令营的帮助下参加的。 一周左右的夏令营&#xff0c;让我从环境都配不好的小白到对pytorch基本入门。 夏令营的氛围督促我赶紧学习&#xff0c;赶紧进步&#xff1b;夏令营群里的大佬推荐的入门视频让我pytorch入门。 了解datawhale是通过b站…

github 无语的问题,Host does not existfatal: Could not read from remote repository.

Unable to open connection: Host does not existfatal: Could not read from remote repository. image.png image.png image.png Please make sure you have the correct access rights and the repository exists. 如果github desktop和git pull 和git clone全部都出问题了&…

pytest常用执行参数详解

1. 查看pytest所有可用参数 我们可以通过pytest -h来查看所有可用参数。 从图中可以看出&#xff0c;pytest的参数有很多&#xff0c;下面是归纳一些常用的参数&#xff1a; -s&#xff1a;输出调试信息&#xff0c;包括print打印的信息。 -v&#xff1a;显示更详细的信息。 …

手眼标定眼在手上

1、为什么要用手眼标定&#xff08;在贴片机上定位已调通&#xff09; 参考手眼标定特别是眼在手上在网上的文章很多&#xff0c;但很多在实际中调试不通。在定位时候&#xff0c;往往希望相机能返回的是机械的世界坐标&#xff0c;而不是相机的的图像坐标。从而间接计算出相机…

【移动机器人运动规划】03 —— 基于运动学、动力学约束的路径规划(一)

文章目录 前言相关代码整理:相关文章&#xff1a; 介绍什么是kinodynamic&#xff1f;为什么需要kinodynamic&#xff1f;模型示例unicycle model&#xff08;独轮车模型&#xff09;differential model&#xff08;两轮差速模型&#xff09;Simplified car model (简化车辆模型…

机器学习参数调优

手动调参 分析影响模型的参数&#xff0c;设计步长进行交叉验证 我们以随机森林为例&#xff1a; 本文将使用sklearn自带的乳腺癌数据集&#xff0c;建立随机森林&#xff0c;并基于泛化误差&#xff08;Genelization Error&#xff09;与模型复杂度的关系来对模型进行调参&…

TOPIAM 社区版 1.0.0 发布,开源 IAM/IDaaS 企业身份管理平台

文章目录 产品概述系统架构功能列表管理端门户端 技术架构后续规划相关地址 ​Hi&#xff0c;亲爱的朋友们&#xff0c;今天是传统 24 节气中的立秋&#xff0c;秋天是禾谷成熟、收获的季节。经过长时间优化和迭代&#xff0c;TOPIAM 企业身份管控平台也迎来了当下的成长和收获…

PWNlab靶机渗透

安装靶机 下载地址&#xff1a;https://www/vulnhub.com/entry/pwnlab-init,158/ 信息收集&#xff1a; 收集靶机ip地址&#xff0c;由于搭建在本地使用kali自带命令 arp-scan -l nmap 扫描端口&#xff0c;服务 nmap -sV -p 1-65535 -A 靶机ip地址 漏洞探测 访问80端口地…

kubernetes 集群命令行工具 kubectl

1、kubectl 概述 kubectl是一种命令行工具&#xff0c;用于管理Kubernetes集群和与其相关的资源。通过kubectl&#xff0c;您可以查看和管理Pod、Deployment、Service、Volume、ConfigMap等资源&#xff0c;也可以创建、删除和更新它们。 kubectl还提供了许多其他功能&#x…

监控Kafka的关键指标

Kafka 架构 上面绿色部分 PRODUCER&#xff08;生产者&#xff09;和下面紫色部分 CONSUMER&#xff08;消费者&#xff09;是业务程序&#xff0c;通常由研发人员埋点解决监控问题&#xff0c;如果是 Java 客户端也会暴露 JMX 指标。组件运维监控层面着重关注蓝色部分的 BROKE…

LabVIEW开发高压配电设备振动信号特征提取与模式识别

LabVIEW开发高压配电设备振动信号特征提取与模式识别 矿用高压配电设备是井下供电系统中的关键设备之一&#xff0c;肩负着井下供配电和供电安全的双重任务&#xff0c;其工作状态直接影响着井下供电系统的安全性和可靠性。机械故障占配电总故障的70%。因此&#xff0c;机械故…

24届近5年江南大学自动化考研院校分析

今天给大家带来的是江南大学控制考研分析 满满干货&#xff5e;还不快快点赞收藏 一、江南大学 学校简介 江南大学&#xff08;Jiangnan University&#xff09;是国家“双一流”建设高校&#xff0c;“211工程”、“985工程优势学科创新平台”重点建设高校&#xff0c;入选…

Django架构图

1. Django 简介 基本介绍 Django 是一个由 Python 编写的一个开放源代码的 Web 应用框架 使用 Django&#xff0c;只要很少的代码&#xff0c;Python 的程序开发人员就可以轻松地完成一个正式网站所需要的大部分内容&#xff0c;并进一步开发出全功能的 Web 服务 Django 本身…

Vue2源码分析-day2

实现数组的响应式原理 首先我们在index.html中定义一个数组&#xff0c;并且打印实例 const vm new MVue({data() {return {name: "zhangsan",age: "16",hobby:[zhangsan,lisi]}} }) console.log(vm);我们会发现定义的数组每一项都有get和set方法虽然数…

线程池-手写线程池C++11版本(生产者-消费者模型)

本项目是基于C11的线程池。使用了许多C的新特性&#xff0c;包含不限于模板函数泛型编程、std::future、std::packaged_task、std::bind、std::forward完美转发、std::make_shared智能指针、decltype类型推断、std::unique_lock锁等C11新特性功能。 本项目有一定的上手难度。推…

计算机网络—TCP

这里写目录标题 TCP头格式有哪些为什么需要TCP&#xff0c;TCP工作在哪什么是TCP什么是TCP连接如何确定一个TCP连接TCP和UDP的区别&#xff0c;以及场景TCP和UDP能共用一个端口&#xff1f;TCP的建立TCP三次握手过程为什么是三次握手、不是两次、四次why每次建立连接&#xff0…

3.1 计算机网络和网络设备

数据参考&#xff1a;CISP官方 目录 计算机网络基础网络互联设备网络传输介质 一、计算机网络基础 1、ENIAC&#xff1a;世界上第一台计算机的诞生 1946年2月14日&#xff0c;宾夕法尼亚大学诞生了世界上第一台计算机&#xff0c;名为电子数字积分计算机&#xff08;ENIAC…

Netty框架自带类DefaultEventExecutorGroup的作用,用来做业务的并发

一、DefaultEventExecutorGroup的用途 DefaultEventExecutorGroup 是 Netty 框架中的一个类&#xff0c;用于管理和调度事件处理器&#xff08;EventExecutor&#xff09;的组。在 Netty 中&#xff0c;事件处理是通过多线程来完成的&#xff0c;EventExecutor 是处理事件的基…