美文网首页
chapter1 让程序 更简洁、更现代: auto & dec

chapter1 让程序 更简洁、更现代: auto & dec

作者: my_passion | 来源:发表于2022-06-06 23:45 被阅读0次

1.1 类型推导

1   auto 
    
    (1) 本质/机制/应用 
    
        1) 本质: auto 是 类型声明 的 "占位符"
                
        2) 机制: 编译时 类型推断 + 替换占位符 auto            
                 
        3) 应用 
        
            2种必须: 编译器 才能 编译时 类型推断 + ...
            
            [1] 声明 变量, `必须` 马上 初始化
                            
            [2] 声明 函数返回类型, `必须` 结合 decltype 来 `后置返回类型` 
                
                template<typename T, typename U>
                auto add(T t, U u) -> decltype(t + u);
                
                template <typename T>
                auto func(T& val) -> decltype( anotherFunc(val) );

    (2) 如何用
        
        1) 何时用 ?
            
            无须关心 变量类型时, 尤其用于 模板
        
        2) 不能用 auto 的场景 

            [1] non-static 成员变量 
            [2] 函数参数 
            [3] 模板参数 
        
    (3) auto 与 指针 / 引用 / cv 限定符( const / volatile )
    
        2大类 丢弃 

        ———————————————————————————————————————————————————————————————————————————
                                auto 声明的 变量类型 
                                    => auto 的 推导结果(是1个类型)
        ———————————————————————————————————————————————————————————————————————————
        [1] 声明为 指针 或 引用     不丢弃 初始化表达式的 单纯 cv / 指针 + cv / 引用 + cv 属性
        
                                    int x = 0;
                                    const int* p = &x; // p: const int*
                                    auto* p2 = p;      // p2 -> const int*  

                                    const int& r = x; // r: const int&
                                    auto& r2 = r;     // r2 -> const int&, auto 被推导为 const int                                      
        ———————————————————————————————————————————————————————————————————————————
        [2] 不声明为 指针         不丢弃 单纯指针 & 指针 + cv
        
                                    1) 指针 -> 不丢弃 
                                    auto  p = &x;  // p -> int*, auto 被推导为 int*
                                
                                    2) 指针 + cv -> 不丢弃
                                    const int* p = &x; // p: const int*
                                    auto p2 = p;       // p2 -> const int*  
        ———————————————————————————————————————————————————————————————————————————
        [3] 不声明为 引用         `丢弃` 单纯引用 & 引用 + cv
                                
                                    1) 引用 -> 丢弃  
                                    int &r = x;  // r: int&
                                    auto r2 = r; // r2 -> int, auto 被推导为 int 
                                
                                    3) 引用 + cv -> 丢弃 
                                    const int& r = x; // r: const int&
                                    auto r2 = r;      // r2 -> int                                      
        ———————————————————————————————————————————————————————————————————————————
        [4] 不声明为 指针 或 引用   `丢弃` 单纯 cv
        
                                    const int y = 0; // y: const int 
                                    auto a = y;      // a -> int, auto 被推导为 int 
        ———————————————————————————————————————————————————————————————————————————
    
2   decltype(expr)
    
    (1) `编译时 推断 表达式 类型` 
        
        `不会真正计算 表达式的值`
        
        可视为 `类型函数`: 用于 `抽取 类型` 
        
    (2) 应用 
        
        只 希望得到 `类型`, `不需要或不能 定义变量` 时, 用 
    
        1) GP 
            
        2) auto + decltype(): 3 小节
            
    (3) Note
        
        1) decltype 与 引用 (&): 引用折叠 
    
        3) 推导规则 
        
        ——————————————————————————————————————————————————————————————————
        expr 是                  decltype(expr) 是 
        ——————————————————————————————————————————————————————————————————
        [1] 标识符 / 类访问表达式    expr 类型 
        
        [2] 函数调用                expr 返回类型  
                                        返回 纯右值 时, 若 返回类型为 class 类型, cv 才不会被丢弃
                                        
        [3] else                    expr 类型的 左值引用 <- expr 是 左值, 但不是 标识符 / 类访问表达式
        
                                    expr 类型              <- expr 是 右值
        ——————————————————————————————————————————————————————————————————
        
        [1]
            decltype(foo.x) d = 0; // Foo::int x; => d -> int 
        
        [2]
            const int& func(); // 返回 左值 
            const int func2(); // 返回 纯右值 & not class  
            const Foo func3(); // 返回 纯右值 & class   
            
            int x = 0;
            decltype( func() ) a = x;   // a -> const int& 
            decltype( func2() ) b = x;  // b -> int (const 被 丢弃)
            decltype( func3() ) c = x;  // c -> const Foo
            
        [3]     
            // Note: 与 [1] 不同, 括号表达式 是 左值, d -> int&
            decltype( (foo.x) ) d = x; 
            
            int n = 0, m = 0;
            decltype(n + m) e = 0;  // c -> int 
            decltype(n += m) f = e; // d -> int&
            

