10.2 初识泛型算法
C++标准库提供的算法有100多个,一下子记不住这么多,我们可以辅助记忆。
大部分的算法接收两个参数表示可以操作的范围,这个范围叫做输入范围。
(其实我觉得也可以叫做迭代器范围)
泛型算法根据对元素的操纵方式分为
是否读取元素、改变元素、或是否重排元素。根据这些可以对算法进行大致划分。
10.2.1 只读算法
accumulate算法
accumulate(iter1,iter2,初始值)
该算法对输入范围内的元素进行累加操作,其中初始值类型和传入的容器元素类型可以不一致,只要容器类型可以转化为初始值类型。
这句话要记住,accumulate算法返回值类型以及在算法中使用的运算符都是使用初始值类型。
vector<int> vec = {1,2,3,2134,3214,123,4,123,321,312,3,123,12,3,12,31,23,12};
long value = 0;
//元素类型和初始值类型是一样的,或者说元素类型可以转化为初始值类型
//泛型算法返回值类型的是初始值类型
//而且在该算法中,使用的是初始值类型的运算符
auto sum = std::accumulate(vec.begin(),vec.end(),value);
对于accumulate的操作,这里我的理解是元素类型先转化为初始值类型,再进行累加 ,即使用+运算符。
对于一下的代码:
vector<string> str_vec = {"123","321","233","xx","gg"};
auto str_sum = std::accumulate(str_vec.begin(), str_vec.end(),"");
先把string类型转化为const char* ,然后使用const char* 的+运算符。
但是const char* 没有+运算符,所以程序编译报错。
equal算法
equal(容器1输入范围1,容器1输入范围2,容器2输入范围1)
equal判断两个输入范围内的元素是否相等,因为传入的是迭代器,所以两个容器类型可以不一致,两个容器内的元素也可以不一致,只要可以执行==运算符即可。
这类算法接收三个参数,前两个参数输入容器1的输入范围,后一个参数输入容器2的首元素。因为容器2只输入2只输入了首元素,所以这类算法都假定容器2中元素的个数大于等于容器1的元素个数。
而另一些方法可以接收四个参数,这也可以输入两个完整的输入范围。
练习
10.3
vector<int> vec = {1,2,3,2134,3214,123,4,123,321,312,3,123,12,3,12,31,23,12};
long value = 0;
auto sum = std::accumulate(vec.begin(),vec.end(),value);
sum是double类型
10.4
vector<double> vec = {1,2,3,2134,3214,123,4,123,321,312,3,123,12,3,12,31,23,12};
int value = 0;
auto sum = std::accumulate(vec.begin(),vec.end(),value);
cout<<sum<<endl;
因为初始值类型为int型,所以double会转为int型,这可能导致精度的丢失。
10.5
元素类型为const char*类型,所以equal是比较指针是否相等。
但是我记得,如果const char* 所赋值的字符串字面值是一样的,那么他们的指针也是一样的。
所以这同样可以比较是否相等。
vector<const char*> str_vec= {"hello","world"};
deque<const char*> str_vec_2 = {"hello","world"};
auto is_equal = std::equal(str_vec.begin(), str_vec.end(),str_vec_2.begin());
cout << is_equal << endl;
该代码is_euqal为true。
写容器元素的算法
算法在对容器中的元素进行操作时,并不会改变容器的大小。
并不会改变容器的大小!!!
并不会改变容器的大小!!!
并不会改变容器的大小!!!
并不会改变容器的大小!!!
这里的大小指的是size(),而不是capacity()
写入算法
fill(序列输入范围1,序列输入范围2,初始值);
fill_n(序列输入范围1,计数器,初始值)
注意这些算法都不会改变容器的大小,所以对空容器使用fill和fill_n都是错误的。算法也不会检查操作是否符合条件。
back_inserter迭代器
算法不能改变容器的大小,这限制了容器的一些操作,C++提供了back_inserter迭代器,用于向容器中插入元素,我们可以在算法中使用该迭代器,向容器中插入元素。
对该迭代器进行赋值操作时,一个与赋值符号右边相等的元素会被添加进容器中。
在进行赋值时,赋值运算符将调用push_back()将给定值添加到容器中。
vector<int> vec;
auto it = std::back_inserter(vec);
*it = 42;
cout << vec.size() << endl;
所以使用back_inserter和fill_n,我们就可以为空容器赋值。
vector<int> vec;
auto it = std::back_inserter(vec);
std::fill_n(it,10,0);
cout << vec.size() << endl;
这里只是了解,对于back_iterator的具体内容,后续的章节会讲到。
copy算法
copy算法将输入序列范围内的值,拷贝的目标序列中.
这要求目标序列的大小至少要等于输入范围的大小。
vector<int> vec = {1,2,3,4,5,6,7,8,9,0};
deque<double> dq(10);
std::copy(vec.begin(),vec.end(),dq.begin());
for (const auto& item:dq) {
cout << item << endl;
}
同样,容器类型和容器内元素类型都可以不一样,只要可以调用赋值运算符就可以。
copy将返回目标序列递增之后,下一个目标迭代器的值,大白话就是copy了n个元素,copy将返回dq.beign()+n这个元素的迭代器。
有很多算法提供了自己的copy版本, 即算法得到的结果不在原来的目标序列上表现,而是在新的序列上保存这些结果。
比如
replace(序列输入范围1,序列输入范围2,原始值,替换值)
它的copy版本
replace(序列输入范围1,序列输入范围1,保存结果的序列的迭代器,原始值,目标值)
它将替换值之后的序列保存在第三个参数指明的序列中。
以下的代码将替换后的结果,保存在deque上面。
vector<int> vec = { 1,2,3,4,5,6,7,8,9,0 };
deque<double> dq(10);
std::replace_copy(vec.begin(), vec.end(),dq.begin(), 0, 233);
练习
10.6
vector<int> int_vec(10,1);
std::fill_n(int_vec.begin(), int_vec.size(), 0);
for (const auto& item:int_vec) {
cout << item << endl;
}
10.7
a.使用copy时,目标序列的size()至少要等于输入序列的size()
b.算法fill_n并不会改变容器本身的大小,而vec的size()为0,所以无法写入10个0.
10.8
不会,因为改变容器大小的并不是算法,而是迭代器。
10.2.3 重排容器元素的算法
有些算法可以改变容器中元素的位置。
比如
sort(输入范围1,输入范围2)
sort可以将输入范围内的元素进行排序
unique()可以消除重复的元素
unique可以去除连续出现的抽工夫元素,所谓的连续出现的重复元素,即1,2,2,2,3。这里的2是重复的,所以使用unique可以“消除”多余的2,但是如果序列为1,2,3,2,3,2,3.序列中元素虽然有重复,但是不是连续出现,所以unique并不能消除这种重复出现的元素。
unique()将返回最后一个非重复元素的下一个元素的迭代器。这么做是有意义的,因为unique(),并不会改变容器中元素的大小,它只是把不重复的元素都放在了前面,容器的大小是没有变化的。
返回这个迭代器之后,我们就可以调用容器本身的删除函数,将多余的重复元素删除。
这也,我们可以通过组合算法,来完成一些特定的任务。
比如练习10.9
练习
//排序并消除重复的元素
void elimDups(vector<string>& word) {
std::sort(word.begin(),word.end());
for (const auto& item:word) {
cout << item << std::ends;
}
cout << endl;
auto end_iter = std::unique(word.begin(),word.end());
cout << word.size() << endl;
for (const auto& item : word) {
cout << item << std::ends;
}
cout << endl;
word.erase(end_iter, word.end());
cout << word.size() << endl;
for (const auto& item : word) {
cout << item << std::ends;
}
cout << endl;
}
测试用例:
vector<string> vec = {"a","c","b","e","x","a","z","q","c"};
elimDups(vec);
输出结果为
可以看到unique并没有改变容器的大小
10.10
之前学习过,改变容器的大小可能会导致迭代器,指针,或者指向元素的引用失效。
如果算法可以改变迭代器的大小,那么就意味着算法需要为了让迭代器有效,而做出更多和算法无关的操作。
这让代码变的很冗余。
同时算法改变了容器的大小,也让程序变得更加复杂,不能够让程序员所控制。
总结一下目前算法序列中元素的操纵方式
1.只读取元素(count(),accumulate())
2.修改元素(fill(),fill_n())
3.改变元素的位置(sort(),unique())
4.将改变之后的序列,或者序列,保存在其他的迭代中(replace(),replace_copy())