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

本节课要点:

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

一、模仿Python做一个range

//range.h
#pragma once

template 
class range { // range 类似于 generator 生成器
public:
    using value_type = value_t;
    using reference = value_type&;

private:
    //TODO;
    value_type b, e, s; //起始、终止、步长


public:
    //TODO
    range(value_type _b, value_type _e, value_type _s = 1) noexcept : b(_b), e(_e), s(_s) {}

    class iterator {
    public:
        using value_type = range::value_type;
        using reference = range::reference; 
    
    private:
        //TODO
        value_type n, s;

    public:
        //TODO
        iterator(value_type _n, value_type _s) : n(_n), s(_s) {}

        bool operator!=(const iterator & iter) {
            return n != iter.n;
        }

        iterator & operator++() {
            n += s;
            return *this;
        }

        reference operator*() {
            return n;
        }
    };

    auto begin() {
        return iterator(b, s);
    }

    auto end() {
        return iterator(e + s, s);
    }
};

二、泛型算法 

- algo.h 第一版

#include 
#include 
#include 

namespace myalgo {

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

    template 
    void print_c(container& c) {
        for (auto &v : c)
            std::cout << '(' << v << ')';
        std::cout << std::endl;
    }

    template 
    void print(types &&...args) {
        (std::cout << ... << args);
        std::cout << std::endl;
    }

    //----------------------------------------------------------------

    //TODO
    template  //用概念名替代 typename,现在 container 受到了概念的限制
    void rand_fill(container& c, size_t n, 
        typename container::value_type min, decltype(min) max) { //auto?
        //随机数发生引擎
        std::default_random_engine generator(time(NULL));
        //均匀分布的整型生成器;如果min和max给成了int以外的类型,则会发生类型提升且可能报错
        std::uniform_int_distribution dis(min, max);

        for (size_t i = 0; i < n; ++i)
            //把随机数 dis(generator) 插入到 c.begin() + i
            c.emplace(c.begin() + i, dis(generator));
    }
    //rand_fill 不需要了解类型,是一种泛型算法

    //TODO
    template  //采用前向迭代器
    auto count(iterator first, iterator last) {
        size_t c = 0;
        for (auto iter = first; iter != last; ++iter)
            ++c;
        return c;
    }

    //TODO
    template 
    void test_algo(iterator first, iterator last) {
        print("count: ", myalgo::count(first, last));
    }

};

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

1. concept 概念

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

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

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

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

typename container::value_type min
decltype(min) max

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

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

- algo.h 第二版 

#include 
#include 
#include 

namespace myalgo {

    template 
    concept iterable = requires {typename T::iterator;};

    template 
    void print_c(container& c) {
        for (auto &v : c)
            std::cout << '(' << v << ')';
        std::cout << std::endl;
    }

    template 
    void print(types &&...args) {
        (std::cout << ... << args);
        std::cout << std::endl;
    }

    //----------------------------------------------------------------

    template 
    void rand_fill(container& c, size_t n, 
        typename container::value_type min, decltype(min) max) {
        std::default_random_engine generator(time(NULL));
        std::uniform_int_distribution dis(min, max);

        for (size_t i = 0; i < n; ++i)
            c.emplace(c.begin() + i, dis(generator));
    }

    template 
    auto count(iterator first, iterator last) {
        size_t c = 0;
        for (auto iter = first; iter != last; ++iter)
            ++c;

        return c;
    }

    //TODO
    template 
    auto count_if(iterator first, iterator last, predicate && pred) {
        size_t c = 0;
        for (auto iter = first; iter != last; ++iter)
            if (pred(*iter))
                ++c;
        return c;
    }

    //TODO
    template 
    void test_algo(iterator first, iterator last, predicate && pred) {
        print("count: ", myalgo::count(first, last));
        print("count_if: ", myalgo::count_if(first, last, pred));
    }
};

1. 新增 count_if

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

//TODO
template 
auto count_if(iterator first, iterator last, predicate && pred) {
    size_t c = 0;
    for (auto iter = first; iter != last; ++iter)
        if (pred(*iter))
            ++c;
    return c;
}

回顾可调用对象:

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

相应的 test_algo 改为:

//TODO
template 
void test_algo(iterator first, iterator last, predicate && pred) {
    print("count: ", myalgo::count(first, last));
    print("count_if: ", myalgo::count_if(first, last, pred));
}

2. 完美转发 

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

predicate && pred

- algo.h 第三版 

#include 
#include 
#include 

namespace myalgo {

    template 
    concept iterable = requires {typename T::iterator;};

    template 
    void print_c(container& c) {
        for (auto &v : c)
            std::cout << '(' << v << ')';
        std::cout << std::endl;
    }

    template 
    void print(types &&...args) {
        (std::cout << ... << args);
        std::cout << std::endl;
    }

    //----------------------------------------------------------------

    template 
    void rand_fill(container& c, size_t n, 
        typename container::value_type min, decltype(min) max) {
        std::default_random_engine generator(time(NULL));
        std::uniform_int_distribution dis(min, max);

        for (size_t i = 0; i < n; ++i)
            c.emplace(c.begin() + i, dis(generator));
    }

    template 
    auto count(iterator first, iterator last) {
        size_t c = 0;
        for (auto iter = first; iter != last; ++iter)
            ++c;
        return c;
    }