3   后置返回类型: 组合 auto + decltype()

    解决的问题
        returnType 依赖 func 参数 或 another func, 而导致的 returnType 难推断 的 问题
    
    (1) 返回类型 仅依赖 func 参数 
    
        问题: 加法 2 个参数 类型不同
        
        解决 
        
        1)  returnType 用 模板参数 + 调用时 用 decltype(...) 显式指定 
        
            template<typename T, typename U, typename R>
            R add(T t, U u);
            
            int a = 1; 
            float b = 2.0;
            
            auto c = add<decltype(a + b) >(a + b);
            
        2) returnType 用 decltype(paraVar ...)
            
            paraVar 尚未定义 -> 语法错
            
            template<typename T, typename U>
            decltype(t + u) add(T t, U u);
            
        3) returnType 用 decltype(paraType() )
            
            paraType 无 默认 Ctor 时, error
            
            template<typename T, typename U>
            decltype(T() + U() ) add(T t, U u);
            
        4) returnType 用 decltype(*(paraType*)0 ) -> 通法
            
            0 地址处用 paraType 指针强转, 再 解引用 来 get 1 个 未初始化 的 paraVar

            template<typename T, typename U>
            decltype( *(T*)0  + *(U*)0 ) add(T t, U u);
            
        5) C++11: auto + decltype 
            
            template<typename T, typename U>
            auto add(T t, U u) -> decltype(t + u);  
            
    (2) ... 还依赖于 another 函数 -> C++98/03 无法解决, 只能是 C++11 的 auto + decltype 
    
        int& func2(int& i);
        float func2(float& f);
        
        template<typename T>
        auto func(T& val) -> decltype( func2(val) )
        {
            return func2(val);
        }

1.2 std::function() 和 std:bind() 绑定器

1   可调用对象: 4 种 

    ——————————————————————————————————————————————————————————————————————————
    [1] 函数指针 
        
        void(* funcPtr)(void) = &func;
        funcPtr();
    ——————————————————————————————————————————————————————————————————————————
    [2] 含 `函数调用运算符 operator()` 的 `函数对象` -> 所有 `lambda` 均可以

        Functor f; // void A::operator()() { /* */ };
        f();
    ——————————————————————————————————————————————————————————————————————————
    [3] 可被 `转换为 函数指针` 的 类对象 
                        |
                        |   指 普通函数指针, 不含 成员函数指针
                        |/
                    类型转换运算符 X::operator T()
    ——————————————————————————————————————————————————————————————————————————              
    [4] 成员函数指针 
    
        void (A::*memFuncPtr)(void) = &A::memFunc;
        
        A a;
        (a.*memFuncPtr)();
    ——————————————————————————————————————————————————————————————————————————
    
    Note: 可调用类型 不包括 函数类型 & 函数引用
    
        1] 函数类型
            
            不能直接 定义对象
            
        2] 函数引用 
        
            可视为 const 函数指针 

    callableObj 
    
        1] 调用 方法: 除 成员函数指针 外, 都 像函数那样
        
        2] 定义 方法: 多样
            => 保存 & 传递 时, 繁琐 
                |
                |   解决
                |/
        C++11 std::function & std::bind() 
            统一 `保存 并 延迟调用` callableObj  

    std::function<void(int, int)> f = callableObj; // 用 callableObj 初始化 
    
2   callableObj 的 包装器(wrapper) std::function() 

    (1) 是 类模板 
    
        <functional>
        
        template< class R, class... Args >
        class function<R(Args...)>;

                        static 成员函数指针: 本质是 普通函数指针, 可 单独用于 函数调用 => 可被 std::function 容纳 
                            |\
                            |
                            |
    (2) 可容纳 除 `non-static 成员函数指针` 之外的 3 种 callableObj
                            |
                            |
                            |/
                        本质是 偏移量 -> 不能单独用于 函数调用, 必须 结合 对象(指针) 
                            |
                            |   解决 
                            |/
                        std::bind `第 2 参数` 提供 `对象指针` 
    
    (3) `模板参数` 为 (3种) callableObj 的 `函数调用运算符 的 signature: returnType (paraListTypes)`       
    
    (4) `结果` 视为 `函数对象`
            
        可作  
            算法参数/回调函数 
            函数参数 k
                    
    int f1(int x)
    {
        return 2*x;
    }
    
    class A
    {
    public:
        int operator()(int x)
        {
            return 2*x;
        }
    };
    
    A a;
    
    std::function<int(int)> f = f1; // 绑定 普通函数
    std::cout << f(1) << std::endl;
    f = a;                          // 绑定 函数对象
    std::cout << f(2) << std::endl;
    
