【“C++ 精妙之道:解锁模板奇谭与STL精粹之门“】

news2024/11/28 2:32:26

【本节目标】

  • 1. 泛型编程

  • 2. 函数模板

  • 3. 类模板

  • 4. 什么是STL

  • 5. STL的版本

  • 6. STL的六大组件

  • 7. STL的重要性

  • 8. 如何学习STL

  • 9.STL的缺陷

1. 泛型编程

如何实现一个通用的交换函数呢?

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;
}
......

使用函数重载虽然可以实现,但是有一下几个不好的地方:

  • 1. 重载的函数仅仅是类型不同,代码复用率比较低,只要有新类型出现时,就需要用户自己增加对应的函数
  • 2. 代码的可维护性比较低,一个出错可能所有的重载均出错

那能否告诉编译器一个模子,让编译器根据不同的类型利用该模子来生成代码呢

如果在C++中,也能够存在这样一个模具,通过给这个模具中填充不同材料(类型),来获得不同材料的铸件 (即生成具体类型的代码),那将会节省许多头发。巧的是前人早已将树栽好,我们只需在此乘凉。

泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础。

2. 函数模板

2.1 函数模板概念

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

2.1 函数模板格式 

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

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

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

int main()
{
	int a = 0, b = 1;
	double c = 1.1, d = 2.2;
	Swap(a, b);
	Swap(c, d);
	return 0;
}

上面我们调用的是不是同一个函数

注意:typename是用来定义模板参数关键字,也可以使用class(切记:不能使用struct代替class)

//单个参数
template<typename T>
void Swap(T& left, T& right)
{
	T temp = left;
	left = right;
	right = temp;
}
//多个参数
//模板参数和函数参数很类似
//函数参数定义的形参对象
//模板参数定义的类型
template<class T1, class T2>
void func(const T1& t1, const T2& t2)
{
	//这里用输出流就不用指定输出格式
	//C语言就需要指定输出格式
	cout << t1 << endl;
	cout << t2 << endl;
}
int main()
{
	int a = 0, b = 1;
	double c = 1.1, d = 2.2;
	Swap(a, b);
	Swap(c, d);

	func(1, 2);
	func('a', 1.1);
	return 0;
}

如果有模板想要多个参数,直接像函数参数一样就可以。

2.3 函数模板的原理

那么如何解决上面的问题呢?大家都知道,瓦特改良蒸汽机,人类开始了工业革命,解放了生产力。机器生 产淘汰掉了很多手工产品。本质是什么,重复的工作交给了机器去完成。有人给出了论调:懒人创造世界。

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

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

2.4 函数模板的实例化

用不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数实例化分为:隐式实例化和显式实例化。

1. 隐式实例化:让编译器根据实参推演模板参数的实际类型
template<class T>
T Add(const T& left, const T& right)
{
	return left + right;
}
int main()
{
	int a1 = 10, a2 = 20;
	double d1 = 10.0, d2 = 20.0;
	cout << Add(a1, a2) << endl;
	cout << Add(d1, d2) << endl;

	/*
	该语句不能通过编译,因为在编译期间,当编译器看到该实例化时,需要推演其实参类型
	通过实参a1将T推演为int,通过实参d1将T推演为double类型,但模板参数列表中只有一个T,
	编译器无法确定此处到底该将T确定为int 或者 double类型而报错
	注意:在模板中,编译器一般不会进行类型转换操作,因为一旦转化出问题,编译器就需要背黑锅
	Add(a1, d1);
	*/

	// 此时有两种处理方式:1. 用户自己来强制转化 2. 使用显式实例化
	cout << Add(a1, (int)d1) << endl;
	return 0;
}
2. 显式实例化:在函数名后的<>中指定模板参数的实际类型
template<class T>
T Add(const T& left, const T& right)
{
	return left + right;
}
template<class T>
T* func(int n)
{
	T* p = new T[n];
	return p;
}
int main()
{
	int a = 1;
	double b = 2.2;

	// 显式实例化
	cout << Add<int>(a, b) << endl;//3
	cout << Add<double>(a, b) << endl;//3.2

	//只能显式调用场景
	//func(4);//error C2672: “func”: 未找到匹配的重载函数
	// message : 可能是“T *func(int)”
	// message : “T *func(int)”: 无法推导“T”的 模板 参数
	func<int>(10);

	return 0;
}

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

2.5 模板参数的匹配原则

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