    template 
    auto count_if(iterator first, iterator last, predicate && pred) {
        size_t c = 0;
        for (auto iter = first; iter != last; ++iter)
            if (pred(*iter))
                ++c;
        return c;
    }

    //TODO
    template 
    auto accumulate(iterator first, iterator last, T init) {
        auto sum = init;
        for (auto iter = first; iter != last; ++iter)
            sum += *iter;
        return sum;
    }

    //TODO
    template 
    void test_algo(iterator first, iterator last, predicate && pred, T t) {
        print("count: ", myalgo::count(first, last));
        print("count_if: ", myalgo::count_if(first, last, pred));
        print("accumulate: ", myalgo::accumulate(first, last, init));
    }
};

1. 新增 accumulate 

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

//main.cpp
#include 
#include 

#include "../dlist8/dlist.h"
#include "../array3/array.h"
#include "algo.h"

int main() {
    dlist l;
    myalgo::rand_fill(l, 10, 1000, 9999);
    myalgo::print_c(l);
    myalgo::test_algo(l.begin(), l.end(), 
    [](int v) {return v > 5000;}, 0);

    array a;
    myalgo::rand_fill(a, 20, 'A', 'Z');
    myalgo::print_c(a);
    myalgo::test_algo(a.begin(), a.end(), 
    [](char c) {return c < 'O';}, '\0');

    std::vector v{"a", " quick", " fox", 
    " jumps over ", "a", " lazy dog"};
    myalgo::print_c(v);
    myalgo::test_algo(v.begin(), v.end(), 
    [](std::string s) {return s == "a";}, std::string(""));

    return 0;
}

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

std::string("")

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

//TODO
template 
auto accumulate(iterator first, iterator last, T init) {
    auto sum = init;
    for (auto iter = first; iter != last; ++iter)
        sum += *iter;
    return sum;
}

//TODO
template 
void test_algo(iterator first, iterator last, predicate && pred, T init) {
    print("count: ", myalgo::count(first, last));
    print("count_if: ", myalgo::count_if(first, last, pred));
    print("accumulate: ", myalgo::accumulate(first, last, init));
}

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

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

//TODO
template 
auto accumulate(iterator first, iterator last, 
typename iterator::value_type init) {
    auto sum = init;
    for (auto iter = first; iter != last; ++iter)
        sum += *iter;
    return sum;
}

//TODO
template 
void test_algo(iterator first, iterator last, predicate && pred, 
typename iterator::value_type init) {
    print("count: ", myalgo::count(first, last));
    print("count_if: ", myalgo::count_if(first, last, pred));
    print("accumulate: ", myalgo::accumulate(first, last, init));
}

即采用:

typename iterator::value_type init

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

- algo.h 第四版 

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

#include 
#include 
#include 

namespace myalgo {

    template 
    concept iterable = requires {typename T::iterator;};

    //traits 用于析取内部类型
    template 
    struct iterator_traits {
        using value_type = typename T::value_type;
    };

    //traits 专门针对于指针类型
    template 
    struct iterator_traits {
        using value_type = T;
    };

    template 
    void print_c(container& c) {
        for (auto &v : c)
            std::cout << '(' << v << ')';
        std::cout << std::endl;
    }

    template 
    void print(types &&...args) {
        (std::cout << ... << args);
        std::cout << std::endl;
    }

    //----------------------------------------------------------------

    template 
    void rand_fill(container& c, size_t n, 
        typename container::value_type min, decltype(min) max) {
        std::default_random_engine generator(time(NULL));
        std::uniform_int_distribution dis(min, max);

        for (size_t i = 0; i < n; ++i)
            c.emplace(c.begin() + i, dis(generator));
    }

    template 
    auto count(iterator first, iterator last) {
        size_t c = 0;
        for (auto iter = first; iter != last; ++iter)
            ++c;
        return c;
    }

    template 
    auto count_if(iterator first, iterator last, predicate && pred) {
        size_t c = 0;
        for (auto iter = first; iter != last; ++iter)
            if (pred(*iter))
                ++c;
        return c;
    }

    //TODO
    template 
    auto accumulate(iterator first, iterator last, 
    typename iterator_traits::value_type init) {
        auto sum = init;
        for (auto iter = first; iter != last; ++iter)
            sum += *iter;
        return sum;
    }

    //TODO
    template 
    void test_algo(iterator first, iterator last, predicate && pred, 
    typename iterator_traits::value_type init) {
        print("count: ", myalgo::count(first, last));
        print("count_if: ", myalgo::count_if(first, last, pred));
        print("accumulate: ", myalgo::accumulate(first, last, init));
    }
};

1. 新增 iterator_traits

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

//traits 用于析取内部类型
template 
struct iterator_traits {
    using value_type = typename T::value_type;
};

//traits 专门针对于指针类型
template 
struct iterator_traits {
    using value_type = T;
};

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

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

2. 相应的改动 

//TODO
template 
auto accumulate(iterator first, iterator last, 
typename iterator_traits::value_type init) {
    auto sum = init;
    for (auto iter = first; iter != last; ++iter)
        sum += *iter;
    return sum;
}

//TODO
template 
void test_algo(iterator first, iterator last, predicate && pred, 
typename iterator_traits::value_type init) {
    print("count: ", myalgo::count(first, last));
    print("count_if: ", myalgo::count_if(first, last, pred));
    print("accumulate: ", myalgo::accumulate(first, last, init));
}

你可能感兴趣的:(C++,c++,开发语言,学习,visual,studio,code)