IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 深入语言全新视角,探索其精华设计 - 泛型编程 | 初访类和对象经典案例:vector类 -> 正文阅读

[C++知识库]深入语言全新视角,探索其精华设计 - 泛型编程 | 初访类和对象经典案例:vector类

💛 前情提要💛

本章节是C++模板初阶&初访STL的相关知识~

接下来我们即将进入一个全新的空间,对代码有一个全新的视角~

以下的内容一定会让你对C++有一个颠覆性的认识哦!!!

以下内容干货满满,跟上步伐吧~


作者介绍:

🎓 作者: 热爱编程不起眼的小人物🐐
🔎作者的Gitee:代码仓库
📌系列文章&专栏推荐: 《刷题特辑》《C语言学习专栏》《数据结构_初阶》《C++轻松学_深度剖析_由0至1》《Linux - 感受系统美学》

📒我和大家一样都是初次踏入这个美妙的“元”宇宙🌏 希望在输出知识的同时,也能与大家共同进步、无限进步🌟
🌐这里为大家推荐一款很好用的刷题网站呀👉点击跳转



💡本章重点

  • 了解C++泛型编程概念

  • 深入认识C++精华:模板【函数模板/类模板】

  • 了解STL库&模拟实现vector


🍞一.泛型编程

💡泛型编程: 编写与类型无关的通用代码,是代码复用的一种手段

  • 简单来说:代码可以广泛的通用于不同类型之间的应用

  • 举个例子,对于想要实现一个通用的函数,即将一个函数可以给不同类型的参数使用,我们有以下办法:

    • 函数重载: 对于某些场景下(Eg:swap函数…),一般是针对不同的参数类型,去重复书写相同的代码,但仅仅是类型不同,而且一旦出现新的参数类型出现,就需要增加对应类型的函数重载,此时的代码复用率就很低

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

  • 所以,针对解决函数重载有可能会出现上述的问题,C++专门提供泛型编程去解决这类的问题,而其中模板就是泛型编程的基础

👉让我们来深入了解模板的概念吧~


🍞二.模板概念

💡模板:

  • 简单来说:就是我们告诉编译器一个代码模板,这样下来,日后我们不论传任何参数去调用这个模板,编译器就会自动推断我们传参的类型,从而在代码模板中填充推断出的类型去执行

  • 相当于将函数重载里,我们为了应对不同类型的参数从而自己实现了不同参数类型的函数,整合为了:编译器自动推断参数类型并通过代码模板,自动生成了对应类型的函数实现

👉模板的优势:

  • 有了以上编译器的推断操作,代码的复用就变得异常轻松了

  • 而且代码的维护性更强了,因为凡是调用模板的,所有实例化出来的代码都是通过这个模板生成的,也就是说我们只用维护这个代码模板即可,不用像函数重载一样需要去维护多个代码逻辑相同,仅仅参数类型不同的函数

?模板有两种:

  • 函数模板

  • 类模板

👉接下来就让我们深入了解这两个模板操作吧~


🥐Ⅰ.函数模板

💡函数模板:

  • 函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特特定类型版本

  • 简单来说:是一个模板,针对函数的模板

??函数模板

template<typename T1, typename T2,......,typename Tn>
函数返回值 函数名(参数列表)
{} 
  • 1??template<>:为模板声明

  • 2??typename T1, typename T2,......,typename Tn:为模板参数,一个模板参数代表可以在函数模板中实例化出一种类型

?特别注意:

  • 模板是不支持分离编译的,所以模板声明后需要紧跟着弄成模板的函数实现,不然编译器不知道模板声明是为哪个函数去声明的,而且每重新定义一个函数模板的时候需要重新模板声明一下,因为每次的模板声明只专属于紧跟着的函数

  • typename是用来定义模板参数关键字,也可以用class,但切记不能用struct

  • T1,T2……为模板参数,又因为一个模板参数代表可以在函数模板中实例化出一种类型

    • 所以如果调用函数模板的时候,实参如果都是同一个类型的,那就可以用同一个模板参数表示这个类型

    • 但是若实参中存在多个不同类型,那就需要定义多个函数模板参数,去代表不同的类型

  • 注意:模板参数用T1,T2……来表示,是因为T是类型(type)的缩写,但这里仅仅是代表一个类型的名字,但是具体类型是什么我们是不知道的【因为这个需要函数去传参,编译器去自动推断实参的参数类型从而去用推断出来的类型去替代模板参数】,但模板参数也可以更换为其他字母其替代,不一定要用字母T去表示

🌰举个例子:

在这里插入图片描述

  • 正如上述代码所展示:

    • 蓝色部分调用swap函数时,编译器会推断实参的类型(此时实参为int类型),所以模板实例化的时候,就会将int类型自动替换掉函数模板内的模板参数T,最终生成一份适用于int类型的swap函数去执行

    • 绿色部分调用swap函数时,编译器会推断实参的类型(此时实参为double类型),所以模板实例化的时候,就会将double类型自动替换掉函数模板内的模板参数T,最终生成一份适用于double类型的swap函数去执行

