C++prime读书笔记(二)C++标准库


layout: post
title: C++prime读书笔记(二)C++标准库
description: C++prime读书笔记(二)C++标准库
tag: 读书笔记


C++Prime读书笔记

  • 第8章:IO库
    • 写文件
    • 读文件
    • string流
  • 第9章:顺序容器
    • 顺序容器类型:
    • 容器操作
      • assign
      • swap
      • 向容器中添加元素insert,emplace
      • 容器中删除元素
      • 容器操作可能使得迭代器失效
    • vector与string的空间分配
    • 额外的string操作
      • 构造string的其他方法
      • substr操作
      • append和replace函数
      • string的搜索操作
      • 数值转换
    • 容器适配器
  • 第10章:泛型算法
    • 概念
    • 常见算法
      • 只读算法
      • 写容器元素的算法
        • back_inserter插入迭代器
        • 拷贝算法
      • 重排容器元素的算法
    • 自定义操作
      • 向算法传递函数
        • 谓词
        • 排序算法
      • lambda表达式
        • 使用捕获列表

第8章:IO库

  1. IO类:C++中有如下IO库类型,表头是它们所属的头文件。常见的cin,cout,这些输入流和输入流对象在头文件iostream中。fstream定义了读写命名文件的类型,sstream定义了读写内存string对象的类型。
    C++prime读书笔记(二)C++标准库_第1张图片
  2. IO对象无拷贝或者赋值,故也不能将形参或者返回类型设置为流类型。但是可以用引用的方法传递和返回流,此外读写一个IO对象会改变其状态,故传递和返回的引用不能是const类型。
  3. IO类的条件状态
    IO操作可能发生错误,有些可以恢复但是有一些不能恢复,IO类定义的一些函数和标志,可以帮助我们访问和操纵流的条件状态。以上的三个流都有一样的标志,标志如下表。
    C++prime读书笔记(二)C++标准库_第2张图片
    C++prime读书笔记(二)C++标准库_第3张图片

下边是io错误的例子,期望的ival是int类型,假如我们键入“Boo”,读操作就会失败,cin进入错误状态。所以代码通常应该在使用一个流之前检查它是否处于良好的状态,最好的方法就是把它当作一个条件使用,例如下边的while语句,当键入int数字时,条件为真,维持键入状态,否则会退出while。

int ival;
cin << ival;

while(cin << ival)
	// ok:读操作成功
  1. 管理输出缓冲:每个输出流管理着一个缓冲区,用来保存程序读写的数据,运行程序将多个输出操作组合为一个写操作。缓冲区管理方式如下:
  • 使用endl操纵符,执行换行并刷新缓冲区。
  • 使用flush操纵符,执行刷新缓冲区。
  • 使用ends操纵符,执行空格并刷新缓冲区。
  • 如果想在每次输出操作后都刷新缓冲区,使用unitbuf操纵符,告诉流对象接下来每次写操作之后都进行一次flush操作。使用nounitbufunitbuf前边加no,取消的意思)操纵符,重置流,恢复默认的刷新机制。

写文件

  1. 包含头文件#include
  2. 创建流对象 ofstream ofs;
  3. 打开文件 ofs.open("文件路径",打开方式);
  4. 写数据 ofs << "写入的数据";
  5. 关闭文件 ofs.close();
    C++prime读书笔记(二)C++标准库_第4张图片
    注:打开方式可以配合使用,利用|操作符
    eg : 用二进制方式写入文件:
    ios::binary | ios::out

读文件

  1. 包含头文件#include
  2. 创建输入流对象 ofstream ifs;
  3. 打开文件 ifs.open("文件路径",打开方式);
  4. 读数据
  5. 关闭文件 ofs.close();
#include
#include
using namespace std;


