但是,用户可能需要其他更多的有用操作,如:排序、查找、查找最大元素、查找最小元素等,
为了应对这种需要,
标准库并没有为每种容器类型都定义实现相应的成员函数,而是定义了一组 泛型算法
因为他们实现共同的操作,因此,称为算法
所谓泛型,指的是,它们可以操作在多种容器类型上,如标准库类型 vector、 list ,内置数组类型、甚至其他类型的序列。
自定义类型,只要与标准库兼容,同样可以使用这些泛型算法。
#include <algorithm>
#include <numeric>
//using std::list;
//using std::find_first_of;
int search_value = 42;
vector<int> ivec;
ivec.push_back(42);
ivec.push_back(10);
vector<int>::const_iterator iter = find(ivec.begin(), ivec.end(), search_value);
cout << "The value " << search_value
<< (iter == ivec.end() ? " is not present." : " is present.")
<< endl;
int ia[6] = {28, 283, 48, 42, 42, 90};
int *pint = find(ia, ia+6, search_value);
// accumulate:累加求和
// 第三个参数,是累加的初值
// 返回类型,即是第三个实参的类型
// 元素类型必须与第三个实参类型兼容
int isum = accumulate(ia, ia+6, 1);
vector<string> svec;
svec.push_back("a");
svec.push_back("b");
string ssum = accumulate(svec.begin(), svec.end(), string(""));
cout << "The value " << search_value
<< (pint == ia+6 ? " is not present." : " is present.")
<< endl;
cout << "int sum : " << isum << endl;
cout << "string sum : " << ssum << endl;
vector<string> svecUsers;
list<char *> clistAdmins;
svecUsers.push_back("Xiaoming");
svecUsers.push_back("Zhangsan");
svecUsers.push_back("Ligang");
clistAdmins.push_back("Xiaoming");
clistAdmins.push_back("Ligang");
vector<string>::iterator iter2 = svecUsers.begin();
size_t cnt = 0;
// 此处之所以没有将 iter2 定义为 const_iterator
// 是因为:svecUsers.end() 的返回类型,是根据 svecUsers 的类型而定的
// svecUsers 如果是 const 对象,则,返回 const_iterator 类型,
// 否则,返回 iterator 类型。
// 而 find_first_of 算法,要求指定范围的两个迭代器必须具有完全一致的类型,
// svecUsers 不是 const 对象
while((iter2 = find_first_of(iter2, svecUsers.end(),
clistAdmins.begin(), clistAdmins.end())) != svecUsers.end())
{
++cnt;
++iter2;
}
cout << "Count: " << cnt << endl;
find_first_of , 在第一个范围内查找,返回第一个同时存在于第一、第二范围内的元素。
find、 find_first_of 这类,属于只读算法。
算法固有地独立于类型
大多数情况下,每个算法都需要使用(至少)两个迭代器来指出该算法操纵的元素范围。
find 操作要求元素类型定义了相等(==)操作符,来比较元素。
否则,可使用 find 的重载版本,需要提供一个额外参数:实现元素比较的函数名字
算法从不使用容器操作,因而其实现与类型无关,元素的所有访问、遍历都是通过迭代器实现。
算法使用“普通”迭代器时,有可能会改变元素的值,移动元素,但是,从不修改基础容器的大小。
使用了插入器(inserter)这类特殊迭代器的时候,也只会通过插入器来添加元素,
而不会由算法直接添加。
绝大多数算法,都在一段范围内的元素上操作,称为:输入范围(Input Range)
带有输入范围的算法总是使用头两个形参标记该范围。
写容器元素的算法
vector<int> ivec(10,8);
// 前5个,置0
fill(ivec.begin(), ivec.begin() + ivec.size()/2, 0);
//vector<int> ivec2;
//fill_n(ivec2.begin(), 10, 0);
//以上会报错。
//fill_n 不会检查目标大小是否足够存储要写入的元素
// 用迭代器给元素赋值,被赋值的是迭代器所指向的元素
// 用插入迭代器赋值,会在容器中添加一个新元素
// #include <iterator>
// back_inserter:迭代器适配器
// 与容器适配器一样,迭代器适配器使用一个对象作为实参,
// 并生成一个适应其实参行为的新对象。
// 以下例子,back_inserter 生成一个插入迭代器。
// 试图通过这个迭代器给元素赋值时,
// 将调用 push_back 在容器中添加一个具有指定值的元素
vector<int> ivec2;
fill_n(back_inserter(ivec2), 10, 9);
list<int> ilist(8, 9);
// 以下效率不高,等价于:
// vector<int> ivec3(ilist.begin(), ilist.end())
vector<int> ivec3;
copy(ilist.begin(), ilist.end(), back_inserter(ivec3));
算法的 _copy 版本
list<int> ilist(10, 5);
replace(ilist.begin(), ilist.end(), 5, 9);
vector<int> ivec;
// replace_copy 会创建副本,替换后,存入 ivec
replace_copy(ilist.begin(), ilist.end(), back_inserter(ivec), 9, 8);
cout << *ilist.begin() << endl; // 9
cout << *ivec.begin() << endl; // 8
// 谓词 predicate
// 谓词是做某些检测的函数,判断条件是否成立
bool isShorter(const string &s1, const string &s2)
{
return s1.size() < s2.size();
}
bool GT6(const string &s)
{
return s.size() >=6;
}
string make_plural(size_t ctr, const string &word, const string &ending)
{
return (ctr==1) ? word : word + ending;
}
// 以下代码,实现功能:
// 计算输入的单词中,长度 >= 6 的单词个数
// 不包括重复词
vector<string> svec;
string temp_word;
// Ctrl + Z 结束 cin
while(cin >> temp_word)
{
svec.push_back(temp_word);
}
// sort 进行排序,相同的元素就会被排到相邻的位置
sort(svec.begin(), svec.end());
// unique 算法,“删除”相邻的重复元素,实际上是重新排序
// 返回的迭代器指向,超出无重复的元素范围末端的下一位置
vector<string>::iterator end_unique = unique(svec.begin(), svec.end());
// 调用容器操作,来真正删除元素(算法不直接删除容器元素)
svec.erase(end_unique, svec.end());
// 按照长度排序,相同长度,仍然保持字典顺序
// isShorter 谓词函数,必须接收两个实参,类型与元素类型相同,
// 并返回一个可用于条件检测的值
stable_sort(svec.begin(), svec.end(), isShorter);
vector<string>::size_type wc = count_if(svec.begin(), svec.end(), GT6);
cout << wc << " " << make_plural(wc, "word", "s") << " 6 characters or longer" << endl;
C++ 提供的另外3种迭代器:插入迭代器(insert iterator)、iostream迭代器(iostream iterator)、反向迭代器(reverse iterator)
在 iterator 头文件中定义
插入器,是一种迭代器适配器。带有一个容器参数,并生成一个迭代器,用于在指定容器中插入元素。
三种插入器:back_inserter、front_inserter、inserter。
front_insert 需要使用 push_front ,因此容器需要提供 push_front 操作,像 vector 这类不支持 push_front 操作的容器,则无法使用 front_inserter
list<int> ilist;
ilist.push_back(20);
ilist.push_back(1);
vector<int> ivec;
ivec.push_back(10);
ivec.push_back(5);
list<int>::iterator iter = find(ilist.begin(), ilist.end(), 20);
// 将 ivec 中的 10 替换成 8,
// 创建相应副本,插入到 ilist 的 iter 指向的位置前面
replace_copy(ivec.begin(), ivec.end(),
inserter(ilist, iter), 10, 8);
list<int> ilist, ilist2, ilist3, ilist4;
// ilist: 3, 2, 1, 0
for(list<int>::size_type i=0; i!=4; ++i)
{
ilist.push_front(i);
}
// ilist2: 0, 1, 2, 3
copy(ilist.begin(), ilist.end(), front_inserter(ilist2));
// ilist3: 3, 2, 1, 0
// 元素在ilist3 的固定位置插入
// 该位置,插入第一个元素之前,是头部,
// 插入一个元素之后,就不是头部了。
copy(ilist.begin(), ilist.end(), inserter(ilist3, ilist3.begin()));
// ilist4: 3, 2, 1, 0
copy(ilist.begin(), ilist.end(), back_inserter(ilist4));
list<int>::iterator iter = ilist4.begin();
iostream 迭代器
iostream 类型不是容器,但标准库同样提供了在 iostream 对象上使用的迭代器
istream_iterator 用于读取输入流, ostream_iterator 用于写输出流
将对应的流,视为特定类型的元素序列。
流迭代器只定义了基本的迭代操作:自增、解引用、赋值。
istream 迭代器可以比较是否相等;
ostream 迭代器不提供比较运算
istream_iterator<T> in(strm);
创建从输入流 strm 中读取 T 类型对象的 istream_iterator 对象
istream_iterator<T> in;
istream_iterator 对象的超出末端迭代器
ostream_iterator<T> in(strm);
创建将 T 类型的对象写到输出流 strm 的 ostream_iterator 对象
ostream_iterator<T> in(strm, delim);
创建将 T 类型的对象写到输出流 strm 的 ostream_iterator 对象,
在写入过程中使用 delim 作为元素的分隔符。delim 是以空字符结束的字符数组
istream_iterator 的操作
it1 == it2
比较两个 istream_iterator 对象是否相等。迭代器读取的必须是相同的类型
it1 != it2
如果两个迭代器都是 end 值,则相等。
对于两个都不指向流结束位置的迭代器,如果它们使用同一个流输入构造,则它们也相等
*it
返回从流中读取的值
it->mem
相当于:(*it).mem。 返回从流中读取的对象的 mem 成员
++it
it++
通过使用元素类型提供的 >> 操作符,从输入流中读取下一个元素的值,使迭代器向前移动。
通常,
前缀版本,迭代器在流中向前移动,再返回加1 后的迭代器的引用。
后缀版本,迭代器在流中向前移动,再返回迭代器的原值。