C++模板初了解

这节我们来学习一下C++的一个便捷之处——模板

文章目录

一、泛型编程

泛型编程的基本思想

泛型编程的优点

泛型编程的应用

二、模板

函数模板

函数模板格式

函数模板的原理

函数模板的实例化

1.隐式实例化

2.显示实例化

函数模板的匹配原则

类模板

类模板的格式

类模板的实例化

三、STL

STL的组成


前言

在我们之前学习C语言的时候,我们初次接触编程,无论什么都得我们自己手搓,这是因为C语言是一种入门的基础编程语言,很多东西无法直接调用出来,需要我们自己手动去实现,尽管手搓代码能让我们熟悉编程,提高代码能力,但是什么东西都得自己来敲,实在有点头疼,C++作为C语言的进阶版,它明显知道了这个问题,于是他为了提高效率,做了很多东西让我们直接调用,这节我们来学习一下C++中的模板。


一、泛型编程

在学习模板之前,我们先来了解一下什么是泛型编程。泛型编程(Generic Programming)是一种编程范式,它强调编写能够处理不同数据类型的通用代码,而不依赖于特定的类型。其核心思想是通过抽象的方式让程序可以在编译时确定具体类型,而在运行时不依赖于具体类型,从而实现代码的高复用性和灵活性。

泛型编程的基本思想

  1. 代码重用:通过泛型编程,我们可以编写通用的算法和数据结构,这些算法和数据结构不依赖于具体的类型,而是通过参数化类型来实现。这样,我们只需编写一次代码,而它可以处理多种不同类型的数据。

  2. 类型独立性:泛型编程的目标是编写不依赖于特定数据类型的程序。通过模板,程序能够在编译时根据具体的类型自动生成所需的代码,而无需为每种类型编写重复的代码。

  3. 提高抽象层次:泛型编程鼓励编写更加抽象的代码,通常将数据的操作通过接口或者模板来进行参数化,从而使得代码更加灵活且具有较好的扩展性。

泛型编程的优点

  • 提高代码复用性:通过模板,算法和数据结构可以与多种数据类型配合使用,避免了重复实现相似功能的代码。
  • 减少错误:因为代码不依赖于具体类型,所以可以减少因为修改某个类型时引入的错误。
  • 提高性能:模板通常是在编译时进行实例化的,这意味着通过模板创建的代码具有与手写的特定类型代码相同的性能。
  • 增强可读性和可维护性:代码更加简洁和抽象,易于理解和修改。

泛型编程的应用

  1. 算法的通用性:很多标准算法,如排序、查找等,都可以通过泛型编程来实现,从而让它们适应不同的数据类型。例如,C++ STL(标准模板库)中的各种算法(如 std::sort)都是泛型算法。

  2. 数据结构的通用性:泛型编程广泛应用于实现通用的数据结构,比如链表、栈、队列、哈希表等。这些数据结构通过模板可以存储任意类型的数据。

二、模板

在C++中,泛型编程几乎完全依赖于模板,C++的模板机制提供了一种非常强大的工具,可以在编译时根据类型自动生成代码,从而实现类型的通用性。我们在学习C语言的时候,大家应该都写过Swap函数,这是一种用来交换两个值的函数,但是我们在C语言阶段,实现这种函数每实现一个函数只能针对一种数据类型,如果有多个数据类型的数据要进行交换,那么我们就要实现多个函数,在C语言阶段我们每实现一种函数就要为其命名一个函数名否则就会发生重命名的错误,在C++阶段,我们学习了函数重载,我们可以设计它们的函数名相同,函数参数不同,但是尽管这样,大量相似的代码重复写,属实有点冗长了,那么如何写一个简洁的函数,能够实现不同类型的数据进行交换呢?

C++中的模板是一种强大的工具,允许程序员编写通用代码,可以与多种数据类型一起使用,而不需要为每种数据类型都编写不同的代码。模板分为两种主要类型:

  1. 函数模板(Function Template)
  2. 类模板(Class Template)

函数模板

函数模板就像一个函数家族,这个家族里面有各种各样的人,该函数模板与参数无关,只有才函数初始化时,我们传递实参给它,然后它根据参数的类型来产生函数的特定类型版本。意思就是,我们设计好一个模子,然后我们需要具体什么样子的,我们继续给它提要求,然后它给我们创造出一个最符合我们需求的东西。

函数模板格式

template <typename T1,typename T2...,typename Tn>

返回值类型 函数名(参数列表){}

template <typename T>
T add(T a, T b) 
{return a + b;
}