void main()
{
	ifstream ifs;
	ifs.open("test.txt", ios::in);
	if (!ifs.is_open())
	{
		cout << "文件打开失败" << endl;
		return;
	}
	// 读数据
	/*
		第一种
	char buf[1024] = { 0 };
	while (ifs >> buf)
	{
		cout << buf << endl;
	}
	ifs.close();
	
	*/
	/*第二种
	char buf[1024] = { 0 };
	// ifs.getline(buf, sizeof(buf),读取到buf,最多读sizeof(buf)个字节
	while (ifs.getline(buf, sizeof(buf)))
	{
		cout << buf << endl;
	}
	*/
	/*
	* 第三种
	* string buf;
	* while(getline(ifs, buf))
	* {
		cout << buf << endl;
	* }
	* 
	*/
	/*第四种
	* 一个字符一个字符读取,判断是否到达文件尾部EOF
	*/
	char c;
	while (( c = ifs.get()) != EOF)
	{
		cout << c;
	}
	ifs.close();
	return;
}
  • 读文件可以利用ifstream,或者fstream
  • 读文件可以利用ifs.is_open()函数判断文件是否打开成功
  • ifs.close(); 关闭文件

string流

下边这段代码用于循环处理下边的信息:
morgan 0003031 324244
drew 12323
lee 9900 33132 331333
文件的每条记录都以人名开始,跟随着一个或者多个电话号码。
最外层的while循环逐行读取数据,直至cin遇到文件尾。
用输入字符串流 istringstream与读取到的文本行line绑定,记录为record。

struct PersonInfo
{
	string name;
	vector<string> phones;
};
int main() {
	string line, word; // 分别保持来自输入的一行和单词
	vector<PersonInfo> people;
	while (getline(cin, line))
	{
		PersonInfo info;
		istringstream record(line); // 将记录绑定到刚读入的行
		record >> info.name; // 读取名字
		while (record >> word)
		{
			info.phones.push_back(word); // 保持它们
		}
		people.push_back(info);
	}
	return 0;
}

第9章:顺序容器

顺序容器类型:

* vector
* deque
* list(双向链表)
* forward_list(单向链表)
* array(固定大小的数组)
* string

容器操作

  • 成员
    • iterator: 容器的迭代器类型
    • const_iterator: 容器的只读迭代器类型
    • reverse_iterator:按逆序寻址的迭代器
    • size_type: 无符号整形,足够保存此种容器最大可能容器大小
    • difference_type: 带符号整形,足够保存两个迭代器之间的距离
    • value_type :元素类型
    • reference:元素的左值类型,与value_type &含义相同(元素指针类型)
    • const_reference
  • 构造
    • C c; 默认构造,构造空容器
    • C c1(c2);拷贝构造
    • C c(b, e); 构造c,将迭代器b和e指定范围内的元素拷贝到c
    • C c{a, b, c……} ;初始化列表
  • 赋值与交换
    • c1 = c2; 将c1中元素替换为c2
    • c1 = {a, b, c……};将c1中元素替换为列表中元素
    • a.swap(b); 交换容器a和容器b的元素
    • swap(a, b);与上一行等价
  • 大小
    • c.size();
    • c.max_size();c可保存的最大元素数目
    • c.empty()
  • 添加删除元素
    • c.insert()
    • c.erase()
    • c.clear()
    • c.emplace()
  • 迭代器
    • c.begin()
    • c.end()
    • c.rbegin()
    • c.rend() :rbegin()和rend()的类型为reverse_iterator,rbegin()指向末尾元素,rend()指向首元素的前一个地址。

注意1:当用一个容器对另一个容器进行拷贝赋值时,两个容器的类型和容器中元素的类型都必须一致。
注意2: 标准库array与内置数组不同,允许赋值和花括号初始化,但不允许花括号列表赋值,因为花括号列表元素的大小可能与固定数组array不一致。

