第七章 类
- 定义在类内部的函数是隐式的inline函数
- 常量成员函数
- C++允许把const 关键字放在成员函数的参数列表之后。此时,紧跟在参数列表后面的const表示this是一个指向常量的指针。
- 常量对象,以及常量对象的引用或指针都只能调用常量成员函数
- 构造函数
- 不能声明成const
- 当创建类的一个const对象时,直到构造函数完成初始化过程,对象才能真正取得其“常量”属性
- c++11新标准,如果需要默认行为,可以通过在参数列表后面写上
=default
来要求编译器生成构造函数。 - 构造函数不应该轻易覆盖掉类内的初始值,除非新赋的值与原值不同。
- 如果不能使用类内初始值,则所有构造函数都应该显式地初始化每个内置类型的成员
- class 和 struct 关键字
- 唯一区别
- 默认访问权限不一样
- struct 默认public
- class 默认private
- 默认访问权限不一样
- 唯一区别
- 友元
- 用一条以friend关键字开始的函数声明语句
- 最好在类定义开始或结束前的位置集中声明友元
- 许多编译器并未强制限定友元函数必须在使用之前在类的外部声明
- 建议提供一个独立的函数声明,做移植兼容
- inline 成员函数也应该与相应类定义在同一个头文件中
- mutable
- 可变数据成员
- 用于不会是const,即使它是const对象的成员
- 用一个const成员函数可以改变一个可变成员的值
- 当提供一个类内初始值时,必须以符号=或花括号表示
- 一个const 成员函数如果以引用的形式返回*this,那么它的返回类型将是常量引用
- 类类型
- 即使两个类的成员列表完全一致,他们也是不同的类型。对于一个类来说,他的成员和其他任何类(或者任何其他作用域)的成员都不是一回事。
- 类之间的友元关系
- 尽管重载函数的名字相同,但他们仍然是不同的函数。因此,如果一个类想把一组重载函数声明成它的友元,它需要对这组函数中每一个分别做声明。
- 构造函数
- 如果成员是const、引用,或者属于某未提供默认构造函数的类类型,我们必须通过构造函数初始值列表为这些成员提供初始值。
- 初始化和赋值的区别事关底层效率问题
- 初始化
- 直接初始化数据成员
- 赋值
- 先初始化再赋值
- 除了效率问题外,更重要的是,一些数据成员必须被初始化。建议使用构造函数初始值。
- 初始化
- 构造函数成员初始化顺序
- 成员的初始化顺序与它在类定义中的出现顺序一致。
- 构造函数初始值列表中初始值的前后位置关系不影响实际的初始化顺序。
- 最好令构造函数初始值的顺序与成员声明的顺序保持一致。尽量避免使用某些成员初始化其他成员。
- 如果一个构造函数为所有参数都提供了默认实参,则它实际上也定义了默认构造函数
- 成员的初始化顺序与它在类定义中的出现顺序一致。
- 委托构造函数
-
一个委托构造函数使用它所属类的其他构造函数执行它自己的初始化过程
class Sales_data { public: //非委托构造函数 Sales_data(std::string s, unsigned cnt, double price) : bookNo(s), units_sold(cnt), revenue(cnt * price) {} //下面都是委托构造函数 Sales_data() : Sales_data("", 0, 0) {} Sales_data(std::string s) : Sales_data(s, 0, 0) {} Sales_data(std::istream &is) : Sales_data() { read(is, *this); } }
-
- 默认构造函数的作用
- 默认初始化在以下情况下发生
- 当在块作用域内不使用任何初始值定义一个非静态变量
- 当一个类本身含有类类型的成员,且使用合成的默认构造函数时
- 当类类型成员没有在构造函数初始值列表中显式的初始化时
- 值初始化在以下情况下发生
- 在数组初始化的过程中,如果我们提供的初始值数量少于数组的大小时
- 当我们不使用初始值定义一个局部静态变量时
- 当我们使用
T()
的表达式显式的请求值初始化时
- 如果定义了其他构造函数,那么最好也提供一个默认构造函数
- 默认初始化在以下情况下发生
- 隐式的类类型转换
- 转换构造函数
- 如果构造函数只接受一个实参,则它实际上定义了转换为此类类型的隐式转换机制
- 只允许一步类类型转换
- 编译器只会自动执行一步类型转换
- 抑制构造函数定义的隐式转换
- 将构造函数声明为explicit
- 只对一个实参的构造函数有效
- explicit 构造函数只能用于直接初始化
- 标准库中含有显式构造函数的类
- 接受一个单参数的const char* 的string构造函数
- 接受一个容量参数的vector构造函数
- 将构造函数声明为explicit
- 转换构造函数
- 聚合类
- 用户可以直接访问其成员,并且有特殊的初始化语法形式
- 聚合类的满足条件
- 所有成员都是public
- 没有定义构造函数
- 没有类内初始值
- 没有基类,也没有virtual函数
- 显式初始化类对象的成员有三个缺陷
- 要求类的所有成员都是public
- 将正确初始化每个对象的每个成员的重任交给了类的用户,可能由于忘掉初始化某个初始值,或提供一个不恰当的初始值,会引起不必要的错误
- 添加或删除一个成员之后,所有初始化语句都要更新
- 字面值常量类
- 定义
- 数据成员都是字面值类型的聚合类
- 如果不是聚合类,满足下列条件也是一个字面值常量类
- 数据成员都必须是字面值类型
- 类必须至少有一个constexpr构造函数
- 如果一个数据成员含有类内初始值,必须是一个常量表达式
- 或者该数据成员是某种类类型,则初始值必须使用自己的constexpr构造函数
- 类必须使用析构函数的默认定义
- 构造函数不能是const的,但是字面值常量类的构造函数可以是constexpr函数
- 一个字面值常量类必须至少提供一个constexpr构造函数
- constexpr构造函数可以声明成 = default
- constexpr构造函数必须初始化所有数据成员
- 定义
- 类的静态成员
- 有时候类需要它的一些成员与类本身直接相关,而不是与类的各个对象保持关联
- 声明静态成员
- 在成员的声明前加上关键字static
- 静态成员可以是public或private
- 静态数据成员的类型可以是常量、引用、指针、类类型等
- 静态成员函数不含有this指针
- 静态成员函数不能声明成const
- 成员函数不用通过作用域运算符就能直接使用静态成员
- 当指向类外部的静态成员时,必须指明成员所属的类名
- static关键字则只出现在类内部的声明语句中
- 类内初始化
- 类的静态成员不应该在类内部初始化
- 可以为静态成员提供const整数类型的类内初始值
- 满足条件
- 静态成员必须是字面值常量类型的constexpr
- 即使一个常量静态数据成员在类内部被初始化,通常也应该在类的外部定义一下该成员
- 满足条件
第八章 IO库
- IO 类
iostream
定义了用于读写流的基本类型istream, wistream
从流读取数据ostream, wostrean
向流写入数据iostream, wiostream
读写流
fstream
定义了读写命名文件的类型iftream, wifstream
从文件读取数据ostream, wostrean
向文件写入数据iostream, wiostream
读写文件
sstream
定义了读写内存string对象的类型istringstream, wistringstream
从string读取数据ostringstream, wostringstrean
向string写入数据stringstream, wstringstream
读写string
- 不能拷贝IO对象
- 条件状态
- strm 表示上述列表中任意一种IO类型
strm::iostate
机器相关的类型,提出表达条件状态的完整功能strm::badbit
指出流已崩溃strm::failbit
IO操作失败strm::eofbit
流到达文件结束strm::gootbit
流末处于错误状态,此值保证为零s.eof()
若是流s的eofbit位置,返回trues.fail()
若是流s的failbit或badbt位置,返回trues.bad()
若是流s的badbit位置, 返回trues.good()
若是流s处于有效状态,返回trues.clear()
将流s中所有条件状态复位,将流的状态设置为有效,返回voids.clear(flags)
根据给定的flags标志位,将流s中所有条件状态复位。flags的类型为strm::iostate,返回voids.setstate(flags)
根据给定的flags标志位,将流s中对应条件状态复位。flags的类型为strm::iostate,返回voids.rdstate()
返回流s的当前条件状态,返回值类型为strm::iostate
- 输出缓冲
- 导致缓冲区刷新的原因
- 程序正常结束,作为main函数的return操作一部分被执行
- 缓冲区满时,需要刷新缓冲,而后新的数据才能继续写入
- 使用endl来显式刷新缓冲区
- 在每个输出操作后,使用unitbuf设置流的内部状态,来清空缓冲区
- 一个输出流可能被关联到另一个流时
- 导致缓冲区刷新的原因
- 使用文件流对象(api可以在使用的时候回来看)
- 当一个fstream对象被销毁时,close会自动被调用
- 以out模式打开文件会丢弃已有数据
- 保留被ofstream打开的文件中已有数据的唯一方法是显式指定app或in模式
第九章 顺序容器
- 现代C++程序应该使用标准库容器,而不是更原始的数据结构,如内置数组
- 选择容器的基本原则
- 除非有很好的理由选择其他容器,否则应使用vector
- 如果程序有很多小的元素,且空间的额外开销很重要,则不要使用list或forward_list
- 如果程序要随机访问元素,应使用vector或deque
- 如果要在容器中间插入或删除元素,应使用list或forward_list
- 如果要在头尾位置插入或删除元素,但不会在中间位置进行插入或删除操作,使用deque
- 如果程序只有在读取输入时才需要在容器中间位置插入元素,所有需要随机访问,则
- 首先,确定是否需要在容器中间位置添加元素,通常先向vector追加数据,再使用标准库sort函数重排容器,从而避免在中间位置添加元素。
- 如果必须在中间位置插入元素,输入阶段使用list,完成输入后,将list中内容复制到一个vector中
- 迭代器范围
- 一个迭代器范围由一对迭代器表示,两个迭代器分别指向同一个容器中的元素,或者是尾元素之后的位置。这两个迭代器通常被称为begin和end,或者first和last。它们用于标记容器元素的范围
- 这种元素范围被称为左闭合区间
[begin, end)
- 将一个容器初始化为另一个容器的拷贝时,两个容器的容器类型和元素类型必须相同
- 只有顺序容器的构造函数才接受大小参数,关联容器并不支持
- assign (仅顺序容器)
- 顺序容器(array除外) 定义了assign成员,允许我们从一个不同但相容的类型赋值,或者从容器的一个子序列赋值。
- assign 操作作用参数所指定的元素(的拷贝)替换左边容器中的所有元素
list<string> names;
vector<const char*> oldstyle;
names = oldstyle;
names.assign(oldstyle.cbegin(), oldstyle.cend());
- 顺序容器(array除外) 定义了assign成员,允许我们从一个不同但相容的类型赋值,或者从容器的一个子序列赋值。
- swap
- 除array外,swap不对任何元素进行拷贝、删除或者插入操作。因此可以保证在常数时间内完成
- 交换两个array会真正交换它们的元素。 因此,交换两个array所需的时间与array中元素的数目成正比
- 在新标准库中,容器提供成员函数版本的swap,也提供非成员版本的swap。而早期标准库版本中提供成员函数版本的swap. 非成员版本的swap在泛型编程中是非常重要的。统一使用非成员版本的swap是一个好习惯。
- 向顺序容器添加元素(api)
- 向一个vector、string、或deque插入元素会使所有指向容器的迭代器、引用和指针失效。
- 在一个vector或string的尾部之外的任何位置、或一个deque的首尾之外的任何位置添加元素,都需要移动元素。
- 向一个vector或string添加元素可能引起整个对象存储空间的重新分配。
- 重新分配一个对象的存储空间需要分配新的内存,并将元素从旧的空间移动到新的空间中
- 容器元素是拷贝
- 使用emplace操作
- 新标准引入了三个新成员(emplace_front、emplace 和 emplace_back), 这些操作构造而不是拷贝元素。
- 容器对象调用push或insert成员函数时,进入到容器的是内容的拷贝
- 容器对象调用emplace成员函数时,是将内容(一般是构造函数的实参)在容器管理内存中直接构造(调用元素类型类的构造函数)
- 新标准引入了三个新成员(emplace_front、emplace 和 emplace_back), 这些操作构造而不是拷贝元素。
- 访问元素
- array在内的每个顺序容器都用一个front成员,而除forward_list之外所有顺序容器都有一个back函数。他们分别返回首元素和尾元素
- 容器访问元素的成员函数返回的都是引用
- 下标操作和安全随机访问
- 如果希望保证下标合法可以使用at成员函数,如果越界,at会抛出out_of_range 异常
- 元素删除
- 删除deque中除首尾元素之外的任何元素都会使所有迭代器、引用、指针失效。
- 指向vector或string中删除点之后位置的迭代器、引用和指针都会失效
- 删除元素的成员函数不会检查其参数,在删除之前必须确保待删除内容存在
- 当在forward_list中添加或删除元素时,要注意两个迭代器
- 指向要处理的内容的
- 指向其前驱的
- 改变容器大小
- 如果resize缩小容器,则指向被删除元素的迭代器、引用和指针都会失效
- 对vector、string或deque进行resize可能导致迭代器、指针和引用失效
- 容器操作可能使迭代器失效
- 向容器中添加元素和从容器中删除元素的操作可能会使指向容器元素的指针、引用或迭代器失效。
- 向容器添加元素后
- 如果容器是vector或string,且存储空间被重新分配,则指向容器的迭代器、指针和引用都会失效。 如果存储空间未重新分配,指向插入位置之前的元素的迭代器、指针和引用仍有效,但指向插入位置之后元素的迭代器、指针和引用 将会失效
- 对于deque,插入到除首尾位置之外的任何位置都会导致迭代器、指针和引用失效。 如果在首尾位置添加元素,迭代器会失效,但指向存在的元素的引用和指针不会失效。
- 对于list和forward_list,指向容器的迭代器(包括尾后迭代器和首前迭代器)、指针和引用仍有效。
- 从容器中删除一个元素后
- 对于list和forward_list,指向容器其他位置的迭代器、引用和指针仍有效
- 对于deque,如果在首尾之外任何位置删除元素、那么指向被删除元素外其他元素的迭代器、引用或指针也会失效。如果删除deque的尾元素,则尾后迭代器也会失效,但其他迭代器、引用和指针不受影响。如果删除首元素,这些也不会受到影响
- 对于vector和string,指向被删除元素之前元素的迭代器、指针和引用仍有效
- 感觉这里解释的很啰嗦,其实只要掌握容器使用的相关数据结构就很好理解了,比如list使用的是链表,增删当然不会存在问题。而vector使用的是线性表,线性表中删除元素会使线性表中,在删除元素之后位置的元素全部向前移位,当然迭代器、引用和指针都会失效。
- 管理迭代器
- 保证每次改变容器操作之后正确的重新定位迭代器
- 在erase后,不必递增迭代器,因为erase返回的迭代器已经指向序列中下一个元素。
- 在一个循环中插入/删除deque、string或vector中的元素,不要缓存end返回的迭代器
- vector对象是如何增长的
- capacity 操作
- 容器在不扩张内存空间的情况下可容纳多少元素
- reserve
- 通知容器它应该保持多少个元素
- 并不改变容器中元素的数量
- 仅影响vector预先分配多大的内存空间
- shrink_to_fit
- 要求deque、vector、string回退不需要的内存空间
- 并不保证一定回退内存空间
- 每个vector实现都可以选择自己的内存分配策略,但是必须遵守的一条原则是:只有当迫不得已时才可以分配新的内存空间。
- 虽然不同的实现可以采用不同的分配策略,但需遵循
- 确保push_back向vector添加元素的操作有高效率
- 虽然不同的实现可以采用不同的分配策略,但需遵循
- capacity 操作
- string搜索操作
- 如果搜索失败,则返回一个名为string::npos的static成员
- 定义为const string::size_type
- string搜索的返回值应使用unsigned保持
- rfind成员函数搜索最后一个匹配(逆向搜索)
- compare 函数
- 与C标准库的strcmp函数相似
- 数值转换
- 如
stoi
- string参数的第一个非空白符必须是符号(+或-)或数字
- 以0x或0X开头表示十六进制数的字符串
- 以(.)开头的浮点数字符串
- 包含e或E的指数字符串
- 当string不能转换为一个数值时
- 抛出invalid_argument一次
- 如果转换得到的数值无法用任何类型表示
- 抛出out_of_range异常
- 如
- 如果搜索失败,则返回一个名为string::npos的static成员
- 容器适配器
- 包括
- stack
- queue
- priority_queue
- 适配器
- 标准库中的一个通用概念
- 是一种机制,能使某一种事物的行为看起来像另一种事物一样
- stack 适配器接受一个顺序容器(除array或forward_list外),并使其操作像一个stack一样
- 标准库中的一个通用概念
- 定义适配器
- 每个适配器都有两个构造函数
- 默认构造函数
- 创建一个空对象
- 接受一个容器的构造函数
- 拷贝容器来初始化适配器
- 默认构造函数
- 默认情况下
- stack和queue是基于deque实现的, 也可以使用list和vector实现
- priority_queue是基于vector上实现的,也可以使用deque实现
- 在创建适配器时,可以将一个顺序容器声明作为第二个类型参数,来重载默认容器类型
stack<string, vector<string>> str_stk;
- 每个适配器都有两个构造函数
- 包括
第十章 泛型算法
- 大多数算法都定义在头文件algorithm中
- 标准库还在头文件numeric中定义一组数值泛型算法
- 算法 find
find(vec.cbegin(), vec.cend(), val);
- 算法永远不会执行容器的操作(这句话感觉不严谨)
- 算法永远不会改变底层容器的大小
- 算法可能改变容器中保存的元素值
- 算法也可能在容器内移动元素
- 算法永远不会直接添加或删除元素
- 只读算法
- find、cout
- accumulate
- 定义在头文件numeric中
- 接受三个参数
- 前两个指出需要求和的元素范围
- 第三个参数是和的初始值
- 序列中元素类型必须与第三个参数匹配,或能转换为第三个参数类型
string sum = accumulate(v.cbegin(), v.cend(), string(""));
- 如果只读,建议使用cbegin()和cend();如果计划使用算法返回迭代器改变元素值,就需要使用begin()和end()
- equal
- 确定两个序列是否保存相同的值
- 其基于一个重要假设
- 假定第二个序列至少与第一个序列一样长
equal(roster1.cbegin(), roster1.cend(), roster2.cbegin());
- find_if
- 可以自定义查找规则
- 写容器元素的算法
- fill
- 前两个参数接受一对迭代器表示范围
- 第三个参数为需要输入的值
- fill_n
- 第一个参数为迭代器
- 第二个参数指定写的个数
- 第三个参数写入的值
- 假定
- 迭代器指向一个元素,而从该元素开始至少含有指定写入个数的元素
- 插入迭代器(back_inserter)
- 保证算法有足够元素空间来容纳输出数据的方法
- 定义在iterator头文件中
- 接受一个指向容器的引用返回一个与该容器绑定的插入迭代器
- 当使用该迭代器赋值时,会调用push_back
vector<int> vec;
auto it = back_inserter(vec);
*it = 42;
fill_n(back_inserter(vec), 10, 0);
- fill
- 拷贝算法
- 实现内置数组的拷贝
int a1[] = {0, 1, 2, 3, 4, 5, 6};
int a2[sizeof(a1)/sizeof(*a1)];
auto ret = copy(begin(a1), end(a1), a2);
- ret指向拷贝到a2的尾元素之后的位置
- replace
- 将等于给定值元素改为另一个值
replace(list.begin(), list.end(), 0 , 42); //所有0替换成42
- 实现内置数组的拷贝
- 重排容器元素算法
- sort
- 去重实现
sort(words.begin(), words.end());
auto end_unique = unique(words.begin(), words.end());
words.erase(end_unique, words.end());
- 去重实现
- stable_sort
- 维持相等元素的原有顺序
- sort
- 定制操作
- lambda表达式
-
理解为一个未命名的内联函数
-
[capture list] (parameter list) -> return type { function body }
- capture list 捕获列表
- 函数中定义的局部变量的列表
- parameter list
- 参数列表
- return type
- 返回值类型
- function body
- 函数体
- capture list 捕获列表
-
如果lambda的函数体包含任何一return语句之外的内容,且未指定返回值类型,则返回void
-
一个lambda只有在其捕获列表中捕获一个它所在函数中的局部变量,才能在函数体中使用该变量
-
捕获列表只用于局部非static变量,lambda 可以直接使用局部static变量和它所在函数之外声明的名字
-
类似参数传递,变量的捕获方式也可以是值或引用
- 与参数不同,被捕获的变量的值是在lambda创建时拷贝,而不是在调用中拷贝
-
当以引用方式捕获一个变量时,必须保证在lambda执行时变量是存在的
-
隐式捕获
- 写一个&或=。
-
可变lambda
- 如果希望能改变一个被捕获的变量的值,就必须在参数列表后加上关键字mutable。
-
lambda与函数的选择
- 如果需要很多地方使用相同操作,建议定义函数
- 如果一个操作需要很多语句才能完成,建议定义函数
-
- 标准库bind函数
- 定义在头文件 functional 中, 看作通用函数适配器
- 接受一个可调用对象,生成一个新的可调用对象来使用原对象的参数列表。
- 形式
auto newCallable = bind(callable, arg_list);
- 占位符 placeholders
- arg_list 中的参数可能包含形如_n的名字,其中n是一个整数。
- _n都定义在一个名为placeholders的命名空间中,而该命名空间本身定义在std命名空间
- _1对应的using声明为
- using std::placeholders::_1;
- 每个占位符名字,必须提供一个单独的using声明
- _1对应的using声明为
- 例子
auto g = bind(f, a, b, _2, c, _1);
- g 是一个由两个参数的可调用对象
- 这两个参数分别用占位符_2和_1表示
- 调用该函数,实际上这个bind调用会将
g(_1, _2)
映射为f(a, b, Y, c, X)
- 定义在头文件 functional 中, 看作通用函数适配器
- lambda表达式
- 迭代器
- 插入迭代器 (insert iterator)
- 有三种类型
- back_inserter
- 只有容器支持push_back情况下可以使用
- front_inserter
- 只有容器支持push_front情况下可以使用
- inserter
- back_inserter
- 有三种类型
- 流迭代器 (stream iterator)
- istream_iterator 读取输入流
- ostream_iterator 向一个输出流写数据
- eof 定义为空的istream_iterator
- 不支持递减运算符
- 反向迭代器 (reverse iterator)
- 除forward_list之外,其他容器都支持反向迭代器
- 可以使用rebegin、rend、crbegin和crend成员函数获取反向迭代器
- 使用普通迭代器初始化一个反向迭代器,给反向迭代器赋值时,结果迭代器与原迭代器指向的并不是相同元素。
- 移动迭代器 (move iterator)
- 插入迭代器 (insert iterator)
- 泛型算法结构
- 5类迭代器
- 输入迭代器 (input iterator):只读不写;单遍扫描,只能递增
- 输出迭代器 (output iterator):只写不读;单遍扫描,只能递增
- 前向迭代器 (forward iterator):可读写;多遍扫描,只能递增
- 双向迭代器 (bidirectional iterator):可读写;多遍扫描,可递增递减
- 随机访问迭代器 (random-access iterator):可读写,多遍扫描,支持全部迭代器运算
- 对于向一个算法传递错误类别的迭代器的问题,很多编译器不会给出任何警告或提示
- 输入迭代器
- 可以读取序列中的元素。
- 支持
- 用于比较两个迭代器的相等和不相等运算符(==、!=)
- 用于推进迭代器的前置和后置递增运算 (++)
- 用于读取元素的解引用运算符(*);
- 箭头运算符(->),等价于
(*it).member
- 输出迭代器
- 看作输入迭代器功能上的补集–只写而不读元素
- 支持
- 推进迭代器的前置和后置递增运算符(++)
- 解引用运算符(*), 只会出现在赋值运算符左侧
- 前向迭代器
- 可以读写元素
- 只能在序列中沿一个方向移动
- 双向迭代器
- 可以正向/反向读写序列中的元素
- 还支持前置和后置递减运算符(- -)
- 随机访问迭代器
- 提供在常量时间内访问序列中任意元素的能力
- 支持双向迭代器所有的功能
- 支持
- 用于比较两个迭代器相对位置的关系运算符(<、<=、>、>=)
- 迭代器和一个整数值的加减运算(+、+=、-、-=)
- 计算结果是迭代器在序列中前进(或后退)给定整数个元素后的位置
- 用于两个迭代器上的减法运算符(- -),得到两个迭代器的距离
- 下标运算符(
iter[n]
), 与*(iter[n])
等价
- 算法形参模式
- 四种形式之一
alg(beg, end, other args);
alg(beg, end, dest, other args);
alg(beg, end, beg2, other args);
alg(beg, end, beg2, end2, other args);
beg
和end
表示算法操作的输入范围dest
表示算法可以写入的目的位置的迭代器beg2
和end2
表示接受迭代器输入的范围
- 四种形式之一
- 算法命名规范
- 一些算法使用重载形式传递一个谓词
unique(beg, end);
unique(beg, end, comp);
- _if 版本算法
- 接受一个元素值的算法通常有另一个不同名的版本(不是重载的),该版本接受一个谓词
find(beg, end, val);
find_if(beg, end, pred);
- 接受一个元素值的算法通常有另一个不同名的版本(不是重载的),该版本接受一个谓词
- 区分拷贝元素的版本和不拷贝的版本
reverse(beg, end);
reverse_copy(beg, end, dest);
- 一些算法使用重载形式传递一个谓词
- 5类迭代器
- 特定容器算法
- list和forward_list
- 通用版本sort要求随机访问迭代器,因此不能用于list和forward_list,因为这两类分别提供双向迭代器和前向迭代器。
- 对于list和forward_list,应该优先使用成员函数版本的算法而不是通用算法
lst.merge(lst2)
将lst2的元素合入lst,lst和lst2都必须是有序的lst.merge(lst2, comp)
lst.remove(val)
调用erase删除元素lst.remove_if(pred)
lst.reverse()
反转lst中元素lst.sort()
lst.sort(comp)
lst.unique
调用erase删除同一个值的连续拷贝lst.unique(pred)
- 通用版本sort要求随机访问迭代器,因此不能用于list和forward_list,因为这两类分别提供双向迭代器和前向迭代器。
- splice 成员
- 链表类型定义了splice算法
lst.splice(args)
或flst.splice_after(args)
(p , lst2)
- p是一个指向lst中元素的迭代器
- splice函数将lst2所有元素移动到lst中p之前位置
- splice_after函数将lst2所有元素移动到lst中p之后的位置
- 元素将从list2中删除
- lst2类型必须与lst相同,且不能是同一个链表
(p , lst2, p2)
- p2是一个指向lst2中位置的有效的迭代器
- 将p2指向的元素移动到lst中
- 或将p2之后的元素移动到flst中
- lst2与lst或flst可以是同一个链表
(p , lst2, b, e)
- b和e必须表示lst2中合法范围
- 将给定范围中元素从lst2移动到lst或flst
- lst2与lst或flst可以是相同链表
- 但p不能指向给定范围中元素
- 链表特有的操作会改变容器
- remove的链表版本会删除指定的元素
- unique的链表版本会删除第二个和后续的重复元素
- list和forward_list
第十一章 关联容器
-
关联容器支持高效的关键字查询和访问。主要有:
- map
- key-value
- set
- 支持高效关键字查询操作
- map
-
标准库提供8个关联容器
- 三个维度
- 每个容器或是set或是map
- 关键字是否允许重复
- 名字中包含multi
- 按顺序保存元素
- 不支持的名字都以单词unordered开头
- 类型
- 有序保存
- map
- set
- multimap
- multiset
- 无序集合: 用哈希函数组织
- unordered_map
- unordered_set
- unordered_multimap
- unordered_multiset
- 有序保存
- 三个维度
-
使用关联容器
- set就是关键字的简单集合,当只是想知道一个值是否存在时,set是最有用的
- 关联容器不支持顺序容器的位置相关操作,如push_front或push_back
- 关联容器的迭代器都是双向的
-
有序容器的关键字类型
- 自定义比较操作
- 所提供的操作必须在关键字类型上定义一个严格弱序
- 严格弱序看作–小于等于
- 定义函数具备如下性质
- 感觉都是废话,不记了。有问题回来再看
- 所提供的操作必须在关键字类型上定义一个严格弱序
- 当使用
decltype
来指出自定义操作的类型时- 必须加上一个*来指出我们要使用一个给定函数类型的指针
multiset<Sales_data, decltype(compareIsbn) *> bookstore(compareIsbn);
- 必须加上一个*来指出我们要使用一个给定函数类型的指针
- 自定义比较操作
-
pair类型
- 定义在头文件utility中
make_pair(v1, v2)
- 返回一个用v1和v2初始化的pair。
- 类型从v1和v2的类型推断出来
- 返回一个用v1和v2初始化的pair。
- 关系运算符按字典顺序定义
- 相等–first和second分别相等时,两个pair相等
-
关联容器操作
- 关联容器额外的类型别名
- key_type 此容器类型的关键字类型
- mapped_type 每个关键字关联的类型,只适用于map
- value_type
- set,与key_type相同
- map, 为
pair<const key_type, mapped_type>
- 关联容器额外的类型别名
-
关联容器迭代器
- 一个map的value_type是一个pair,我们可以改变pair的值,但不能改变关键字成员的值
- set的迭代器是const的
- 虽然set类型同时定义了iterator和const_iterator类型,但两种类型都只能允许只读访问set中元素。
- 一个set中的关键字是const的,可以用一个set迭代器来读取元素的值,但不能修改
-
关联容器和算法
- 通常不对关联容器使用泛型算法
- 关键字是const意味着不能将关联容器传递给修改或重排容器的算法
- 关联容器可用于只读取元素的算法
- 如果确定要对一个关联容器使用算法,可以使用泛型copy算法将元素从关联容器拷贝到另一个序列
-
关联容器添加元素
- insert
- 有两个版本
- 接收一对迭代器
- 一个初始化器列表
- map
word_count.insert({word, 1});
word_count.insert(make_pair(word, 1));
word_count.insert(pair<string, size_t>(word, 1));
word_count.insert(map<string, size_t>::value_type(word, 1));
- insert操作
- v 是value_type类型的对象;args用来构造一个元素
- c.insert(v)
- c.emplace(args)
- 只有当元素的关键字不存在c中时才插入元素,返回一个pair
- 包含一个指向指定关键字元素的迭代器
- 一个指示是否插入成功的bool值
- 只有当元素的关键字不存在c中时才插入元素,返回一个pair
- c.insert(b, e)
- b和e是迭代器,表示一个c::value_type类型值的范围
- c.insert(il)
- il为花括号列表,返回void
- 对于map和set,只插入关键字不在c中的元素
- 对于multimap和multiset,则会插入范围中每个元素
- c.insert(p, v) 或 c.emplace(p, args)
- 将迭代器p作为一个提示,指出从哪里开始搜索新元素应该存储的位置
- 返回一个迭代器,指向具有给定关键字的元素
- 有两个版本
- insert
-
删除元素
- 使用erase,传递一个迭代器来删除元素或者一个元素范围
- 指定元素被删除,函数返回void
- 关联容器提供一个额外的erase操作,接受一个key_type参数
- 删除匹配的关键字元素,返回实际删除的元素数量
- erase返回值是0或1。若返回0,则表明想要删除的元素不在容器中
- 删除元素
c.erase(k)
删除每个关键字为k的元素,返回删除元素的数量c.erase(p)
从c中删除迭代器p指定的元素,不能等于c.end()。返回一个指向p之后元素的迭代器c.erase(b, e)
删除迭代器对b和e所表示的范围中的元素
- 使用erase,传递一个迭代器来删除元素或者一个元素范围
-
map的下标操作
- 行为与数组或vector上的下标操作很不相同
- 使用一个不在容器中的关键字作为下标,会添加一个具有此关键字的元素到map中
- 使用下标时,如果关键字不在map中,则新增
- 与vector、string不同,map的下标运算符返回的类型与解引用map迭代器得到的类型不同。
- 如果关键字还未在map中,下标运算符会添加一个新元素
-
访问元素
- find(k)
- 返回一个迭代器
- 特定元素是否在容器
- count(k)
- 返回数量
- 统计元素个数
- 如果允许重复还会统计有多少元素有相同关键字
- lower_bound(k)
- 返回一个迭代器,指向第一个关键字不小于k的元素
- upper_bound(k)
- 返回一个迭代器, 指向第一个关键字大于k的元素
- equal_range(k)
- 返回一个迭代器pair,表示关键字等于k的元素的范围。若k不存在,pair的两个成员均等于c.end()
- 如不需要计数,最好使用find
- lower_bound和upper_bound不适用于无序容器
- 下标和at操作只适用于非const的map和unordered_map
- find(k)
-
multimap 和multiset 中查找元素
string search_item("Alain de Botton"); auto entries = authors.count(search_item); auto iter = authors.find(search_item); while(entries) { cout << iter->second << endl; ++iter; --entries; } for(auto beg = authors.lower_bound(search_item), end = authors.upper_bound(search_itme); beg != end; ++beg) { cout << iter->second << endl; } //equal_range for(auto pos= authors.equal_range(search_item); pos.first!= pos.second; ++pos.first) { cout << pos.first->second << endl; }
- 如果lower_bound和upper_bound返回相同迭代器,则给定关键字不存在容器中
-
无序容器
- 不使用比较运算符来组织元素,而是使用哈希函数和关键字类型的==运算符
- 如果关键字类型固有就是无序的, 或者性能测试发现问题可以用哈希技术解决,就可以使用无序容器。
- 管理桶
- 无序容器的性能依赖于哈希函数的质量和桶的数量和大小
- 无序容器操作
- 桶接口
c.bucket_cout()
正在使用的桶的数目c.max_bucket_cout()
容器能容纳的最多的桶的数量c.bucket_size(n)
第n个桶中有多少个元素c.bucket(k)
关键字为k的元素在哪个桶中
- 桶迭代
local_iterator
可以用来访问桶中元素的迭代器类型const local_iterator
const 版本c.begin(n), c.end(n)
桶n的首元素迭代器和尾元素迭代器c.cbegin(n), c.cend(n)
返回const_local_iterator
- 哈希策略
c.load_factor
每个桶的平均元素数量,返回float值c.max_load_factor()
c试图维护的平均桶大小,返回float值- c会在需要时添加新桶,以得到load_factor <= max_load_factor
c.rehash(n)
重组存储。使得bucket_count >= nc.reserve(n)
重组存储。使得c可以保存n个元素且不必rehash
- 桶接口