[温习C++]0x01-STL泛型算法-持续更新长文版

泛型算法概述

针对一段范围内的元素序列执行操作的一系列函数。

  • 大多数算法定义在algorithm中,此外还有一组数值型泛型算法定义在numeric
  • 泛型,是因为他们可以用于不同类型的元素和多种容器类型(不仅包括标准库类型,还包括内置的数组类型)。
  • 算法,是因为他们实现了一些经典算法公共接口,如排序和搜索。

迭代器、容器、算法之间的关系

迭代器独立于特定容器

利用迭代器解引用运算符可以实现元素访问,甚至通过迭代器访问的元素可以不是容器元素,比如数据来自数组。
int a[] = {1, 2, 3, 4, 5, 6};
int val = 3;
int* result = find(std::begin(a), std::end(a), val); // 通过begin(a)和end(a)分别表示首元素和尾元素之后位置的指针,并传递给find

// 在子序列范围里面访问
auto result = find(a + 1, a + 3, val); // 在a[1], a[2], a[3]中查找元素

算法只执行迭代器操作

永远不会执行容器的操作,但会运行在迭代器之上,执行迭代器的操作。

即:算法不会改变容器的大小,但可能改变容器中保存的元素的值,通过迭代器操作可以实现在容器内移动元素,但不会执行添加或删除元素。

可能会依赖元素特性操作

某一种元素类型需要定义+或者-, 亦或是==操作类型, 比如:

Find算法

  • 三个参数,前两个入参为迭代器,表示一个范围,第三个参数是目标值

  • 逐个遍历迭代器中的元素和定值相比较

  • 返回指向第一个匹配的元素的迭代器,否则返回第二个入参

  • 依赖元素==比较操作,如果第三个参数类型不匹配,编译报错

  • vector

int val = 31;
vector<int> vec = {10, 23, 32};
auto result = find(vec.cbegin(), vec.cend(), val);
  • list
int num = 0;
string val = "a value";
list<string> list = {"a value", "xxx", "yyy"};
auto result = find(str.cbegin(), str.cend(), num); 

报错: num类型与list容器类元素类型不匹配,不支持find里面 == 运算符操作

在这里插入图片描述

  • array
    int num = 63;
    int a[] = {32, 63, 55};
    auto *result = std::find(std::cbegin(a), std::cend(a), num);
    // result类型是 const int*

算法分类

只读容器算法

访问一个范围内的元素

只读取输入范围内的元素,不改变元素,迭代器区间[a, b)

  • find
    例子见前文
std::find

template <class InputIterator, class T> InputIterator find (InputIterator first, InputIterator last, const T& val);

Find value in range

Returns an iterator to the first element in the range [first,last) that compares equal to val. If no such element is found, the function returns last.

The function uses operator== to compare the individual elements to val.
The behavior of this function template is equivalent to:

template<class InputIterator, class T>
  InputIterator find (InputIterator first, InputIterator last, const T& val)
{
  while (first!=last) {
    if (*first==val) return first;
    ++first;
  }
  return last;
}
std::count

find类似, 只不过返回的范围内目标值的数量。

std::accumulate
  • 定义在numeric头文件
  • 第三个参数为初始值,最终返回范围内元素值累加的结果,决定了函数中使用哪个+运算符,以及返回结果的类型
  • 要求元素定义+运算,因此string也适用

总结:

  • 对于只读操作,建议使用cbegin()cend()迭代器
  • 对于读写操作,建议使用begin()end()迭代器

双序列的只读容器算法

equal

The behavior of this function template is equivalent to:

template <class InputIterator1, class InputIterator2>
  bool equal ( InputIterator1 first1, InputIterator1 last1, InputIterator2 first2 )
{
  while (first1!=last1) {
    if (!(*first1 == *first2))   // or: if (!pred(*first1,*first2)), for version 2
      return false;
    ++first1; ++first2;
  }
  return true;
}