// ans.rbegin()用法
vector<int>ans({ 1, 2, 3 });
for (vector<int>::reverse_iterator it = ans.rbegin(); it != ans.rend();it++)
{
	cout << (*it) << endl;
}
// 拷贝赋值
vector<int>copy_ans(ans);
// 双端队列可以用vector的子序列拷贝赋值,这是由于双端队列的底层有用vector来实现
deque<int> dq(ans.begin(), ans.end());	

// 标准库array与内置数组不同,允许赋值和花括号初始化,但不允许花括号列表赋值,因为花括号列表元素的大小可能与固定数组array不一致.
array<int, 10> a1 = {0,1,2,3,4,5};	
array<int, 10> a2 = {0}; // 10个0
a1 = a2; //用a2给a1赋值
a2 = {0}; // 错误,不允许用花括号列表给array赋值

assign

assign允许将一个不同但是相容的类型赋值,或者从容器的一个子序列赋值。例如使用assign可以实现将一个vector中的一段char*值赋予一个list中的string:

list<string> names;
vector<const char*> oldstyle;
names = oldstyle; // 错误,容器类型不相符
names.assign(oldstyle.cbegin(), oldstyle.cend());

list<string> slist1(1); // 一个空的字符串元素
slist.assign(10, "Hiya");

swap

swap交换两个相同类型的容器的内容,swap不对任何元素进行拷贝,删除或插入,因此可以在很快的常数时间内完成。假定iter在swap前指向svec1[3]的string,那么在swap交换后,iter指向了svec2[3]的元素。与其他容器不同,对一个string调用swap会导致迭代器,引用和指针失效。

向容器中添加元素insert,emplace

  • insert除了可以接收单个元素外,还可以接收指定数量或者范围内的元素
    例如:svec.insert(svec.end(), 10, “Anna”),注意,第一个参数必须是迭代器,插入位置是包含第一个参数迭代器指向的位置的。
svec.insert(svec.end(), 10, "Anna"); //从某个位置起(包含),插入10个“Anna” 
ans.insert(ans.end(), ans.begin(), ans.end());

C++11引入了三个新成员——emplace_front、emplace、emplace_back();
这些操作对于push_front,push,push_back();二者的区别可以用下边一个例子说明:
假定用容器c保存PersonInfo元素。
test.emplace_back(“lzy”, 22); // 正确
相当于:test.push_back(PersonInfo(“lzy”, 22)); //正确创建一个临时的PersonInfo对象传递给push_back.
emplace_back会在容器管理的内存中直接创建对象,而调用push_back则会创建一个局部临时对象,并将它压入容器,所以emplace是原地构造新的元素对象。

struct PersonInfo
{
	PersonInfo(string _name, int _age) : name(_name), age(_age) {}
	string name;
	int age;
};
int main() {
	vector<PersonInfo> test;
	test.emplace_back("lzy", 22); // 正确
	test.push_back("lzy", 22); // 错误,没有接收2个参数push_back
	test.push_back(PersonInfo("lzy", 22)); //正确创建一个临时的PersonInfo对象传递给push_back.
	return 0;
}

容器中删除元素

删除单个元素:
注意下边语句中迭代器仅在不需要删除元素时后移,这是因为删除操作会动态改变容器数据,迭代器指向的元素会发生变化。

list<int> lst = {0,1,2,3,4,5};
auto it = lst.begin();
while(it != lst.end()){
	if(*it % 2){
		it = lst.erase(it); // 删除此元素
	}else{
		++it;
	}
}

删除多个元素:
接首两个迭代器参数的erase允许我们删除一个范围内的元素。同样因为删除会改变数据结构长度,迭代器指向的元素会变化。它可以由返回值,返回指向最后一个被删元素之后位置的迭代器。
elem1指向删除的第一个元素位置,elem2指向删除的最后一个元素之后的位置。(左闭右开)
删除完毕后,返回的迭代器就是elem2,故最后elem1 = elem2

elem1 = slist.erase(elem1, elem2);

