🥕 关联容器操作
type
status
date
slug
summary
tags
category
icon
password
Property
🥕
目录

 
关联容器还定义了额外的一些类型,这些类型表示容器的keyvalu类型:
  • key_type :容器的 key 的类型
  • mapped_type :每个 key 对应的值的类型,只有map定义了此类型
  • value_type :对于 set 来说与 key_type 一样,对于 map 来说就是 pair<const key_type, mapped_type>
notion image
 
对于 set 类型来说,key_type 和 value_type 是一样的;set中保存的值就是关键字。对于 map 来说,其元素是关键字-值对,意味着每个元素是一个pair对象,pair对象中包含了键和与其对应的值。由于我们不能改变元素的关键字,所以 pair 的关键字部分是 const 的:
🥕 无序关联容器
type
status
date
slug
summary
tags
category
icon
password
Property
 
 
新标准库定义了4个无序关联容器(unordered associative container),这些容器不是使用比较运算符来组织元素,而是使用哈希函数(hash function) 和关键字类型的 == 运算符组织元素。
如果关键字类型固有就是无序的,或者性能测试发现问题可以用哈希技术解决,就可以使用无序容器。
无序容器和对应的有序容器通常可以相互替换。但是由于元素未按顺序存储,使用无序容器的程序输出一般会与有序容器的版本不同。
无序容器在存储上组织为一组桶,每个桶保存零或多个元素。无序容器使用一个哈希函数将元素映射到桶。为了访问一个元素,容器首先计算元素的哈希值,它指出应该搜索哪个桶。容器将具有一个特定哈希值的所有元素都保存在相同的桶中。因此无序容器的性能依赖于哈希函数的质量和桶的数量及大小。
 
无序容器管理操作:
notion image
默认情况下,无序容器使用关键字类型的==运算符比较元素,还使用一个 hash<key_type> 类型的对象来生成每个元素的哈希值。标准库为内置类型和一些标准库类型提供了hash模板。因此可以直接定义关键字是这些类型的无序容器,而不能直接定义关键字类型为自定义类类型的无序容器,必须先提供对应的hash模板版本。
 
除了使用默认的hash 模板之外,还可以提供自己的相等比较函数和计算哈希值的函数
🍄 空间配置器
type
status
date
slug
summary
tags
category
icon
password
Property
 
 
为什么需要空间配置器
频繁使用malloc分配内存的造成的问题:
  • 空间申请与释放需要用户自己管理,容易造成内存泄漏
  • 频繁向系统申请小块内存块,容易造成内存碎片
  • 频繁向系统申请小块内存,影响程序运行效率
  • 直接使用malloc与new进行申请,每块空间前有额外空间浪费
  • 申请空间失败怎么应对
  • 代码结构比较混乱,代码复用率不高
  • 未考虑线程安全问题
 
🥔 泛型算法
type
status
date
slug
summary
tags
category
icon
password
Property
 
🥔
目录

 
标准库容器定义的操作相当少:大部分用于添加移除元素,访问首尾元素,判断容器是否为空,以及获取首元素和尾后元素的迭代器。还需要其它有用的操作:查找特定的元素,替换或移除特定值,重排序容器中的元素。
与其将这些操作定义为每个容器类型的成员,标准库定义了一系列通用算法:算法意思是它们实现了常见的经典算法如排序和搜索,通用是由于它们操作与不同类型的元素以及跨越多种容器类型——不仅仅是标准库中的类型如 vectorlist,还包括内置数组类型以及其它自定义的序列类型。
 
 
绝大部分算法定义在 algorithm 头文件中。标准库还在numeric 头文件中定义了一组数值泛型算法。
通常来说,算法并不直接在容器上进行工作,而是通过遍历由两个迭代器组成的元素范围进行操作。当算法遍历整个范围时,它会对每个元素做一些事情。如要查找容器中的特定元素值最简单的方式是调用 find 算法:
🥔 定制操作
type
status
date
slug
summary
tags
category
icon
password
Property
 
 

 
大多数算法会比较容器中的元素。默认情况下算法使用的是 < 或 == 操作符。算法还定义了允许我们提供自己的操作来替换默认操作符的版本。
比如sort 算法使用的是 < 操作符。但序列可能不是按照<进行排序,或者有些类型没有<操作符,在这两种情况下都需要重载默认的 sort 行为。
 