template的中文意思就是模板,typename的中文意思就是类型名字,这里我们还可以使用class来替换它,但是我们要记住我们不能使用struct去替换它们)

函数模板的原理

先来看看我们C语言阶段来实现不同类型的Swap函数

//1.int类型
void Swapi(int& left, int& right)
{int tmp = left;left = right;right = tmp;
}//2.double类型
void Swapd(double& left, double& right)
{double tmp = left;left = right;right = tmp;
}//3.char类型
void Swapc(char& left, char& right)
{char tmp = left;left = right;right = tmp;
}

再来看看我们使用C++中的函数模板来实现的Swap函数

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

通过上面那两组的代码进行比较,我们可以很清楚的看出,上面几个函数都是很相似的,基本上函数体函数名都差不多,不过是改了个函数参数和数据类型。没错就是我们所看到的那样,那些函数基本都差不多,我们每次调用不同类型的函数改一个参数不就行了嘛。函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。 所以其实模板就是将本来应该我们做的重复的事情交给了编译器。

在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应 类型的函数以供调用。比如:当用double类型使用函数模板时,编译器通过对实参类型的推演, 将T确定为double类型,然后产生一份专门处理double类型的代码,对于字符类型也是如此。

函数模板的实例化

我们把编译器根据传入实参类型来推演生成相应的类型叫做函数模板的实例化。模板的实例化又分为:隐式实例化和显示实例化

1.隐式实例化

隐式实例化是让编译器根据传入的实参类型自己推演出来对应的函数类型

template <class T>
T Add(T left, T right)
{return left + right;
}int main()
{int a = 10,b=20;double m = 1.2, n = 2.3;cout << Add(a, b) << endl;cout << Add(m,n) << endl;/*cout << Add(a, n) << endl;*我们传入两种不同数据类型的参数,编译器是无法进行隐式实例化的* 因为我们编译器是根据我们传入的参数进行隐式实例化的,我们现在一次性传两种类型,编译器无法确定* 实例化哪种类型的函数,于是索性就报错了* 对于这种情况,我们可以使用强制类型转换,使其只有一种数据类型,那么就可以进行隐式实例化了*/cout << Add(a, (int)n) << endl;cout << Add((double)a, n) << endl;return 0;
}

2.显示实例化

显示实例化,是在我们定义的函数模板名后面加上一个<数据类型>来指定函数模板实例化的参数类型

template <class T>
T Add(T left, T right)
{return left + right;
}int main()
{int a = 10,b=20;double m = 1.2, n = 2.3;cout << Add<int>(a, m) << endl;cout << Add<double>(b, n) << endl;//在显示实例化中,我们指明了函数类型,编译会进行隐式类型转换,如果无法进行转换,编译器就会发生报错return 0;
}

函数模板的匹配原则

其实函数模板并非唯一的,它就像函数重载一样,可以有好多个函数模板,那么在有好几个函数模板/函数的时候应该选择调用谁呢?这其中也有匹配的原则。

//1
int Add(int left, int right)
{return left + right;
}//2
template <class T>
T Add(T left, T right)
{return (left + right)*5;
}//3
template <class T1, class T2>
T1 Add( T1 left, T2 right)
{return (left + right)*10;
}//进行一个函数匹配的测试
int main()
{int a = 6, b = 4;double m = 2.1, n = 2.4;//这里匹配的规则就是:如果有现成的函数就直接调用现成的函数,不调用函数模板中的函数//如果现成的函数中没有对应参数的函数就调用函数模板中的函数,不同的函数模板根据函数参数进行调用cout << Add(a, b) << endl;//1  10cout << Add(m, n) << endl;//2  22.5cout << Add(a, m) << endl;//3  81return 0;
}

我们使用上面三组比较经典的函数/函数模板进行匹配测试,上面三组分别是我们自己显示实现的一个具体的函数,另外两个则是函数模板,但是两个函数模板的参数不同,因此它们两个是可以共存的。从上面的运行的结果我们可以看出,传递不同的参数,会调用不同的函数/函数模板。

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

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

3. 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换。

说到这里可能会有人来问,我们使用函数模板推演生成的函数如果和我们显示实现的函数类型一样的怎么办,函数模板不过是在我们显示实现的函数的基础上使参数类型自由化了,现在参数类型和我们显示实现的函数都一样,那么两个函数不就一样了嘛,会不会发生一些冲突呢?这个我们其实不用担心的,虽然我们的模板函数于我们显示实现的函数很像,但是编译器在编译时会做一些手脚的(比如我们如果使用函数模板来实例化一个int类型的函数,编译器会将它的函数名设置为Add<int>,与我们自己显示实现的函数还是有所区别的)