从这里可以看出序列比较范围是由firstlast确定的。

  • 说明:
    • 将前两个参数表示第一序列范围,第三个参数表示第二序列迭代器开始位置

    • 在第一序列范围内与第二序列对应元素都相等返回true, 否则返回false

    • 第二序列元素数量少于第一个序列会返回false,不会报错; 多余则没有影响

测试代码:

    std::list<string> list = {"a value", "from a", "to b"};
    std::list<string> lst_less = {"a value", "from a"};
    std::list<string> lst_more = {"a value", "from a", "find c"};
    auto res1 = std::equal(list.cbegin(), list.cend(), list.cbegin());     // true 和自己比较
    auto res2 = std::equal(list.cbegin(), list.cend(), lst_less.cbegin()); // false
    auto res3 = std::equal(list.cbegin(), list.cend(), lst_more.cbegin()); // false

写容器算法

将新值赋给序列中的元素

std::fill

将指定的值填充到指定的序列

Fill range with value
Assigns val to all the elements in the range [first,last).

The behavior of this function template is equivalent to:

template <class ForwardIterator, class T>
  void fill (ForwardIterator first, ForwardIterator last, const T& val)
{
  while (first != last) {
    *first = val;
    ++first;
  }
}

测试代码:

std::list<string> list = {"a value", "from a", "to b"};
std::fill(list.begin(), list.end(), "1");  // {"1", "1", "1"}

std::fill_n

- 
- 将新值赋给指定序列中的指定个数的元素
- 第一个参数迭代器,表示序列;第二个参数为数量; 第三个参为数值,填充所用的值

测试代码:

vector<int> vec;
std::fill_n(vec.begin(), vec.size(), 0);
std::fill_n(vec.begin(), 10, 0); // 报错,vec中没有足够的元素,改语句的结果未定义

报错如图所示:
在这里插入图片描述
说明:

向目迭代器所指向的位置写入数据的位置空间要足够大,能容纳要写入的元素。

std::back_inserter

插入迭代器(back_inserter,inserter, front_inserter)

创建一个使用push_back的迭代器。一种用于为容器添加元素的迭代器,其设计目的是避免容器中的原元素被覆盖,在容器的末尾自动插入新元素。

std::back_inserter (Container& x);

x:Container in which new elements will 
be inserted at the end.

return:A back_insert_iterator that inserts 
elements at the end of container x.
  • 说明
    • 接收指向容器的引用,返回一个插入迭代器
    • 通过插入迭代器赋值,赋值运算符会调用push_back将元素添加到容器中
    • 通过该迭代器再调用fill_n就会添加n个元素

测试代码:

    // Declaring second container for
    // copying values
    vector<int> v2 = { 4, 5, 6 };

    // Using std::back_inserter inside std::copy
    std::copy(v1.begin(), v1.end(), std::back_inserter(v2));
    // v2 now contains 4 5 6 1 2 3
vector<int> vec;
auto it = back_inserter(vec);
*it = 42;

std::copy

Copy range of elements
Copies the elements in the range [first,last) into the range beginning at result.

The function returns an iterator to the end of the destination range (which points to the element following the last element copied).

The ranges shall not overlap in such a way that result points to an element in the range [first,last). For such cases, see copy_backward.

The behavior of this function template is equivalent to:

template<class InputIterator, class OutputIterator>
  OutputIterator copy (InputIterator first, InputIterator last, OutputIterator result)
{
  while (first!=last) {
    *result = *first;
    ++result; ++first;
  }
  return result;
}
  • 向目的序列的元素写入数据
  • 第一、二个参数代表范围,第三个参数代表目的序列的起始位(目的序列至少要包含与输入序列一样多的元素)
  • 返回目的位置的迭代器(递增后)的值,示例中ret指向a2尾元素之后的位置

测试代码:

