C++泛型算法初步

版权声明:所有的博客都是作为个人笔记的。。。。。。 https://blog.csdn.net/qq_35976351/article/details/82787879

C++的泛型算法永远不会执行容器的操作,这些算法只是在迭代器上进行操作。编程假定是算法永远不会改变容器的大小,不会直接添加和删除元素。算法操作的是一组迭代器的范围。

初识泛型算法

标准算法库都是对一个范围内的元素进行操作,一般都要输入要操作的区间。

只读算法

只读取输入范围的值,不改变任何数据。一般使用cbegin()cend()进行返回确定。

常用的有:

  • find() :查找
  • count():计数
  • accumulate():累加
  • equal()判断序列是否相等,假设第二个序列的元素和第一个一样长。

写容器算法

因为算法不改变容器的大小,所以要写入的数据必须小于等于容器能容纳的量。一个算法从两个序列中读取元素,构成这两个序列的元素可以来自于不同类型的容器。操作两个序列算法之间的区别在于我们如何传递第二个序列。用一个单一迭代器表示第二个序列的算法都假设第二个序列的长度大于等于第一个序列的长度。

算法不检查写入操作是否是合法的。如果想要向容器中插入数据,需要借助插入迭代器back_insert进行操作。

vector<int> vec;
auto it = back_insert(vec);
*it = 42;  // 向容器中添加元素

vector<int> vec1;
fill_n(back_insert(vec1), 10, 0);  // 正确的,向容器中插入10个元素

拷贝操作copy

3个参数:目的容器的起始位置,目的容器终止位置的后一个迭代器,要拷贝区间的起始位置。需要保证目的容器的容量大于等于要拷贝区间的长度。返回尾后元素的位置。

重排容器元素算法

排序算法sort是用于元素排序的,默认使用<进行比较。自定义元素需要重载<运算符。

在排序完成后,可以使用去重复元素的unique操作。注意,根据泛型算法的不会改变容器大小的特性,这种操作只是把重复的元素移动到容器的最后,而且返回不重复序列的尾后元素,真正删除的操作需要使用容器本身的删除操作。以vector为例子:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
  vector<int>vec{1,3,5,7,9,9,7,6,5,4,3,2,4,5,6,7,8,9,0};
  sort(vec.begin(),vec.end());
  auto it=unique(vec.begin(),vec.end());  // 移动到尾后
  for(const auto &c:vec){
    cout<<c<<" ";
  }
  cout<<endl;
  vec.erase(it,vec.end());    // 执行真正的删除
  for(const auto &c:vec){
    cout<<c<<" ";
  }
  cout<<endl;
  return 0;
}
/*
输出:
0 1 2 3 4 5 6 7 8 9 6 6 7 7 7 8 9 9 9 
0 1 2 3 4 5 6 7 8 9
*/

定制操作

需要执行自定义的操作的时候使用。向算法传递参数的时候,需要借助与谓词进行。一元谓词是传递一个参数,二元传递两个。

使用一般的函数

以sort为例子:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

bool bigger(const int &a, const int &b){
  return a>b;
}

int main() {
  vector<int>vec{1,3,5,7,9,9,7,6,5,4,3,2,4,5,6,7,8,9,0};
  sort(vec.begin(),vec.end(),bigger);
  for(const auto &c:vec){
    cout<<c<<" ";
  }
  cout<<endl;
  return 0;
}
/*
输出
9 9 9 8 7 7 7 6 6 5 5 5 4 4 3 3 2 1 0
*/

使用lambda表达式

lambda不能有默认参数相当于一个可调用对象。简单的使用和for_each结合。

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
  vector<int>vec{1,3,5,7,9,9,7,6,5,4,3,2,4,5,6,7,8,9,0};
  sort(vec.begin(),vec.end(),
      [](const int &a,const int &b){return a>b;});
  for_each(vec.begin(),vec.end(),
      [](const int &a){cout<<a<<" ";});
  cout<<endl;
  return 0;
}

