STL 源码剖析 | 第1章:概论

news2024/11/15 17:21:40

STL 是一套程序库

1、STL 概论

1、从子程序、程序、函数、类别,到函数库、类别库、各种组件,从结构化设计、模块化设计、面向对象设计,到模式的归纳整理

为的就是 复用性 的提升

复用性 必须建立在某种标准之上 —— 不论是 语言层次的标准,或 数据交换的标准,或 通讯协议的标准

2、STL 的价值在于 两方面。就低层次而言,STL 带给我们一套 极具实用价值的零部件,以及 一个整合的组织;STL 还带给我们一个高层次的、以泛型思维 为基础的、系统化的、条理分明的 “软件组件分类学”

以抽象概念 为主体而非以实际类 为主体的结构,形成了一个严谨的接口标准。在此接口之下,任何组件都有最大的独立性,并以 迭代器(iterator)胶合起来,或以 配接器(adapter)互相配接,或以 仿函数(functor)动态选择某种策略

2、STL 六大组件 功能与运用

  1. 容器(containers): 各种数据结构,如 vector, list, deque, set, map, … 用来存放数据。从实现的角度来看,STL 容器是一种 类模板 template <typename T> class MyClass
  2. 算法: 各种常用算法如 sort, search, copy, erase…
  3. 迭代器(iterators): 扮演 容器与算法之间的胶合剂,是所谓的 “泛型指针(能够 指向任何类型数据的指针,通常是没有具体类型限制的指针 void*void* 指针常用于函数参数,允许该函数接收不同类型的数据。要解引用一个 void* 指针,必须先将它强制转换为具体类型的指针:*(static_cast<int*>(p)))”,共有五种类型,以及 其它衍生变化
    从实现的角度来看,迭代器是一种将 operator*, operator->, operator++, operator-- 等指针相关操作予以重载的 类模板。所有 STL 容器都附带有自己专属的迭代器——只有容器设计者 才知道如何遍历自己的元素。原生指针(后来为了 增强指针操作的安全性和简化内存管理 使用智能指针) 也是一种迭代器
  4. 仿函数(functors): 行为类似函数,可作为算法的某种策略。从实现的角度来看, 仿函数是一种重载了 operator() 的 类 或 模板类。一般函数指针 可视为狭义的仿函数
  5. 配接器(adapters): 一种用来修饰 容器 或 仿函数 或 迭代器接口 的东西。例如,STL 提供的 queue 和 stack,虽然看似容器,其实只能算是一种容器配接器,因为它们的底部完全借助 deque。所有操作都由底层的 deque 供应。改变 functor 接口者,称为 function adapter; 改变 container 接口者,称为 container adapter; 改变 iterator 接口者,称为 iterator adapter
  6. 配置器(allocators): 负责空间配置与管理。从实现的角度来看,配置器是一个实现了 动态空间配置、空间管理、空间释放的 模板类

目前所有的 C++ 编译器 一定支持有一份 STL。在相应的各个 C++ 头文件(headers)中。STL 并非以二进制代码 面貌出现,而是 以源代码面貌供应。按 C++ Standard 的规定,所有标准头文件都不再有扩展名
在这里插入图片描述

3、语法

3.1 临时对象的产生和应用

刻意制造 临时对象的方法是,在型别名称之后 直接加一对小括号,并可指定初值,例如 Shape(3,5)int(8),其意义相当于 调用相应的 constructor 且 不指定对象名称。STL 最常将此技巧应用于 仿函数 与 配置器

3.2 静态常量整数成员 在 class 内部直接初始化

template <typename T>
class testClass {
public: // expedient
	static const int _datai = 5;
	static const long _datal = 3L;
	static const char _datac = 'c';
};

3.3 increment / decrement / dereference 操作符

increment / decrement / dereference 操作符在迭代器的实现上 占有非常重要的地位,因为任何一个迭代器 都必须体现出前进(increment, operator++) 和 取值(dereference, operator*)功能,前者还分为 前置式(prefix)和后置式(postfix)两种

class INT
{
	friend ostream& operator<<(ostream& os, const INT& i);

	public:
		INT(int i) : m_i(i) { };
		// prefix : increment and then fetch
		INT& operator++()
		{
    		++(this->m_i); // 随着 class 的不同,该行应该有不同的操作
    		return *this;
		}