int a1[] = (0, 2, ,3 ,4 ,5 ,6 ,7 ,8, 9);
int a2[sizeof(a1)/sizeof(*a1)]; // a2与a1大小相同
fill/auto ret = std::copy(begin(a1), end(a1), a2); // ret指向拷贝到a2的末尾元素之后的位置

std::replace & std::replace_copy

指定范围内元素替换

手册定义:

Replace value in range
Assigns new_value to all the elements in the range [first,last) that compare equal to old_value.
The function uses operator== to compare the individual elements to old_value.
The behavior of this function template is equivalent to:

template <class ForwardIterator, class T>
  void replace (ForwardIterator first, ForwardIterator last,
                const T& old_value, const T& new_value)
{
  while (first!=last) {
    if (*first == old_value) *first=new_value;
    ++first;
  }
}

例子:

  int myints[] = { 10, 20, 30, 30, 20, 10, 10, 20 };
  std::vector<int> myvector (myints, myints+8);            // 10 20 30 30 20 10 10 20
  std::replace (myvector.begin(), myvector.end(), 20, 99); // 10 99 30 30 99 10 10 99

删除容器算法

std::remove

Transforms the range [first,last) into a range with all the elements that compare equal to val removed, and returns an iterator to the new end of that range.

template <class ForwardIterator, class T>
  ForwardIterator remove (ForwardIterator first, ForwardIterator last, const T& val)
{
  ForwardIterator result = first;
  while (first!=last) {
    if (!(*first == val)) {
      if (result!=first)
        *result = move(*first);
      ++result;
    }
    ++first;
  }
  return result;
}

remove也需要一对迭代器来指定所要进行操作的元素区间。因为它并没有容器作为参数,所以remove本身并不知道这些元素被存放到哪个容器中。并且,remove并不能从迭代器获知对应的容器以及容器类型。

结论:如果要从容器中删除元素需要调用人哦年起成员函数erase(除了list)。

    vector<int> c = {0,1,2,3,4,5,6,7,8,9,1};
    cout << "size : " << c.size() << endl;

    remove(c.begin(), c.end(), 1);
    cout << "size : " << c.size() << endl;

    vector<int>::iterator newEnd(remove(c.begin(), c.end(), 1));

    vector<int>::iterator iter = c.begin();
    while(iter + 1 != newEnd) {
        cout << *iter << " ";
        iter++;
    }
    cout << endl;

执行结果:

size : 11
size : 11
0 2 3 4 5 6 7 8 9 

可以看到remove并没有真正从容器里删除元素;remove 移动了区间中的元素。其结果是,“需要被删除”的元素被移到了区间的尾部。remove返回一个新的迭代器,指向第一个“需要被删除”的元素。

  • remove之前
    在这里插入图片描述

  • remove之后

vector<int>::iterator newEnd(remove(c.begin(), c.end(), 1));

在这里插入图片描述

可以看到最后两个元素的值没有发生变化。在内部,remove 遍历整个区间,用需要保留的元素的值覆盖掉那些要被删除的元素的值,这种覆盖是通过对那些需要被覆盖的元素的赋值来完成的。

元素的移动对应关系如下图所示:
在这里插入图片描述

因此,要想删除这些元素,必须调用区间形式的 erase

还用两个类似的算法:remove_if unique。都需要在调用 remove_if unique 后调用 erase

其中 list 的调用是不一致的,list::remove 会真正删除元素(并且比使用 erase-remove 习惯用法更加高效),list::unique 也会真正删除元素(并且比使用 erase-unique 更加高效)

注意:当容器中存放的是指向动态分配的对象的指针时,应该避免使用 remove 和类似的算法(remove_if unique)。

可以使用智能指针。

参考资料

  1. 《C++ Primer 第5版》 第10章 泛型算法
  2. https://cplusplus.com/reference/algorithm/find/
  3. https://cplusplus.com/reference/algorithm/equal/
  4. https://cplusplus.com/reference/algorithm/fill/
  5. https://cplusplus.com/reference/algorithm/fill_n/
  • 14
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值