美文网首页
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)

    constexpr 优化方式 动态规划 可以做到O(N)的算法,如果想做到O(1),那么可以把计算消耗挪到编译期间...

  • C++17新特性

    程序喵之前已经介绍过C++11的新特性和C++14的新特性,链接如下:xxx,今天向亲爱的读者们介绍下C++17的...

  • 记一次macOS Mojave升级GCC

    目录 前言 安装GCC 最后 前言 最近迷上了泛型编程, 看到了C++11, 14, 17的很多酷炫新特性. 之前...

  • C++11/14/17新特性

    C++11/14/17常用特性 关键字 auto 让编译器根据上下文情况,确定auto变量的真正类型,可以作为函数...

  • C++14新特性的所有知识点全在这儿啦!

    前面程序喵介绍过C++11的新特性,在这里(),这篇文章介绍下C++14的新特性。 函数返回值类型推导 C++14...

  • c++11/14/17新特性(1)

    2.1auto关键字 auto expr; 当expr包含cv描述符的时候,比如const int a = 1;a...

  • c++11 新特性

    c++11的新特性 1.1 lambda表达式 lambda表达式讲解 例子1: 例子2: C++11中的Lamb...

  • 阿里巴巴面试题基础篇 C++11

    ● 请问C++11有哪些新特性? 参考回答: C++11 最常用的新特性如下: auto关键字:编译器可以根据初始...

  • 开篇第一章--开发环境

    1. Boost简介 Boost 是一款C++准标准库,其好多特性都被C++11/14/17标准所引用。Boost...

  • C++11/14新特性

    1.nullptr nullptr 出现的目的是为了替代 NULL,传统 C++ 会把 NULL、0 视为同一种东...

网友评论

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

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