类模板

类模板与我们上面的函数模板很像,我们定义一个类模板,可以使他能够与不同类型的数据一起工作,就比如我们之前实现的Stack类,我们在实现类之前会使用typedef来定义一种类型为StTypeData,为的就是我们后面如果想让栈类的数据换成其他数据类型的,只要在这里修改就行了。但是这样做还是只能够在栈类中放一种类型,如果我们想让这个Stack类可以实例化为使用不同类型元素的对象,我们就可以使用类模板了。

类模板的格式

template<class T1, class T2, ..., class Tn> 
class 类模板名
{// 类内成员定义
};

类模板就是在类格式上面加上一个模板和参数,在类内成员的定义中如果有关于参数类型的都要改,这里要注意一下。

类模板的实例化

类模板实例化与函数模板实例化不同,没有什么隐式实例化还是显示实例化之分,类模板实例化需要在类模板名字后跟<>,然后将实例化的 类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。

template<class T>
class Stack
{
public:Stack(int capacity = 4){arr = new T[capacity];_capacity = capacity;_top = 0;if (arr == nullptr){perror("new fail");}}//我们写成员函数可以将函数的声明与定义分开写(但是最好不用分文件写,可能会导致编译错误)void Push(const T& data);void print(Stack& st);private:T* arr;int _capacity;int _top;
};//函数的定义我们在类外写的时候,要指明类域,我们写类域的时候要类名<类型参数>一起写
//另外我们还要写下面的模板参数的声明,不然会发生无法定义参数T的报错
template<class T>
void Stack<T>::Push(const T& data)
{if (_top >= _capacity){cerr << "Stack overflow" << endl;return;}arr[_top++] = data;}
template<class T>
void Stack<T>::print(Stack&st)
{for (T i = (T)0; i < (T)st._top; i++){cout << st._top << " " << endl;}
}int main()
{//我们要注意:Stack是类名,Stack<int>才是类型Stack<int>st1;Stack<double>st2;Stack<char>st3;st1.Push(1);st1.Push(2);st1.Push(3);st1.Push(4);st1.print(st1);st2.Push(1.1);st2.Push(2.1);st2.Push(3.1);st2.Push(4.1);st2.print(st2);st3.Push('a');st3.Push('b');st3.Push('c');st3.Push('d');st3.print(st3);return 0;
}

三、STL

STL(Standard Template Library,标准模板库)是C++编程语言中的一个重要库,它提供了一组通用的、模板化的数据结构和算法,用于处理常见的编程任务。STL旨在提高程序开发效率,减少代码重复,确保代码的可重用性和可维护性。如果你是学习C++的,如果别人问你知不知道STL,你说不知道,那别人可得笑话你了,STL在C++中是一个极其重要的部分,换句话来说就是,STL是C++的精华,这节我们只是简单地介绍一下STL,在后续的学习中,我们会慢慢地去学习STL的。

STL的组成

STL由以下六个主要部分组成:

  1. 容器(Containers): 容器是STL中的数据结构,用来存储对象或元素。STL提供了多种类型的容器,包括顺序容器(如 vector, deque, list, array)和关联容器(如 set, map, unordered_set, unordered_map)等。每种容器适用于不同的用途和需求。

  2. 算法(Algorithms): STL提供了许多常用的算法,如排序(sort)、查找(find)、拷贝(copy)、删除(remove)等。算法是泛型的,可以与各种容器类型配合使用,这样同一算法可以作用于不同类型的数据结构。

  3. 迭代器(Iterators): 迭代器是用来遍历容器中元素的对象,它提供了一种统一的方式来访问不同类型的容器。迭代器类似于指针,可以通过它们访问容器中的元素。STL中的常用迭代器类型包括 begin(), end(), rbegin(), rend() 等。

  4. 仿函数(函数对象)(Function Objects): 函数对象是可以像普通函数一样调用的对象,它们通常是通过重载 operator() 来实现的。函数对象可以与算法一起使用,提供比普通函数更高的灵活性和效率。STL提供了一些标准的函数对象,如 less, greater, equal_to 等。

  5. 配接器(Adapters): 配接器是STL中的一个特性,它允许在不修改容器或算法的情况下,为容器或算法提供不同的接口。配接器包括:

    • 容器适配器:如 stackqueuepriority_queue,这些适配器基于底层容器(如 deque 或 vector)提供了不同的接口。
    • 迭代器适配器:如 reverse_iterator,用于改变迭代器的方向。
    • 函数对象适配器:如 bindnot1not2,用于修改或组合函数对象。
  6. 空间配置器(分配器)(Allocators): 分配器是STL中用于管理内存的组件,它们为容器提供内存分配和释放的功能。STL默认使用一个标准的分配器,但你也可以自定义分配器来优化内存管理,尤其在一些特殊场景下,如高性能应用。