向算法传递函数

对于字符串序列进行排序可以是先按照长度进行排序,对于长度一样的再按照字典顺序进行排序。为了这样做需要使用第二个版本的 sort,这个版本的 sort是重载过的,它有第三个参数称之为谓词(predicate)
🥔 迭代器和算法结构
type
status
date
slug
summary
tags
category
icon
password
Property
 
🥔
目录

iterator

除了定义在每个容器中的迭代器,标准库还在iterator 头文件中定义了几种别的迭代器:
  • 插入迭代器(Insert iterators):这些迭代器绑定到容器上,并且可用于往容器中插入元素
  • 流迭代器(Stream iterators):这些迭代器绑定到输入输出流上,并可用于迭代相关的 IO 流
  • 反向迭代器(Reverse iterators):这些迭代器向后移动而不是向前移动,除了 forward_list 之外都提供反向迭代器
  • 移动迭代器(Move iterators):特殊的迭代器用于移动元素而不是拷贝
 
🥔 特定容器算法
type
status
date
slug
summary
tags
category
icon
password
Property
 
与别的容器不同的是,list 和 forward_list 将几个算法定义为自己的成员。如:链表类型定义自己的 sortmergeremovereverseunique 版本。通用的 sort 算法需要随机访问迭代器。因而,sort 不能被用于 list 和 forward_list,原因是它们分别只提供双向迭代器和前向迭代器。
其它一些通用算法可用于链表类型,但是会损失性能。这些算法交换输入序列中的元素。链表可以通过交换链接来达到目的,而不是交换元素的值。因而,链表特定的版本会比通用版本有一个更好的性能。
以下是链表特定的算法,没有出现在下面的算法在链表和别的容器类型上表现一样好:
notion image
notion image
以上操作返回 void ,对于 list 和 forward_list 有限使用成员版本而不是通用算法版本。
 
splice 成员
链表类型还定义了 splice(拼接) 算法,这是链表特有的算法
notion image
链表特有的算法类似于其通用算法版本。然而一个重大的区别就是链表版本会改变其底层容器。如链表版本的 remove 会真的执行移除操作,链表版本的 unique 会移除第二个及之后的重复元素。
🌽 动态内存管理
type
status
date
slug
summary
tags
category
icon
password
Property

 

C/C++内存分布

在计算机中,每个应用程序之间的内存是相互独立的,通常情况下应用程序 A 并不能访问应用程序 B,当然一些特殊技巧可以访问。例如在计算机中,一个视频播放程序与一个浏览器程序,它们的内存并不能访问,每个程序所拥有的内存是分区进行管理的。
在计算机系统中,运行程序 A 将会在内存中开辟程序 A 的内存区域 1,运行程序 B 将会在内存中开辟程序 B 的内存区域 2,内存区域 1 与内存区域 2 之间逻辑分隔。
notion image
在程序 A 开辟的内存区域 1 会被分为几个区域,这就是内存四区,内存四区分为栈区、堆区、数据区与代码区。
🌽 智能指针
type
status
date
slug
summary
tags
category
icon
password
Property
 
🌽
目录

 
新版本的 C++ 最重要的更新之一就是提供了更为强大的智能指针(smart pointer),智能指针是模拟指针的抽象数据结构,提供了额外的功能包括内存管理(memory management)或者界限检查(bounds checking)。在保留性能的情况下,减少了因为指针滥用导致的难以查找的bug。智能指针常用于跟踪其指向的内存,亦可用于管理其它资源,比如:网络连接和文件句柄。
在智能指针中,一个对象什么情况下被析构或被删除,是由指针本身决定的,并不需要用户进行手动管理。有自动垃圾回收机制的语言不需要智能指针用于内存管理,但依然可以用于缓存管理和其它资源管理(如:文件句柄和网络)。
 