//专门处理int的加法函数
int Add(int left, int right)
{
	return left + right;
}
// 通用加法函数
template<class T>
T Add(T left, T right)
{
	return left + right;
}
void Test()
{
	Add(1, 2); // 与非模板函数匹配,编译器不需要特化
	//调用:int Add(int left, int right)
	Add<int>(1, 2); // 调用编译器特化的Add版本
	//调用:T Add(T left, T right)

	Add(1.1, 2.2); // 调用编译器特化的Add版本
	//调用:T Add(T left, T right)
	//如果没有函数模板,会调用非模板函数

}
int main()
{
	Test();
	/*
		匹配调用原则:
		1.合适匹配的情况下,有现成的就用现成的
		Add(1, 2);直接使用非模板函数 - 现成的

		2.没有就将就用
		Add(1.1, 2.2);没有模板函数时,会隐式转换为int,然后调用非模板函数 

		3.有更合合适的就用更合适的,那怕自己做
		Add(1.1, 2.2);自己去隐式实例化,然后调用模板函数
	*/
	return 0;
}

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

// 专门处理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); // 与非函数模板类型完全匹配,不需要函数模板实例化
	Add(1, 2.0); // 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函数
}

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

3. 类模板

我们首先来看一下之前我们的栈类类型的数据类型是通过typedef来决定的。

typedef int STDataType;
class Stack
{
public:
	Stack(int capacity = 4)
	{
		cout << "Stack(int capacity = 4)" << endl;
		_a = new STDataType[capacity];
		_top = 0;
		_capacity = capacity;
	}
	~Stack()
	{
		cout << "~Stack()" << endl;
		delete[] _a;
		_a = nullptr;
		_top = 0;
		_capacity = 0;
	}
private:
	STDataType* _a;
	int _top; 
	int _capacity;
};

假如有一天,我们要在当前的栈类一个存int,另外一个栈存double,那我们就要这样写:写两个不同的栈类。

typedef int STDataType;
class Stackint
{
public:
	Stackint(int capacity = 4)
	{
		cout << "Stack(int capacity = 4)" << endl;
		_a = new STDataType[capacity];
		_top = 0;
		_capacity = capacity;
	}
	Stackint()
	{
		cout << "~Stack()" << endl;
		delete[] _a;
		_a = nullptr;
		_top = 0;
		_capacity = 0;
	}
private:
	STDataType* _a;
	int _top; 
	int _capacity;
};

typedef double STDataType;
class Stackdouble
{
public:
	Stackdouble(int capacity = 4)
	{
		cout << "Stack(int capacity = 4)" << endl;
		_a = new STDataType[capacity];
		_top = 0;
		_capacity = capacity;
	}
	~Stackdouble()
	{
		cout << "~Stack()" << endl;
		delete[] _a;
		_a = nullptr;
		_top = 0;
		_capacity = 0;
	}
private:
	STDataType* _a;
	int _top;
	int _capacity;
};

同时为了方便区分,我们还要取不同的类名。我们发现上面的代码除了数据的类型不一样,代码基本上都是相同的,为了简便,C++提出了类模板的概念:

3.1 类模板的定义格式

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

3.2 类模板的实例化

类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<> 中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。

#include <iostream>
using namespace std;

template<class T>
class Stack
{
public:
	// 不能写成Stack<T>()
	// 构造函数定义:函数名和类名相同,而不是类型相同
	Stack(int capacity = 4)
	{
		cout << "Stack(int capacity = 4)" << endl;
		_a = new T[capacity];
		_top = 0;
		_capacity = capacity;
	}
	~Stack()
	{
		cout << "~Stack()" << endl;
		delete[] _a;
		_a = nullptr;
		_top = 0;
		_capacity = 0;
	}
private:
	T* _a;
	int _top;
	int _capacity;
};

int main()
{
	//类模板只能自己推
	//显示实例化

	//普通类:类名就是类型
	//类模板实例化的类:类名不是类型,类名<数据类型>才是整个类的类型

	// st1和st2是用一个类模板实例化的类
	// 不是同一个类型
	// 同一个工厂生产出来的不同产品
	// 显示实例化的类型不同,它们就是不同的类
	Stack<int> st1;
	Stack<double> st2;

	return 0;
}

运行结果:

 这样我们就实现了一个栈类存储不能类型的数据了。

3.3.类模板声明和定义分离

template<class T>
class Stack
{
public:
	Stack(int capacity = 4);//类里面声明
	~Stack()
	{
		cout << "~Stack()" << endl;
		delete[] _a;
		_a = nullptr;
		_top = 0;
		_capacity = 0;
	}
private:
	T* _a;
	int _top;
	int _capacity;
};

//规定缺省参数在声明处提供缺省值
template<class T>
Stack<T>::Stack(int capacity)//类外面定义
{
	cout << "Stack(int capacity = 4)" << endl;
	_a = new T[capacity];
	_top = 0;
	_capacity = capacity;
}

注意:类模板声明和定义分离不能到两个文件上,会发生链接错误。

4. 什么是STL

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

5. STL的版本

原始版本

