C++ Primer : 第十章 : 泛型算法 之 只读、写和排序算法

时间:2023-03-08 18:19:22
C++ Primer : 第十章 : 泛型算法 之 只读、写和排序算法

大多数算法都定义在<algorithm>头文件里,而标准库还在头文件<numeric>里定义了一组数值泛型算法,比如accumulate。

●  find算法,算法接受一对迭代器表示要搜寻的范围,还接受一个给定的值,算法从给定的范围内查找,返回指向第一个等于给定值的元素的迭代器,若没有找到,则返回第二个参数。

int val = 5;
vector<int> vec = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
auto find_val = find(vec.cbegin(), vec.cend(), val);
cout << "The val " << val << (find_val == vec.cend() ? "is not present" : "is present") << endl;

我们不仅可以查找标准库中的元素,也可以查找内置数组中的,我们就要用到标准库的begin() 和 end()函数来获取一个数组的首迭代器和尾后迭代器。

★关键概念: 算法永远不会执行容器的操作

泛型算法本身不会执行容器的操作,它们知识运行在迭代器之上, 执行迭代器的操作。因此,这样的特性就有一个编程假定:算法不会改变容器的大小。 可能会改变容器的值,或者移动容器的元素,但永远不会添加或删除元素。 后面介绍到的一类特殊的迭代器,它们会执行容器的操作,来改变容器大小。

◆只读算法

一些算法只会读取其输入范围内的元素,而不会改变这些值,上面提到的find算法就是其中的一种。

● 只读算法  accumulate,它包含在头文件<numeric>中,它接受三个参数:前两个表示了需要求和的元素的范围,第三个表示和的初值。

accumulate的第三个值决定了函数中使用哪个加法运算符以及返回值类型。

而且,accumulate有一个编程假定:将范围里的元素加到第三个参数上的操作是可行的,这些元素类型必须和第三个参数的类型匹配,或者能转换为第三个参数值的类型。

将vector中所有的string连接起来:

vector<string> v = {"Hello", "World"};
string sum = accumulate(v.cbegin(), v.cend(), string("")); // 错误,const char* 上并没与定义 + 运算符
string sum = accumulate(v.cbegin(), v.cend(), "");

对于只读算法,最好使用cbegin()、cend()来避免算法改变容器元素的值。

● 只读算法  equal,用于比较两个序列中的值是否相等。 算法接受其中一个容器的一对迭代器和另一个容器的首元素。我们必须能使用 == 来比较来自两个序列的元素

如果两个容器相等,返回true,否则返回false。

equal算法基于一个非常重要的假设: 它假定第二个容器的元素至少和第一个容器一样多。 此算法要处理第一个序列中的每一个元素。

那些只接受一个单一的迭代器来表示第二个序列的算法,都假定第二个序列至少和第一个序列一样长。

vector<int> v1{1, 2, 3, 4, 5}
vector<int> v2{1, 2, 3, 4, 6}
cout << (euqal(v1.cbegin(), v1.cend(), v2.cbegin()) == true ? "euqal" : "not euqal") << endl;

◆ 写容器算法

算法fill接受一对迭代器表示要写入元素的范围,接受第三个元素表示要写入的值。

fill(v1.begin(), v1.end(), 0);

算法fill_n接受三个参数: 一个迭代器表示要写入的位置,第二个参数表示要写入元素的个数,最后一个表示要写入的值

fill_n假定写入指定个数的元素是安全的。

在一个空的容器上调用fill_n,这样的行为是未定义的。

向目的位置写入数据的算法假定目的位置足够大,至少能容纳写入的元素。

算法copy把一个表示容器范围内的元素写入到目的位置,接受三个参数,前两个表示要写入元素的范围,第三个参数表示要写入的目的位置。

目的序列至少要包含和第一个序列一样多的元素,这一点很重要!

int a1[] = {0, 1, 2, 3, 4, 5};
int a2[sizeof(a1) / sizeof(*a1)];
auto dest = copy(begin(a1), end(a1), a2);

copy返回目的位置迭代器(递增后)的值,dest恰好指向a2尾元素之后的位置。

算法replace读入一个序列,并将容器内所有等于第三个参数表示的值都改变为第四个参数所表示的值。

// 将vec容器里所有值为0的元素都变为10
replace(vec.begin(), vec.end(), 0, 10);

如果希望原序列保持不变,可以使用replace_copy, 此算法接受额外的第三个迭代器参数,指出调整后序列的保存位置。

replace_copy(vec1.begin(), vec1.end(), back_inserter(vec2), 0, 10);

vec2是vec1的一份拷贝,只不过vec2里的元素都进行了改变。

◆ 重排元素算法

算法sort 默认使用元素类型的 < 运算符来实现排序的,它接受一对迭代器,表示要排序元素的范围。

算法unique将容器重新排序,使得不重复的元素都排在前面,接受一对迭代器表示要操作的容器范围,返回指向不重复区域之后的一个位置的迭代器。

一个消除重复单词的函数:

void elimDups (vector<string>& words){
sort(words.begin(), words.end());
auto end_unique = unique(words.begin(), words.end());
words.erase(end_unique, words.end());
}

算法那paitition,接受一对迭代器和一个谓词,对容器进行划分,使得谓词为true的元素会排在容器的前面,否则排在容器后面。返回一个迭代器,指向最后一个使得谓词为true的元素之后的位置。

● 向算法传递函数

sort的第二个版本是重载过的,接受第三个参数,此参数是一个谓词

谓词是一个可调用的表达式,其返回结果是一个能用做条件的值。标准库使用的谓词分为 一元谓词(意味着只接受一个参数) 和二元谓词(接受两个参数)

接受两个参数的sort默认使用元素的 < 运算符来对元素进行排序,我们使用接受三个参数的sort时,就可以定制自己的sort

// 比较函数, 用来按长度排序单词
bool isShorter(const string& s1, const string& s2){
return (s1.size() < s2.size());
}
sort(words.begin(), words.end(), isShorter);

我们就可以将单词长度来排序,如果我们还希望具有相同长度的元素按字典排序,可以使用stable_sort算法,这种未定排序算法保持相等元素的原有排序。