这六个部分共同组成了STL,为C++程序员提供了强大且灵活的工具,能够高效地处理数据结构和算法。


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

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

相关文章

PPT演示设置:插入音频同步切换播放时长计算

PPT中插入音频&同步切换&放时长计算 一、 插入音频及音频设置二、设置页面切换和音频同步三、播放时长计算 一、 插入音频及音频设置 1.插入音频&#xff1a;点击菜单栏插入-音频-选择PC上的音频&#xff08;已存在的音频&#xff09;或者录制音频&#xff08;现场录制…

React

1. React 基础 1) 环境准备 创建项目 首先&#xff0c;通过 react 脚手架创建项目 npx create-react-app client --template typescriptclient 是项目名目前 react 版本是 18.x 运行项目 cd client npm start会自动打开浏览器&#xff0c;默认监听 3000 端口 修改端口 在…

[ESP32:Vscode+PlatformIO]新建工程 常用配置与设置

2025-1-29 一、新建工程 选择一个要创建工程文件夹的地方&#xff0c;在空白处鼠标右键选择通过Code打开 打开Vscode&#xff0c;点击platformIO图标&#xff0c;选择PIO Home下的open&#xff0c;最后点击new project 按照下图进行设置 第一个是工程文件夹的名称 第二个是…

3、从langchain到rag

文章目录 本文介绍向量和向量数据库向量向量数据库 索引开始动手实现rag加载文档数据并建立索引将向量存放到向量数据库中检索生成构成一条链 本文介绍 从本节开始&#xff0c;有了上一节的langchain基础学习&#xff0c;接下来使用langchain实现一个rag应用&#xff0c;并稍微…

【自然语言处理(NLP)】基于Transformer架构的预训练语言模型:BERT 训练之数据集处理、训练代码实现

文章目录 介绍BERT 训练之数据集处理BERT 原理及模型代码实现数据集处理导包加载数据生成下一句预测任务的数据从段落中获取nsp数据生成遮蔽语言模型任务的数据从token中获取mlm数据将文本转换为预训练数据集创建Dataset加载WikiText-2数据集 BERT 训练代码实现导包加载数据构建…

41【文件名的编码规则】

我们在学习的过程中&#xff0c;写出数据或读取数据时需要考虑编码类型 火山采用&#xff1a;UTF-16 易语言采用&#xff1a;GBK php采用&#xff1a;UTF-8 那么我们写出的文件名应该是何种编码的&#xff1f;比如火山程序向本地写出一个“测试.txt”&#xff0c;理论上这个“测…

NLP深度学习 DAY4:Word2Vec详解:两种模式(CBOW与Skip-gram)

用稀疏向量表示文本&#xff0c;即所谓的词袋模型在 NLP 有着悠久的历史。正如上文中介绍的&#xff0c;早在 2001年就开始使用密集向量表示词或词嵌入。Mikolov等人在2013年提出的创新技术是通过去除隐藏层&#xff0c;逼近目标&#xff0c;进而使这些单词嵌入的训练更加高效。…

HarmonyOS简介:应用开发的机遇、挑战和趋势

问题 更多的智能设备并没有带来更好的全场景体验 连接步骤复杂数据难以互通生态无法共享能力难以协同 主要挑战 针对不同设备上的不同操作系统&#xff0c;重复开发&#xff0c;维护多套版本 多种语言栈&#xff0c;对人员技能要求高 多种开发框架&#xff0c;不同的编程…

Windows11 不依赖docker搭建 deepseek-R1 1.5B版本(附 Open WebUi搭建方式)

零、前言 过年这几天发现 DeepSeek 非常火&#xff0c;试用了一下发现确实不错。与豆包、kimi、perplexity 这些相比完全不是一个次元的存在&#xff0c;特别是用ta写文章的时候体验非常好。所以试着自己搭一个环境。 一、安装 Ollama和DeepSeek-R1 我的安装方式很简单&#xf…

解决whisper 本地运行时GPU 利用率不高的问题

我在windows 环境下本地运行whisper 模型&#xff0c;使用的是nivdia RTX4070 显卡&#xff0c;结果发现GPU 的利用率只有2% 。使用 import torch print(torch.cuda.is_available()) 返回TRUE。表示我的cuda 是可用的。 最后在github 的下列网页上找到了问题 极低的 GPU 利…