之前的程序只使用了静态(static)栈(stack)内存。静态内存用于本地静态变量、类静态成员和定义在任何函数之外的变量。栈内存则用于保存函数内定义的非静态对象。在静态和栈内存中分配的对象,其创建和销毁都由编译器自动管理。
除此之外,每个程序还有一个内存池,这种内存被称为自由内存(free store)堆(heap)。程序使用堆来分配给动态对象,即在运行时分配内存给对象。这种对象的生命周期由程序进行管理,代码中必须显式销毁不再使用的对象。除非是必要的情况下,不应该直接管理动态内存,因为,这是非常容易出错的。
 
🌽 动态数组
type
status
date
slug
summary
tags
category
icon
password
Property
🌽

 
有时候希望在程序中可以一次性分配一个数组的内存。C++提供了两种方式来这样做:通过新的new ,或者通过模板类allocator来分离分配和初始化过程。一般来说 allocator 将得到更好的性能以及更灵活的内存管理。
然而,除非是希望直接管理动态数组,使用标准库中容器通常是更好的方式。使用容器将更加简单,更少的 bug 而且具有更好的性能。而且,可以使用默认定义的拷贝、赋值和析构函数。而动态分配数组的类则需要自己定义这些函数来进行相应的内存管理。
 

new 和数组

为了让new 分配一个对象数组,需要在类型名之后跟一对方括号,在其中指明要分配的对象的数目。
通过在类型后给出数组的长度,new创建出一个动态数组,并返回指向首元素的指针。
🌶️ 拷贝、赋值与析构
type
status
date
slug
summary
tags
category
icon
password
Property
 
🌶️
目录

 
C++ 的核心概念是类。C++ 类定义构造函数来控制当类对象初始化时应该做什么。类同样可以定义函数来控制如何进行拷贝、赋值、移动和销毁。在这些方面C++有别于其它语言,很多其它语言并不提供控制这些方面的基础设施。
 
类是如何控制当此类对象进行拷贝、赋值、移动和销毁时所做的事。类控制这些动作的成员函数分别是:
  • 拷贝构造函数(copy constructor)
  • 拷贝赋值操作符(copy-assignment operator)
  • 移动构造函数(move constructor)
🌶️ 拷贝控制和资源管理
type
status
date
slug
summary
tags
category
icon
password
Property
 
通常,如果类有自己管理的资源(动态分配的内存、网络、文件句柄等)肯定需要定义拷贝控制成员。这些类需要定义析构函数来释放资源。一旦其需要析构函数,那就意味着肯定需要拷贝构造函数和拷贝赋值操作符。
 
拷贝类对象有两个设计决定:以值的方式拷贝,以指针的方式拷贝。
  • 行为像值的类: 每个类的对象都有自己的实现
  • 行为像指针的类: 所有类的对象共享类的资源(类似于shared_ptr智能指针,每有一个对象持有该资源则引用计数+1,每有一个对象释放该资源则引用计数-1,引用计数为0时释放内存)
行为与值一样的类有其自己的状态,当拷贝这种对象时,拷贝后的对象与原始对象是互相独立的,对任何一方作出改变都不会影响到另外一方;行为与指针类似的类则共享状态,当拷贝这种对象时,原始对象和拷贝后的对象具有相同的底层数据,对任何一样的改变都会影响到另外一方。
通常,类直接拷贝内置类型成员,这些成员就是值,其行为与值是完全一样的。拷贝指针的不同方式将影响对象是值还是指针。
 

行为像值的类

对于类管理的资源,每个对象都应该有一份自己的拷贝。如下面的string类型的指针 ,使用拷贝构造函数或者赋值运算符时,每个对象拷贝的都是指针成员ps指向的string而非ps本身 。换言之,每个对象都有一个ps而不是给ps加引用计数。