本条款讲解用的函数是 transform
函数。
transform
函数有两个重载函数,当前简介一个版本即可:transform(first, last, result, op)
。
transform
函数的具体实现如下:
template <class InputIterator, class OutputIterator, class UnaryOperator>
OutputIterator transform (InputIterator first1, InputIterator last1,
OutputIterator result, UnaryOperator op)
{
while (first1 != last1) {
*result = op(*first1); // or: *result=binary_op(*first1,*first2++);
++result; ++first1;
}
return result;
}
transform
函数的作用是,将某操作(op
)应用于指定范围(first-last
)的每个元素,移动到 result
来存放结果;
op
操作会被调用 last - first + 1
次,每次都会存放到 result + i
的位置上(i
指的是第 i
次被调用)。【返回的迭代器指向输出序列所保存的最后一个元素的下一个位置】本条款的代码如下:
int transmogrify(int x) { return (x + 1); }
int test_item_30()
{
std::vector<int> values{ 1, 2, 3 };
std::vector<int> results;
// 第一种情况:back_inserter
results.reserve(results.size() + values.size()); // 可避免内存的重新分配
//std::transform(values.cbegin(), values.cend(), results.end(), transmogrify); // 错误,segmentation fault
std::transform(values.cbegin(), values.cend(), std::back_inserter(results), transmogrify); // 正确
// 在内部,std::back_inserter返回的迭代器将使得push_back被调用,所以back_inserter可适用于所有提供了push_back方法的容器
// 第二种情况:front_inserter
std::list<int> results2;
std::transform(values.begin(), values.end(), std::front_inserter(results2), transmogrify);
// 前面这个语句插入的结果可能不如预期,
// 例如,将values = [1,2,3]传到到results = [4,5,6]的前面
// 用前面的语句,结果会变成[3,2,1,4,5,6],所以需要将[1,2,3]反序插入
std::transform(values.cbegin(), values.cend(), std::front_inserter(results2), transmogrify);
// std::front_inserter在内部利用了push_front,所以front_inserter仅适用于那些提供了push_front成员函数的容器
// 第三种情况:inserter【插入任意位置】
std::transform(values.begin(), values.end(),inserter(results, results.begin() + results.size()/2),transmogrify);
// 把它们的结果插入容器中的任意位置
// 第四种情况:用resize而并非reverse
if (results.size() < values.size()){ // 确保results至少和values一样大
results.resize(values.size()); }
transform(values.begin(), values.end(), results.begin(), transmogrify); // 覆盖values.size()个
// 第五种情况:清空results然后用通常的方式使用插入迭代器
results.clear();// 销毁results中的所有元素
results.reserve(values.size()); // 保留足够空间
transform(values.begin(), values.end(), back_inserter(results), transmogrify);
return 0;
}
上面的代码有几点值得进行讨论:
transform
错误操作的时候,result
迭代器参数位置传入的是 results.end()
,所以在调用 transform
的时候,就会给不存在的对象赋值。【第一种情况】
back_inserter(results)
的时候,问题就能解决了;调用 back_inserter
来产生指定目标区间起点的迭代器,在内部,back_inserter
返回的迭代器会调用 push_back
。【插入后面的同时push_back
】front_inserter
;在内部,front_inserter
利用了 push_front
,所以 front_insert
只和提供那个成员函数的容器配合(也就是 deque
和 list
)。【插入前面的同时 push_front
】【第二种情况】vector
和 string
中,想要调用 transform
,可以提前先调用 reverse
来分配足够内存。【第一种情况】reverse
:可以考虑保障 results
至少有 values
的元素个数,即使用 resize
【第四种情况】;又或者先清空 results
再用通常方式插入【第五种情况】。最后的总结如下:
ostream_iterators
或从 back_inserter
、front_inserter
或 inserter
返回的迭代器。sort
相关函数及简介如下:
std::sort
、std::stable_sort
和 std::partial_sort
:
std::sort
对给定区间所有元素进行排序,采用类似快速排序算法来实现;【非稳定排序】std::stable_sort
对给定区间所有元素进行稳定排序,采用类似归并算法实现;std::partial_sort
对给定区间所有元素进行部分排序,采用类似堆排序算法实现。【非稳定排序】std::nth_element
:【与 partial_sort
不同的点在于,partial_sort
的前半部分是完全排序的; nth_element
把前 n
个值放在前部分,但是整体无序】【其实就是快速选择】
std::nth_element
使得位置 n
上的元素正好是全排序情况下的第 n
个元素;前 n-1
个比第 n
个值小的值在 n
位置之前,n
位置之后比第 n
个值大的值在 n
位置之后。std::partition
和 std::stable_partition
:【与 nth_element
区别在于 partition
自定义判断条件】【只需要双向迭代器,不需要随机访问】
std::partition
会将区间 [first,last)
中的元素重新排列,满足判断条件 pred
的元素会被放在区间的前段,不满足 pred
的元素会被放在区间的后段。【非稳定】std::stable_partition
保证初始相对位置。从容器中删除元素的唯一方法是调用该容器的成员函数,而 remove
并不知道它操作的元素所在的容器(remove
并不是成员函数,list
除外),所以 remove
不可能从容器中删除元素。
remove
并没有做到真正的删除元素,只是把该删除的元素移动到容器最后,然后把尾部迭代器前移 n
个(假设只删除 n
个元素);【可以把 remove
想象成一个压缩过程,被删除的值表演了在压缩中被填充的洞的角色】
remove
返回的一个迭代器指向最后一个“不用被删除”的元素之后的元素,这个返回值相当于该区间“新的逻辑结尾”。std::list
的 remove
成员函数是 STL 中唯一一个名为 remove
并且确实删除了容器中元素的函数。
remove()
函数并不是真正的删除,要想真正删除元素则可以使用 erase()
或者 resize()
函数。
v.erase(std::remove(v.begin(), v.end(), 99), v.end()); // 真正删除所有值等于99的元素
std::remove
并不是唯一一个适用于这种情形的算法,其它还有两个属于 remove
类的算法:remove_if
和 unique
。
remove_if
和 remove
的情况一致,只不过 remove_if
传入了判断式,满足判断式的才 remove
。unique
的情况和 remove
一致,相当于把重复值移到后面。list::remove
一样,list::unique
会真的删除元素;它旨在删除临近的重复元素(使元素唯一),而且比使用 erase
+ unique
更为高效。remove
和类似的算法(remove_if
和 unique
)。
remove
函数移走指针元素后(此时已经造成资源泄露),再接着调用 erase
函数真正删除元素,那么会造成资源泄露。remove
走的元素一一删除(delete
)并置空。【先调用 for_each
把满足条件的指针元素 delete
,最后再 erase
+ remove
,过程中将指针置空】erase
+ remove
的习惯用法。【要知道的是,因为存放的是智能指针,要使 erase
+ remove
生效,需要保证智能指针能够隐式转换为智能指针内元素的指针】remove
算法要求单向迭代器并且要求可以通过这些迭代器向容器中的对象赋值;
remove
不能用于由输入迭代器指定的区间,也不适用于 map
或 multimap
,同样不适用于某些 set
和 multiset
的实现。list
的元素不可能调用这些算法。binaray_search
、lower_bound
、upper_bound
、equal_range
、set_union
、set_intersection
、set_difference
、set_symmetric_difference
、merge
、inplace_merge
和 includes
。unique
和 unique_copy
。binaray_search
、lower_bound
、upper_bound
和 equal_range
用于查找,且它们用二分法(所以要求排序);当它们接受了随机访问迭代器,才保证对数时间的查找效率。set_union
、set_intersection
、set_difference
和 set_symmetric_difference
用于 set
的各类操作,这四个函数保证了线性时间(所以要求排序)。merge
和 inplace_merge
实际上实现了合并和排序的联合操作,它们读入两个排序的区间,然后合并成一个新的排序区间,其中包含了原来两个区间中的所有元素;同样的,因为要求区间排序,所以保证了线性时间的承诺。includes
用来判断一个区间中的所有对象是否都在另一个区间中,它承诺线性时间的效率。unique
和 unique_copy
即使对于未排序的区间也有很好的行为,unique
使用了与 remove
类似的办法来删除区间中的元素,而并非真正意义上的删除。unique
、unique_copy
以外)都使用等价来判断两个对象是否相同,这是与关联容器相同的;unique
、unique_copy
在默认情况下使用相等来判断两个对象是否相同,当然你可以改变这种默认的行为,只需要给算法提供一个用于比较的谓词。sort
函数和 binary_search
函数共同使用在同一个容器上,如果 sort
调用的时候使用了自定义的比较函数,那么 binary_search
就必须要使用同样的比较函数。首先介绍条款中相关的几个函数:
mismatch
将标记出两个区间中第一个对应位置不同的位置;如果两个字符串长度不同,那么我们必须把短的字符串作为第一个区间传入。【类似 strcmp
(返回一个负数、零或正数)】lexicographical_compare
是 strcmp
的一个泛化版本,可以比较任何类型的值的区间;lexicographical_compare
可以接受一个判别式,由判别式来决定两个值是否满足一个用户自定义的准则。【类似 operator
(返回 true
或 false
)】strcmp
和 strcmpi
通常是被优化过的,它们在长字符串的处理上一般要比通用算法 mismatch
和 lexicographical_compare
快。代码实现如下:
// 实现一:【忽略大小写string比较的函数】
// 第一个函数,比较字符
// 忽略大小写地比较字符c1和c2,如果c1c2,返回1;如果c1==c2,返回0
int ciCharCompare(char c1, char c2)
{
int lc1 = std::tolower(static_cast<unsigned char>(c1));
int lc2 = std::tolower(static_cast<unsigned char>(c2));
if (lc1 < lc2) return -1;
if (lc1 > lc2) return 1;
return 0;
}
// 第二个函数
// 用作边界检查,真正工作,即调用前面第一个函数
int ciStringCompareImpl(const std::string& s1, const std::string& s2)
{
typedef std::pair<std::string::const_iterator, std::string::const_iterator> PSCI;
PSCI p = std::mismatch(s1.begin(), s1.end(), s2.begin(), std::not2(std::ptr_fun(ciCharCompare)));
if (p.first == s1.end()) { // 如果为true,要么s1和s2相等,或者s1比s2短
if (p.second == s2.end()) return 0;
else return -1;
}
return ciCharCompare(*p.first, *p.second); // 字符串之间的关系和这两个不匹配的字符之间的关系相同
}
// 第三个函数,即string比较函数
int ciStringCompare(const std::string& s1, const std::string& s2)
{
// 把短的字符串作为第一个区间传入
if (s1.size() <= s2.size()) return ciStringCompareImpl(s1, s2);
else return -ciStringCompareImpl(s2, s1);
}
// 实现二:
// 实现函数子类,方便后续lexicographical_compare调用
// 返回在忽略大小写的情况下,c1是否在c2之前
bool ciCharLess(char c1, char c2)
{
return std::tolower(static_cast<unsigned char>(c1)) <
std::tolower(static_cast<unsigned char>(c2));
}
bool ciStringCompare2(const std::string& s1, const std::string& s2)
{
return std::lexicographical_compare(s1.begin(), s1.end(), s2.begin(), s2.end(), ciCharLess);
}
// 实现三:
bool ciStringCompare3(const std::string& s1, const std::string& s2)
{
// 前提:不考虑国际化支持,也确定字符串中不会包含内嵌的空字符
return strcmp(s1.c_str(), s2.c_str());
}
int test_item_35()
{
std::string str1{ "xxz" }, str2{ "xxx" };
fprintf(stdout, "str1:str2: %d\n", ciStringCompare(str1, str2));
fprintf(stdout, "str1:str2: %d\n", ciStringCompare2(str1, str2));
fprintf(stdout, "str1:str2: %d\n", ciStringCompare3(str1, str2));
return 0;
}
copy_if()
算法,C++11 已经添加了该函数。
copy_if()
算法可以从源序列复制使谓词(判断式)返回 true
的元素,所以可以把它看作一个过滤器。copy_if()
一个实现:template<typename InputIterator, // 一个copy_if的正确实现
typename OutputIterator,
typename Predicate>
OutputIterator copy_if(InputIterator begin,
InputIterator end,
OutputIterator destBegin,
Predicate p) {
while (begin != end) {// 逐字节拷贝
if (p(*begin))
*destBegin++ = *begin;
++begin;
}
return destBegin;
}
copy_if()
函数的使用如下:int test_item_36()
{
std::vector<int> v1{ 1, 2, 3, 4, 5 }, v2(v1.size());
auto it = std::copy_if(v1.begin(), v1.end(), v2.begin(), [](int i) { return (i % 2 == 1); });
v2.resize(std::distance(v2.begin(), it));
for (const auto& v : v2)
fprintf(stdout, "%d\n", v); // 1 3 5
return 0;
}
accumulate()
不存在于
,它和其他三个数值算法(inner_product()
、adjacent_difference()
和 partial_sum()
)都在
中。
accumulate()
只要求输入迭代器,所以你可以使用 std::istream_iterator
和 std::istreambuf_iterator
。accumulate()
有两种形式:
accumulate()
,不仅可以计算累加,也可以计算累乘(乘积的时候初值需要设置为 1
)。
但是,如果有更复杂的情况,accumulate()
函数就很吃力,例如计算一个区间所有点的平均值;
如果使用 accumulate()
函数,在此场景下仍能使用,但是它不符合 STL 标准,即传给 accumulate()
的函数不允许有副作用,而操作的过程中会带来副作用。
代码如下:
struct Point{
Point(double initX, double initY): x(initX), y(initY) {}
double x, y;
};
//定义传入accumulate的计算函数
class PointAverage : public binary_function<Point, Point, Point> {//此处的继承参见条款40
public:
PointAverage() : xSum(0), ySum(0), numPoints(0){}
const Point operator()(const Point& avgSoFar, const Point& p){
++numPoints;
xSum += p.x;
ySum += p.y;
return Point(xSum/numPoints, ySum/numPoints);//每次返回当前遍历元素的平均值
}
private:
size_t numPoints;//记录当前读取元素的总数
double xSum;//记录所有横坐标的总和
double ySum;//记录所有纵坐标的总和
};
//调用函数
list<Point> lp;
Point avg = accumulate(lp.begin(), lp.end(), Point(0, 0), PointAverage());
for_each()
是另一个可被用来统计区间的算法,且它不受 accumulate()
的那些限制。
for_each()
也带两个参数:一个是区间(两个迭代器),另一个是函数(通常是函数对象)。for_each()
的这个函数只接收一个实参(即当前的区间元素)。【只会将第一个元素(即 first
迭代器)传入函数中】std::for_each()
和 std::accumulate()
在两个方面有所不同:
accumulate()
暗示着这个算法将会计算出一个区间的统计信息,而 for_each()
听起来就好像是对一个区间的每个元素做一个操作。accumulate()
直接返回我们所要的统计结果,而 for_each()
却返回一个函数对象,我们必须从这个函数对象中提取出我们所要的统计信息(可以保存状态)。struct Point{
Point(double initX, double initY): x(initX), y(initY) {}
double x, y;
};
//定义传入accumulate的计算函数
class PointAverage : public unary_function<Point, void> {//此处继承的函数不同了
public:
PointAverage() : xSum(0), ySum(0), numPoints(0){}
void operator()(const Point& p){
++numPoints;
xSum += p.x;
ySum += p.y;
return Point(xSum/numPoints, ySum/numPoints);//每次返回当前遍历元素的平均值
}
Point result() const{
return Point(xSum/numPoints, ySum/numPoints);
}
private:
size_t numPoints;//记录当前读取元素的总数
double xSum;//记录所有横坐标的总和
double ySum;//记录所有纵坐标的总和
};
//调用函数
list<Point> lp;
Point avg = for_each(lp.begin(), lp.end(), PointAverage()).result();// 取出返回值的状态