Alexander Stepanov、Meng Lee 在惠普实验室完成的原始版本,本着开源精神,他们声明允许任何人任意 运用、拷贝、修改、传播、商业使用这些代码,无需付费。唯一的条件就是也需要向原始版本一样做开源使 用。 HP 版本--所有STL实现版本的始祖。

P. J. 版本

由P. J. Plauger开发,继承自HP版本,被Windows Visual C++采用,不能公开或修改,缺陷:可读性比较低, 符号命名比较怪异。

RW版本

由Rouge Wage公司开发,继承自HP版本,被C+ + Builder 采用,不能公开或修改,可读性一般。

SGI版本

由Silicon Graphics Computer Systems,Inc公司开发,继承自HP版 本。被GCC(Linux)采用,可移植性好, 可公开、修改甚至贩卖,从命名风格和编程 风格上看,阅读性非常高。我们后面学习STL要阅读部分源代码, 主要参考的就是这个版本。

6. STL的六大组件

7. STL的重要性

        网上有句话说:“不懂STL,不要说你会C++”。STL是C++中的优秀作品,有了它的陪伴,许多底层的数据结构以及算法都不需要自己重新造轮子,站在前人的肩膀上,健步如飞的快速开发。

8. 如何学习STL

简单总结一下:学习STL的三个境界:能用,明理,能扩展 。

9.STL的缺陷

  • 1. STL库的更新太慢了。这个得严重吐槽,上一版靠谱是C++98,中间的C++03基本一些修订。C++11出 来已经相隔了13年,STL才进一步更新。
  • 2. STL现在都没有支持线程安全。并发环境下需要我们自己加锁。且锁的粒度是比较大的。
  • 3. STL极度的追求效率,导致内部比较复杂。比如类型萃取,迭代器萃取。
  • 4. STL的使用会有代码膨胀的问题,比如使用vector/vector/vector这样会生成多份代码,当然这是模板语 法本身导致的。

本章结束啦!

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

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

相关文章

Hadoop学习笔记(HDP)-Part.14 安装YARN+MR

目录 Part.01 关于HDP Part.02 核心组件原理 Part.03 资源规划 Part.04 基础环境配置 Part.05 Yum源配置 Part.06 安装OracleJDK Part.07 安装MySQL Part.08 部署Ambari集群 Part.09 安装OpenLDAP Part.10 创建集群 Part.11 安装Kerberos Part.12 安装HDFS Part.13 安装Ranger …

PRCD-1229 : An attempt to access configuration of database

今天维护oda一体机时&#xff0c;发现无法在grid用户下面关闭数据库实例&#xff0c;如下 ASM1:/home/gridoda0>srvctl stop database -d orcl -o immeidate PRCD-1229 : An attempt to access configuration of database orcl was rejected because its version 11.2.0.4.…

Lombok的踩坑系列之@Builder

背景&#xff1a; Lombok 这个插件大家日常工作中几乎是必备的&#xff0c;几个简单的注解就可以帮助我们减少一大坨get/set方法等&#xff1b;其中Builder注解使用的也很广泛&#xff0c;使用了建造者模式帮助我们构建出个性化的对象&#xff0c;本次踩坑点就在这个地方。 先…

智能优化算法应用:基于混沌博弈算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于混沌博弈算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于混沌博弈算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.混沌博弈算法4.实验参数设定5.算法结果6.参考…

Learning Memory-guided Normality for Anomaly Detection 论文阅读

Learning Memory-guided Normality for Anomaly Detection 摘要1.介绍2.相关工作3.方法3.1网络架构3.1.1 Encoder and decoder3.1.2 Memory 3.2. Training loss3.3. Abnormality score 4.实验5.总结总结&代码复现&#xff1a; 文章信息&#xff1a; 发表于&#xff1a;cvpr…

我最喜欢的白版应用,AI加持的新功能开源!强烈推荐

Excalidraw 把他们的文本到图表的功能开源了 Excalidraw是一个虚拟白板应用&#xff0c;专门用于绘制类似手绘的图表。它提供了一个无限的、基于画布的白板&#xff0c;具有手绘风格&#xff0c;支持多种功能。 之前我分享的&#xff1a;72张PNG&#xff0c;图解机器学习 里面…

Linux的IO模型——非阻塞IO

非阻塞IO就是当用户recvfrom时&#xff0c;如果内核数据没有准备好&#xff0c;那么就直接返回结果&#xff0c;而不是阻塞用户进程&#xff0c;让其一直等待。 我们可以发现&#xff0c;非阻塞IO模型中&#xff0c;用户进程在第一个阶段是非阻塞&#xff0c;第二个阶段是阻塞状…

Oracle(2-11)RMAN Backups

文章目录 一、基础知识1、RMAN Backup Concepts RMAN备份概念2、RMAN Backup Modes RMAN备份的类型3、Backup File Types 备份文件类型4、RMAN Backup Destinations RMAN备份目标5、Backup Constraints 备份约束6、Recovery Manager Backups 恢复管理器备份7、Characteristics …