springCload快速入门

原作者&#xff1a;3. SpringCloud - 快速通关 前置知识&#xff1a; Java17及以上、MavenSpringBoot、SpringMVC、MyBatisLinux、Docker 1. 分布式基础 1.1. 微服务 微服务架构风格&#xff0c;就像是把一个单独的应用程序开发为一套小服务&#xff0c;每个小服务运行在自…

Gradle配置指南:深入解析settings.gradle.kts(Kotlin DSL版)

文章目录 Gradle配置指南&#xff1a;深入解析settings.gradle.kts&#xff08;Kotlin DSL版&#xff09;settings.gradle.kts 基础配置选项单项目配置多项目配置 高级配置选项插件管理&#xff08;Plugin Management&#xff09;基础配置模板案例&#xff1a;Android项目标准配…

C++ Primer 标准库类型string

欢迎阅读我的 【CPrimer】专栏 专栏简介&#xff1a;本专栏主要面向C初学者&#xff0c;解释C的一些基本概念和基础语言特性&#xff0c;涉及C标准库的用法&#xff0c;面向对象特性&#xff0c;泛型特性高级用法。通过使用标准库中定义的抽象设施&#xff0c;使你更加适应高级…

[EAI-028] Diffusion-VLA,能够进行多模态推理和机器人动作预测的VLA模型

Paper Card 论文标题&#xff1a;Diffusion-VLA: Scaling Robot Foundation Models via Unified Diffusion and Autoregression 论文作者&#xff1a;Junjie Wen, Minjie Zhu, Yichen Zhu, Zhibin Tang, Jinming Li, Zhongyi Zhou, Chengmeng Li, Xiaoyu Liu, Yaxin Peng, Chao…

使用MATLAB进行雷达数据采集可视化

本文使用轮趣科技N10雷达&#xff0c;需要源码可在后台私信或者资源自取 1. 项目概述 本项目旨在通过 MATLAB 读取 N10 激光雷达 的数据&#xff0c;并进行 实时 3D 点云可视化。数据通过 串口 传输&#xff0c;并经过解析后转换为 三维坐标点&#xff0c;最终使用 pcplayer 进…

UE求职Demo开发日志#19 给物品找图标,实现装备增加属性,背包栏UI显示装备

1 将用到的图标找好&#xff0c;放一起 DataTable里对应好图标 测试一下能正确获取&#xff1a; 2 装备增强属性思路 给FMyItemInfo添加一个枚举变量记录类型&#xff08;物品&#xff0c;道具&#xff0c;装备&#xff0c;饰品&#xff0c;武器&#xff09;--> 扩展DataT…

Docker 部署 Starrocks 教程

Docker 部署 Starrocks 教程 StarRocks 是一款高性能的分布式分析型数据库&#xff0c;主要用于 OLAP&#xff08;在线分析处理&#xff09;场景。它最初是由百度的开源团队开发的&#xff0c;旨在为大数据分析提供一个高效、低延迟的解决方案。StarRocks 支持实时数据分析&am…

(9) 上:学习与验证 linux 里的 epoll 对象里的 EPOLLIN、 EPOLLHUP 与 EPOLLRDHUP 的不同

&#xff08;1&#xff09;经过之前的学习。俺认为结论是这样的&#xff0c;因为三次握手到四次挥手&#xff0c;到 RST 报文&#xff0c;都是 tcp 连接上收到了报文&#xff0c;这都属于读事件。所以&#xff1a; EPOLLIN : 包含了读事件&#xff0c; FIN 报文的正常四次挥手、…

python学opencv|读取图像(五十二)使用cv.matchTemplate()函数实现最佳图像匹配

【1】引言 前序学习了图像的常规读取和基本按位操作技巧&#xff0c;相关文章包括且不限于&#xff1a; python学opencv|读取图像-CSDN博客 python学opencv|读取图像&#xff08;四十九&#xff09;原理探究&#xff1a;使用cv2.bitwise()系列函数实现图像按位运算-CSDN博客…

数据分析系列--⑦RapidMiner模型评价(基于泰坦尼克号案例含数据集)

一、前提 二、模型评估 1.改造⑥ 2.Cross Validation算子说明 2.1Cross Validation 的作用 2.1.1 模型评估 2.1.2 减少过拟合 2.1.3 数据利用 2.2 Cross Validation 的工作原理 2.2.1 数据分割 2.2.2 迭代训练与测试 ​​​​​​​ 2.2.3 结果汇总 ​​​​​​​ …