3   std::bind() 绑定器 
    
    <functional>
    
    template< class F, class... Args >
    /*unspecified*/ bind( F&& f, Args&&... args );

    (1) 弥补了 std:function 不能 容纳/绑定 non-static 成员函数指针 的 callableObj 
        
        bind 配合 function 
            
            => 统一 对 callableObj 的 操作
        
    (2) 机制 
        
        绑定 `可调用对象` 与其 `参数`
         |
         |  结果/返回 
         |/
        函数对象: 其 参数` = `未绑定的 剩余参数`
            |
            |
            |/
        可
            ———————————————————————————————————
            1) 直接调用 
            
            2) auto 推断出 变量类型 + 变量定义 
            
            3) 用 std::function() 保存
            ———————————————————————————————————
        
    (3) 2 大作用
        
        [1] 将 callableObj 与其 参数 绑定为 1个 函数对象
        
        [2] 将 多元(参数个数 n > 1) callableObj 转成 1元或 (n-1) 元 callableObj, 即 只绑定部分参数
    
    (4) "占位符" std::placeholders::_1/... 
        
        该位置 将在 函数调用时, 被 传入的 第1/... 个实参替代            
        
    (5) 简化和增强了 标准库 bind1st / bind2nd
    
        bind1st / bind2nd 
            将 二元算子 变为 一元算子
            绑定 左 右 操作数
            
        // 找 elemValue > 10 的 元素数
        int count = std::count_if(collection.begin(), collection.end(), 
            std::bind1st(std::less<int>(), 10) ); // 10 < rOperNum
            
        // 找 elemValue < 10 的 元素数
        int count = std::count_if(collection.begin(), collection.end(), 
            std::bind2nd(std::less<int>(), 10) ); // lOperNum < 10
        
        // bind: 统一调用方式
        int count = std::count_if(collection.begin(), collection.end(), 
            std::bind(std::less<int>(), 10, _1) ); // 10 < rOperNum     
        int count = std::count_if(collection.begin(), collection.end(), 
            std::bind(std::less<int>(), _1, 10) ); // lOperNum < 10 
            
    (6) 组合使用 bind 
    
        1) 用于 判断 是否 > 5 的 `闭包`
        
        std::bind(std::greater<int>(), std::placeholders::_1, 5 )
            |
            |/
            返回的对象 只有1个 int 参数 
            
        2) `逻辑与` 组合 `是否 > 5` 与 `是否 < 10` 的 `2个 闭包` 
            
            using std::placeholders::_1;
            
            auto f = std::bind(std::logical_and<bool>(), 
                                std::bind(std::greater<int>(), _1, 5 ),
                                std::bind(std::less_equal<int>(), _1, 10 )
                              );
            
            int count = count_if(v.begin(), v.end(), f);
    
    例:
        void f(int n1, int n2, int n3, const int& n4, int n5)
        {
            std::cout << n1 << ' ' << n2 << ' ' << n3 << ' ' << n4 << ' ' << n5 << '\n';
        }
        n = 7;
        auto f1 = std::bind(f, _2, 42, _1, std::cref(n), n); // std::cref(n) 引用传递,; n 值传递 => 此处绑定为 7, 后面都是 7
        n = 10;
        
        f1(1, 2, 1001); // 1 is bound by _1, 2 is bound by _2, 1001 is unused
                        // makes a call to f(2, 42, 1, n, 7) // Note: 最后1个参数 已被绑定为 7
                    
    (1)
        class A
        {
        public:
            void f(int x, int y);
            // ...
        };
        void test()
        {
            A a;
            
            std::function<void(int, int)> f = 
                std::bind(&A::f, &a,
                           std::placeholders::_1, std::placeholders::_2 );
                           
            f(1, 2);
        }
        
    (3)
        void output(int x) { cout << x << " "; }
        
        void call_when_even(int x, const std::function< void(int) >& f)
        { 
            if( ! (x&1) ) // x % 2 
                f(x);
        }
            
        void test()
        {
            auto f = std::bind(output, std::placeholders::_1); // output 第1实参 未绑定, 传入 std::placeholders::_1 指定的位置 
            
            call_when_even(1, f); // 输出 1
        }
        
        void f2(int x, int y) { cout << x << " " << y << " "; }
        void test2()
        {
            std:bind(f2, 1, 2)();
            std:bind(f2, 2, std::placeholders::_1)(1);
                                                |\  | 第1实参 1 -> 传入 std::placeholders::_1 
                                                |_ _|
            
            std:bind(f2, std::placeholders::_2, std::placeholders::_1)(1,  2);
        }                                   |\                              | 第 2 实参 2 -> 传入 std::placeholders::_2
                                            |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|   

1.3 lambda 表达式

源于 FP

1   完整 lambda 

    [captureList] (paraList) -> returnType { funcBody; }    
        
        -> returnType 可选  
    
        auto f = [](int x) -> int { return x + 1; };
        
    (1) 无参 时, (paraList) 可省         
        [] { return 1; };
        
    (2) 通常, 编译器 可据 return 语句 自动推导出 returnType => -> returnType 可省
    
        auto f = [](int x) { return x + 1; };
        
        例外: 初始化列表 {} -> 必须 explicit 指定 returnType
        
            auto f2 = []() { return {1, 2}; }; // => 编译 error 
            
            auto f2 = []() -> std::vector<int> { return {1, 2}; };
            
    (3) 捕获列表
    
        控制 lambda 能访问的 external variable, 及 如何访问 它们
        
        ——————————————————————————————————————————————————————————————————————————————
        某变量 前 用 &   => 引用捕获 [=, &foo]
            else        => 值捕获  [bar]
        ——————————————————————————————————————————————————————————————————————————————
        &               引用捕获    所有 (other) 外部 scope 变量  
        =               值捕获     所有 ...
        ——————————————————————————————————————————————————————————————————————————————
        [this]          捕获 current Class 中 this 指针,
                            使 lambda 拥有与 current Class 成员函数 同样的 访问权限
                        Note: 若已使用 &/=, 默认 添加 this 
        ——————————————————————————————————————————————————————————————————————————————
        
            class A
            {
            public:
                int d;

                void f(int x, int y)
                {
                    auto f = [=, &y] { return d + x + y; }; // 已使用 &/=, 默认 添加 this, d <=> this->d
                }
            };

2   lambda 与 仿函数 

    lambda 称为 `闭包` 类型: 匿名 类类型 
        
        可视为 仿函数: 带 operator() 的 类
        
    [captureList] (paraList) -> returnType { funcBody; }    
    
    ————————————————————————————————————————————————
    lambda              仿函数
    ————————————————————————————————————————————————
    captureList         ctor 的 参数 + 传递方式(值/引用)
                        memData + 类型(值/引用)
    
    paraList            operator()(paraList) 的 参数列表
    
    returnType          operator() 的 returnType
    
    funcBody            operator() 的 funcBody
    ————————————————————————————————————————————————
    
    => lambda 可用 std::function 和 std::bind 存储/操作 
    
    std::function<int(int)> f1 = [](int x) { return x; };
    
    std::function<int(void)> f2 = std::bind( [](int x) { return x; }, 1 );
                        |                           |                 |
                        |_ _ _ _ _ _ _ _ _ _ _ _ _ _| _ _ _ _ _ _ _ _ |
                          
        lambda 的 参数 x 被绑定为 1 => bind 所得的 仿函数 无参
                          
3   3 点注意 
    
    (1) `延迟调用` lambda 时, 对 捕获的 外部变量 的 访问
        
        [1] 按 捕获时的值 访问  <- 值捕获
        
        [2] 即时访问            <- 引用捕获
    
    (2) lambda 中, `修改` 捕获的外部变量 (的 copy)
    
        默认 + 值捕获 -> 无法修改
        
            原因: lambda 的 operator() 是 const => 无法修改 memData(由 值捕获的外部变量 值传递)

        [1] 修改 外部变量 的 copy(lambda 的 memData)
                
            值捕获 + mutable
                        |
                取消 operator() 的 const
                
                Note: mutable 时, lambda 无参 也要写明 参数列表
            
        [2] 修改 外部变量
            
            引用捕获 
    
    (3) lambda 能否 直接 `转换为 函数指针` 
        
        无 捕获变量 -> 能 
        else        -> 不能
        
        using Func = int (*)(int);
        Func f = [](int x) { return x; };
        std::cout << f(1) << std::endl;


    例 
    
    #include <iostream>
    void test1()
    {
        int x = 0;
        auto f = [=] { return x; };
        x += 1;
        std::cout << f() << std::endl; // 0
    }

    void test2()
    {
        // (2) `修改` 捕获的外部变量 的 copy
        int x = 0;
        auto f = [=]() mutable { return ++x; };

        // 1 0
        std::cout << f() << " ";
        std::cout << x << std::endl;
    }

    void test3()
    {
        // (3) 修改 外部变量
        int x = 0;
        auto f = [&] { return ++x; };

        // 1 1
        // Note, std::cout 依赖于编译器实现, vs 下可能从右到左算, gcc 下 可能从左到右算 
        // => 依赖与顺序的计算 不要放同一条 std::cout 语句
        std::cout << f() << " ";
        std::cout << x << std::endl;
    }

    int main()
    {
        test3();
    }
    
4   lambda 2大 优点
    
    (1) 声明式编程 => code 简洁
    
        就地定义 闭包, 不需要定义 仿函数 对象, 大大简化了 标准库算法的调用
        
        int evenCount = 0;
        for_each(v.begin(), v.end(), 
            [&evenCount](int value) 
            {
                if( !(value & 1) )
                    ++evenCount;
            }
        );
            |\
            |
            |
        class CountEven
        {
        private:
            int& count; //Note: Ctor 参数 与 memData 都是 引用
        public:
            CountEven(int& count_): count(count_) { }
            
            void operator() (int value)
            {
                if( !(value & 1) )
                    ++count;
            }
        };
        
        int evenCount = 0;
        for_each(v.begin(), v.end(), CountEven(evenCount) );

    (2) 就地封装短小的 `闭包`, 灵活性更好 

        1) 比 std::bind 更灵活 
        
            int count = count_if(v.begin(), v.end(), 
                                 [](int x){ return x > 5 && x < 10; } ); 
                                 
            int count = count_if(v.begin(), v.end(), 
                                 [](int x){ return x > 10; } ); 
                |\
                |
                |
            using std::placeholders::_1;
            
            auto f = std::bind(std::logical_and<bool>(), 
                                std::bind(std::greater<int>(), _1, 5 ),
                                std::bind(std::less_equal<int>(), _1, 10 )
                              );
            
            int count = count_if(v.begin(), v.end(), f);
            
        2) 与 std:function 效果一样, 还更简洁 
            
            通常, 可用 lambda 代替 std::function 
            
            不能完全替代 std:function
                老库, 如 boost 库 不支持 lambda        

1.4 tuple 元组: 编译时 + 可变模板实参

可容纳 任意类型、任意数量 的 元素

1.5 模板改进

1   右尖括号 

2   模板别名: using 而不能是 typedef
 
    using FuncType = void (*)(int);
    
3   函数模板 的 默认模板参数 
    
    函数模板 实参推断 生效时, 默认模板参数 被 忽略 
        
        template<typename T = int> // error in C++03, OK in C++11
        void func(T t = 0);
        
        func(1.0); // T -> double 

1.6 列表初始化 {}

1   统一 初始化 

2   防止窄化转换 

3   同类型 任意长度的初始化列表 
        
        只保存 初始化列表 中 元素的引用

1.7 范围 for 循环


统一 容器 和 数组 的 遍历方法

1   ——————————————————————————————————————————————————————————
    值传递  : <=> auto + 元素 copy  
    
        for(auto x: v) 
                
            auto 自动推断出的 是 `序列` 的 元素类型/value_type, 而不是 迭代器 
    ——————————————————————————————————————————————————————————
    引用传递: <=> auto + 迭代器 版本 
    
        for(auto& x: v) 
        <=> for(auto iter = v.begin(); iter != v.end(); ++iter)
        
        for(const auto& x: v)
    ——————————————————————————————————————————————————————————
    
    std::vector<int> arr;
    for(char elem: arr); // int 隐式转换为 char 
    
2   Note
    
    范围 for 循环
    
    [1] 冒号后 expr 只执行 1 次
    
    [2] 迭代时 修改 容器, 可能使 迭代器失效 
    
    [3] 通常 循环开始前 确定好迭代范围

相关文章

网友评论

      本文标题:chapter1 让程序 更简洁、更现代: auto & dec

      本文链接:https://www.haomeiwen.com/subject/cxodmrtx.html