容器操作可能使得迭代器失效

  • 向容器添加元素后:
    • 如果容器是vector或string,且存储空间被重新分配,则指向容器的迭代器、指针和引用都会失效。如果存储空间未重新分配(vector与string都是动态增长存储空间,空间增长后就会重新分配存储空间),指向插入位置之前的元素的迭代器,指针和引用仍然有效,但指向插入位置之后元素的迭代器、指针和引用失效。
    • 对于deque,插入到除首尾位置之外的任何位置都会导致迭代器,指针和引用失效。如果在首尾位置添加元素,迭代器会失效,但是指向存在的元素的引用和指针不会失效。
    • 对于list和forward_list,指向容器的迭代器(包括尾后迭代器和首前迭代器),指针和引用都仍有效。
  • 当从容器删除元素后。
    • 对于vector和string,指向被删除元素之前的元素的 迭代器、引用和指针任然有效
    • 对于deque,如果在首尾之外任何位置删除元素,那么指向被删除元素外的其他元素的迭代器、指针和引用都会失效。删除deque的尾部元素,尾后迭代器也会失效,其他迭代器、引用、指针不受影响。删除首元素,这些不受影响。
    • 对于list和forward_list,指向容器的迭代器(包括尾后迭代器和首前迭代器),指针和引用都仍有效。

vector与string的空间分配

vector和string通常会分配比新空间需求更大的内存空间,以预留空间备用。这种分配策略避免了每次添加新元素时都需要重新分配内存空间。
vector中string提供了一些成员函数,允许我们与它实现中的内存分配互动。

  • c.shrink_to_fit:只适用于vector、string和deque,用于将capacity减少为与size()相同大小
  • c.capacity() 不重新分配内存空间的话,c可以保存多少元素
  • c.resize():重新指定容器大小,多余元素赋值0
  • c.reserve(n) 分配至少能容纳n个元素的内存空间。(reserve并不改变容器中元素的数目,仅影响预先分配的多大的内存,它与resize是相对的。resize划定的大小范围是可以通过下标访问的,reserve则不行)

额外的string操作

构造string的其他方法

  • string s(cp, n); s是cp指向的数组中前n个字符的拷贝,此数组至少包含n个字符
  • string s(s2, pos); s是从字符串s2从下标pos开始的字符子串的拷贝
  • string s(s2, pos, len);s是字符串s2从pos开始,长len的字符子串的拷贝,至多拷贝s2.size() - pos个字符。
const char *cp = "Hello world!!!"; // 以空字符结束的数组
char noNull[] = {'H', 'i'}; //不是空字符结束的数组
string s1(cp); // 拷贝cp中的字符,直到遇到空字符
string s2(noNull, 2); // 从noNull拷贝两个字符
string s3(noNull);   // 可以会出现问题,因为noNull不以空字符结尾,所以拷贝时不知何时结束
string s4(cp + 6, 5) // 从cp[6]开始拷贝5个字符,得到"world"
string s5(s1,6, 5 ) // 从s1[6]开始拷贝5个字符
string s6(s1, 6) // 从s1[6]开始拷贝到结束
string s7(s1, 6, 20); // 正确,只会拷贝到结尾
string s8(s1, 16); // 错误,抛出一个越界的错误 

substr操作

s.substr(pos, n); // 返回从字符串s的下标pos开始,长为n的子字符串,n的默认大小为s.size() - pos

append和replace函数

append操作在string末尾插入字符串
replace操作是调用erase和insert的一种简写形式

string s("C++ prime"), s2 = s;
s.insert(s.size(), " 4th Ed."); // s == "C++ prime 4th Ed."
s2.append(" 4th Ed."); // 与上一行含义一致

s.erase(11, 3);
s.insert(11, "5th")
s2.replace(11, 3, "5th"); // 从s2的11开始删除3个字符替换为5th,等价上边两句

string的搜索操作

