• 课堂笔记| 第九章:泛型算法


    本节课要点:

    • 容器
    • 迭代器
    • 概念(concept)
    • 泛型算法
    • 完美转发
    • 析取类型
    • 特性(traits)

    一、模仿Python做一个range

    1. //range.h
    2. #pragma once
    3. template <typename value_t = int>
    4. class range { // range 类似于 generator 生成器
    5. public:
    6. using value_type = value_t;
    7. using reference = value_type&;
    8. private:
    9. //TODO;
    10. value_type b, e, s; //起始、终止、步长
    11. public:
    12. //TODO
    13. range(value_type _b, value_type _e, value_type _s = 1) noexcept : b(_b), e(_e), s(_s) {}
    14. class iterator {
    15. public:
    16. using value_type = range::value_type;
    17. using reference = range::reference;
    18. private:
    19. //TODO
    20. value_type n, s;
    21. public:
    22. //TODO
    23. iterator(value_type _n, value_type _s) : n(_n), s(_s) {}
    24. bool operator!=(const iterator & iter) {
    25. return n != iter.n;
    26. }
    27. iterator & operator++() {
    28. n += s;
    29. return *this;
    30. }
    31. reference operator*() {
    32. return n;
    33. }
    34. };
    35. auto begin() {
    36. return iterator(b, s);
    37. }
    38. auto end() {
    39. return iterator(e + s, s);
    40. }
    41. };

    二、泛型算法 

    - algo.h 第一版

    1. #include
    2. #include
    3. #include
    4. namespace myalgo {
    5. template <typename T>
    6. //concept 概念名 = requires {这个类型必须要含有一个iterator类;};
    7. //concept 将在编译期间判断是否满足要求,如果满足则返回真,编译器将会放过它
    8. concept iterable = requires {typename T::iterator;};
    9. template
    10. void print_c(container& c) {
    11. for (auto &v : c)
    12. std::cout << '(' << v << ')';
    13. std::cout << std::endl;
    14. }
    15. template <typename ...types>
    16. void print(types &&...args) {
    17. (std::cout << ... << args);
    18. std::cout << std::endl;
    19. }
    20. //----------------------------------------------------------------
    21. //TODO
    22. template //用概念名替代 typename,现在 container 受到了概念的限制
    23. void rand_fill(container& c, size_t n,
    24. typename container::value_type min, decltype(min) max) { //auto?
    25. //随机数发生引擎
    26. std::default_random_engine generator(time(NULL));
    27. //均匀分布的整型生成器;如果min和max给成了int以外的类型,则会发生类型提升且可能报错
    28. std::uniform_int_distribution<decltype(min)> dis(min, max);
    29. for (size_t i = 0; i < n; ++i)
    30. //把随机数 dis(generator) 插入到 c.begin() + i
    31. c.emplace(c.begin() + i, dis(generator));
    32. }
    33. //rand_fill 不需要了解类型,是一种泛型算法
    34. //TODO
    35. template <typename iterator> //采用前向迭代器
    36. auto count(iterator first, iterator last) {
    37. size_t c = 0;
    38. for (auto iter = first; iter != last; ++iter)
    39. ++c;
    40. return c;
    41. }
    42. //TODO
    43. template <typename iterator>
    44. void test_algo(iterator first, iterator last) {
    45. print("count: ", myalgo::count(first, last));
    46. }
    47. };

    因为我们是在粗劣地模仿 C++ 原生的容器,为了避免与 C++ 原生的方法产生冲突,所以设置名字空间 myalgo,以指定方法作用域。

    1. concept 概念

    1. template <typename T>
    2. //concept 概念名 = requires {这个类型必须要含有一个iterator类;};
    3. //concept 将在编译期间判断是否满足要求,如果满足则返回真,编译器将会放过它
    4. concept iterable = requires {typename T::iterator;};

    这里的要求是类型 T 必须含有 iterator 迭代器类,我们称之为 iterable 可迭代的。

    2. 保证与容器元素类型一致 

    我们需要 min 和 max 的类型与元素类型一致: 

    1. typename container::value_type min
    2. decltype(min) max

    对于 min,我们直接析取容器元素类型;对于 max,我们让它与 min 保持一致即可。

    存在的问题:对于数组,其迭代器采用的是原生指针的别名,即伪迭代器。而原生指针内部没有再包含类型,因此将导致编译错误。

    - algo.h 第二版 

    1. #include
    2. #include
    3. #include
    4. namespace myalgo {
    5. template <typename T>
    6. concept iterable = requires {typename T::iterator;};
    7. template
    8. void print_c(container& c) {
    9. for (auto &v : c)
    10. std::cout << '(' << v << ')';
    11. std::cout << std::endl;
    12. }
    13. template <typename ...types>
    14. void print(types &&...args) {
    15. (std::cout << ... << args);
    16. std::cout << std::endl;
    17. }
    18. //----------------------------------------------------------------
    19. template
    20. void rand_fill(container& c, size_t n,
    21. typename container::value_type min, decltype(min) max) {
    22. std::default_random_engine generator(time(NULL));
    23. std::uniform_int_distribution<decltype(min)> dis(min, max);
    24. for (size_t i = 0; i < n; ++i)
    25. c.emplace(c.begin() + i, dis(generator));
    26. }
    27. template <typename iterator>
    28. auto count(iterator first, iterator last) {
    29. size_t c = 0;
    30. for (auto iter = first; iter != last; ++iter)
    31. ++c;
    32. return c;
    33. }
    34. //TODO
    35. template <typename iterator, typename predicate>
    36. auto count_if(iterator first, iterator last, predicate && pred) {
    37. size_t c = 0;
    38. for (auto iter = first; iter != last; ++iter)
    39. if (pred(*iter))
    40. ++c;
    41. return c;
    42. }
    43. //TODO
    44. template <typename iterator, typename predicate>
    45. void test_algo(iterator first, iterator last, predicate && pred) {
    46. print("count: ", myalgo::count(first, last));
    47. print("count_if: ", myalgo::count_if(first, last, pred));
    48. }
    49. };

    1. 新增 count_if

    作为一种过滤器(filter),帮助我们按需求进行计数。我们使用谓词来帮助过滤,谓词即返回值为 bool 类型的回调函数。 

    1. //TODO
    2. template <typename iterator, typename predicate>
    3. auto count_if(iterator first, iterator last, predicate && pred) {
    4. size_t c = 0;
    5. for (auto iter = first; iter != last; ++iter)
    6. if (pred(*iter))
    7. ++c;
    8. return c;
    9. }

    回顾可调用对象:

    全局函数、类的成员函数、lambda表达式、重载了()运算符的类对象。

    相应的 test_algo 改为:

    1. //TODO
    2. template <typename iterator, typename predicate>
    3. void test_algo(iterator first, iterator last, predicate && pred) {
    4. print("count: ", myalgo::count(first, last));
    5. print("count_if: ", myalgo::count_if(first, last, pred));
    6. }

    2. 完美转发 

    谓词的参数是 *iter,其类型是容器元素类型。为了避免类型折叠,我们使用完美转发:

    predicate && pred

    - algo.h 第三版 

    1. #include
    2. #include
    3. #include
    4. namespace myalgo {
    5. template <typename T>
    6. concept iterable = requires {typename T::iterator;};
    7. template
    8. void print_c(container& c) {
    9. for (auto &v : c)
    10. std::cout << '(' << v << ')';
    11. std::cout << std::endl;
    12. }
    13. template <typename ...types>
    14. void print(types &&...args) {
    15. (std::cout << ... << args);
    16. std::cout << std::endl;
    17. }
    18. //----------------------------------------------------------------
    19. template
    20. void rand_fill(container& c, size_t n,
    21. typename container::value_type min, decltype(min) max) {
    22. std::default_random_engine generator(time(NULL));
    23. std::uniform_int_distribution<decltype(min)> dis(min, max);
    24. for (size_t i = 0; i < n; ++i)
    25. c.emplace(c.begin() + i, dis(generator));
    26. }
    27. template <typename iterator>
    28. auto count(iterator first, iterator last) {
    29. size_t c = 0;
    30. for (auto iter = first; iter != last; ++iter)
    31. ++c;
    32. return c;
    33. }
    34. template <typename iterator, typename predicate>
    35. auto count_if(iterator first, iterator last, predicate && pred) {
    36. size_t c = 0;
    37. for (auto iter = first; iter != last; ++iter)
    38. if (pred(*iter))
    39. ++c;
    40. return c;
    41. }
    42. //TODO
    43. template <typename iterator, typename T>
    44. auto accumulate(iterator first, iterator last, T init) {
    45. auto sum = init;
    46. for (auto iter = first; iter != last; ++iter)
    47. sum += *iter;
    48. return sum;
    49. }
    50. //TODO
    51. template <typename iterator, typename predicate, typename T>
    52. void test_algo(iterator first, iterator last, predicate && pred, T t) {
    53. print("count: ", myalgo::count(first, last));
    54. print("count_if: ", myalgo::count_if(first, last, pred));
    55. print("accumulate: ", myalgo::accumulate(first, last, init));
    56. }
    57. };

    1. 新增 accumulate 

    分析累加操作知,我们需要一个累加起点。对于数值,起点为0;对于字符串,起点为空串;对于字符,由于其累加无意义,因此我们不作考虑。

    1. //main.cpp
    2. #include
    3. #include
    4. #include "../dlist8/dlist.h"
    5. #include "../array3/array.h"
    6. #include "algo.h"
    7. int main() {
    8. dlist<int> l;
    9. myalgo::rand_fill(l, 10, 1000, 9999);
    10. myalgo::print_c(l);
    11. myalgo::test_algo(l.begin(), l.end(),
    12. [](int v) {return v > 5000;}, 0);
    13. array<char, 20> a;
    14. myalgo::rand_fill(a, 20, 'A', 'Z');
    15. myalgo::print_c(a);
    16. myalgo::test_algo(a.begin(), a.end(),
    17. [](char c) {return c < 'O';}, '\0');
    18. std::vector v{"a", " quick", " fox",
    19. " jumps over ", "a", " lazy dog"};
    20. myalgo::print_c(v);
    21. myalgo::test_algo(v.begin(), v.end(),
    22. [](std::string s) {return s == "a";}, std::string(""));
    23. return 0;
    24. }

    说明:"" 的类型是 const char *,加运算符不能作用在 const char * 上。因此,我们采用以下形式创建我们希望得到的空串:

    std::string("")

    “和”的类型肯定与容器存储的对象的类型是相同的,因此我们需要使用迭代器指向(容器元素的)类型。由于该类型对算法来说是未知的,因此,我们容易想到的一个解决问题的方法是用附加的类型参数明确的给出。

    1. //TODO
    2. template <typename iterator, typename T>
    3. auto accumulate(iterator first, iterator last, T init) {
    4. auto sum = init;
    5. for (auto iter = first; iter != last; ++iter)
    6. sum += *iter;
    7. return sum;
    8. }
    9. //TODO
    10. template <typename iterator, typename predicate, typename T>
    11. void test_algo(iterator first, iterator last, predicate && pred, T init) {
    12. print("count: ", myalgo::count(first, last));
    13. print("count_if: ", myalgo::count_if(first, last, pred));
    14. print("accumulate: ", myalgo::accumulate(first, last, init));
    15. }

    accumulate 返回值采用 auto 关键字,让编译器去自动推导类型,不仅可以确保类型的正确性,还可以使代码更加精简。 

    2. 优化:确保 init 类型与容器元素类型一致 

    1. //TODO
    2. template <typename iterator>
    3. auto accumulate(iterator first, iterator last,
    4. typename iterator::value_type init) {
    5. auto sum = init;
    6. for (auto iter = first; iter != last; ++iter)
    7. sum += *iter;
    8. return sum;
    9. }
    10. //TODO
    11. template <typename iterator, typename predicate>
    12. void test_algo(iterator first, iterator last, predicate && pred,
    13. typename iterator::value_type init) {
    14. print("count: ", myalgo::count(first, last));
    15. print("count_if: ", myalgo::count_if(first, last, pred));
    16. print("accumulate: ", myalgo::accumulate(first, last, init));
    17. }

    即采用:

    typename iterator::value_type init

    存在问题:对于数组,其迭代器(伪迭代器)是原生指针的别名。而原生指针是一种简单类型,它不可能有内嵌的其它类型,因此将导致编译错误。 

    - algo.h 第四版 

    使用特性(traits)解决上述问题。

    1. #include
    2. #include
    3. #include
    4. namespace myalgo {
    5. template <typename T>
    6. concept iterable = requires {typename T::iterator;};
    7. //traits 用于析取内部类型
    8. template <typename T>
    9. struct iterator_traits {
    10. using value_type = typename T::value_type;
    11. };
    12. //traits 专门针对于指针类型
    13. template <typename T>
    14. struct iterator_traits {
    15. using value_type = T;
    16. };
    17. template
    18. void print_c(container& c) {
    19. for (auto &v : c)
    20. std::cout << '(' << v << ')';
    21. std::cout << std::endl;
    22. }
    23. template <typename ...types>
    24. void print(types &&...args) {
    25. (std::cout << ... << args);
    26. std::cout << std::endl;
    27. }
    28. //----------------------------------------------------------------
    29. template
    30. void rand_fill(container& c, size_t n,
    31. typename container::value_type min, decltype(min) max) {
    32. std::default_random_engine generator(time(NULL));
    33. std::uniform_int_distribution<decltype(min)> dis(min, max);
    34. for (size_t i = 0; i < n; ++i)
    35. c.emplace(c.begin() + i, dis(generator));
    36. }
    37. template <typename iterator>
    38. auto count(iterator first, iterator last) {
    39. size_t c = 0;
    40. for (auto iter = first; iter != last; ++iter)
    41. ++c;
    42. return c;
    43. }
    44. template <typename iterator, typename predicate>
    45. auto count_if(iterator first, iterator last, predicate && pred) {
    46. size_t c = 0;
    47. for (auto iter = first; iter != last; ++iter)
    48. if (pred(*iter))
    49. ++c;
    50. return c;
    51. }
    52. //TODO
    53. template <typename iterator>
    54. auto accumulate(iterator first, iterator last,
    55. typename iterator_traits::value_type init) {
    56. auto sum = init;
    57. for (auto iter = first; iter != last; ++iter)
    58. sum += *iter;
    59. return sum;
    60. }
    61. //TODO
    62. template <typename iterator, typename predicate>
    63. void test_algo(iterator first, iterator last, predicate && pred,
    64. typename iterator_traits::value_type init) {
    65. print("count: ", myalgo::count(first, last));
    66. print("count_if: ", myalgo::count_if(first, last, pred));
    67. print("accumulate: ", myalgo::accumulate(first, last, init));
    68. }
    69. };

    1. 新增 iterator_traits

    使用 traits 技术,设计一个 iterator_traits 类模板,用它来析取常规迭代器中的元素类型;再定义一个针对原生指针类型的特化版本。

    1. //traits 用于析取内部类型
    2. template <typename T>
    3. struct iterator_traits {
    4. using value_type = typename T::value_type;
    5. };
    6. //traits 专门针对于指针类型
    7. template <typename T>
    8. struct iterator_traits {
    9. using value_type = T;
    10. };

    对于一般迭代器,直接析取其内部类型;对于数组的迭代器,采用其所指元素的类型。

    这里的模板参数是 T,而特化参数类型是 T*,我们称之为部分特化或偏特化。 

    2. 相应的改动 

    1. //TODO
    2. template <typename iterator>
    3. auto accumulate(iterator first, iterator last,
    4. typename iterator_traits::value_type init) {
    5. auto sum = init;
    6. for (auto iter = first; iter != last; ++iter)
    7. sum += *iter;
    8. return sum;
    9. }
    10. //TODO
    11. template <typename iterator, typename predicate>
    12. void test_algo(iterator first, iterator last, predicate && pred,
    13. typename iterator_traits::value_type init) {
    14. print("count: ", myalgo::count(first, last));
    15. print("count_if: ", myalgo::count_if(first, last, pred));
    16. print("accumulate: ", myalgo::accumulate(first, last, init));
    17. }
  • 相关阅读:
    C/C++语言标准
    Python开发环境搭建
    一文知晓Linux文件权限
    Debian Linux安装minikube&kubectl
    【C语言基础】近期所学到的函数以及关键字(函数memset、scanf、关键字staric、 inline、volatile)
    二叉树正传 - 二叉树的基本操作及构建
    布隆过滤器浅析
    Web jQuery 事件与其他
    win10如何清除ftp自动保存的账户密码
    C++初阶 | [三] 类和对象(中)
  • 原文地址:https://blog.csdn.net/m0_64140451/article/details/127801133