黑苹果配置清单

手里的MacBookPro已经快沦为电子垃圾了&#xff0c;平时用MacOS比较多&#xff0c;Window用的比较少&#xff0c;而苹果电脑的价格不管是MacBookPro还是MacMini丐版的便宜但是面对现在Window动不动就64g内存的情况就显得微不足道了&#xff0c;高配的价格直接把我劝退&#xff…

iphone/安卓手机如何使用burp抓包

iphone 1. 电脑 ipconfig /all 获取电脑网卡ip&#xff1a; 192.168.31.10 2. 电脑burp上面打开设置&#xff0c;proxy&#xff0c;增加一条 192.168.31.10:8080 3. 4. 手机进入设置 -> Wi-Fi -> 找到HTTP代理选项&#xff0c;选择手动&#xff0c;192.168.31.10:8080 …

[Redis]基础入门

Redis入门 一、初识Redis Redis是一种键值型的NoSql数据库。 其中键值型&#xff0c;是指Redis中存储的数据都是以key、value对的形式存储&#xff0c;而value的形式多种多样&#xff0c;可以是字符串、数值&#xff0c;甚至是json。 NoSql则是相对于传统关系型数据库而言&a…

pthread学习遇到的问题

1.pthread_t 是个类型&#xff0c;指的是线程ID。pthread_create&#xff08;&#xff09;的时候穿地址进去&#xff0c;线程创建好后就会成为线程ID&#xff08;即输出型参数&#xff09; 2.pthread_self() pthread_self()获得是调用这个函数的线程ID &#xff08;我以为是…

Java异步编程之利器:Guava异步编程实践

第1章&#xff1a;引言 - 为什么要用Guava进行异步编程&#xff1f; 大家好&#xff0c;我是小黑&#xff01;今天咱们要聊的是Guava在异步编程中的应用。首先&#xff0c;让我们搞清楚为什么要用Guava来处理异步任务。在Java的世界里&#xff0c;异步编程是个老话题了&#x…

信号是怎么搞到电磁波上面去的呢?

在之前的文章中&#xff0c;我们曾多次讲到电磁波的美妙&#xff0c;但是有了电磁波就可以通信了吗&#xff1f; No&#xff0c;我们要把信息加载到电磁波上&#xff0c;这个电磁波就可以作为信息的载体来工作了。可是信号是怎么加载到电磁波上的呢&#xff1f; 今天我们一起…

宝塔上安装mysql遇到的问题

宝塔上安装mysql遇到的问题 文章目录 宝塔上安装mysql遇到的问题一、下载mysql二、启动报错三、设置密码四、解决报错bash未找到命令mysql五、继续修改root密码五、宝塔中设置端口六、使用连接工具连接数据库 一、下载mysql 宝塔软件商店里下载mysql&#xff0c;然后点击启动。…

SpringSecurity6 | 默认用户生成

SpringSecurity6 | 默认用户生成 ✅作者简介&#xff1a;大家好&#xff0c;我是Leo&#xff0c;热爱Java后端开发者&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;Leo的博客 &#x1f49e;当前专栏&#xff1a; Java…

(四) python门面模式

文章目录 4.1 结构型设计模式4.1.1 简介4.1.2 常见的几种结构型设计模式 4.2 理解门面设计模式4.2.1 门面设计模式概述4.2.2 门面设计模式的作用 4.3 UML类图4.3.1 门面4.3.2 系统4.3.3 客户端 4.4 门面模式的代码实现4.4.1 场景&#xff1a;4.4.2 python实现 4.5 原理&#xf…

【C++】动态内存管理——new和delete

这篇文章我们讲一下C的动态内存管理&#xff0c;从一个比较陌生的知识说起&#xff0c;我们知道&#xff0c;一个工程可以创建很多.c文件&#xff0c;我们如果定义一个全局变量&#xff0c;只要用extern声明一下&#xff0c;在每个文件都可以用。而用static修饰的全局变量只能在…

Vue Computed

小满&#xff0c;我的神&#xff01; 视频链接 // 只读 const plusOne computed(() > count.value 1) // 可读可写 const plusOne computed({get: () > count.value 1,set: (val) > {count.value val - 1} }, { // 用于调试onTrack(e) {debugger},onTrigger(e) …

软件测试基础知识总结

之前有将基础的软件测试知识做了一个总结&#xff0c;但比较潦草&#xff0c;很多内容只是一笔带过&#xff0c;快到年底了&#xff0c;自己也有个写年终知识总结文档的计划&#xff0c;就将基础的理论知识重新整理一番。 有人问我&#xff0c;这些都是能搜索到的知识&#xf…