args必须是以下形式之一:

  • c, pos 从s中位置pos开始查找字符c,pos默认为0

  • s2, pos从s中位置pos开始查找字符串s2.pos默认为0

  • cp,pos从s中位置pos开始查找指针cp指向的以空字符结尾的c风格字符串,pos默认为0

  • cp, pos, n,从s中位置pos开始查找指针cp指向数组前n个字符,pos和n无默认值

    • s.find(args); //查找s中第一次出现args的位置
    • s.rfind(args);// 查找s中最后一次出现args的位置
    • s.find_first_of(args);//在s中查找args中任何一个字符第一次出现的位置
    • s.find_last_of(args);// 在s中查找args中任何一个字符最后一次出现的位置
    • s.find_first_not_of(args); //在s中查找第一个不在args中的字符
    • s.find_last_not_of(args);//在s中查找最后一个不在args中的字符

数值转换

  • to_string(val):返回数值val的string的string表示,val可以是任何算术类型
  • stoi(a, p, b):返回s的起始子串(表示整数内容)的数值,返回值类型分别是int,long,unsigned_long……p是size_t指针,用来保存s中第一个非数值字符的下标,p默认为0,即函数不保存下标,而b是转换所用的基数。
  • stol(a p,b)
  • stoul(a p,b)
  • stoll(a p,b)
  • stoull(a p,b)
  • stof(s, p)
  • stod(s, p)
  • stold(s, p)

容器适配器

除了顺序容器,标准库还定义了三个顺序容器适配器:stack,queue和priority_queue。

  1. 每个适配器都定义两个构造函数:默认构造函数创建一个空对象,接受一个容器的构造函数拷贝该容器来初始化适配器。假定deq是一个deque,我们可以用deq来初始化一个新的stack:stack stk(deq); // 从deq拷贝元素构造stk
  2. 默认情况下,stack和queue都是基于deque实现的,priority_queue是在vector之上实现的。我们可以在创建一个适配器时将一个命名的顺序容器作为第二个类型参数,来重载默认容器类型。
// 在vector上实现的空栈
stack<string, vector<string>> str_stk;
// str_stk2在vector上实现,初始化时保存svec的拷贝
stack<string, vector<string>>str_stk2(svec);

第10章:泛型算法

概念

大多数算法都定义在头文件algorithm中,标准库还在头文件numeric中定义了一组数值泛型算法。一般情况下算法并不直接操作容器,而是遍历由两个迭代器指定的一个元素范围。迭代器令算法不依赖于容器,算法永远不会执行容器的操作。

常见算法

标准库提供超过100个算法,但这些算法有一致的结构,理解结构可以帮助我们更容易地学习和使用这些算法

只读算法

一些算法只读取输入范围的元素,但不改变元素。例如findcount和定义在numeric中的accumulate,它接受3个参数,前两个指定了求和的元素的范围,第三个参数是和的初始值。例如下边这条语句sum求取了vec中所有元素的和。
int sum = accumulate(vec.cbegin(), vec.cend(), 0);
accumulate将第三个参数作为求和的起点,这里隐含地假设了元素类型是可以求和的操作的,故上例中元素类型可以是int,long,double,long long等。
由于string定义了+运算符,因此可以调用accumulate来将vector中的string元素连接起来:
string sum = accumulate(v.cbegin(), v.cend(), string(""));
注意这里最后一个参数显式地创建了一个string,而非直接将字面值""作为参数传递,原因在于如果我们传递的是字符串字面值,用于保存和的对象的类型将是const char *.这样便会产生矛盾,所以应该构建一个临时的string变量作为参数传递,而不能使用字面值常量。

equal是另一个只读算法,用于确定两个序列是否保存相同的值。如果两序列所有对应元素相等,返回true,否则返回false,此算法也可以接受三个迭代器,前两个表示第一个序列的元素范围,第三个表示第二个序列的首元素。这些只接受一个单一迭代器来表示第二个序列的算法,都假定第二个序列至少与第一个序列一样长,并且比较的长度基于第一个序列的长度。例如下边的例子,str1与str2的equal结果是1.

	string str1 = "abc";
	string str2 = "abcd";
	cout << equal(str1.begin(), str1.end(), str2.begin()); // 打印结果为1,相等

