一、标准库并未给每个容器都定义成员函数来实现操作,而是定义了一组泛型算法,称算法,是因为它们实现了一些经典算法的公共接口。称泛型,是因为它们可以用于不同类型的元素和多种容器类型
二、find算法工作步骤: 利用迭代器解引用运算符可以实现元素访问,如果发现匹配元素,find可以返回指向该元素的迭代器,用迭代器递增运算符可以移动到下一个元素,尾后迭代器可以用来判断find是否到达给定序列的末尾,find可以返回尾后迭代器来表示未找到给定元素
三、除了少数例外,标准库算法都对一个范围内的元素进行操作,被称为“输入范围”,接受输入范围的算法总是使用前2个参数来表示此范围,2个参数分别是指向要处理的第一个元素和尾元素之后位置的迭代器
四、只读算法
1、find、count、accumulate、equal
2、accumulate定义在头文件numeric中,接受3个参数,前2个支持需要求和的元素的范围,第三个参数是和的初始值
3、由于string定义了+运算符,所以也可以通过调用accumulate来将vector中所有string元素连接起来发,但第三个参数不能传递一个字符串字面值,因为传递字面值,用于保存和的对象类型将是const char*,而const char*没有+运算符,应使用string("")而不使用“”
4、equal将第一个序列中的每个元素于第二个序列中的对应元素比较,所以equal可以比较2个不同类型的容器中的元素,其元素类型也可以不同,但是要能使用==比较; equal假定第二个序列至少与第一个序列一样长
五、写容器元素的算法
1、写容器元的算法必须确保序列原大小至少不小于要求算法写入的元素的数目,算法不会执行容器操作,因此算法自身不可能改变容器大小
2、fill、fill_n、back_inserter、copy、replace、replace_copy
3、fill接受一对迭代器表示一个范围,还接受一个值作为第三个参数,fill算法将给定的这个值赋予输入序列中的每个元素, fill_n接受一个单迭代器、一个计数值、一个值,将给定值赋予迭代器指向的元素开始的指定个元素
4、back_inserter接受一个指向容器的引用,返回一个与该容器绑定的插入迭代器,当通过此迭代器赋值时,赋值运算符会调用push_back将一个具有给定值的元素添加到容器中
5、插入迭代器是一种向容器中添加元素的迭代器,通常情况下,当我们通过一个迭代器向容器元素赋值时,值被赋予迭代器指向的元素,而当我们通过一个插入迭代器赋值时,一个与赋值号后侧值相等的元素被添加到容器中
6、copy接受3个迭代器,前2个表示一个输入范围,第3个表示目的序列的起始位置,传递给copy的目的序列至少要包含与输入序列一样多的元素,返回的是其目的位置迭代器的值
7、replace接受4个参数,前2个是迭代器表示输入序列,后2个是要搜索的值,另一个是要替换的新值; replace_copy保留原序列不变,通过第三个迭代器参数指出调整后序列的保存位置
六、重排容器元素的算法
1、sort、unique、
2、unique将相邻的重复项“消除”,并返回一个指向不重复值范围末尾额迭代器; unique不改变容器大小,重复元素也不是真正删除了,而是在最后一个不重复元素之后的位置之后。
七、谓词是一个可调用的表达式,其返回结果是一个能用作条件的值; 标准库算法所使用的谓词分2类:一元谓词,只接受1个参数, 二元谓词,只接受2个参数; 接受谓词参数的算法对输入序列中的元素调用谓词,因此,元素类型必须能转换未谓词的参数类型
八、如果希望排序按大小重排的同时也希望具有相同长度的元素按字典排序,可以使用stable_sort
九、lambda表达式
1、根据算法接受一元谓词还是二元谓词,传递给算法的谓词必须严格接受1个或2个参数,但有时希望接受多个参数,就超出了算法对谓词的限制
2、对于一个对象或一个表达式,如果可以对其使用调用运算符(调用运算符:当用点运算符访问一个成员函数时,通常是想调用该函数,就使用调用运算符()来调用一个函数,调用运算符是一对圆括号,里面放置实参列表)则称它为可调用的,即如果e是一个可调用的表达式,则我们可以编写代码e(args),其中args是一个逗号分隔的一个或多个参数列表
3、可调用对象: 函数、函数指针、重载了函数调用运算符的类、lambda表达式
4、一个lambda具有一个返回类型、一个参数列表、一个函数体
5、[capture list] (parameter list) -> return type {function body}; capture list被称为捕获列表,是一个lambda所在函数中定义局部变量的列表; parameter list是参数列表; return type是返回类型; function body是函数体
6、lambda可以忽略参数列表和返回类型,但必须包含捕获列表和函数体,捕获列表可以为空
7、lambda不能有默认参数
8、变量的捕获方式可以是值,也可以是引用
9、采用值捕获的前提是变量可以拷贝,被捕获的变量的值是在lambda创建时拷贝,而不是调用时拷贝
10、当采用引用捕获时,在lambda函数体内使用此变量时,实际上使用的是引用所绑定的对象
11、 隐式捕获: 为了指示编译器推断捕获列表,应在捕获列表中使用&或=; &告诉编译器采用引用捕获方式; =告诉编译器采用值捕获方式
12、当混合使用隐式捕获和显式捕获时,捕获列表中的第一个元素必须时一个&或者=,指定默认捕获方式为引用或值
13、当混合使用隐式捕获和显式捕获时,显示捕获的变量必须使用与隐式捕获不同的方式,即如果隐式捕获是引用方式(使用了&),则显式捕获命名变量必须采用值方式,即在名字前加=;相反,如果隐式捕获采用的是值方式(使用了=),则显示捕获命名变量必须采用引用方式,即在名字前加&
14、默认情况下,对于要给值被拷贝的变量,lambda不会改变其值,如果希望能改变一个被捕获的变量的值,就必须在参数列表首加上关键字mutable,一个引用捕获的变量是否可以修改依赖于此引用指向的是一个const类型还是一个非const类型
十、bind函数
1、可以将bind函数看作一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对象
2、auto newCallable = bind(callable, arg_list); newCallable本身是一个可调用对象; arg_list是一个逗号分隔的参数列表,对应callable的参数
3、arg_list中的参数可能包含形如_n的名字,n是一个整数,这些参数是“占位符”, 表示newCallable的参数,它们占据了传递给newCallable的参数的位置, 如_1为newCallable的第一个参数,_2为newCallable的第二个参,依次类推; _n定义在一个名为placeholders的命名空间中,该命名空间定义在std命名空间 std::placeholders::_1
4、可以用bind绑定给定可调用对象中的参数或重新安排其顺序; 如 auto g = bind(f, a, b, _2, c, _1), 当使用g(x, y)时会调用f(a, b, y, c, x)
5、有时对有些绑定的参数希望以引用的方式传递,或是绑定参数的类型无法拷贝,就可以使用ref函数,ref函数返回一个对象,包含给定的引用, 如ostream &os,不能直接用bind来代替对os的捕获,因为bind需要拷贝其参数,而我们不能拷贝一个ostream,就可以使用bind(pring, ref(os), _1, ' ')
十一、标准库在头文件iterator中还定义了几种迭代器: 插入迭代器、流迭代器、反向迭代器、移动迭代器
1、当我们通过一个插入迭代器进行赋值时,该迭代器调用容器操作来向给定容器的指定位置插入一个元素
2、back_inserter创建一个使用push_back的迭代器; front_inserter创建一个使用push_front的迭代器; inserter创建一个使用insert的迭代器
3、当使用front_inserter时,元素总是插入到容器第一个元素之前,所以生成的迭代器会将插入的元素序列的顺序颠倒过来
4、iostream流迭代器: istream_iterator读取输入流; ostream_iterator向一个输出流写数据
5、当将一个istream_iterator绑定到一个流时,标准库并不保证迭代器立即从流读取数据,具体实现可以推迟从流中读取数据,直到使用迭代器时才真正读取
6、反向迭代器是在容器中从尾元素向首元素反向移动的迭代器,对于反向迭代器,递增和递减的操作会颠倒,递增一个方向迭代器会移动到前一个元素,递减一个迭代器会移动到下一个元素
7、流迭代器不指出递减运算,因为不可能在一个流中反向移动
8、不能从一个forward_list或一个流迭代器创建反向迭代器
9、对于字符串,反向迭代器也会反向处理,如last变成了tsal, 可以通过调用reverse_iterator的base成员函数来返回对应的普通迭代器
十二、算法所要求的迭代器操作可以分为5个迭代器类别:
输入迭代器 | 只读不写;单遍扫描;只能递增 |
输出迭代器 | 只写不读;单遍扫描;只能递增 |
前向迭代器 | 可读写;多遍扫描;只能递增 |
双向迭代器 | 可读写;多遍扫描;可递增递减 |
随机访问迭代器 | 可读写;多遍扫描;支持全部迭代器运算 |
十三、大多数算法具有以下4中形式之一
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算法名字; beg和end表示算法所操作的输入范围;dest表示算法可以写入的目的位置的迭代器;beg2和end2表示第二个输入范围
十四、算法命名规范
1、一些算法使用重载形式传递一个谓词; 如unique(beg, end);unique(beg, end, comp);
2、_if版本的算法; 如find(beg, end, val);find_if(beg, end, pred);
3、区分拷贝元素的版本和不拷贝的版本; 如reverse(beg, end);reverse_copy(beg, end, dest);
十五、链表类型list和forward_list有自己的特定容器算法;如merge,remove、reverse、sort、unique、splice
十六、链表特有版本和通用版本的一个重要区别是链表版本会改变底层的容器,如remove的链表版本会删除指定的元素;unique的链表版本会删除第二个和后继的重复元素;通用版本的merge将合并的序列写到一个给定的目的迭代器,2个输入序列是不变的,而链表版本的merge会销毁给定的链表,元素从参数指定的链表中删除,被合并到调用merge的链表对象中,在merge之后,来自2个链表中的元素仍然存在,但它们都已在同一个链表中
bool isShorter(const string& s1, const string& s2)
{
return s1.size() < s2.size();
}
bool check_size(const string& s, string::size_type sz)
{
return s.size() >= sz;
}
void elimDups(vector<string>& words)
{
// 排序words,以便查找重复单词
sort(words.begin(), words.end());
// unique重新输入范围,使得每个单词只出现一次
// unique并不真的删除任何元素,只是覆盖相邻的重复元素,大小也并未改变
auto end_unique = unique(words.begin(), words.end());
// unique返回的迭代器指向最后一个不重复元素之后的位置,此位置后的元素仍然存在,但不知道他们的值
// 要删除重复单词, 需要使用容器的erase
words.erase(end_unique, words.end());
}
void biggies(vector<string>& words, vector<string>::size_type sz, ostream &os = cout, char c = ' ')
{
elimDups(words);
// stable_sort按大小排序的同时,具有相同长度的元素按字典排序
stable_sort(words.begin(), words.end(), isShorter);
//为了指示编译器推断捕获列表,应在捕获列表中写一个&或=
// &告诉编译器采用引用捕获方式,=则表示采用值捕获方式
//auto wc = find_if(words.begin(), words.end(), [=](const string& s) {return s.size() > sz; }); // sz为隐式捕获
auto wc = find_if(words.begin(), words.end(), bind(check_size, placeholders::_1, sz));
auto count = words.end() - wc;
cout << count << " " << sz << endl;
//for_each(wc, words.end(), [&os, c](const string& s) {os << s << c; });
// os隐式捕获,引用捕获方式; c显式捕获,值捕获方式
//for_each(wc, words.end(), [&, c](const string& s) {os << s << c; });
//os显式捕获,引用捕获方式,c隐式捕获,值捕获方式
// 当混合使用显式和隐式捕获时,捕获列表第一个元素必须时一个&或=
for_each(wc, words.end(), [=, &os](const string& s) {os << s << c; });
cout << endl;
}
// 值捕获,被捕获的变量的值是在lambda创建时拷贝,而不是调用时
void fcn2()
{
size_t v1 = 42;
auto f = [v1] {return v1; };
v1 = 0;
auto j = f();
cout << v1 << endl; //0
cout << j << endl; // 42
}
// 引用捕获,当在lambda函数体内使用此变量时,实际上使用的是引用所绑定的对象
void fcn3()
{
size_t v1 = 42;
auto f = [&v1] {return v1; };
v1 = 0;
auto j = f();
cout << v1 << endl; //0
cout << j << endl; // 0
}
void fcn4()
{
size_t v1 = 42;
// 如果想要能改变一个被捕获的变量的值,就必须在参数列表首加上关键字mutable
auto f = [v1] () mutable {return ++v1; };
v1 = 0;
auto j = f();
cout << v1 << endl; //0
cout << j << endl; // 43
}
void fcn5()
{
size_t v1 = 42;
// 一个引用捕获的变量释放可以修改依赖于此引用指向的是一个const类型还是非const类型
auto f = [&v1] {return ++v1; };
v1 = 0;
auto j = f();
cout << v1 << endl; //1
cout << j << endl; // 1
}
int main()
{
// 由于string定义了+运算符,可以通过accumulate将vector中所有string元素链接起来
vector<string> strs = { "hello", "world", "cplus" };
// 第三个参数显式的创建了一个string,如果将空字符串当作一个字符串字面值传递给第三个参数是不行的
// 因为传递一个字符串字面值,用于保存和的对象类型将是const char*,而const char*没有+运算符
//string sum = accumulate(strs.cbegin(), strs.cend(), "");
string sum = accumulate(strs.cbegin(), strs.cend(), string(""));
cout << sum << endl;
// 一些算法将新值赋予序列中的元素,当使用这类算法时, 必须注意确保序列原大小至少不小于要求算法写入的元素数量
vector<int> ilist(10, 5);
fill(ilist.begin(), ilist.end(), 1);
for (vector<int>::iterator i = ilist.begin(); i != ilist.end(); ++i) {
cout << * i << endl;
}
// back_inserter接受一个指向容器的引用,返回一个于该容器绑定的插入迭代器
// 当通过此迭代器赋值时,赋值运算符会调用push_back将一个具有给定值的元素添加到容器中
vector<int> ilist2;
auto it = back_inserter(ilist2);
*it = 42;
for (vector<int>::iterator i = ilist2.begin(); i != ilist2.end(); ++i) {
cout << *i << endl;
}
int a1[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int a2[sizeof(a1) / sizeof(*a1)];
auto ret = copy(begin(a1), end(a1), a2);
for (int i = 0; i < sizeof(a2) / sizeof(*a2); ++i) {
cout << i << endl;
}
vector<int> ilist3 = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
replace(ilist3.begin(), ilist3.end(), 0, 1);
for (vector<int>::iterator i = ilist3.begin(); i != ilist3.end(); ++i) {
cout << *i << endl;
}
vector<string> words = { "the", "quick", "red", "fox", "jumps", "over", "the", "slow", "red", "turtle" };
elimDups(words);
for (vector<string>::iterator str = words.begin(); str != words.end(); ++str) {
cout << *str << endl;
}
cout << "-------------" << endl;
// 使用谓词将元素按大小重新排序
// 谓词是一个可调用的表达式,其返回结果是一个能用作条件的值
//sort(words.begin(), words.end(), isShorter);
// stable_sort按大小排序的同时,具有相同长度的元素按字典排序
//stable_sort(words.begin(), words.end(), isShorter);
// lambda [capture list](parameter lsit)->return type{function body}
stable_sort(words.begin(), words.end(), [](const string& s1, const string& s2) {return s1.size() < s2.size(); });
for (vector<string>::iterator str = words.begin(); str != words.end(); ++str) {
cout << *str << endl;
}
cout << "-------------" << endl;
fcn2();
cout << "-------------" << endl;
fcn3();
cout << "-------------" << endl;
fcn4();
cout << "-------------" << endl;
fcn5();
cout << "-------------" << endl;
auto check = bind(check_size, placeholders::_1, 6);
string s = "hello";
bool b1 = check(s); // check会调用check_size(s, 6)
cout << b1 << endl;
cout << "-------------" << endl;
vector<string> words2 = { "the", "quick", "red", "fox", "jumps", "over", "the", "slow", "red", "turtle" };
biggies(words2, words2.size());
//可以用bind绑定给定可调用对象中的参数或重新安排其顺序
//auto g = bind(f, a, b, placeholders::_2, c, placeholders::_1);
//g(x, y) = > g(a, b, y, c, x);
cout << "-------------" << endl;
list<int> lst = { 1, 2, 3, 4 };
list<int> lst2, lst3;
// front_inserter, 元素总是插入到容器第一个元素之前, 所以生成的迭代器会将插入的元素的顺序颠倒
copy(lst.cbegin(), lst.cend(), front_inserter(lst2));
copy(lst.cbegin(), lst.cend(), inserter(lst3, lst3.begin()));
for (auto i : lst2) {
cout << i << endl;
}
cout << "-------------" << endl;
for (auto i : lst3) {
cout << i << endl;
}
cout << "-------------" << endl;
vector<int> vec3 = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
ostream_iterator<int> out_iter(cout, " ");
for (auto e : vec3) {
//*out_iter++ = e;
out_iter = e; // 运算符*和++实际上对ostream_iterator对象不做任何十七,
}
cout << endl;
// 反向迭代器
for (auto r_iter = vec3.crbegin(); r_iter != vec3.crend(); ++r_iter) {
cout << *r_iter << endl;
}
cout << "-------------" << endl;
string line("the,quick,red,fox,jumps,over");
// 查找line中的第一个单词
auto comma = find(line.cbegin(), line.cend(), ',');
string commaStr(line.cbegin(), comma);
cout << commaStr << endl; // the
auto rcomma = find(line.crbegin(), line.crend(), ',');
string rcommaStr(line.crbegin(), rcomma);
cout << rcommaStr << endl; // revo 反向迭代器也会反向处理string
string rcommaStr2(rcomma.base(), line.cend());
cout << rcommaStr2 << endl; // over 通过调用reverse_iterator的base成员函数来返回其对应的普通迭代器
return 0;
}