IO库与STL.

IO库

  • istream:输入流类型,提供输入操作。
  • ostream:输出流类型,提供输出操作
  • cin:一个istream对象,从标准输入读取数据。
  • cout:一个ostream对象,向标准输出写入数据。
  • cerr:一个ostream对象,向标准错误写入消息。
  • >>运算符:用来从一个istream对象中读取输入数据。
  • <<运算符:用来向一个ostream对象中写入输出数据。
  • getline函数:从一个给定的istream对象中读取一行数据,存入到一个给定的string对象中。

IO类

  • iostream头文件:从标准流中读写数据,istream, ostream, iostream等。
  • fstream头文件:从文件中读写数据,ifstream, ofstream, fstream等。
  • sstream头文件:从字符串中读写数据,istringstream, ostringstream, stringstream等。

IO对象无拷贝或赋值

  • 1.IO对象不能存在容器里.
  • 2.形参和返回类型也不能是流类型。
  • 3.形参和返回类型一般是流的引用
  • 4.读写一个IO对象会改变其状态,因此传递和返回的引用不能是const的。

条件状态

状态 解释
strm:iostate 是一种机器无关的类型,提供了表达条件状态的完整功能
strm:badbit 用来指出流已经崩溃
strm:failbit 用来指出一个IO操作失败了
strm:eofbit 用来指出流到达了文件结束
strm:goodbit 用来指出流未处于错误状态,此值保证为零
s.eof() 若流s的eofbit置位,则返回true
s.fail() 若流s的failbit置位,则返回true
s.bad() 若流s的badbit置位,则返回true
s.good() 若流s处于有效状态,则返回true
s.clear() 将流s中所有条件状态位复位,将流的状态设置成有效,返回void
s.clear(flags) 将流s中指定的条件状态位复位,返回void
s.setstate(flags) 根据给定的标志位,将流s中对应的条件状态位置位,返回void
s.rdstate() 返回流s的当前条件状态,返回值类型为strm::iostate

上表中,strm是一种IO类型,(如istream), s是一个流对象。

管理输出缓冲

每个输出流都管理一个缓冲区,执行输出的代码,文本串可能立即打印出来,也可能被操作系统保存在缓冲区内,随后再打印。

刷新缓冲区,可以使用如下IO操纵符:

  • endl:输出一个换行符并刷新缓冲区。
  • flush:刷新流,单不添加任何字符。
  • ends:在缓冲区插入空字符null,然后刷新。
  • unitbuf:告诉流接下来每次操作之后都要进行一次flush操作。
  • nounitbuf:回到正常的缓冲方式。
1
cout << unitbuf;    //所有输出操作后都会立即刷新缓冲区。
2
//... 任何输出操作都立即刷新,无缓冲。
3
cout << nounitbuf;  //回到正常的缓冲方式。

文件输入输出

  • ifstream从一个给定文件读取数据。
  • ofstream向一个给定文件写入数据。
  • fstream可以读写给定文件。

文件流:需要读写文件时,必须定义自己的文件流对象,并绑定在需要的文件上。

fstream特有的操作

操作 解释
fstream fstrm; 创建一个未绑定的文件流。
fstream fstrm(s); 创建一个文件流,并打开名为s的文件,s可以是string也可以是char指针
fstream fstrm(s, mode); 与前一个构造函数类似,但按指定mode打开文件
fstrm.open(s) 打开名为s的文件,并和fstrm绑定
fstrm.close() 关闭和fstrm绑定的文件
fstrm.is_open() 返回一个bool值,指出与fstrm关联的文件是否成功打开且尚未关闭

当一个fstream对象被销毁时,close会自动被调用。

文件模式

文件模式 解释
in 以读的方式打开
out 以写的方式打开
app 每次写操作前均定位到文件末尾
ate 打开文件后立即定位到文件末尾
trunc 截断文件
binary 以二进制方式进行IO操作。

默认情况下,打开一个ofstream时,文件内容会被丢弃。保留被ofstream打开的文件中已有数据的唯一方法是显示指定appin模式ofstream app("file", ofstream::out | ofstream::app)

string流

头文件sstream定义了三个类型来支持内存IO:

  • istringstreamstring读取数据。
  • ostringstreamstring写入数据。
  • stringstream可以读写给定string

stringstream特有的操作

操作 解释
sstream strm 定义一个未绑定的stringstream对象
sstream strm(s) s初始化对象
strm.str() 返回strm所保存的string的拷贝
strm.str(s) s拷贝到strm中,返回void

顺序容器

类型 说明
vector 可变大小数组。支持快速随机访问。在尾部之外的位置插入或删除元素可能很慢。
deque 双端队列。支持快速随机访问。在头尾位置插入/删除速度很快。
list 双向链表。只支持双向顺序访问。在list中任何位置进行插入/删除速度都很快。
forward_list 单向链表。只支持单向顺序访问。在链表任何位置进行插入/删除操作速度都很快。
array 固定大小数组。支持快速随机访问。不能添加或删除元素。
string 与vector相似的容器,但专门用于保存字符。随机访问快。在尾部插入/删除速度快。

迭代器

迭代器范围:beginend,即第一个元素到最后一个元素的后面一个位置(one past the last element)。
左闭合区间:$ [begin, end) $

左闭合范围蕴含的编程设定:

  • 如果beginend相等,则范围为空。
  • 如果二者不等,则范围至少包含一个元素,且begin指向该范围中的第一个元素。
  • 可以对begin递增若干次,使得begin == end

获取迭代器

操作 解释
c.begin(), c.end() 返回指向c的首元素和尾元素之后位置的迭代器
c.cbegin(), c.cend() 返回const_iterator
reverse_iterator 按逆序寻址元素的迭代器
const_reverse_iterator 不能修改元素的逆序迭代器
c.rbegin(), c.rend() 返回指向c的尾元素和首元素之前位置的迭代器
c.crbegin(), c.crend() 返回const_reverse_iterator
  • c开头的版本是C++11新标准引入的
  • 当不需要写访问时,应该使用cbegincend
  • 不支持forward_list

