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

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;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • 这篇文章以《C++ Primer》(第五版)为基础,结合自己的理解,将C++11的新特性加以总结、概括,以加深印象...
    toMyLord阅读 817评论 2 6
  • 空指针 nullptr 空指针的字面值常量,它的类型是std::nullptr_t(定义位于cstddef) 自动...
    龙遁流阅读 477评论 1 2
  • 李亮审稿人:徐方友,邱帅 说起C++语言,它现在被公认为是在各种编程语言中最难学的语言之一,它的语法知识点之广泛,...
    senju阅读 14,484评论 0 1
  • 0、C++编译环境版本判断 1. C++0x 2. C++11 3. C++14 4. C++17 5. 使用示例...
    xiongzenghui阅读 10,987评论 2 2
  • 本文按照 cppreference[https://en.cppreference.com/w/] 列出的特性列表...
    401阅读 20,734评论 2 18