写容器元素的算法

一些算法将新值赋予序列中的元素,当使用这类算法时,必须确保序列的大小,至少不小于我们要求算法写入的元素的数目。例如fill,它接受一对迭代器表示一个范围,还接受一个值作为第三个参数,将这个给定值赋予输入序列范围中每个元素。
fill(vec.begin(), vec.end(), 0); // 将每个元素重置为0
fill(vec.begin(), vec.begin() + vec.size() / 2, 10;)
一些算法接受一个迭代器指出一个单独的目的位置,从该位置开始赋值。例如fill_n
fill_n(vec.begin(), vec.size(), 0);

back_inserter插入迭代器

一种保证算法有足够元素空间来容纳输出数据的方法是使用插入迭代器back_inserter,它是定义在头文件iterator中的一个函数。它接受一个指向容器的引用,返回一个与该容器绑定的插入迭代器,当我们向此迭代器赋值时,赋值运算符会调用push_back将一个具有给定值的元素添加到容器中。

vector<int>vec;
auto it = back_inserter(vec); // 通过它赋值,会将元素添加到vec中
*it = 42; // vec中现有一个元素,值为42

我们常常使用back_inserter来创建一个迭代器,作为算法的目的位置来使用:

vector<int>vec;
fill_n(back_inserter(vec), 10, 0); // 添加10个元素到vec

在每步迭代中,fill_n向给定序列的一个元素赋值,由于我们传递的参数是back_inserter返回的迭代器,因此每次赋值都会在vec上调用push_back.

拷贝算法

copy算法接受三个迭代器,前两个表示一个输入范围,第三个表示目的序列的起始位置,此算法将输入范围中的元素拷贝到目的序列中,传递给copy的目的序列至少要包含与输入序列一样多的元素。可以使用copy实现内置数组的拷贝:

int a1[] = {0,1,2,3,4,5};
int a2[sizeof(a1) / sizeof(*a1)];
auto ret = copy(begin(a1), end(a1), a2); // 把a1的内容拷贝给a2,ret指向a2尾元素下一个位置

replace算法读入一个序列,并将其中所有等于给定值的元素都改为另一个值。此算法接受4个参数,前两个是迭代器,后两个一个是要搜索的值,另一个是新值。
如果希望保留原序列不变,可以调用replace_copy算法,此算法额外接受第三个迭代器参数,指向调整后序列的保存位置:

replace(ilst.begin(), ilst.end(), 0, 42); // 将ilst中所有的0替换为42
replace_copy(ilst.cbegin(), ilst.cend(), back_inserter(ivec), 0, 42);
// 此调用后,ilst并未改变,ivec包含ilst的一份拷贝,不过原来在ilst中0替换为了42

重排容器元素的算法

某些算法会重排容器中的元素的顺序,比如sort
为了消除重复单词,首先将vector排序,使得重复的单词相邻出现,一旦vector排序完毕,使用unique算法重排vector,使得不重复的元素出现在vector的开始部分,返回一个迭代器,指向元素不重复出现序列尾部的下一个位置。由于算法不能执行容器的操作,所有我们使用erase成员函数来完成真正的删除操作。

void elimDumps(vector<string> &words){
	// 按字典序排序words
	sort(words.begin(), words.end());
	auto end_unique = unique(words.begin(), words.end());
	// 使用向量操作erase删除重复单词
	words.erase(end_unique, words.end());
}

自定义操作

向算法传递函数

sort算法默认使用元素类型的<运算符,但可能我们希望的排序顺序与<所定义的顺序不同,或是我们的序列可能保存的是未定义<运算符的元素类型,在这两种情况下,都需要重载sort的默认行为。重载的sort接收第三个参数,它是一个谓词