		// postfix : fetch and then increment
		const INT operator++(int)
		{
    		INT temp = *this;
    		++(*this);
    		return temp;
		}

		// prefix : decrement and then fetch
		INT& operator--()
		{
    		--(this->m_i); // 随着 class 的不同,该行应该有不同的操作
    		return *this;
		}

		// postfix : fetch and then decrement
		const INT operator--(int)
		{
    		INT temp = *this;
    		--(*this);
    		return temp;
		}

		// dereference
		int& operator*() const
		{
   			return (int)m_i;
    		// 以上转换操作告诉编译器,你确实要将 const int 转为 non-const lvalue.
   			// 如果没有这样明白地转型,有些编译器会给你警告,有些更严格的编译器会视为错误
		}
	private:
		int m_i;
};

ostream& operator<<(ostream& os, const INT& i)
{
	os << '[' << i.m_i << ']';
	return os;
}
return (int)m_i;
// 以上转换操作告诉编译器,你确实要将 const int 转为 non-const lvalue.
// 如果没有这样明白地转型,有些编译器会给你警告,有些更严格的编译器会视为错误

const 成员函数:当 在成员函数的声明后面加上 const 关键字时,表示这个函数 不能修改类中的成员变量。因此,在 const 成员函数内部,类的成员变量会被视为 const,即使 它们在类的定义中不是 const 的。也就是说,m_i 在函数 int& operator*() const 内被视为 const int

返回左值引用:函数 int& operator*() const 的返回类型是 int&,表示返回的是一个左值引用。左值引用是 指向某个内存位置的引用,允许通过 该引用去修改该位置存储的值。但是由于该函数被标记为 const,如果直接返回 m_i,编译器会认为 试图返回一个 const 引用,并可能会报错或给出警告,因为 不能从 const 成员函数返回非 const 引用

3.4 前闭后开区间表示法 [ )

任何一个 STL 算法,都需要 获得由一对迭代器(泛型指针)所标示的区间,用以表示操作范围。这一对迭代器所标示的是个 前闭后开区间,以 [first, last) 表示
在这里插入图片描述

3.5 function call 操作符(operator())

1、函数调用操作 也可以被重载

许多 STL 算法都提供了 两个版本,一个用于一般状况(例如 排序时 以递增方式排列), 一个用于特殊状况(例如排序时 由使用者指定以何种特殊关系 进行排列)。像这种情况,需要用户 指定某个条件或某个策略

过去 C 语言时代,欲将 函数当做参数传递,唯有通过函数指针 才能达成

int fcmp( const void* elem1, const void* elem2);

qsort(ia, sizeof(ia)/sizeof(int), sizeof(int), fcmp);

但是函数指针 有缺点,最重要的是 它无法持有自己的状态(所谓 局部状态),也无法达到 组件技术中的可适配性 ——也就是无法 再将某些修饰条件加诸于其上 而改变其状态

#include <iostream>

int applyDiscount(int price) {
    return price * 0.9;  // 10% 折扣
}

int main() {
    int (*discountFunc)(int) = applyDiscount;  // 定义函数指针
    int price = 100;
    std::cout << "Price after discount: " << discountFunc(price) << std::endl; // 输出 90
    return 0;
}

函数指针 discountFunc 指向了 applyDiscount,并且我们通过调用 discountFunc(price) 来应用折扣。这种方法简单直接,但是存在以下局限性:

  1. 无法持有自己的状态:函数指针 只能指向函数,但无法拥有 函数的内部状态。例如,如果 想动态改变折扣的比例(例如从10% 变化为 20%),那么你无法在函数指针中 实现这种状态持有。所有状态变化 只能通过外部逻辑控制
  2. 无法扩展功能:假设 想在应用折扣之前,先执行 某些额外的操作(例如记录日志、统计调用次数等),使用函数指针 是无法做到的,因为函数指针 仅仅指向某个单一的函数,并且无法附加额外的逻辑

2、通过闭包或仿函数解决局限性
1】使用闭包(Lambda表达式)
通过闭包,可以创建 一个带有状态的函数,同时 还可以动态修改它的行为:

#include <iostream>
#include <functional>

int main() {
    int discountRate = 10;  // 初始折扣率为10%
    
    // 使用闭包来创建带有状态的函数
    auto discountFunc = [discountRate](int price) mutable {
        return price * (1 - discountRate / 100.0);
    };

    int price = 100;
    std::cout << "Price after discount: " << discountFunc(price) << std::endl; // 输出 90

    // 修改闭包的状态(折扣率)
    discountRate = 20;
    discountFunc = [discountRate](int price) mutable {
        return price * (1 - discountRate / 100.0);
    };

    std::cout << "Price after discount: " << discountFunc(price) << std::endl; // 输出 80
    return 0;
}

discountFunc 是一个闭包,封装了折扣率 discountRate 的状态。可以在后续代码中 通过修改折扣率 来动态改变折扣函数的行为,避免了 函数指针无法保存状态的缺陷

Lambda 表达式基本结构

[capture](parameters) mutable -> return_type { body }

1)[capture](捕获列表)
捕获列表 用于 指定 lambda 表达式可以“捕获”哪些外部变量,也就是说,可以使用 lambda 表达式之外的变量。在这段代码中,捕获列表是 [discountRate],表示 discountRate 这个外部变量将被捕获并可以在 lambda 表达式中使用

具体捕获方式包括:
1、按值捕获([discountRate]):捕获 discountRate 的当前值,并在 lambda 表达式内部 保留这个值的副本。即使 discountRate 在外部环境中改变,lambda 中的 discountRate 也不会变化,因为它是按值捕获的
2、按引用捕获([&discountRate]):捕获 discountRate 的引用。如果外部的 discountRate 值发生变化,lambda 表达式中的 discountRate 也会随之变化
3、[=] 或 [&]:捕获所有外部变量,分别按值或按引用捕获。[=] 按值捕获所有外部变量,[&] 按引用捕获所有外部变量
在代码中,[discountRate] 表示按值捕获变量 discountRate

2)mutable
默认情况下,lambda 表达式中的捕获变量 在函数体内是不可修改的,尤其是 按值捕获时,lambda 表达式会把捕获的变量视为 const。因此,如果想在 lambda 内部修改按值捕获的变量,需要加上 mutable 关键字
discountRate 是按值捕获的,但通过使用 mutable 关键字,你可以在 lambda 内部修改 discountRate 的值(即使是在按值捕获的情况下)。不过在这段代码中,mutable 虽然使捕获的变量可修改,但实际并没有修改 discountRate

3)auto
auto 关键字用于 自动推导类型。在这里,auto discountFunc 表示 discountFunc 的类型 将由编译器根据右边的 lambda 表达式自动推导。lambda 表达式 实际上 会生成一个匿名的闭包类型(通常称为 lambda closure),可以用 auto 来持有它

2】用仿函数(Functor)
另一种方式是 使用仿函数,即通过重载函数 调用运算符 operator() 来定义一个带有状态的对象:

#include <iostream>

class Discount {
public:
    Discount(int rate) : discountRate(rate) {}

    // 重载函数调用运算符,变成仿函数
    int operator()(int price) const {
        return price * (1 - discountRate / 100.0);
    }

private:
    int discountRate;  // 折扣率
};

int main() {
    Discount discountFunc(10);  // 调用 Discount 的构造函数,创建一个带有10%折扣的仿函数
    int price = 100;
    std::cout << "Price after discount: " << discountFunc(price) << std::endl;  
    // 输出 90,使用重载的调用运算符

    Discount discountFunc20(20);  // 创建一个带有20%折扣的仿函数
    std::cout << "Price after discount: " << discountFunc20(price) << std::endl;  // 输出 80

    return 0;
}

Discount 类是一个仿函数,它内部存储了折扣率。通过创建不同的 Discount 对象,我们可以灵活地应用不同的折扣率,而且还可以在同一个对象中保持状态。这种方式相比函数指针更具扩展性和灵活性

2、STL 算法的特殊版本 所接受的所谓“条件”或 “策略”或 “一整组操作”,都以仿函数形式呈现。所谓仿函数(functor)就是 使用起来像函数一样的东西。如果 针对某个 class 进行 operator() 重载,它就成为一个仿函数

