算法篇!带你领略STL的算法<algorithm>

  • STL标准没有区分基本算法或复杂算法,但是SGI却把一些常用的基本算法定义在<stl_algobase.h>中其他算法定义与<stl_algo.h>中(其他算法将会在后面的文章介绍)
  • 应用层使用的头文件<algorithm>包含<stl_algobase.h>与<stl_algo.h>
  • 本文介绍的算法有:
    • equal
    • fill
    • fill_n
    • iter_swap
    • lexicographical_compare
    • max
    • min
    • mismatch
    • swap
    • copy(重点介绍)
    • copy_backword(重点介绍)

一、equal

  • 功能:用来比较两个序列在[first,last)区间内是否相等
  • 返回值:相同返回true,否则返回false
  • 备注:
    • 如果第二个序列元素比较多,多出来的元素不考虑
    • 如果第二个序列元素比第一个序列元素少,则比较会超越第二个序列的尾端,后果不可预期
  • 版本②允许我们提供一个仿函数作为比较依据
  • 因此,如果我们想比较两个容器是否相等,可以使用下面的代码
if(vec1.size()==vec2.size()&&
    equal(vec1.begin(),vec1.end(),vec2.begin()));

版本①

版本②

二、fill

  • 功能:将[first,last)区间内的所有元素填充为value所指的值
  • 无返回值

源码

三、fill_n

  • 功能:将[first,last)区间内的前n个元素填充为value所指的值
  • 返回值:返回迭代器指向被填入的最后一个元素的下一个位置

源码

n越界怎么办

  • 如果n越界,因为每次迭代进行的是赋值操作,是一种覆写操作,所以操作区间超过了容器大小,后果不可预期
  • 解决办法:利用inserter()产生一个具有插入而非覆写能力的迭代器。inserter()可产生一个用来修饰迭代器的配接器
int ia[3]={0,1,2};
vector<int> iv(ia,ia+3);
fill_n(inserter(iv,iv.begin()),5,7);
  • inserter()见后面的配接器介绍

四、iter_swap

  • 待续

源码

五、lexicographical_compare

  • 待续

版本①

版本②

特化版本

六、max

  • 功能:取两个对象中的较大值
  • 版本①使用类型T所提供的greater-than操作符来判断大小
  • 版本②使用仿函数comp来判断大小

版本①

版本②

七、min

  • 功能:取两个对象中的较小值
  • 版本①使用类型T所提供的greater-than操作符来判断大小
  • 版本②使用仿函数comp来判断大小

版本①

版本②

八、mismatch

  • 待续

版本①

版本②

九、swap

  • 功能:用来交换(对调)两个对象的内容

源码