?此时有同学可能就会存在疑惑:

  • 上述两个函数模板调用的是同一个函数吗

👉接着这个问题,我们继续深入函数模板的原理中吧~


🧇1.函数模板的原理

在这里插入图片描述

💡通过汇编代码我们可以看到:

  • 两个地方的swap函数所调用的函数地址是不一样的,也就说明,这个两个函数其实是不是同一个函数,而是不同的两个经过实例化出来后的具体的函数

在这里插入图片描述

?综上:

  • 本质:函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具,所以其实模板就是将本来应该我们做的重复的事情交给了编译器

    简单来说:即我们造好了轮子,而剩下的就靠编译器根据实参类型去推演,并填充、补全轮子,形成对应实参类型的函数以供调用

  • 所以,在真正编译的时候,编译器看到的其实就已经是模板实例化后的样子了,即运用代码的时候就其实已经没有模板的概念了,即相当于实例化的过程早在预处理阶段就完成了


🧇2.函数模板的实例化

💡函数模板的实例化:

  • 用不同类型的参数使用函数模板时,称为函数模板的实例化

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

??实例化:

  • 1??隐式实例化:让编译器根据实参推演模板参数的实际类型

  • 2??显式实例化:在函数名后中使用<>去指定模板参数的实际类型

🌰举个例子:

  • 调用函数模板的时候,如果实参的其中一个参数传的是double类型,一个传的是int类型,此时的模板参数最终会变为哪种类型呢

在这里插入图片描述

  • 我们不可以看到,其实会报错的,这是因为正如前文所说:一个模板参数仅代表一种类型

  • 而如今我们传了两个不同类型的实参,编译器在自动推断的时候就不知道该将模板参数T替换为int or double类型,就会产生歧义

  • 所以,遇到这种情况,可以有三种解决方法:

    • 1??设置两个模板参数去代表这两个不同的类型

      在这里插入图片描述

    • 2?? 传参时自己先强制类型转化

    • 3??显示实例化,表示提前指定模板实例化的类型,这样即使传参时类型不匹配,编译器便会尝试进行隐式类型转换,如果无法转换成功才报错

?特别注意:

  • 若同时即存在普通函数和函数模板,当上述例子去调用函数的时候,会调用哪个呢?

    • 如果存在现成的非函数模板类型与之需求完全匹配的话,就不会去走函数模板实例化的道路,即优先匹配最合适的

    • 但是如果没有现成的函数,就会再去找与之匹配类型的函数模板类型,再去函数模板实例化


🥐Ⅱ.类模板

💡类模板:

  • 类模板代表了一个类家族,在使用时被参数化,根据显示实例化时指定的实例化类型,去产生类的特特定类型版本

  • 简单来说:是一个模板,针对类的模板

??函数模板

template<class T1, class T2, ..., class Tn>
class 类模板名
{
 // 类的成员
}; 
  • 1??template<>:为模板声明

  • 2??typename T1, typename T2,......,typename Tn:为模板参数,一个模板参数代表可以在函数模板中实例化出一种类型

?特别注意:

  • 类模板和函数模板基本定义都相同,但唯独有一点不同就是:

    • 函数模板的模板参数类型可以由编译器根据实参推演模板参数的实际类型

    • 而类模板则规定使用时必须显示实例化,才能使用类模板

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

🌰举个例子:

template<class T>
class A
{
	//...
}

int main()
{
	A<int> aa1;
	A<double> aa2;
	return 0;
}
  • 以上我们便可以看到:A只是类名,而A<指定类型>才是真正的类型【即类模板实例化出来的类型】

?综上:

  • C++相较于C语言进步的优势之一,也正是支持了泛型编程

  • 有了这个基础下,C++的编码方式也便开始变得多样化起来了,而STL正是最经典的代表

👉有了上述的基础,接下来就让我们进入到C++的一个全新阶段:源码剖析STL


🍞三.初访STL

💡STL:

  • STL是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架

  • 简单来说:STL本质是一个“数据结构与算法”的库,也正是有了泛型编程的加持下,产生了STL

👉接下来,就让我们接触STL中第一个数据结构:vector


🥐Ⅰ.vector类

💡vector类:

  • 简单来说:vector类就是数据结构中的顺序表

  • 即我们使用vector就可以像使用数组一样,因为它的底层本质就是动态数组(顺序表)

    如果想详细了解线性表,可以>点击<跳转食用呀~

👆vector常用接口:

  • 尾插:push_back()

  • 获取当前顺序表内的数据个数:size()

  • 判空:empty()