// file: lfunctor.cpp 
#include <iostream> 
using namespace std;

// 由于将 operator() 重载了,因此 plus 成了一个仿函数 
template <class T> 
struct plus {     
	T operator()(const T& x, const T& y) const { return x + y; } 
};

// 由于将 operator() 重载了,因此 minus 成了一个仿函数 
template <class T> 
struct minus {     
	T operator()(const T& x, const T& y) const { return x - y; } 
};

int main() 
{     
	// 以下产生仿函数对象     
	plus<int> plusobj; 
	minus<int> minusobj;
	// 以下使用仿函数,就像使用一般函数一样 
	cout << plusobj(3,5) << endl; // 8 
	cout << minusobj(3,5) << endl; // -2

	// 以下直接产生仿函数的临时对象(第一对小括号),并调用之(第二对小括号) 
	cout << plus<int>()(43,50) << endl; // 93 
	cout << minus<int>()(43,50) << endl; // -7
}

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

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

相关文章

关于MATLAB计算3维图的向量夹角总是不正确的问题记录

文章目录 问题描述解决方法完整代码 问题描述 因为最近在做无人机的一个项目&#xff0c;所以需要画出无人机的轨迹&#xff0c;然后再提取特征值&#xff0c;我这里在计算夹角的时候发现为什么在视觉上明明看的是钝角但是实际计算出来却是锐角的角度。 如下图所示&#xff0c…

大觅网之环境部署(Environment Deployment of Da Mi Network)

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:Linux运维老纪的首页…

数据保护从现在开始:如何抵御 .[RestoreBackup@cock.li].SRC 勒索病毒

导言 勒索病毒是一种不断演变的网络威胁&#xff0c;.[RestoreBackupcock.li].SRC、[chewbaccacock.li].SRC勒索病毒便是其中一种新型的攻击手段。该病毒通过加密用户文件并要求支付赎金来恢复访问&#xff0c;给个人和企业带来了严重的安全风险和经济损失。本文91数据恢复将探…

uniapp使用uview2上传图片功能

官网地址Upload 上传 | uView 2.0 - 全面兼容 nvue 的 uni-app 生态框架 - uni-app UI 框架 前提&#xff0c;需要下载vuew2插件 <view class"upload"><view class"u-demo-block__content"><view class"u-page__upload-item"&…

进程状态的优先级

1.进程的状态&#xff08;所有系统&#xff09; 因为是对于所有系统的&#xff0c;所以描述会很抽象。 补充知识&#xff1a; 并行和并发 并行&#xff1a;多个进程再多个cpu下分别同时运行并发&#xff1a;多个进程在一个cpu下采取进程切换的方式&#xff0c;在一段时间内&…

fiddler抓包06_抓取https请求(chrome)

课程大纲 首次安装Fiddler&#xff0c;抓https请求&#xff0c;除打开抓包功能&#xff08;F12&#xff09;还需要&#xff1a; ① Fiddler开启https抓包 ② Fiddler导出证书&#xff1b; ③ 浏览器导入证书。 否则&#xff0c;无法访问https网站&#xff08;如下图&#xff0…

prometheus通过nginx-vts-exporter监控nginx

Prometheus监控nginx有两种方式。 一种是通过nginx-exporter监控&#xff0c;需要开启nginx_stub_status,主要是nginx自身的status信息&#xff0c;metrics数据相对较少&#xff1b; 另一种是使用nginx-vts-exporter监控&#xff0c;但是需要在编译nginx的时候添加nginx-module…

MyBatis 分批次执行(新增,修改,删除)

