C++ Primer 读书笔记03
输出缓冲
每个输出流都管理一个缓冲区,用来保存程序读写的数据
导致缓冲刷新的原因:
- 程序正常结束,作为
main
函数的return
操作的一部分,缓冲刷新 - 缓冲区满时
- 使用操纵符
endl
来显示刷新缓冲区 - 每个输出操作后用操作符
unitbuf
设置流的内部状态,来清空缓冲区,默认cerr
是设置unitbuf
的 - 读写关联的流时,如读
cin
会导致cout
的缓冲区被刷新
刷新缓冲区
cout << "hi" << endl; // 输出 hi 和一个换行,然后刷新缓冲区
cout << "hi" << flush; // 输出 hi ,然后刷新缓冲区
cout << "hi" << ends; // 输出 hi 和一个空字符,然后刷新缓冲区
cout << unitbuf; // 所有之后的输出操作都会立即刷新缓冲区
cout << nounitbuf; // 恢复正常
如果程序异常终止,输出缓冲区是不会被刷新的
顺序容器
向一个vector
、string
或deque
插入元素会使所有指向容器的迭代器、引用和指针失效
vector
和string
不支持push_front
和emplace_back
emplace
使用push_back
时实际上放入容器的是对象值得一个拷贝,而不是对象本身,而当调用一个emplace
成员函数时,则是将参数传递给元素类型的构造函数,emplace
成员使用这些参数在容器管理的内存空间中直接构造元素
c.emplace_back("1234", 25, 15.99); // 在 c 的末尾构造一个 Sales_data 对象
c.push_back(Sales_data("1234", 25, 15.99)); // 创建一个临时的 Sales_data 对象传递给 push_back
调用emplace_back
会在容器管理的内存空间中直接构造对象,而调用push_back
则会创建一个局部临时对象并将其压入容器中
front
和back
返回首元素和尾元素的引用,注意在调用之前,先用enpty
判断是否为空
迭代器失效
在向容器添加元素后:
- 如果容器是
vector
或string
,且存储空间被重新分配,则指向容器的迭代器、引用和指针失效,如果存储空间为被重新分配,指向插入位置之前的元素的迭代器、引用和指针仍有效,但指向插入位置之后的迭代器、引用和指针失效 - 对于
deque
,插入到除首尾位置之外的任何位置都会导致迭代器、引用和指针失效,如果在首尾位置添加元素,迭代器会失效,但指向存在的元素的指针和引用不会失效 - 对于
list
和forward_list
,指向容器的迭代器、引用和指针有效
删除一个元素后:
- 对于
list
和forward_list
,指向容器其他位置的迭代器(包括尾后迭代器和首前迭代器)、引用和指针有效 - 对于
deque
,如果在首尾位置之外的任何位置删除位置,那么指向被删除元素外其他元素的迭代器、引用和指针会失效;如果删除deque
的尾元素,则尾后迭代器会失效,其他不受影响;如果删除首元素,这些也不会受影响 - 对于
vector
或string
,指向被删元素之前的迭代器、引用和指针仍有效;注意:当我们删除元素时,尾后迭代器总是会失效
vector
size
指已经保存的元素的数量capacity
是在不分配新的内存空间前提下最多可以保存多少元素
容器适配器
适配器是标准库的一个通用概念,容器、迭代器和函数都有适配器,本质上,一个适配器是一种机制,能使某种事物的行为看起来像另外一种事物一样,一个容器适配器接受一种已有的容器类型,使其行为看起来像一种不同的类型
栈适配器
默认情况下stack
和queue
是基于deque
实现的,可以使用除array
和forward_list
之外的任何容器类型来构造
stack<int> stk(deq); // 假设 deq 是一个 deque<int> ,从deq 拷贝元素到 stk
stack<string, vector<string>> str_stk; // 在 vector 上实现的空栈
队列适配器
queue
适配器可以构造于list
和queue
之上,但不能基于vector
构造
priority_queue
可以构造于vector
和deque
之上,但不能基于list
构造
lambda
一个lambda
表达式表示一个可调用的代码单元,可以理解为一个未命名的内联函数,其形式如下:
[capture list](parameter list) -> return type { function body }
其中 capture list (捕获列表)实验一个lambda
所在函数中定义的局部变量的列表,与普通函数不同,lambda
必须使用尾置返回
可以忽略参数列表和返回类型,但是必须包含捕获列表和函数体,如:
auto f = [] { return 42; }
cout << f() << endl;
捕获列表
lambda
可以通过捕获列表使用定义它的函数的局部变量,但是只能使用哪些明确指明的变量
捕获列表只用于局部非static
变量,一个lambda
可以使用定义在当前函数之外的名字,如cout
void biggies(vector<string> &words, vector<string>::size_type sz)
{
elimDups(words); // 将 words 按字典排序,删除重复单词
// 按长度排序,长度相同的单词维持字典排序
stable_sort(words.begin(), words.end(), [](const string &a, const string &b) { return a.size() < b.size();});
// 获取一个迭代器,指向第一个满足 size() >= sz 的元素
auto wc = find_if(words.begin(), words.end(), [sz](const string &a) { return a.size() >= sz; });
// 计算满足 size() >= sz 的元素的数目
auto count = words.end() - wc;
cout << count << " " << make_plural(count, "word", "s")
<< " of length " << sz << " or longer" << endl;
// 打印长度大于等于给定值得单词
for_each(wc, words.end(), [](const string &s){cout << s << " ";});
cout << endl;
}
当定义一个lambda
时,编译器生成一个与lambda
对应的新的类类型
-
值捕获
被
lambda
捕获的变量的值是在lambda
创建时拷贝,而不是在调用时拷贝:void fun1() { size_t v1 = 42; auto f = [v1] {return v1;}; v1 = 0; auto j = f(); // j 为 42 }
-
引用捕获
void fun2() { size_t v1 = 42; auto f = [&v1] {return v1;}; v1 = 0; auto j = f(); // j 为 0 }
-
隐式捕获
可以让编译器根据
lambda
体中的代码来推断我们要使用哪些变量,为了指示编译器推断捕获列表,应在捕获列表中写一个&或=
,&
告诉编译器采用引用方式,=
采用值捕获方式
可变 lambda
默认一个值被拷贝的变量,lambda
不会改变其值,如果希望改变,需要在参数列表前加上mutable
void fun3() {
size_t v1 = 42;
auto f = [v1] mutable {return ++v1;};
v1 = 0;
auto j = f(); // j 为 43
}
指定 lambda 的返回类型
如果一个lambda
体中包含return
以外的任何语句,则编译器假定此lambda
返回void
当我们要为一个lambda
指定返回类型时,需要使用尾置返回类型
transform(vi.begin(), vi.end(), vi.begin(), [](int i) -> int {if (i < 0) return -i; else return i;});
bind(11)
定义在functional
中,可以看做一个通用的函数适配器,接受一个可调用对象,生成一个新的可调用对象来适应原函数的参数列表
其形式如下:
auto newCallable = bind(callable, arg_list);c
arg_list
的参数包含形如_n
的名字,这些参数是占位符,表示newCallable
的参数
ostream &print(ostream &os, const string &s, char c) {
return os << s << c;
}
for_each(words.begin(), words.end(), bind(print, ref(cout), _1, ' '));// _1 表明新的print函数只接受一个参数
为了使用_n
,需要声明using namespace std::placeholders;
默认情况下,bind
将不是占位符的参数拷贝到bind
返回的可调用对象中,为了给bind
传递一个对象而又不拷贝它,需要使用标准库
ref
函数,该函数也定义在functional
中
插入迭代器
插入迭代器是一种迭代器适配器,接受一个容器,生成一个迭代器,能实现向给定容器插入元素
back_inserter
创建一个使用push_back
的迭代器front_inserter
创建一个使用```push_front的迭代器inserter
创建一个使用insert
的迭代器,次函数接受第二个参数,一个指向容器的迭代器,元素被插入到给定迭代器的前面
iostream 迭代器
istream_iterator
读取输入流,ostream_iterator
向一个输出流写数据
istream_iterator<int> int_it(cin); // 从 cin 中读 int
istream_iterator<int> int_eof; // 默认初始化迭代器,相当于创建了一个尾后迭代器
vector<int> v(int_it, int_eof); // 构造 v
反向迭代器
除了forward_list
之外,其他容器都支持反向迭代器
sort(vec.rbegin(), vec.rend()); // 逆序排序
// first,middle,last 在一个逗号分隔的列表中查找最后一个元素
auto rcomma = find(line.crbegin(), line.crend(), ',');
cout << string(lien.crbegin(), rcomma); // 错误,将输出 tsal
cout << string(rcomma.base(), line.cend());