?有同学可能在这里就会产生疑惑: 以前的顺序表实现需要初始化、释放等函数,现在呢

  • 其实这个问题早在认识类和对象的六大默认成员函数的时候已经被解决了,其初始化、释放等函数,早已被整合到这个vector类的成员函数中

  • 也就是说,这是有了的出现,让我们可以更加专心的使用这些数据结构了,无需再像以前C语言一样,现在方法和对象是集合在一起的,我们可以对其结构的功能统一做出封装管理

  • 在类外使用这个数据结构时,只需要关心其使用逻辑即可,无需再担心其内存方面等问题,因为这些问题都会在类的方法实现中做出解决(自动增容……)

👉为了了解得更加透彻,我们尝试自己实现一个vector类吧~


🥐Ⅱ.vector的模拟实现

💡vector的实现:

  • 注意:以下的实现并非完全是STL源码中真正的实现方式,以下是为容易理解、入门而呈现的代码

    STL的源码剖析将在后续文章中继续呈现】

  • 为了避免与STL库中的vector类产生命令冲突的问题,我们将此类放在自己的命名空间中实现

??以下的接口实现将围绕此源码进行:

namespace Dream_Yocean
{
	template<class T>
	class vector
	{
	public:
		vector()
		{
			//...
		}
		
		void push_back(const T& x);
		
		T& operator[](size_t pos);
		
		size_t size()~vector()
		{	
			//...
		}

	private:
		T* _a;
		int _size;
		int _capacity;
	};
}

👉由上述我们可知:

  • _a是指向动态开辟的数组

  • _size是用来记录动态数组中有效数据个数

  • _capicity是用来记录动态开辟的数组的总大小

?特别注意:

  • 此处正是用了类模板去实现这个vector类,这样这个类就可以支持任意类型的使用了

  • 这里暂时不详细讲解拷贝构造函数赋值运算符这两个默认成员函数【因为这里涉及深、浅拷贝的知识点,我们将放在string类的源码剖析中细致分析】


🧇1.初始化vector

??初始化函数的实现: 初始化函数直接在构造函数中实现即可

vector()
	:_a(nullptr)
	, _size(0)
	, _capacity(0)
{}

🧇2.尾插vector

??尾插函数的实现:

template<class T>  
void vector<T>::push_back(const T& x) //只有这样才能让编译器知道:这个T是个模板
{	
	//增容的过程
	if (_size == _capacity)
	{
		int newcapacity = _capacity == 0 ? 4 : _capacity * 2;
		T* tmp = new T[newcapacity];
			
		if (_a)
		{
			memcpy(tmp, _a, sizeof(T)*_size);
			delete[] _a;
		}
			
		_a = tmp;
		_capacity = newcapacity;
	}
	//尾插的过程	
	_a[_size] = x;
	_size++;
}

?特别注意:

  • 因为此函数我们是在类外实现的,类里只是声明,所以此时我们需要再一次模板声明

  • 而且需要运用到vector<T>::这样的域作用限定符,表明此函数是vector<T>这个类里的

  • 如果方法是在类中直接实现的话,就无需再次模板声明,直接使用模板参数即可


🧇3.获取有效数据个数

👉简单来说: 就是获取vector类中的动态开辟的数组的有效个数(即获取成员变量_size

??获取有效数据个数的实现:

size_t size()
{
	return _size;
}

🔥4.解引用操作符重载

👉简单来说: 为了让此类的对象可以像数组一样随机访问,便可以重载[]操作符,让对象像数组一样被我们操作

??解引用操作符重载实现:

template<class T>
T& vector<T>::operator[](size_t pos)
{
	assert(pos < _size);
	return _a[pos];
}

?亮点操作: 为什么这里的返回类型为引用

  • 举个例子:对vector类的对象中的动态开辟的数组进行遍历
for (int i = 0; i < v1.size(); i++)
{
	cout << v1[i] << endl;
}

👆我们不难发现:

  • 既然要像数组一样去操作vector类的对象,那[]操作符不仅能实现随机访问,当然也可以进行随意位置的修改

  • 这也就是为什么我们成员函数的返回类型为引用的原因了,这样可以返回一个动态开辟的数组中某个位置的别名,在类外面我们便可以操作此别名(相当于可以通过地址)对数组中的某个位置实现:读 ? 写 的功能了


🧇5.销毁vector

??销毁函数的实现: 这一步便可以借助析构函数去实现了(释放类中所申请的资源)

~vector()
{
	delete[] _a;
	_a = nullptr;
	_size = _capacity = 0;
}

🥐Ⅲ.总结

?综上:

  • 就是初试模拟实现STL中vector类啦~

  • 这部分仅仅是为了加深大家对类模板方法的使用,但vector类中仍有许多细节,待后续文章中细致分解


🫓总结

综上,我们基本了解了C++中的 “初阶模板&初访STL” 🍭 的知识啦~

恭喜你的内功又双叒叕得到了提高!!!

感谢你们的阅读😆

后续还会继续更新💓,欢迎持续关注📌哟~

💫如果有错误?,欢迎指正呀💫

?如果觉得收获满满,可以点点赞👍支持一下哟~?

在这里插入图片描述

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-10-31 11:35:33  更:2022-10-31 11:35:43 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 3:30:45-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码