当前位置: 首页 > news >正文

[C++]模板与STL简介

🥁作者: 华丞臧
📕​​​​专栏:【C++】
各位读者老爷如果觉得博主写的不错,请诸位多多支持(点赞+收藏+关注)。如果有错误的地方,欢迎在评论区指出。

推荐一款刷题网站 👉LeetCode


文章目录

  • 模板初阶
    • 泛型编程
    • 函数模板
      • 函数模板概念
      • 函数模板格式
      • 函数模板原理
      • 函数模板实例化
      • 多个参数
      • 模板参数的匹配原则
    • 类模板
      • 类模板定义格式
      • 类模板的实例化
    • 模板的声明和定义分离
  • []的重载
  • STL初识
    • 什么是STL
    • STL版本
    • STL的六大组件
    • STL重要性
    • STL的缺陷


模板初阶

泛型编程

首先来看下面一段代码:

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++中提供了一个模具,通过给这个模具中填充不同的类型,来获得不同类型的代码。

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

在这里插入图片描述

函数模板

函数模板概念

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

函数模板格式

C++中提供了一个新的关键字template,用来定义函数模板,template后面跟一个尖括号用来定义类型,这个类型不是具体的类型而是一个模板类型(虚拟类型)。

template<typename T1, typename T2...,typename Tn>
返回值类型 函数名(参数列表)
{}

//也可以使用class
template<class T1, class T2..., class Tn>
返回值类型 函数名(参数列表)
{}


//例子
template<class T>
void swap(T& left, T& right)
{
	T tmp = left;
	left = right;
	right = tmp;
}

//使用
int main()
{
	int a = 1, b = 2;
	swap(a, b);

	double c = 1.1, d = 2.2;
	swap(c, d);

	char e = 'a', f = 'b';
	swap(e, f);

	return 0;
}

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

函数模板原理

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

在这里插入图片描述

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

函数模板实例化

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

  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;
 	Add(a1, a2);
 	Add(d1, d2);
 

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

 
// 此时有两种处理方式:1. 用户自己来强制转化 2. 使用显式实例化
 	Add(a, (int)d);
 	return 0;
}
  1. 显式实例化:在函数名后的<>中指定模板参数的实际类型
template<class T>
T Add(const T& left, const T& right)
{
 	return left + right;
}

int main(void)
{
 int a = 10;
 double b = 20.0;
 
 // 显式实例化
 Add<int>(a, b);
 return 0;
}

多个参数

template<class T1, class T2>
T Add(const T1& left, const T2& right)
{
 	return left + right;
}

模板参数的匹配原则

  1. 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数;
  2. 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生一个实例,如果模板可以产生一个具有更好匹配的函数,那么将选择模板;
  3. 模板函数不允许自动类型转换,但普通函数可以自动进行类型转换。

类模板

我们知道typedef可以给类型重命名,typedef很好的解决了代码的可维护性,但不是所谓的泛型编程,它不能满足泛型编程的要求;假设有一个栈,如果我们要在栈中存放不同类型的数据就需要用到模板。

类模板定义格式

类模板的成员函数都是模板函数。

//例子
template<typename T>
class Stack
{
public:
	Stack(int capacity = 4)
	{
		cout << "Stack(int capacity = )" <<capacity<<endl;

		_a = (T*)malloc(sizeof(T)*capacity);
		if (_a == nullptr)
		{
			perror("malloc fail");
			exit(-1);
		}

		_top = 0;
		_capacity = capacity;
	}
	
	~Stack()
	{
		cout << "~Stack()" << endl;

		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}

	void Push(const T& x)
	{
		// ....
		// 扩容
		_a[_top++] = x;
	}

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

int main()
{
	// 类模板一般没有推演时机,函数模板实参传递形参,推演模板参数
	// 类模板一般显式实例化
	// 他们是同一个模板实例化出来的
	// 但是模板参数不同,他们就是不同的类型
	Stack<int> st1;

	Stack<double> st2;

	return 0;
}

类模板的实例化

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

// Vector类名,Vector<int>才是类型
Vector<int> s1;
Vector<double> s2;

模板的声明和定义分离

注意模板不支持分离编译;这涉及到编译的过程,一个程序编译需要经过预编译、编译、汇编、链接四个过程,在链接之前的三个过程(预编译、编译、汇编)都是单线进行的。如果分离编译,那么用的地方在实例化但是只有声明,没有定义;而定义的地方无法完成实例化,只有定义,定义的地方只有模板而没有实例化的对象,因此链接时定义文件.cpp的符号表中并没有对应函数的地址。
代码地址👉git仓库地址
在这里插入图片描述
解决方案:

  1. 显式实例化:缺点很明显换个类型还得显式实例化
    在这里插入图片描述
    程序运行成功:
    在这里插入图片描述
  2. 声明和定义不分离
    文件后缀为.hpp是模板文件,.hpp表示是声明和定义放在一起。
//Stack.h
#pragma once
#include <iostream>
using namespace std;

template<typename T>
class Stack
{
public:
	Stack(int capacity = 4);
	~Stack();
	void Push(const T& x);

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

template<class T>
Stack<T>::Stack(int capacity)
{
	cout << "Stack(int capacity = )" << capacity << endl;

	_a = (T*)malloc(sizeof(T) * capacity);
	if (_a == nullptr)
	{
		perror("malloc fail");
		exit(-1);
	}

	_top = 0;
	_capacity = capacity;
}

template<class T>
Stack<T>::~Stack()
{
	cout << "~Stack()" << endl;

	free(_a);
	_a = nullptr;
	_top = _capacity = 0;
}

template<class T>
void Stack<T>::Push(const T& x)
{
	// ....
	// 
	_a[_top++] = x;
}

//test.cpp
#include "Stack.h"


int main()
{
	Stack<int> st1;
	st1.Push(1);
	return 0;
}

程序运行成功。
在这里插入图片描述

[]的重载

#define N  10

using namespace std;

namespace hcz
{
	template<class T>
	class array
	{
	public:
		T& operator[](size_t i)
		{
			assert(i < N); //断言检查是否越界
			return _a[i];
		}
	private:
		T _a[N];
	};
}

int main()
{
	//int a2[10];
	//a2[20] = 0; //编译器检查不出越界访问
	//a2[10];  //对数组边界抽查

	hcz::array<int> a1;
	for (size_t i = 0; i < N; ++i)
	{
		// a1.operator[](i)= i;

		a1[i] = i;
	}

	for (size_t i = 0; i < N; ++i)
	{
		// a1.operator[](i)
		cout << a1[i] << " ";
	}
	cout << endl;

	for (size_t i = 0; i < N; ++i)
	{
		a1[i]++;
	}

	for (size_t i = 0; i < N; ++i)
	{
		cout << a1[i] << " ";
	}
	cout << endl;

	//a1[20];
	//a1[10];


	return 0;
}

重载运算符[]可以让我们像使用数组一样使用某些自定义类型,如上述代码。
编译器对于数组越界的检查是不严格的,编译器只会对数组的边界进行抽查并且只有修改了数据才会报错,这种检查是很 不安全;而自定义类型中我们无疑可以可以对数组越界进行更加严格的检查,比如加上断言。

在这里插入图片描述
从上述图片可以看到,vs对于越界访问检查不出,并且对于越界访问并且修改了数据也可能检查不出,这是因为编译器对于数组的越界检查是抽查;编译器只会对于数组边界进行抽查,只有修改数组边界上的数据编译器才可能会报错。

在这里插入图片描述可以看到在VS2019中,越界访问程序能正常运行并结束,vs编译器检查不出数组越界访问的问题。

在这里插入图片描述

STL初识

什么是STL

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

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要阅读部分源代码,主要参考的就是这个版本。

STL的六大组件

在这里插入图片描述

STL重要性

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

STL的缺陷

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

相关文章:

  • Docker- 7.2、跨主机网络-overlay
  • 【Git】一文带你入门Git分布式版本控制系统(分支管理策略、Bug分支)
  • 独占指针 std::unique_ptr
  • 斐波那契的几种思路,你都会吗
  • 关于新冠的几点总结
  • 开源项目推荐 | 中科院自动化所历时9年打造的类脑认知智能引擎“智脉”正式开源部署至OpenI启智社区
  • Servlet基础教程 (保姆级教学)
  • vue经历从2.0到3.0更新
  • 安装mysqlclient失败解决办法
  • 华为OD机试真题 Java 实现【单词接龙】
  • Spring boot 日志直接推送到elasticsearch上
  • 网络部署运维实验(pat 端口映射含命令)
  • 软件测试期末复习(二)试题及答案
  • 智能家居创意DIY之智能灯泡
  • Linux——磁盘在网络中共享
  • [deviceone开发]-do_Webview的基本示例
  • express如何解决request entity too large问题
  • JavaScript 无符号位移运算符 三个大于号 的使用方法
  • Java教程_软件开发基础
  • LeetCode18.四数之和 JavaScript
  • leetcode讲解--894. All Possible Full Binary Trees
  • Mocha测试初探
  • Mysql数据库的条件查询语句
  • RxJS 实现摩斯密码(Morse) 【内附脑图】
  • SegmentFault 技术周刊 Vol.27 - Git 学习宝典:程序员走江湖必备
  • Vue2.0 实现互斥
  • Yeoman_Bower_Grunt
  • 近期前端发展计划
  • 类orAPI - 收藏集 - 掘金
  • 深入浏览器事件循环的本质
  • 国内开源镜像站点
  • 我们雇佣了一只大猴子...
  • 曾刷新两项世界纪录,腾讯优图人脸检测算法 DSFD 正式开源 ...
  • !$boo在php中什么意思,php前戏
  • # 手柄编程_北通阿修罗3动手评:一款兼具功能、操控性的电竞手柄
  • (3)nginx 配置(nginx.conf)
  • (4.10~4.16)
  • (一)C语言之入门:使用Visual Studio Community 2022运行hello world
  • (原创)boost.property_tree解析xml的帮助类以及中文解析问题的解决
  • (转) SpringBoot:使用spring-boot-devtools进行热部署以及不生效的问题解决
  • (转)EOS中账户、钱包和密钥的关系
  • (转)JVM内存分配 -Xms128m -Xmx512m -XX:PermSize=128m -XX:MaxPermSize=512m
  • (转)Mysql的优化设置
  • (转)VC++中ondraw在什么时候调用的
  • ******之网络***——物理***
  • .NET Core 将实体类转换为 SQL(ORM 映射)
  • .NET Framework 4.6.2改进了WPF和安全性
  • .NET HttpWebRequest、WebClient、HttpClient
  • .netcore 6.0/7.0项目迁移至.netcore 8.0 注意事项
  • .net打印*三角形
  • .NET设计模式(7):创建型模式专题总结(Creational Pattern)
  • .NET是什么
  • .Net下C#针对Excel开发控件汇总(ClosedXML,EPPlus,NPOI)
  • /etc/fstab 只读无法修改的解决办法
  • @transaction 提交事务_【读源码】剖析TCCTransaction事务提交实现细节