在向容器添加元素后:

  • 如果容器是vectorstring,且存储空间被重新分配,则指向容器的迭代器、指针、引用都会失效。
  • 对于deque,插入到除首尾位置之外的任何位置都会导致指向容器的迭代器、指针、引用失效。如果在首尾位置添加元素,迭代器会失效,但指向存在元素的引用和指针不会失效。
  • 对于listforward_list,指向容器的迭代器、指针和引用依然有效。

在从一个容器中删除元素后:

  • 对于listforward_list,指向容器其他位置的迭代器、引用和指针仍然有效。
  • 对于deque,如果在首尾之外的任何位置删除元素,那么指向被删除元素外其他元素的迭代器、指针、引用都会失效;如果是删除deque的尾元素,则尾后迭代器会失效,但其他不受影响;如果删除的是deque的头元素,这些也不会受影响。
  • 对于vectorstring,指向被删元素之前的迭代器、引用、指针仍然有效。
  • 注意:当我们删除元素时,尾后迭代器总是会失效。
  • 注意:使用失效的迭代器、指针、引用是严重的运行时错误!
  • 建议:将要求迭代器必须保持有效的程序片段最小化。
  • 建议:不要保存end返回的迭代器。

类型

操作 解释
iterator 此容器类型的迭代器类型
const_iterator 可以读取元素但不能修改元素的迭代器类型
size_type 无符号整数类型,足够保存此种容器类型最大可能的大小
difference_type 带符号整数类型,足够保存两个迭代器之间的距离
value_type 元素类型
reference 元素的左值类型;和value_type &含义相同
const_reference 元素的const左值类型,即const value_type &

容器定义和初始化

操作 解释
C c; 默认构造函数,构造空容器
C c1(c2);或C c1 = c2; 构造c2的拷贝c1
C c(b, e) 构造c,将迭代器be指定范围内的所有元素拷贝到c
C c(a, b, c…) 列表初始化c
C c(n) 只支持顺序容器,且不包括array,包含n个元素,这些元素进行了值初始化
C c(n, t) 包含n个初始值为t的元素
  • 只有顺序容器的构造函数才接受大小参数,关联容器并不支持。
  • array具有固定大小。
  • 和其他容器不同,默认构造的array是非空的。
  • 直接复制:将一个容器复制给另一个容器时,类型必须匹配:容器类型和元素类型都必须相同。
  • 使用迭代器复制:不要求容器类型相同,容器内的元素类型也可以不同。

赋值和swap