十、copy

  • 不论是对客户端程序还是对STL内部而言,copy()都是一个常常被调用的函数,copy做的就是复制操作
  • copy的实现有两种:一种利用赋值运算符(=),另一种直接复制内存(memmove或memcpy)
    • 对于简单类型的复制,copy选择前者。对于复杂类型的复制,copy选择后者
  • SGI STL的copy算法用尽各种办法,包括函数重载、类型特性、偏特化等变成技巧来增强其效率。下图是copy()操作的脉络

  • 功能:将输入区间[first,last)内的元素复制到输出区间[result,result+(last-first)内
  • 参数的类型:
    • copy算法的输入区间由InputIterators构成,输出区间由OutputItertor构成
    • 因此可以将任何容器一段区间的内容复制到任何容器的任何一段区间上

copy算法的区间重叠问题(重点)

  • copy算法使用时要注意区间重叠问题
  • 如果输入区间和输出区间完全没有重叠,那么复制操作就不会发生任何错误。但是如果输入区间和输出区间发生重叠,那么就需要考虑区间重叠问题
  • 如果输出区间的终点与输入区间重叠:那么就不会发生错误

  • 如果输出区间的起点与输入区间重叠:
    • 那么可能会发生错误。如果copy执行的是逐个的复制,那么就会发生重叠问题(见下图)
    • 但是也可能不发生错误。如果copy算法使用的是memmove()来执行拷贝,那么就不会造成区间重叠问题

区间重叠演示案例

//程序1
#include <iostream>
#include <algorithm>
using namespace std;

template<typename T>
struct display{
    void operator()(const T& x){
        std::cout << x << " ";
    }
};

int main()
{
    int ia[] = { 0,1,2,3,4,5,6,7,8 };

    copy(ia + 2, ia + 7, ia);
    for_each(ia, ia + 9, display<int>());
    std::cout << std::endl;

    return 0;
}
  • 程序1:运行结果与上面图①一致

//程序2
#include <iostream>
#include <algorithm>
using namespace std;

template<typename T>
struct display{
    void operator()(const T& x){
        std::cout << x << " ";
    }
};

int main()
{
    int ia[] = { 0,1,2,3,4,5,6,7,8 };

    copy(ia + 2, ia + 7, ia);
    for_each(ia, ia + 9, display<int>());
    std::cout << std::endl;

    return 0;
}
  • 程序2:此程序本来想模仿上图2,看看是否会发生覆盖操作,但是没有,因此这里调用的copy使用的是memmove()执行的复制操作

//程序3
#include <iostream>
#include <algorithm>
#include <deque>
using namespace std;

template<typename T>
struct display{
    void operator()(const T& x){
        std::cout << x << " ";
    }
};

int main()
{
    int ia[] = { 0,1,2,3,4,5,6,7,8 };

    deque<int> id(ia, ia + 9);
    auto iter = id.begin();

    copy(iter + 2, iter + 7, iter);
    for_each(id.begin(), id.end(), display<int>());
    std::cout << std::endl;

    return 0;
}
  • 程序3:运行结果与上面图①一致。输出区间的重点与输入区间重叠,因此不会发生错误

//程序4
#include <iostream>
#include <algorithm>
#include <deque>
using namespace std;

template<typename T>
struct display {
    void operator()(const T& x) {
        std::cout << x << " ";
    }
};

int main()
{
    int ia[] = { 0,1,2,3,4,5,6,7,8 };

    deque<int> id(ia, ia + 9);
    auto iter = id.begin();

	
    copy(iter + 2, iter + 7, iter + 4);
    for_each(id.begin(), id.end(), display<int>());
    std::cout << std::endl;

    return 0;
}
  • 程序4:此处模仿上图2,可以看到发生了内容覆盖。覆盖是因为针对于deque的迭代器类型(RandomAccessiterator),调用的copy版本不再使用memmove()执行复制操作,因此会造成覆盖行为
    • 因为deque的内存空间不是连续的,所以需要一个一个的进行移动复制

  • 如果你以vector代替deque进行测试,复制结果会是正确的,因为vector迭代器是原生指针,内存是连续的,因此是采用memmove()来复制的

copy不能复制给空容器

  • copy更改的是[result+result+(last-first))中的迭代器所指对象,而非更改迭代器本身,copy会为输出区间内的元素赋予新值,而不是产生新的元素。因此copy不能直接用来将元素插入空容器
  • 如果你想要将元素插入(而非赋值)序列之内,要么使用序列容器的insert成员函数,要么使用copy算法并搭配insert_iterator来使用(见后面的interator adapters)

编码实现

  • copy算法比较庞大,下面是唯一的对外接口(完全泛化版本)

  • 下面是两个重载函数,针对原生指针const char*和const wchar_t*,进行内存直接拷贝操作

__copy_dispatch()函数

  • copy()完全泛化版本中调用了这个函数,这个函数有一个完全泛化版本和两个偏特化版本
  • 完全泛化版本如下:
    • 其内部又调用了__copy()函数
    • __copy()又根据迭代器种类不同,分为了2种(这2中所使用的循环条件不同,速度也不一样)

  • 两个偏特化版本如下:
    • 特化版本在“参数为原生指针”的前提下,希望进一步探测“指针所指之物是否具有普通的赋值运算符=”
      • 如果指针所指对象没有普通的赋值运算符=,那么复制操作就需要根据赋值运算符=来进行
      • 如果指针所指对象有普通的赋值运算符,那么复制操作就可以不通过它,直接以最快的内存拷贝(memmove())方式完成
    • C++没有侦测某个对象的类型是否具有普通的赋值运算符=,但是SGI STL采用所谓的__type_traits<>技巧来实现

十一、copy_backword

  • 这个算法考虑的实现技巧与copy十分类似,源代码就不列出了

  • 注意,此算法是将[first,last)区间内的每一个元素,以逆行的方向复制到以result-1为起点,方向亦为逆行的区间上

  • 注意,你可以使用copy_backward算法将任何容器的任何一段区间内容复制给任何容器的任何一段区间上。如果输入区间和输出区间没有完全重叠,那么也不会有问题,否则就需要像copy一样,考虑区间重叠问题

  • 我是小董,V公众点击"笔记白嫖"解锁更多【C++ STL源码剖析】资料内容。

相关推荐
©️2020 CSDN 皮肤主题: 1024 设计师:白松林 返回首页