谓词

谓词是一个可调用的表达式,其返回结果是一个能用作条件的值。谓词分为两类:一元谓词二元谓词(意味着它有两个参数)。接受谓词参数的算法对输入序列中的元素调用谓词。因此元素类型必须能转换为谓词的参数类型。

接受一个二元谓词的sort使用这个谓词来代替**<**来比较元素。
例如:下边这段函数可以按长度由短到长排序words

bool isShorter(const string &s1, const string &s2){
	return s1.size() < s2.size();
}

sort(words.begin(), words.end(), isShorter); // 按长度由短到长排序words

排序算法

在words按大小重排的同时,还希望具有相同长度的元素按字典序重排,为了保存相同长度的单词按字典序排列,可以使用stable_sort算法。稳定排序算法维持相等元素的原有顺序。

elimDups(words); // 将words按照字典序重排并消除重复单词
stable_sort(word.begin(), word.end(), isShorter);
for(const auto &s : words){
	cout << s << " ";
}
cout << endl;

lambda表达式

根据算法接受一元谓词还是二元谓词,我们传递给算法的谓词必须验证接受一个或两个参数。但有时,我们希望进行的操作需要更多参数。以下边这个需求为例:
求大于等于一个给定长度的单词有多少。
使用标准库find_if算法来查找第一个具有特定大小是元素,find_if算法接受三个参数,前两个是一对迭代器,表示一个范围,第三个参数是一个谓词,返回第一个使得谓词非0的元素,如果不存在这样的元素,返回尾迭代器。find_fi接受一个参数。没有办法再传递给他第二个参数表示长度。为此,需要引用lambda表达式。

void biggies(vector<string>& words, vector<string>::size_type sz){
	elimDups(words); // 字典序排序,去重
	stable_sort(words.begin(), words.end(), isShorter); 
	// 获取一个迭代器,指向第一个满足size() >= sz的元素
	// 计算满足size  >= sz的元素的数目
	// 打印长度大于等于给定值的单词,每个单词后边接一个空格
}

一个lambda表达式表示一个可以调用的代码单元,可以把他理解为一个未命名的内联函数。与任何函数类似,一个lambda具有一个返回类型,一个参数列表和一个函数体。但与函数不同的是,lambda可能定义在函数内部。
一个lambda表达式具有如下形式:
[capture list] (parameter list) ->return type{function body}
其中capture list(捕获列表)是一个lambda所在函数中定义的局部变量列表,参数列表与函数体与普通函数一样。不同的是,lambda必须使用尾置返回来指定返回类型。我们可以忽略参数列表与返回类型,但是必须永远包含捕获列表和函数体
auto f = [] {return 42;};
上边这句代码定义了一个可调用对象f,不接受参数,返回42.
cout << f() << endl; // 打印42
在lambda中忽略括号和参数列表等价指定一个空参数列表。

下边采用lambda表达式来编写一个功能与isShorter函数相同的函数:
空捕获列表表明此lambda不使用它所在函数中任何局部变量,lambda的参数与isShorter是类似的

[] (const string& a, const string& b){return a.size() < b.size()}

// 使用lambda调用算法
stable_sort(words.begin(), words.end(), [](const string & a, const string &b){return a.size() < b.size();});

使用捕获列表

一个lambda可以使用一个函数中的局部变量,但必须明确地在捕获列表中指明。

[sz](const string &a, const string &b){return a.size() >= sz;}

完整的biggies

void biggies(vector<string>& words, vector<string>::size_type sz){
	elimDups(words); // 字典序排序,去重
	stable_sort(words.begin(), words.end(), [](const string &a, const string &b) {return a.size() < b.size();});  // 使用lambda按照长度排序
	// 获取一个迭代器,指向第一个满足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;	
}

你可能感兴趣的:(读书,笔记,c++)