import com.google.common.collect.Lists;import java.util.Iterator; import java.util.List; import java.util.function.Consumer;/*** Description mybatis分批插入数据使用* Author WangKun* Date 2024/9/19 11:20* Version*/ public class MyBatisSqlUtils {/*** param d…

用户态缓存:高效数据交互与性能优化

目录 1. 用户态缓存区工作背景 1.1 为什么每条连接都需要读写缓存区 1.1.1 读缓存区&#xff08;Read Buffer&#xff09; 1.1.2 写缓存区&#xff08;Write Buffer&#xff09; 1.2 用户态缓存区的工作流程 1.3 用户态缓存区的重要性 2. UDP 和 TCP 的设计差异 2.1 UD…

神经网络 卷积层 参数共享

参数共享常用于神经网络卷积层中&#xff0c;共享的实际上就是说卷积核中的参数一直保持不变&#xff0c;如下所示就可以称为共享参数啦&#xff01;&#xff01;

C# 实时流转换为m3u8

主要通过FFmpeg 执行命令进行转换 FFmpeg 下载地址 命令行 ffmpeg -i "rtsp://your_rtsp_stream_address" -codec: copy -start_number 0 -hls_time 10 -hls_list_size 12 -f hls "output.m3u8"start_number 设置播放列表中最先播放的索引号&#xff0c;…

JVM基础篇学习笔记

【注&#xff1a;本文章为自学笔记&#xff0c;仅供学习使用。】 一、JVM简介 JVM是Java虚拟机的缩写&#xff0c;本质上是运行在计算机上面的程序&#xff0c;作用是运行Java字节码文件。 1.1 JVM的功能 Java如果不做优化&#xff0c;则性能不如C/C&#xff0c;因为后者会…

uv-ui组件的使用——自定义输入框的样式

一、官网的使用 二、自定义修改样式 我是在小程序中使用此组件 想要自定义修改样式的话&#xff0c;需要placeholderClass加上 placeholderStyle配合使用 tip1&#xff1a;单独使用placeholderClass&#xff0c;他只会第一次渲染时生效&#xff0c;输入文字再清除后就不生效…

Spring面试题合集

Spring 1.谈谈你对Spring的理解 首先Spring是一个轻量级的开源框架&#xff0c;为Java程序的开发提供了基础架构支持&#xff0c;简化了应用开发&#xff0c;让开发者专注于开发逻辑&#xff1b; 同时Spring是一个容器&#xff0c;它通过管理Bean的生命周期和依赖注入&#…

flask项目初始化

1、初始环境 python3.8 2、flask文档地址&#xff1a;https://flask.palletsprojects.com/en/latest/installation/#install-flask 3、初始化项目 $ mkdir myproject $ cd myproject $ python3 -m venv .venv $ . .venv/bin/activate $ pip install Flask4、打开项目mypr…

机器翻译之多头注意力(MultiAttentionn)在Seq2Seq的应用

目录 1.多头注意力&#xff08;MultiAttentionn&#xff09;的理念图 2.代码实现 2.1创建多头注意力函数 2.2验证上述封装的代码 2.3 创建 添加了Bahdanau的decoder 2.4训练 2.5预测 3.知识点个人理解 1.多头注意力&#xff08;MultiAttentionn&#xff09;的理念图…

云服务器使用

最近搭建一个内网穿透工具&#xff0c;推荐一个云服务器&#xff1a; 三丰台&#xff1a;https://www.sanfengyun.com/ 作为学生党这个服务器是免费的可以体验使用&#xff01;可以使用免费虚拟主机和云服务器&#xff0c;写一个申请的基本步骤方便大家构建 申请步骤&#x…

11.1图像的腐蚀和膨胀

基本概念-图像腐蚀 图像腐蚀是一种用于去除图像中小的对象或者突出物体边缘的形态学操作。 图像腐蚀&#xff08;erosion&#xff09;的基本概念 图像腐蚀通常用于二值图像&#xff0c;其基本原理是从图像中“侵蚀”掉一些像素点&#xff0c;这些像素点通常是边界上的或者是孤…

Word中引用参考文献和公式编号的方法

文章目录 应用参考文献对于单个文献引用多于多个文献同时引用 公式编号手动编号自动编号 参考&#xff1a; 应用参考文献 对于单个文献引用 word中的参考文献用交叉应用实现。 首先&#xff0c;将参考文献编号&#xff1a; 然后&#xff0c;在需要引用的地方用交叉引用插入…

VM虚拟机使用的镜像文件下载

文章目录 Windows系统进入微软官网下载工具以Windows10为例下载镜像文件 Windows系统 进入微软官网下载工具 微软中国官网&#xff1a;https://www.microsoft.com/zh-cn/ 以Windows10为例下载镜像文件 选择下载的路径 开始下载 安装windows10操作系统出现Time out问题及解决办…