捕获对象的例子:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
  vector<int>vec{1,3,5,7,9,9,7,6,5,4,3,2,4,5,6,7,8,9,0};
  int n=5;
  for_each(vec.begin(),vec.end(), // 捕获局部变量n=5
      [n](const int &a){if(a<n)cout<<a<<" ";});
  cout<<endl;
  return 0;
}
/*
输出:
1 3 4 3 2 4 0
*/

捕获对象更加适合只能使用一元谓词,但是需要和当前环境下某些条件相比较的情况。

lambda捕获参数的方式

主要有以下几个:

  • 值捕获:复制原来的值
  • 引用捕获:原来值的引用,需要在变量名钱添加&
  • 隐式捕获:捕获列表中添加&或者=&表示引用,=表示传值。
    • [&, identifier_list]identifier_list是一个逗号分隔的列表,包含0个或多个来自函数的变量,这些变量是传值的,其余没声明的是传递引用
    • [=, identifier_list]:与上述相反,声明的是传递引用的,其余的是传值的。

lambda的返回值

如果一个lambda表达式包含了return之外的任意一条语句,那么表达式都返回void类型,如果想返回希望的类型,需要添加尾置声明。

transform(v.begin(), v.end(), v.begin(),
         [](int i) -> int
         { if (i < 0) return -i; else return i; });

上述代码返回int,如果不添加-> int,则返回void

参数绑定

lambda表达式适合于那些一两个简单操作的地方,如果多次调用相同的操作,此时最好借助于函数完成。如果lambda的捕获列表是空,通常用函数进行替换。如果非空,则考虑用bind函数进行处理。

#include <iostream>
#include <algorithm>
#include <functional>
using namespace std;
using namespace std::placeholders;

bool test(string &s, int n) {
    if(s.size() > n) {
        return true;
    } else {
        return false;
    }
    s = "change";
}

int main() {
    // 在这里执行绑定,_1是占位符,在命名空间std::placeholders中
    auto f = bind(test, _1, 5);
    string s = "hello world !";
    cout << f(s) << endl;
    cout << s << endl;
    return 0;
}
/*
输出:
1
hello world !
*/

可以理解为,占位符就是可变的参数,而绑定的过程需要依次传入原来函数的参数,调用绑定的新函数时,需要按照占位符的次序依次传入参数。

注意到上述输出的s,依然是原来的数据,说明这里传递的是拷贝,是先拷贝一份数据到bind内部,然后test引用的是拷贝后的参数,所以不会改变。

对于非占位符bind函数默认是值传递的。若要使用引用,则需要调用ref进行说明,代码实例:

#include <iostream>
#include <algorithm>
#include <functional>
using namespace std;
using namespace std::placeholders;

void test(string &s, int n) {
    reverse(s.begin(), s.end());
    cout << n << endl;
}

int main() {
    string s = "hello world !";
    auto f = bind(test, s, _1);  // 这里传值
    f(0);
    cout << s << endl;
    auto fr = bind(test, ref(s), _1); // 这里引用
    fr(1);
    cout << s << endl;
    return 0;
}
/*
输出结果:
0
hello world !
1
! dlrow olleh
*/

常引用使用cref进行操作。

迭代器

这里介绍的4中迭代器是标准迭代器之外的类型。

插入迭代器

迭代器用于绑定到容器上,用来向容器插入元素。分为3个:

  • back_inserter:创建一个使用push_back的迭代器
  • front_inserter:创建一个使用push_front的迭代器
  • inserter:创建一个使用insert的迭代器,该函数接受第二个参数,这个参数是指向给定容器的迭代器,元素被插入到给定迭代器所表示的元素之前。

流迭代器

绑定到输入输出流上,遍历所有与IO有关的流。暂时未用到。

反向迭代器

向后移动的迭代器,除了forward_list之外的标准库容器都有反向迭代器。

调用的方式是rbegin()rend()crbegind()crend()

移动迭代器

用于移动容器中的元素。

猜你喜欢

转载自blog.csdn.net/qq_35976351/article/details/82787879