美文网首页
c++11/14/17新特性(2)

c++11/14/17新特性(2)

作者: Teech | 来源:发表于2021-10-28 00:41 被阅读0次

    constexpr

    //递归版本
    int fib(int n){
        if (n<=2) return n;
        return fib(n-1) + fib(n-2)
    }
    
    

    优化方式 动态规划 可以做到O(N)的算法,如果想做到O(1),那么可以把计算消耗挪到编译期间,通过模板也可以实现

    template<int n>
    struct FIB{
        enum {
            result = FIB<n-1>::result + FIB<n-2>::result,
        };
    };
    template<>
    struct FIB<1>{
        enum {
            result = 1,
        };
    };
    template<>
    struct FIB<2>{
        enum {
            result = 2,
        };
    };
    

    在来个编译期间的例子

    template<int x,int y>
    struct IF{
        enum {
            result = 0,
        };
    };
    
    template<int x>
    struct IF<x,x>{
        enum {
            result = 1,
        };
    };
    

    可以发现

    1. 有些计算可以挪到编译期间计算
    2. 所有计算都可以编译期间计算吗?(确实,因为模板是图灵完备的)
      • 所有的基本的表达式都可以用模板一一对应
      • 值,函数,控制语句也可以
      • etc。。

    c++11中还有关键字constexpr

    template<int x,int y>
    struct FUN{
        enum {
            result = x+y,
        };
    };
    等价于 同样编译期计算
    constexpr int FUN(int x,int y){
        return x + y;
    }
    

    定义变量

    struct SomeVal{
        enum {
            value = 1,
        };
    };
    
    constexpr int value = 1;
    

    分支语句

    if constexpr(x == y){
    }
    else{}
    

    用constexpr来实现fib数列

    constexpr int fib(int n){
        return n<=2?n:fib(n-1)+fib(n-2);
    }
    

    constexpr类似语法糖一样的东西,可以简化写法

    模板类型推倒也有一定的局限性,模板参数必须要能在编译期间计算出来的,返回类型只能是普通数字类型

    template<int x,int y>
    struct ADD{
        enum {
            result = x + y,
        };
    };
    //这个如果传递进来是编译可以推倒出来的,那么走模板这种
    //否则退化成普通c函数,运行时开销
    constexpr add(int x,int y){
        return x+y;
    }
    int x=1,y=2;
    cout<<ADD<x,y>::result; //编译错误
    
    add(x,y); //运行时计算
    add(1,2); //编译期计算
    

    constexpr 修饰的函数

    • 返回类型必须字面值LiteralType
    • 参数必须字面值

    LiteralType

    比如 12,'c'

    • cv void
    • 基本数字类型
    • 引用类型
    • Literaltype的数组

    针对class类型

    • 有Trivial的析构函数 =default
    • 至少有1个constexpr 构造函数,不是拷贝或移动构造
    • 所有的非静态成员和基类必须都是非volatile的literalType
    class LiteralType : BaseLiteralType{
        constexpr LiteralType{...} //不是拷贝或者移动构造
        LiteralType1 mem1;
        LiteralType2 mem2;
    }
    

    在编译期间如果能出触发constexpr LiteralType的版本,那么就会走到编译期间的版本

    struct conststr {
        const char* p;  //满足条件1
        std::size_t sz; //满足条件1
        template<std::size_t N>
        constexpr conststr(const char(&a)[N]) : p(a), sz(N - 1) {} //3
        //以上三条满足就是个LiteralType
        constexpr char operator[](std::size_t n) const  {
            return n < sz ? p[n] : throw std::out_of_range("");
        }
        constexpr std::size_t size() const { return sz; } 
    }
    
    conststr cstr{"hello"}; // a literalType 编译期构造对象
    char a[] = "hello";
    conststr cstr{a};  // not literalType
    
    constexpr std::size_t countlower(conststr s) {
        std::size_t c = 0;
        for (int i = 0; i < s.size(); i++) { 
            if (s[i] >= 'a' && s[i] <= 'z') {
                    c++;         
                }           
            }     
        return c; 
    }
    
    conststr s{"hello"};
    countlower(s); //编译期计算
    
    char cs[] = "hello";
    conststr ns{cs};
    countlower(ns); //运行时计算
    

    怎么确定constexpr 定义的函数是在运行期计算还是编译期计算呢

    template <int x>
    struct EmptyCls {}
    //查看这个有没有编译出错
    EmptyCls<func_to_check(5)>();
    

    if constexpr

    template<class T>
    std::string str(T t){
        if(std::is_same_v<T,std::string>)
            return t;
        else
            return std::to_string(t);
    }
    
    str("11"s); //编译出错 
    //编译的时候 需要if 和 else分支同时编译 std::to_string 参数不能传递string就会编译不过
    

    所以需要修改上述代码为

    template<class T>
    std::string str(T t){
        if constexpr (std::is_same_v<T,std::string>)
            return t;
        else
            return std::to_string(t);
    }
    //如果传递可以string进来 那么就不会编译else分支
    
    str(100);
    //相反如果传递int进来等价
    template<class T>
    std::string str(T t){
        return std::to_string(t);
    }
    
    

    lambda表达式

    • 常用例子
    bool cmp(float a,float b){
        return a < b;
    }
    float arr[5] = {1.0,-1.0,2.0,-2.0,0};
    
    //版本1
    std::sort(arr,arr + sizeof(arr) / sizeof(decltype(arr[0])), &cmp);
    
    struct cmpclass {
        bool operator()(float a, float b){
            return a < b;
        }
    };
    //版本2
    std::sort(arr,arr + sizeof(arr) / sizeof(decltype(arr[0])), cmpclass());
    
    //版本3
    std::sort(arr,arr + sizeof(arr) / sizeof(decltype(arr[0])), [](float a,float b){return a < b;});
    
    

    其他常用

    std::vector<int> num{3,2,1,5};
    std::for_each(num.begin(),num.end(),[](int &n){n++;});
    std::find_if(num.begin(),num.end(),[](int i){return i%2 == 0;});
    
    

    lambda 定义

    • campture list specifier {body}
    • specifier 修饰符 mutable const etc.
    • params h和普通函数一致,如果没有的话 可以忽略[]{body}
    lamda参数类型可以为auto,而普通函数不行
    auto l= [](auto i){return i;};
    
    • return type
    auto l= [](int i){return i;};
    auto l2 = [](int i) -> int{return i;};
    不写返回值类型用auto来推倒
    也可以这么写
    auto l2 = [](int i) -> auto{return i;};
    
    • campture list
      类似lua的upvalue,c++需要显示的捕获
      lua中捕获进来后写时复制,python27的lambda捕获,显示写了就是函数参数,否则引用捕获,但是不能lambda body中赋值
      [&] 表示通过引用来捕获
      [=] 表示通过复制来捕获
      [=,&x,y=r+1,this,*this]
    int x = 1;
    auto lam1 = [&]{x++;};//ok x = 2
    auto lam2 = [=]{x++;};//error 复制捕获(只读) 改变个只读变量
    //想改的话 通过mutable,由于是复制捕获,改完并不会影响外部x的值
    auto lam3 = [=] mutable {x++;};//ok 外部依然是1
    
    int x = 1,y=1;
    auto l1 = [x,&y] {y = x + 1;}; //x复制捕获,y引用捕获 外部y = 2
    auto l2 = [x=x+1,&y] {y = x + 1;}; //
    auto l3 = [obj = std::move(obj)] { //... }
    

    this捕获

    • [this] 引用捕获 [*this] 复制捕获
    • [] 默认引用捕获
    struct S {
        void f(int i);
        int m_i;
    }
    
    //引用捕获
    void S::f(int i){
        [this](){m_i + 1;}();
    }
    
    //复制捕获
    void S::f(int i){
        //调用复制构造函数了 一个新的对象生成了
        [*this](){m_i + 1;}();
    }
    
    • 捕获作用域,只能捕获本作用域变量
    int x = 1;
    int main(){
        auto lam = []{x++;}
        lam(); 
        //x本作用域捕获不到  这里不是捕获而是引用到 global x
        //非局部变量的时候,可以直接访问的,没有发生捕获的动作,捕获作用在局部变量
    }
    

    lambda本质

    [x,&y](int i) mutable->int{
        y = x + i + 1;
        return y
    }
    //等价于编译器生成这样的闭包
    class Closure {
        mutable int x;
        int & y;
        Closure(int x,int &y){//...}
        int operator()(int i) const {
            y = x + 1;
            return y;
        }
    }
    //捕获对应着,捕获可以等价于传入Closure(int x,int &y)的参数构造
    
    • std::function
    auto f1 = [](int i,int j) {return i+j;};
    auto f2 = [](int i,int j) {return i+j;};
    auto f3 = [](int i,int j) {return i-j;};
    //这3个不同的closure type
    std::vector<??> qu;
    qu.push_back(f1);
    qu.push_back(f2);
    qu.push_back(f3);
    

    std::function 是个function warpper,下面可以存储进std::function

    • function
    • lambda
    • functor
    • 可以callable对象
    //存储function
    void print_num(int x){}
    std::function<void(int)> f_display = print_num; 
    f_display(-1);
    
    //存储lambda
    std::function<void()> f_display_l = []() { print_num(100); };
    f_display_l();
    
    //存储functor
    struct cmpclass {
        bool operator()(float a, float b){
            return a < b;
        }
    };
    
    std::function<boo(float,float)> f_cmp_functor = cmpclass();
    f_cmp_functor(1,2)
    
    auto f1 = [](int i,int j) {return i+j;};
    auto f2 = [](int i,int j) {return i+j;};
    auto f3 = [](int i,int j) {return i-j;};
    //这3个不同的closure type
    std::vector<std::function<int(int,int)>> qu;
    qu.push_back(f1);
    qu.push_back(f2);
    qu.push_back(f3);
    

    std::function实现

    template<typename RetType>
    struct Function{
    };
    template<typename RetType,typename ArgType>
    struct Function<RetType(ArgType)>{
        struct CallableBase{
            virtual RetType operator()(ArgType arg) = 0;
            virtual ~CallableBase(){}
        };
        //适配各种callable obj
        template<typename T>
        struct CallableDerive: public CallableBase {
            T callable;
            CallableDerive(T c):callable(c){}
            RetType operator()(ArgType arg) override {
                return callable(arg);
            }
        };
        CallableBase* base = nullptr;
        template<typename T>
        Function(T callable):base(new CallableDerive<T>(callable)){}
        ~Function(){delete base;}
        RetType operator()(ArgType arg){
            return (*base)(arg);
        }
    };
    int main(){
        //标准库
        std::function<int(int)> l = [](int a) {return a+1;};
        std::vector<decltype(l)> que;
        que.push_back(l);
    
        //自定义的Function
        auto l1 = [](int a) {return a;};
        Function<int(int)> ml = l1;
        std::vector<decltype(ml)> que2;
        que2.push_back(ml);
    }
    

    结构化绑定

    //例子1

    //传统写法
    std::set<std::string> set;
    std::pari<decltype(set),bool> res = set.insert("hello");
    if(res.second){}
    
    //结构化绑定
    auto [iter,isSucc] = set.insert("hello");
    if(isSucc){}
    

    例子2

    struct Point {
        int x;
        int y;
    }
    Point p{1,2};
    //传统写法
    auto dis = sqrt(p.x*p.x + p.y*p.y);
    p.x = p.x + 1
    p.y = p.y + 1
    
    //
    auto& [x,y] = p;
    auto dis = sqrt(x*x + y*y);
    x = x + 1
    y = y + 1
    

    cv-auto (& or &&)[id1,id2,id3] = expr
    cv-auto (& or &&)[id1,id2,id3]{expr}
    cv-auto (& or &&)[id1,id2,id3](expr)
    expr可以是个array 或者非union class 类型

    • cv-auto (& or &&)[id1,id2,id3] = expr
      • auto e = expr //copy
      • auto&e = expr //lvalue ref
      • auto&&e = expr //rvalue ref
        绑定到id1,id2,id3 到e的成员中去 绑定到e 而不是expr上去
    auto [x,y] = p;
    等价于
    auto e = p;
    int &x = e.x;
    int &y = e.x;
    
    auto& [x,y] = p;
    等价于
    auto& e = p;
    int &x = e.x;
    int &y = e.x;
    
    
    struct Point {
        int x;
        int y;
        Point(const Point&) = delete;
    }
    auto [x,y] = p; //error
    

    绑定到数组

    int a[2] = {1,2};
    auto [x,y] = a;
    

    range base for

    for (auto itr = smap.begin();iter != smap.end();iter++){}
    
    //c++11
    for (const auto& item : smap){
        //item.first,item.second
    }
    //结构化绑定
    for(const auto&[first,second] :smap){
        
    }
    

    推倒展开,本质是语法的封装

    for(range_declaration : range_expression){
        loop_statement
    }
    auto&& __range = range_expression
    auto __begin = begin_expr
    auto __end = end_expr
    for(;__begin != __end;++__begin){
        range_declaration = *__begin
        loop_statement
    }
    

    枚举类型

    unscoped enum

    enum Color {red,green,blue}
    //可以外部访问 不够安全 没有经过Color
    Color r = red;
    switch(r){
        case red://
        case green:
    }
    //甚至赋值给一个int
    int a = red;
    
    enum Color {red,green,blue}
    enum OColor {red,green,blue}
    //如果两个enum相同的名字,都出现在外部 就会冲突无法编译通过
    
    struct X {
       enum Color {red,green,blue}; 
    }
    //就算定义在class中,还是会绕过Color的命名空间
    int a = X::red
    

    综上 所以unscoped enum的问题

    1. 名字冲突问题
    2. 会隐试转换

    scoped enum

    类型安全的枚举类型

    enum class Color {red,blue,green}
    Color r = Color::red;
    switch(r){
        case Color::red://
        case Color::green:
    }
    
    int n = Color::red;//error 不会隐式转换
    int n = static_cast<int>(Color::red); //ok
    

    if初始化语句

    //old style
    std::set<std::string> set;
    auto [iter,succ] = set.insert("hello");
    if(succ){
    }
    
    //c++ 17
    if (auto [iter,succ] = set.insert("hello");succ){
    }
    
    if(init-statement;condition){
    }
    else{
    }
    等价与
    {
        init-statement; 
        if(init-statement;condition){
        }
        else{
        }
    }
    
    好处在于ifelse这个block后 就会析构掉变量
    RAAI
    if(auto lock = get_lock();lock){
    }
    //if 结束后就立马释放
    

    新的数据类型

        std::nullptr_t c;
        long long a;
        char16_t b;
        char32_t d;
        auto a = 0b010101;
        //自定义字面常量格式15_km
    

    老版中NULL和0 无法区分
    问题1

    void f(std::string *){}
    void f(int){}
    f(NULL);//编译不过
    

    问题2

    template<class T>
    T clone(const T& t){
        return t;
    }
    void g(int*){}
    g(NULL); //ok
    g(0);    //ok
    //经过转发后 失去NULL特性
    g(clone(NULL)); //error
    

    所以要引入null_ptr 类型为 std::nullptr_t,可以转换成任意类型指针

    std::string* a = nullptr;
    int* a = nullptr;
    

    还可以这么重载

    void g(int*){}
    void g(double*){}
    void g(std::nullptr_t){}
    

    模板转发也不会丢失类型信息

    自定义字面常量

    有定义函数operator "" _km(long double);
    当使用33_km
    就有_km(33) 这样的函数被调用

    比如要实现1.0_km + 24.0_m = 1024_m

    long double operator "" _km(long double v){
        return 1000*v;
    }
    
    long double operator "" _m(long double v){
        return v;
    }
    auto a = 1.0_km + 24.0_km;
    

    相关文章

      网友评论

          本文标题:c++11/14/17新特性(2)

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