操作 解释
c1 = c2; c1中的元素替换成c2中的元素
c1 = {a, b, c…} c1中的元素替换成列表中的元素(不适用于array
c1.swap(c2) 交换c1c2的元素
swap(c1, c2) 等价于c1.swap(c2)
c.assign(b, e) c中的元素替换成迭代器be表示范围中的元素,be不能指向c中的元素
c.assign(il) c中的元素替换成初始化列表il中的元素
c.assign(n, r) c中的元素替换为n个值是t的元素
  • 使用非成员版本的swap是一个好习惯(非成员版本的swap在泛型编程中非常重要)。
  • assign操作不适用于关联容器和array,仅顺序容器有效。
  • 除array外,swap不对任何元素进行拷贝,删除或插入操作。

大小

操作 解释
c.size() 容器中元素的数目(不支持forward_list
c.max_size() 容器中可保存的最大元素数目
c.empty() 若容器中存储了元素,返回false,否则返回true

顺序容器添加元素

操作 解释
c.push_back(t) 在容器尾部创建一个值为t的元素,返回void
c.emplace_back(args) 同上
c.push_front(t) 在容器头部创建一个值为t的元素,返回void
c.emplace_front(args) 同上
c.insert(p, t) 在迭代器p指向的元素之前创建一个值是t的元素,返回指向新元素的迭代器
c.emplace(p, args) 同上
c.inset(p, n, t) 在迭代器p指向的元素之前插入n个值为t的元素,返回指向第一个新元素的迭代器;如果n是0,则返回p
c.insert(p, b, e) 将迭代器be范围内的元素,插入到p指向的元素之前;如果范围为空,则返回p
c.insert(p, il) il是一个花括号包围中的元素值列表,将其插入到p指向的元素之前;如果il是空,则返回p
  • 向vector,string或deque插入元素会使所有指向容器的迭代器,引用和指针失效。
  • 因为这些操作会改变大小,因此不适用于array
  • forward_list有自己专有版本的insertemplace
  • forward_list不支持push_backemplace_back
  • 当我们用一个对象去初始化容器或者将对象插入到容器时,实际上放入的是对象的拷贝。
  • emplace开头的函数是新标准引入的,这些操作是构造而不是拷贝元素。
  • 传递给emplace的参数必须和元素类型的构造函数相匹配。

访问元素

操作 解释
c.back() 返回容器中尾元素的引用。若容器为空,函数行为未定义
c.front() 返回容器中头元素的引用。若容器为空,函数行为未定义
c[n] 返回容器中下标是n的元素的引用,n时候一个无符号证书。若n>=c.size(),则函数行为未定义
c.at(n) 返回下标为n的元素引用。如果下标越界,则抛出out_of_range异常
  • 访问成员函数返回的是引用
  • at和下标操作只适用于stringvectordequearray
  • back不适用于forward_list
  • 如果希望下标是合法的,可以使用at函数。

删除元素

操作 解释
c.pop_back() 删除容器中尾元素,若容器为空,则函数行为未定义。函数返回void
c.pop_front() 删除容器中首元素,若容器为空,则函数行为未定义。函数返回void
c.erase(p) 删除迭代器p指向的元素,返回一个指向被删除元素之后的元素的迭代器,若p本身是尾后迭代器,则函数行为未定义
c.erase(b, e) 删除迭代器be范围内的元素,返回指向最后一个被删元素之后元素的迭代器,若e本身就是尾后迭代器,则返回尾后迭代器
c.clear() 删除容器中所有元素,返回void
  • 会改变容器大小,不适用于array
  • forward_list有特殊版本的erase
  • forward_list不支持pop_back
  • vectorstring不支持pop_front

特殊的forwad_list操作

  • 链表在删除元素时需要修改前置节点的内容,双向链表会前驱的指针,但是单向链表没有保存,因此需要增加获取前置节点的方法。
  • forward_list定义了before_begin,即首前(off-the-begining)迭代器,允许我们再在首元素之前添加或删除元素。
操作 解释
lst.before_begin() 返回指向链表首元素之前不存在的元素的迭代器,此迭代器不能解引用。
lst.cbefore_begin() 同上,但是返回的是常量迭代器。
lst.insert_after(p, t) 在迭代器p之后插入元素。t是一个对象
lst.insert_after(p, n, t) 在迭代器p之后插入元素。t是一个对象,n是数量。若n是0则函数行为未定义
lst.insert_after(p, b, e) 在迭代器p之后插入元素。由迭代器be指定范围。
lst.insert_after(p, il) 在迭代器p之后插入元素。由il指定初始化列表。
emplace_after(p, args) 使用argsp之后的位置,创建一个元素,返回一个指向这个新元素的迭代器。若p为尾后迭代器,则函数行为未定义。
lst.erase_after(p) 删除p指向位置之后的元素,返回一个指向被删元素之后的元素的迭代器,若p指向lst的尾元素或者是一个尾后迭代器,则函数行为未定义。
lst.erase_after(b, e) 类似上面,删除对象换成从be指定的范围。

改变容器大小

操作 解释
c.resize(n) 调整容器的大小为n个元素,若n<c.size(),则多出的元素被丢弃。若必须添加新元素,对新元素进行值初始化
c.resize(n, t) 调整容器的大小为n个元素,任何新添加的元素都初始化为值t

vector分配原理

vectorstring在内存中是连续保存的,如果原先分配的内存位置已经使用完,则需要重新分配新空间,将已有元素从就位置移动到新空间中,然后添加新元素。

操作 解释
c.shrink_to_fit() capacity()减少到和size()相同大小
c.capacity() 不重新分配内存空间的话,c可以保存多少个元素
c.reverse(n) 分配至少能容纳n个元素的内存空间
  • shrink_to_fit只适用于vector、string和deque。
  • capacity和reverse只适用于vector和string。

额外的string操作

构造string的其他方法

操作 解释
string s(cp, n) scp指向的数组中前n个字符的拷贝,此数组
string s(s2, pos2) sstring s2从下标pos2开始的字符的拷贝。若pos2 > s2.size(),则构造函数的行为未定义。
string s(s2, pos2, len2) sstring s2从下标pos2开始的len2个字符的拷贝。
  • n,len,pos2都是无符号值。

substr操作

操作 解释
s.substr(pos, n) 返回一个string,包含s中从pos开始的n个字符的拷贝。pos的默认值是0,n的默认值是s.size() - pos,即拷贝从pos开始的所有字符。

改变string的其他方法

操作 解释
s.insert(pos, args) pos之前插入args指定的字符。pos可以使是下标或者迭代器。接受下标的版本返回指向s的引用;接受迭代器的版本返回指向第一个插入字符的迭代器。
s.erase(pos, len) 删除从pos开始的len个字符,如果len被省略,则删除后面所有字符,返回指向s的引用。
s.assign(args) s中的字符替换成args指定的字符。返回一个指向s的引用。
s.append(args) args指定的字符追加到s,返回一个指向s的引用。
s.replace(range, args) 删除s中范围range中的字符,替换成args指定的字符。返回一个指向s的引用。

string搜索操作

  • string类提供了6个不同的搜索函数,每个函数都有4个重载版本。
  • 每个搜索操作都返回一个string::size_type值,表示匹配发生位置的下标。如果搜索失败则返回一个名为string::nposstatic成员(类型是string::size_type,初始化值是-1,也就是string最大的可能大小)。
搜索操作 解释
s.find(args) 查找容器中args第一次出现的位置
s.rfind(args) 查找容器中args最后一次出现的位置
s.find_first_of(args) 在容器中查找args中任何一个字符第一次出现的位置
s.find_last_of(args) 在容器中查找args中任何一个字符最后一次出现的位置
s.find_first_not_of(args) 在容器中查找第一个不在args中的字符
s.find_first_not_of(args) 在容器中查找最后一个不在args中的字符

args必须是一下的形式之一:

args形式 解释
c, pos 从容器中位置pos开始查找字符cpos默认是0
s2, pos 从容器中位置pos开始查找字符串spos默认是0
cp, pos 从容器中位置pos开始查找指针cp指向的以空字符结尾的C风格字符串。pos默认是0
cp, pos, n 从容器中位置pos开始查找指针cp指向的前n个字符。posn无默认值。

s.compare的几种参数形式

逻辑类似于C标准库的strcmp函数,根据s是等于、大于还是小于参数指定的字符串,s.compare返回0、正数或负数。

参数形式 解释
s2 比较ss2
pos1, n1, s2 比较spos1开始的n1个字符和s2
pos1, n1, s2, pos2, n2 比较spos1开始的n1个字符和s2
cp 比较scp指向的以空字符结尾的字符数组
pos1, n1, cp 比较spos1开始的n1个字符和cp指向的以空字符结尾的字符数组
pos1, n1, cp, n2 比较spos1开始的n1个字符和cp指向的地址开始n2个字符

string和数值转换

转换 解释
to_string(val) 一组重载函数,返回数值valstring表示。val可以使任何算术类型。对每个浮点类型和int或更大的整型,都有相应版本的to_string()。和往常一样,小整型会被提升。
stoi(s, p, b) 返回s起始子串(表示整数内容)的数值,ps中第一个非数值字符的下标,默认是0,b是转换所用的基数。返回int
stol(s, p, b) 返回long
stoul(s, p, b) 返回unsigned long
stoll(s, p, b) 返回long long
stoull(s, p, b) 返回unsigned long long
stof(s, p) 返回s起始子串(表示浮点数内容)的数值,ps中第一个非数值字符的下标,默认是0。返回float
stod(s, p) 返回double
stold(s, p) 返回long double

容器适配器(adapter)

  • 适配器是使一事物的行为类似于另一事物的行为的一种机制,例如stack可以使任何一种顺序容器以栈的方式工作。
  • 初始化 deque<int> deq; stack<int> stk(deq);deq拷贝元素到stk
  • 创建适配器时,指定一个顺序容器,可以覆盖默认的基础容器: stack<string, vector<string> > str_stk;

适配器的通用操作和类型

操作 解释
size_type 一种类型,须以保存当前类型的最大对象的大小
value_type 元素类型
container_type 实现适配器的底层容器类型
A a; 创建一个名为a的空适配器
A a(c) 创建一个名为a的适配器,带有容器c的一个拷贝
关系运算符 每个适配器都支持所有关系运算符:==!=<<=>>=这些运算符返回底层容器的比较结果
a.empty() a包含任何元素,返回false;否则返回true
a.size() 返回a中的元素数目
swap(a, b) 交换ab的内容,ab必须有相同类型,包括底层容器类型也必须相同
a.swap(b) 同上

stack

操作 解释
s.pop() 删除栈顶元素,不返回。
s.push(item) 创建一个新元素,压入栈顶,该元素通过拷贝或移动item而来
s.emplace(args) 同上,但元素由args来构造。
s.top() 返回栈顶元素,不删除。
  • 定义在stack头文件中。
  • stack默认基于deque实现,也可以在listvector之上实现。

queue和priority_queue

操作 解释
q.pop() 删除队首元素,但不返回。
q.front() 返回队首元素的值,不删除。
q.back() 返回队尾元素的值,不删除。只适用于queue
q.top() 返回具有最高优先级的元素值,不删除。
q.push(item) 在队尾压入一个新元素。
q.emplace(args)
  • 定义在queue头文件中。
  • queue默认基于deque实现,priority_queue默认基于vector实现。
  • queue可以在listvector之上实现,priority_queue也可以用deque实现。

泛型算法

因为它们实现共同的操作,所以称之为“算法”;而“泛型”、指的是它们可以操作在多种容器类型上。

泛型算法本身不执行容器操作,只是单独依赖迭代器和迭代器操作实现。

1
#include <algorithm>
2
#include <numeric> //算数相关

大多数算法是通过遍历两个迭代器标记的一段元素来实现其功能。

必要的编程假定:算法永远不会改变底层容器的大小。算法可能改变容器中保存的元素的值,也可能在容器内移动元素,但不能直接添加或者删除元素。

1
vector<int>::const_iterator result = find(vec.begin(), vec.end(), search_value);

输入:两个标记范围的迭代器和目标查找值。
返回:如果找到,返回对应的迭代器,否则返回第二个参数,即标记结尾的迭代器。

只读算法

  • 只读取范围中的元素,不改变元素。
  • findaccumulate(在numeric中定义,求和)。
  • find_first_of,输入:两对迭代器标记两段范围,在第一段中找第二段中任意元素,返回第一个匹配的元素,找不到返回第一段的end迭代器。
  • 通常最好使用cbegincend
  • equal:确定两个序列是否保存相同的值。

写容器元素的算法

一些算法将新值赋予序列中的元素。算法不检查写操作(假定目的位置足够大)。

1
fill(vec.begin(), vec.end(), 0);    //将每个元素重置为0
2
fill_n(vec.begin(), 10, 0);

插入迭代器back_inserter,接受一个指向容器的引用,返回一个与该容器绑定的插入迭代器。通过此迭代器赋值时,赋值运算符会调用push_back将一个具有给定值的元素添加到容器中。

1
#include <iterator>
2
3
vector<int> vec;
4
auto it = back_inserter(vec);
5
*it = 42;
6
fill_n(back_inserter(vec), 10, 0);
  • 拷贝算法copy
  • 输入:前两个参数指定输入范围,第三个指向目标序列。
  • copy (ilst.begin(), ilst.end(), back_inserter(ivec));
  • copy时必须保证目标目的序列至少要包含与输入序列一样多的元素。

重排容器元素的算法

排序算法sort,接受两个迭代器,表示要排序的元素范围。

消除重复unique

  • 之前要先调用sort
  • 返回的迭代器指向最后一个不重复元素之后的位置。
  • 顺序会变,重复的元素被“删除”。
  • 并没有真正删除,真正删除必须使用容器操作。
1
void elimDups(vector<string> &words)
2
{
3
    sort(words.begin(), words.end());
4
    auto end_unique = unique(words.begin(), words.end());
5
    words.erase(end_unique, words.end());
6
}

定制操作

向算法传递函数:

谓词(predicate)是一个可调用的表达式,返回结果是一个能用作条件的值。一元谓词接受一个参数,二元谓词接受两个参数。

1
bool isShorter(const string &s1, const string &s2)
2
{
3
    return s1.size() < s2.size();
4
}
5
stable_sort(words.begin(), words.end(), isShorter);

lambda表达式

lambda表达式表示一个可调用的代码单元,可以理解成是一个未命名的内联函数。

1
[capture list](parameter list) -> return type {function body}
  • capture list捕获列表是一个lambda所在函数定义的局部变量的列表(通常为空),不可忽略。
  • return type是返回类型,lambda必须使用尾置返回形式,可忽略。
  • parameter是参数列表,可忽略。
  • function body是函数体,不可忽略。
  • 可以忽略参数列表和返回类型,但必须包含捕获列表和函数体。
1
auto f = [] {return 42;}
2
cout << f() <<endl;

lambda不能有默认参数,实参与形参类型必须匹配且数目相等。

lambda只有在其捕获列表中捕获一个它所在函数中的局部变量,才能在lambda函数体中使用该变量。

捕获列表只用于局部非static变量,lambda可以直接使用局部static变量和在它所在函数之外声明的名字。

1
void biggies(vector<string> &words,
2
                vector<string>::size_type sz)
3
{
4
    elimDups(words);
5
    stable_sort(words.begin(), words.end(),
6
            [](const string &a, const string &b)
7
                { return a.size() < b.size(); });
8
9
    auto wc = find_if(words.begin(), words.end(),
10
            [sz](const string &a)
11
                { return a.size() >= sz; });
12
13
    auto count = words.end() - wc;
14
    cout << count << " " << make_plural(count, "word", "s")
15
        << " of length " << sz << " or longer" << endl;
16
17
    for_each(wc, words.end(),
18
            [](const string &s) {const << s << " ";});
19
    cout << endl;
20
}

lambda捕获和返回

定义lambda时,编译器会生成一个与lambda对应的新的(未命名的)类类型。当向一个函数传递一个lambda时,同时定义了一个新类型和该类型的一个对象:传递的参数就是此编译器生成的类类型的未命名对象。

默认情况下,从lambda生成的类都包含一个对应该lambda所捕获的变量的数据成员,在lambda对象创建时被初始化。

值捕获:被捕获的变量是可以拷贝,且被捕获的变量是在lambda创建时拷贝,而不是调用时拷贝

1
void fcn1()
2
{
3
    size_t v1 = 42; //局部变量
4
    auto f = [v1] { return v1; };
5
    v1 = 0;
6
    auto j = f();   //j为42,f保存了我们创建它时v1的拷贝
7
}

引用捕获:必须保证在lambda执行时,变量是存在的,当函数返回一个lambda,此lambda也不能包含引用捕获。

1
void fcn2()
2
{
3
    size_t v1 = 42;
4
    auto f2 = [&v1] {return v1;};
5
    v1 = 0;
6
    auto j = f2(); // j为0
7
}

尽量减少捕获的数据量,尽可能避免捕获指针或引用。

隐式捕获:让编译器推断捕获列表,在捕获列表中写一个&(引用捕获方式)或=(值捕获方式), 也可以混合使用隐式捕获和显示捕获。

1
void biggies(vector<string> &words, vector<string>::size_type sz,
2
        ostream &os = cout, char c = ' ')
3
{
4
    for_each(words.begin(), words.end(),
5
        [&, c](const string &s) { os << s << c; });
6
7
    for_each(words.begin(), words.end(),
8
        [=, &os](const string &s) { os << s << c; });
9
}

当混合使用隐式和显示捕获时,捕获列表中的第一个元素必须是&或=。此符号指定了默认捕获方式。

lambda捕获列表

捕获列表 解释
[] 空捕获列表。lambda不能使用所在函数中的变量。一个lambda只有在捕获变量后才能使用它们。
[names] names是一个逗号分隔的名字列表,这些名字都是在lambda所在函数的局部变量,捕获列表中的变量都被拷贝,名字前如果使用了&,则采用引用捕获方式。
[&] 隐式捕获列表,采用引用捕获方式。lambda体中所使用的来自所在函数的实体都采用引用方式使用。
[=] 隐式捕获列表,采用值捕获方式。
[&, identifier_list] identifier_list是一个逗号分隔的列表,包含0个或多个来自所在函数的变量。这些变量采用值捕获方式,而任何隐式捕获的变量都采用引用方式捕获。identifier_list中的名字前面不能使用&
[=, identifier_list] identifier_list中的变量采用引用方式捕获,而任何隐式捕获的变量都采用值方式捕获。identifier_list中的名字不能包括this,且前面必须使用&

默认情况下,对于一个值被拷贝的变量,lambda不会改变其值。如果我们希望能改变一个被捕获的变量的值,就必须在参数列表首加上关键字mutable。因此,可变lambda能省略参数列表。

1
void fcn3()
2
{
3
    size_t v1 = 42;
4
    auto f = [v1] () mutable { return ++v1; };
5
    v1 = 0;
6
    auto j = f(); //j==43
7
}

默认情况下,如果一个lambda体包含return之外的任何语句,则编译器假定此lambda返回void。被推断为void的lambda不能返回值。当我们需要为一个lambda定义返回类型时,必须使用尾置返回类型。

1
transform(v1.begin(), v1.end(), v1.begin(),
2
        [](int i) ->int { if(i < 0) return -i; else return i; });

参数绑定

lambda表达式更适合在一两个地方使用的简单操作。如果是很多地方使用相同的操作,还是需要定义函数。

标准库bind函数:定义在头文件functional中,可以看做为一个通用的函数适配器。接受一个可调用对象,生成一个新的可调用对象来”适应”原对象的参数列表。

1
auto newCallable = bind(callable, arg_list);

调用newCallable的时候,newCallable会调用callable并传递给它arg_list中的参数。arg_list中的参数可能包含形如_n的名字,其中n是一个整数,这些参数表示占位符的意思, 表示newCallable的参数,占据了传递给newCallable的参数的”位置”。数值n表示生成的可调用对象中参数的位置: _1为newCallable的第一个参数, _2为第二个参数,以此类推。

名字_n定义在placeholders的命名空间中。

1
using namespace std::placeholders;
1
auto g = bind(f, a, b, _2, c, _1);
2
//g(_1, _2) = f(a, b, _2, c, _1)

非占位符的参数要使用引用传参,必须使用标准库ref函数或者cref(const引用类)函数。

1
for_each(words.begin(), words.end(),
2
        bind(print, ref(os), _1, ' '));

再探迭代器

插入迭代器

插入器是一种迭代器适配器,接受一个容器,生成一个迭代器,能实现向给定容器添加元素。

  • back_inserter创建一个使用push_back的迭代器。
  • front_inserter创建一个使用push_front的迭代器。
  • inserter创建一个使用insert的迭代器。接受第二个参数,即一个指向给定容器的迭代器,元素会被查到迭代器所指向的元素之前。

插入迭代器操作

操作 解释
it=t it指定的当前位置插入值t。假定cit绑定的容器,依赖于插入迭代器的不同种类,此赋值会分别调用c.push_back(t)c.push_front(t)c.insert(t, p),其中p是传递给inserter的迭代器位置
*it, ++it, it++ 这些操作虽然存在,但不会对it做任何事情,每个操作都返回it

iostream迭代器

迭代器可与输入或输出流绑定在一起,用于迭代遍历所关联的 IO 流。通过使用流迭代器,我们可以用泛型算法从流对象中读取数据以及向其写入数据。

istream_iterator的操作

操作 解释
istream_iterator in(is); in从输入流is读取类型为T的值
istream_iterator end; 读取类型是T的值的istream_iterator迭代器,表示尾后位置
in1 == in2 in1in2必须读取相同类型。如果他们都是尾后迭代器,或绑定到相同的输入,则两者相等。
in1 != in2 类似上条
*in 返回从流中读取的值
in->mem *(in).mem含义相同
++in, in++ 使用元素类型所定义的>>运算符从流中读取下一个值。前置版本返回一个指向递增后迭代器的引用,后置版本返回旧值。

ostream_iterator的操作

操作 解释
ostream_iterator out(os); out将类型为T的值写到输出流os
ostream_iterator out(os, d); out将类型为T的值写到输出流os中,每个值后面都输出一个dd指向一个空字符结尾的字符数组。
out = val <<运算符将val写入到out所绑定的ostream中。val的类型必须和out可写的类型兼容。
*out, ++out, out++ 这些运算符是存在的,但不对out做任何事情。每个运算符都返回out

反向迭代器

反向迭代器就是在容器中从尾元素向首元素反向移动的迭代器。

对于反向迭代器,递增和递减的操作含义会颠倒。

实现向后遍历,配合rbeginrend

泛型算法结构

5类迭代器

迭代器类别 解释 支持的操作
输入迭代器 只读,不写;单遍扫描,只能递增 ==,!=,++,*,->
输出迭代器 只写,不读;单遍扫描,只能递增 ++,*
前向迭代器 可读写;多遍扫描,只能递增 ==,!=,++,*,->
双向迭代器 可读写;多遍扫描,可递增递减 ==,!=,++,--,*,->
随机访问迭代器 可读写,多遍扫描,支持全部迭代器运算 ==,!=,<,<=,>,>=,++,--,+,+=,-,-=,*,->,iter[n]==*(iter[n])

算法的形参模式

1
alg(beg, end, other args);
2
alg(beg, end, dest, other args);
3
alg(beg, end, beg2, other args);
4
alg(beg, end, beg2, end2, other args);

其中,alg是算法名称,begend表示算法所操作的输入范围。destbeg2end2都是迭代器参数,是否使用要依赖于执行的操作。

算法命名规范

  • 一些算法使用重载形式传递一个谓词。
  • 接受一个元素值的算法通常有一个不同名的版本:加_if,接受一个谓词代替元素值。
  • 区分拷贝元素的版本和不拷贝的版本:拷贝版本通常加_copy

特定容器算法

  • 对于listforward_list,优先使用成员函数版本的算法而不是通用算法。

list和forward_list成员函数版本的算法

操作 解释
lst.merge(lst2) 将来自lst2的元素合并入lst,二者都必须是有序的,元素将从lst2中删除。
lst.merge(lst2, comp) 同上,给定比较操作。
lst.remove(val) 调用erase删除掉与给定值相等(==)的每个元素
lst.remove_if(pred) 调用erase删除掉令一元谓词为真的每个元素
lst.reverse() 反转lst中元素的顺序
lst.sort() 使用<排序元素
lst.sort(comp) 使用给定比较操作排序元素
lst.unique() 调用erase删除同一个值的连续拷贝。使用==
lst.unique(pred) 调用erase删除同一个值的连续拷贝。使用给定的二元谓词。

上面的操作都返回void

list和forward_list的splice**成员函数版本的参数:

参数 解释
(p, lst2) p是一个指向lst中元素的迭代器,或者一个指向flst首前位置的迭代器。函数将lst2中的所有元素移动到lstp之前的位置或是flstp之后的位置。将元素从lst2中删除。lst2的类型必须和lst相同,而且不能是同一个链表。
(p, lst2, p2) 同上,p2是一个指向lst2中位置的有效的迭代器,将p2指向的元素移动到lst中,或将p2之后的元素移动到flst中。lst2可以是于lstflst相同的链表。
(p, lst2, b, e) be表示lst2中的合法范围。将给定范围中的元素从lst2移动到lstfirst中。lst2lst可以使相同的链表,但p不能指向给定范围中的元素。

使用lst.splice(args)flst.splice_after(args)

关联容器

关联容器和顺序容器的不同:关联容器中的元素时按照关键字来保存和访问的。

关联容器支持通过关键字来高效地查找和读取元素,基本的关联容器类型是 mapset

关联容器类型

容器类型 解释
按顺序存储
map 关键数组:保存关键字-值
set 关键字即值,即只保存关键字的容器
multimap 支持同一个键多次出现的map
multiset 支持同一个键多次出现的set
无序集合
unordered_map 用哈希函数组织的map
unordered_set 用哈希函数组织的set
unordered_multimap 哈希组织的map,关键字可以重复出现
unordered_multiset 哈希组织的set,关键字可以重复出现

关联容器概述

定义关联容器

需要指定元素类型。

列表初始化:

关键字类型的要求

对于有序容器,关键字类型必须定义元素比较的方法。默认是<

如果想传递一个比较的函数,可以这样定义:multiset<Sales_data, decltype(compareIsbn)*> bookstore(compareIsbn);

pair

utility头文件中定义。

一个pair保存两个数据成员,两个类型不要求一样。

pair的操作

操作 解释
pair<T1, T2> p; p是一个pair,两个类型分别是T1T2的成员都进行了值初始化。
pair<T1, T2> p(v1, v2); firstsecond分别用v1v2进行初始化。
pair<T1, T2>p = {v1, v2}; 等价于`p(v1, v2)
make_pair(v1, v2); pair的类型从v1v2的类型推断出来。
p.first 返回p的名为first的数据成员。
p.second 返回p的名为second的数据成员。
p1 relop p2 运算关系符按字典序定义。
p1 == p2 必须两对元素两两相等
p1 != p2 同上

关联容器操作

关联容器额外的类型别名

类型别名 解释
key_type 此容器类型的关键字类型
mapped_type 每个关键字关联的类型,只适用于map
value_type 对于map,是pair<const key_type, mapped_type>; 对于set,和key_type相同。

关联容器迭代器

解引用一个关联容器迭代器时,会得到一个类型为容器的value_type的值的引用。

set的迭代器是const的。

遍历关联容器:使用beginend,遍历mapmultimapsetmultiset时,迭代器按关键字升序遍历元素。

添加元素

关联容器insert操作

insert操作 关联容器
c.insert(v) c.emplace(args) vvalue_type类型的对象;args用来构造一个元素。 对于mapset,只有元素的关键字不存在c中才插入或构造元素。函数返回一个pair,包含一个迭代器,指向具有指定关键字的元素,以及一个指示插入是否成功的bool值。对于multimapmultiset则会插入范围中的每个元素。
c.insert(b, e) c.insert(il) be是迭代器,表示一个c::value_type类型值的范围;il是这种值的花括号列表。函数返回void。对于 mapset,只插入关键字不在c中的元素。
c.insert(p, v) c.emplace(p, args) 类似insert(v),但将迭代器p作为一个提示,指出从哪里开始搜索新元素应该存储的位置。返回一个迭代器,指向具有给定关键字的元素。

map添加元素:

1
word_count.insert({word, 1});
2
word_count.insert(make_pair(word, 1));
3
word_count.insert(pair<string, size_t>(word, 1));
4
word_count.insert(map<string, size_t>::value_type (word, 1));

删除元素

从关联容器中删除元素

操作 解释
c.erase(k) c中删除每个关键字为k的元素。返回一个size_type值,指出删除的元素的数量。
c.erase(p) c中删除迭代器p指定的元素。p必须指向c中一个真实元素,不能等于c.end()。返回一个指向p之后元素的迭代器,若p指向c中的尾元素,则返回c.end()
c.erase(b, e) 删除迭代器对be所表示范围中的元素。返回e

下标操作

mapunordered_map的下标操作

操作 解释
c[k] 返回关键字为k的元素;如果k不在c中,添加一个关键字为k的元素,对其值初始化。
c.at(k) 访问关键字为k的元素,带参数检查;若k不存在在c中,抛出一个out_of_range异常。

查找元素

在一个关联容器中查找元素

操作 解释
c.find(k) 返回一个迭代器,指向第一个关键字为k的元素,若k不在容器中,则返回尾后迭代器
c.count(k) 返回关键字等于k的元素的数量。对于不允许重复关键字的容器,返回值永远是0或1。
c.lower_bound(k) 返回一个迭代器,指向第一个关键字不小于k的元素。
c.upper_bound(k) 返回一个迭代器,指向第一个关键字大于k的元素。
c.equal_range(k) 返回一个迭代器pair,表示关键字等于k的元素的范围。若k不存在,pair的两个成员均等于c.end()

lower_boundupper_bound不适用于无序容器。

下标和at操作只适用于非constmapunordered_map

无序容器

有序容器使用比较运算符来组织元素;无序容器使用哈希函数和关键字类型的==运算符。

理论上哈希技术可以获得更好的性能。

无序容器在存储上组织为一组桶(bucket),每个桶保存零个或多个元素。无序容器使用一个哈希函数将元素映射到桶。

无序容器管理操作

操作 解释
桶接口
c.bucket_count() 正在使用的桶的数目
c.max_bucket_count() 容器能容纳的最多的桶的数目
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>=n,且bucket_count>size/max_load_factor
c.reverse(n) 重组存储,使得c可以保存n个元素且不必rehash

动态内存与智能指针

对象的生命周期:

  • 全局对象在程序启动时分配,结束时销毁。
  • 局部对象在进入程序块时创建,离开块时销毁。
  • 局部static对象在第一次使用前分配,在程序结束时销毁。
  • 动态分配对象:只能显式地被释放。

对象的内存位置:

  • 静态内存用来保存局部static对象、类static对象、定义在任何函数之外的变量。
  • 栈内存用来保存定义在函数内的非static对象。
  • 堆内存,又称自由空间,用来存储动态分配的对象。

动态内存与智能指针

动态内存管理:

  • new:在动态内存中为对象分配空间并返回一个指向该对象的指针。
  • delete:接受一个动态对象的指针,销毁该对象,并释放与之关联的内存。

智能指针:

  • 管理动态对象。
  • 行为类似常规指针。
  • 负责自动释放所指向的对象。
  • 智能指针也是模板。

shared_ptr类

shared_ptr和unique_ptr都支持的操作

操作 解释
shared_ptr sp unique_ptr up 空智能指针,可以指向类型是T的对象
p p用作一个条件判断,若p指向一个对象,则为true
*p 解引用p,获得它指向的对象。
p->mem 等价于(*p).mem
p.get() 返回p中保存的指针,要小心使用,若智能指针释放了对象,返回的指针所指向的对象也就消失了。
swap(p, q) p.swap(q) 交换pq中的指针

shared_ptr独有的操作

操作 解释
make_shared(args) 返回一个shared_ptr,指向一个动态分配的类型为T的对象。使用args初始化此对象。
shared_ptrp(q) pshared_ptr q的拷贝;此操作会递增q中的计数器。q中的指针必须能转换为T*
p = q pq都是shared_ptr,所保存的指针必须能互相转换。此操作会递减p的引用计数,递增q的引用计数;若p的引用计数变为0,则将其管理的原内存释放。
p.unique() p.use_count()是1,返回true;否则返回false
p.use_count() 返回与p共享对象的智能指针数量;可能很慢,主要用于调试。

使用动态内存的三种原因

  • 程序不知道自己需要使用多少对象(比如容器类)。
  • 程序不知道所需要对象的准确类型。
  • 程序需要在多个对象间共享数据。

直接管理内存

new动态分配和初始化对象。

  • new无法为分配的对象命名(因为自由空间分配的内存是无名的),因此是返回一个指向该对象的指针。
  • int *pi = new int(123);
  • 一旦内存耗尽,会抛出类型是bad_alloc的异常。

delete将动态内存归还给系统。

  • 接受一个指针,指向要释放的对象。
  • delete后的指针称为空悬指针(dangling pointer)。

使用newdelete管理动态内存存在三个常见问题:

  • 1.忘记delete内存。
  • 2.使用已经释放掉的对象。
  • 3.同一块内存释放两次。

坚持只使用智能指针可以避免上述所有问题。

shared_ptr和new结合使用

定义和改变shared_ptr的其他方法

操作 解释
shared_ptr p(q) p管理内置指针q所指向的对象;q必须指向new分配的内存,且能够转换为T*类型
shared_ptr p(u) punique_ptr u那里接管了对象的所有权;将u置为空
shared_ptr p(q, d) p接管了内置指针q所指向的对象的所有权。q必须能转换为T*类型。p将使用可调用对象d来代替delete
shared_ptr p(p2, d) pshared_ptr p2的拷贝,唯一的区别是p将可调用对象d来代替delete
p.reset() p是唯一指向其对象的shared_ptrreset会释放此对象。若传递了可选的参数内置指针q,会令p指向q,否则会将p置空。若还传递了参数d,则会调用d而不是delete来释放q
p.reset(q) 同上
p.reset(q, d) 同上

智能指针和异常

如果使用智能指针,即使程序块由于异常过早结束,智能指针类也能确保在内存不需要的时候将其释放。

智能指针陷阱

  • 不用相同的内置指针初始化(或reset)多个智能指针
  • delete get()返回的指针。
  • 如果你使用get()返回的指针,记得当最后一个对应的智能指针销毁后,你的指针就无效了。
  • 如果你使用智能指针管理的资源不是new分配的内存,记住传递给它一个删除器。

unique_ptr

某一个时刻只能有一个unique_ptr指向一个给定的对象。

不支持拷贝或者赋值操作。

向后兼容:auto_ptr:老版本,具有unique_ptr的部分特性。特别是,不能在容器中保存auto_ptr,也不能从函数返回auto_ptr

unique_ptr操作

操作 解释
unique_ptr u1 unique_ptr,可以指向类型是T的对象。u1会使用delete来是释放它的指针。
unique_ptr<T, D> u2 u2会使用一个类型为D的可调用对象来释放它的指针。
unique_ptr<T, D> u(d) unique_ptr,指向类型为T的对象,用类型为D的对象d代替delete
u = nullptr 释放u指向的对象,将u置为空。
u.release() u放弃对指针的控制权,返回指针,并将u置空。
u.reset() 释放u指向的对象
u.reset(q) u指向q指向的对象
u.reset(nullptr) u置空

weak_ptr

weak_ptr是一种不控制所指向对象生存期的智能指针。

指向一个由shared_ptr管理的对象,不改变shared_ptr的引用计数。

一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放,不管有没有weak_ptr指向该对象。

weak_ptr操作

操作 解释
weak_ptr w weak_ptr可以指向类型为T的对象
weak_ptr w(sp) shared_ptr指向相同对象的weak_ptrT必须能转换为sp指向的类型。
w = p p可以是shared_ptr或一个weak_ptr。赋值后wp共享对象。
w.reset() w置为空。
w.use_count() w共享对象的shared_ptr的数量。
w.expired() w.use_count()为0,返回true,否则返回false
w.lock() 如果expiredtrue,则返回一个空shared_ptr;否则返回一个指向w的对象的shared_ptr

动态数组

new和数组

new一个动态数组:

  • 类型名之后加一对方括号,指明分配的对象数目(必须是整型,不必是常量)。
  • 返回指向第一个对象的指针
  • int *p = new int[size];

delete一个动态数组:

  • delete [] p;

unique_ptr和数组:

  • 指向数组的unique_ptr不支持成员访问运算符(点和箭头)。
操作 解释
unique_ptr<T[]> u u可以指向一个动态分配的数组,整数元素类型为T
unique_ptr<T[]> u(p) u指向内置指针p所指向的动态分配的数组。p必须能转换为类型T*
u[i] 返回u拥有的数组中位置i处的对象。u必须指向一个数组。

allocator类

  • 标准库allocator类定义在头文件memory中,帮助我们将内存分配和对象构造分离开。
  • 分配的是原始的、未构造的内存。
  • allocator是一个模板。
  • allocator<string> alloc;

标准库allocator类及其算法

操作 解释
allocator a 定义了一个名为aallocator对象,它可以为类型为T的对象分配内存
a.allocate(n) 分配一段原始的、未构造的内存,保存n个类型为T的对象。
a.deallocate(p, n) 释放从T*指针p中地址开始的内存,这块内存保存了n个类型为T的对象;p必须是一个先前由allocate返回的指针。且n必须是p创建时所要求的大小。在调用deallocate之前,用户必须对每个在这块内存中创建的对象调用destroy
a.construct(p, args) p必须是一个类型是T*的指针,指向一块原始内存;args被传递给类型为T的构造函数,用来在p指向的内存中构造一个对象。
a.destroy(p) pT*类型的指针,此算法对p指向的对象执行析构函数。

allocator伴随算法

操作 解释
uninitialized_copy(b, e, b2) 从迭代器be给定的输入范围中拷贝元素到迭代器b2指定的未构造的原始内存中。b2指向的内存必须足够大,能够容纳输入序列中元素的拷贝。
uninitialized_copy_n(b, n, b2) 从迭代器b指向的元素开始,拷贝n个元素到b2开始的内存中。
uninitialized_fill(b, e, t) 在迭代器be执行的原始内存范围中创建对象,对象的值均为t的拷贝。
uninitialized_fill_n(b, n, t) 从迭代器b指向的内存地址开始创建n个对象。b必须指向足够大的未构造的原始内存,能够容纳给定数量的对象。
  • 定义在头文件memory中。
  • 在给定目的位置创建元素,而不是由系统分配内存给他们。