C++进阶学习(三)constexpr关键字、值类别与decltype关键字、lambda表达式
创始人
2024-05-14 10:47:02
0

五、constexpr说明符

  • constexpr说明符声明该变量或函数在编译期进行求值,从而适用于需要编译器常量表达式的地方

  • 在变量声明constexpr时,对象或非静态成员函数蕴含const,函数或静态成员变量蕴含inline

  • constexpr变量必须立刻被初始化

constexpr int a = 5;
// a = 6; /*error*/

  • 如果一个函数或模板的一个声明具有constexpr,那么该函数或模板的所有声明都必须具有constexpr

  • const承担“只读”的修饰,而constexpr承担“常量”的修饰,并且在编译期就可以求出该变量或函数的值

int num = 5;
// constexpr auto n1 = num; /*error*/
// constexpr array arr1(); /*error*/
constexpr auto n2 = 4;
constexpr array arr2();

在上面的例子中,n1与arr1的定义都需要num的参与,而num在编译期无法获得它的值,故无法正确定义n1和arr1。而n2是constexpr的,故arr2在编译期可以得到n2的值。


六、值类别与decltype

  • 每个表达式都属于三种基本值类别中的一种:纯右值、亡值、左值

  • 我们可以通过decltype判断一个表达式的值类别,若产生T&&,则为亡值;若产生T&,则为左值;若产生T,则为纯右值

  • 除了字符串字面量,其他所有字面量都为纯右值字面量

  • const T&方式的左值引用可以接收右值(纯右值和亡值)

int operator""_i(size_t num)
{return num;
}int num(int n)
{return n;
}void func(const int &num)
{cout << num << endl;
}int main()
{cout << "---begin---" << endl;int val{0};// 以下表达式均能传参成功,即均为右值func(val);func(num(5));func(move(val));func(12_i);cout << "---end---" << endl;return 0;
}
  • 使用decltype时,带有括号的对象通常被认为是左值表达式

int main()
{cout << "---begin---" << endl;int val{0};using T1 = decltype((val));using T2 = decltype(val);T2 b = 5;T1 a = b;cout << &a << endl;cout << &b << endl;cout << "---end---" << endl;return 0;
}

输出结果:

---begin---
0x8f29bff8ac
0x8f29bff8ac
---end---

可以看出,当我们把b赋值给a后,两者的地址相同,易见T1是T2的引用类型。


七、lambda表达式

  • lambda是一个无名非联合非聚合类类型,被称为闭包类型,类的操作同样适用于它(注:下列代码只适用于C++20之后的版本)

auto lambda = [](int n)
{ cout << n << endl; };
// 继承适用
struct Test : decltype(lambda){};int main()
{cout << "---begin---" << endl;Test test;test(1);// 指针适用auto ptr = make_shared(lambda);(*ptr)(2);cout << "---end---" << endl;return 0;
}

输出结果:

---begin---
1
2
---end---

  • lambda对象常用auto进行自动类型推导

  • lambda捕获符只有=和&,其中,=是以复制的形式进行捕获,&是通过引用的形式进行捕获

  • 捕获的组合应保证每个变量的捕获不重复,且名字不能与传入形参相同

int main()
{cout << "---begin---" << endl;int a = 5, b = 5;// 值传递捕获指定对象auto ptr1 = [a]{cout << a << endl;// b++; /*error*/};ptr1();// 引用传递捕获指定对象auto ptr2 = [&a]{cout << ++a << endl;};ptr2();cout << a << endl;// 值传递捕获所有变量auto ptr3 = [=]{cout << a << ' ' << b << endl;// a++, b++; /*error*/};ptr3();// 引用传递捕获所有变量auto ptr4 = [&]{cout << ++a << ' ' << ++b << endl;};ptr4();// 值传递捕获所有变量,引用传递捕获部分变量auto ptr5 = [=, &a]{cout << a << ' ' << b << endl;a++;// b++; /*error*/};ptr5();// auto ptr6 = [&, &a] {}; /*error:对a重复进行引用捕获*/// auto ptr7 = [&a](int a){}; /*error:捕获的变量名与传入形参名称相同*/cout << "---end---" << endl;return 0;
}

输出结果:

---begin---
5
6
6
6 5
7 6
7 6
---end---

  • lambda捕获对象默认为const,若使用mutable进行修饰则可以去除const

int main()
{cout << "---begin---" << endl;// auto p = [num = 0]// {//     num++; /*error*///     cout << num << endl;// };auto p = [num = 0] mutable{num++;cout << num << endl;};p();cout << "---end---" << endl;return 0;
}

  • 对所有局部变量进行捕获时,只会捕获lambda体中被调用的对象

int main()
{cout << "---begin---" << endl;int a = 5, b = 5;auto ptr1 = [=] {};auto ptr2 = [=]{cout << a << endl;};cout << "ptr1 memory:" << sizeof ptr1 << "\nptr2 memory:" << sizeof ptr2 << endl;cout << "---end---" << endl;return 0;
}

在上面的实例中,ptr1虽然为全域捕获,但lambda体中没有使用外部变量,因此实际上为空类,故只有1字节来进行定址;而ptr2中由于只使用了对象a,故内存大小为4。


  • 如果变量是非局部变量,或具有静态或线程局部存储期的时候,或该变量是以常量表达式初始化的引用,lambda表达式在使用该变量前不需要对其进行捕获

int main()
{cout << "---begin---" << endl;static int a = 5;int b = 5;auto ptr = [=](){a++;// b++; /*error*/};ptr();cout << a << endl;cout << "---end---" << endl;return 0;
}

输出结果:

---begin---
6
---end---

  • 如果变量满足下列条件时,lambda在读取它的值前不需要进行捕获:该变量具有const而非volatile的整型或枚举类型并已经用常量表达式初始化,或该变量是constexpr的且没有mutable成员

int main()
{cout << "---begin---" << endl;const int a = 5;auto ptr = []{cout << a << endl;};ptr();cout << "size:" << sizeof ptr << endl;cout << "---end---" << endl;return 0;
}

输出结果:

---begin---
5
size:1
---end---

可以看到,lambda体可以正常读取a的值,但本身仍为一个空类,可见lambda在读取a前并没有捕获a。

但是,我们并不能直接使用该常量:

const int a = 5;auto ptr = []
{// cout << &a << endl; /*error*/
};

  • 若外部对象在typeid中被使用,lambda体会捕获该const对象

int main()
{cout << "---begin---" << endl;const int a = 5;auto ptr = [=](auto n){typeid(a + n);};cout << "size:" << sizeof ptr << endl;cout << "---end---" << endl;return 0;
}

输出结果:

---begin---
size:4
---end---

  • 泛型lambda:在lambda的形参中使用auto传参,都会虚设一个与auto参数顺序相对应的模板形参,效果与显式的模板参数类似

int main()
{cout << "---begin---" << endl;auto p = [](auto a, auto b){return a + b;};cout << p(1, 2) << endl;cout << p(string("1"), string("2")) << endl;auto p2 = [](T a, T b){return a + b;};cout << p2(1, 2) << endl;cout << p2(string("1"), string("2")) << endl;cout << "---end---" << endl;return 0;
}

输出结果:

---begin---
3
12
3
12
---end---

  • 每一个lambda都对应了一个不同的类,如果要保持lambda的泛型性进行存储,可以使用C++17中的any,并通过any_cast重新获取对象

int main()
{cout << "---begin---" << endl;auto ptr1 = [](auto x){ cout << x << endl; };auto ptr2 = [](auto x){ cout << x + 1 << endl; };vector v = {ptr1, ptr2};any_cast(v[0])(1);any_cast(v[1])(2);cout << "---end---" << endl;return 0;
}

输出结果:

---begin---
1
3
---end---

  • lambda可以通过转换函数变为函数指针,而泛型lambda转化为函数指针时将无法保持泛型性,但指定类型后仍可以正常转化

int main()
{cout << "---begin---" << endl;auto ptr1 = [](auto x){ cout << x << endl; };// void (*p0)(auto x) = ptr1; /*error*/void (*p)(int x) = ptr1;(*p)(1);cout << "---end---" << endl;return 0;
}

输出结果:

---begin---
1
---end---

  • 可以显示指定lambda为constexpr,但如果没有指定constexpr,而能满足constexpr的所有要求,那么它也将是constexpr的

相关内容

热门资讯

原生安卓系统怎样升级,从基础到... 你有没有发现,你的安卓手机用久了,有时候就像老牛拉车一样,慢吞吞的?别急,今天就来给你支个招,让你的...
安卓13系统怎么开发,开发者的... 你有没有听说安卓13系统已经发布了?这可是个大新闻呢!作为一个热衷于手机开发的小伙伴,你是不是也跃跃...
安卓q系统镜像下载,轻松升级体... 你有没有听说安卓Q系统已经发布了?这可是安卓家族里的一大亮点呢!今天,我就要来给你详细介绍一下安卓Q...
安卓系统色彩校正软件,打造个性... 你有没有发现,手机屏幕的色彩有时候会让人感觉不太对劲?有时候,画面看起来有点灰蒙蒙的,有时候又太艳丽...
苹果能否下个安卓系统,开启新篇... 你有没有想过,苹果的iOS系统会不会有一天突然宣布,它要拥抱安卓的大家庭呢?想象iPhone和iPa...
树莓派 装 安卓系统,轻松安装... 你有没有想过,用树莓派装上安卓系统,那会是怎样一番景象呢?想象一个迷你电脑,竟然能运行起我们日常使用...
安卓系统怎么打印小票,安卓系统... 你是不是也遇到了这样的烦恼:手机里存了好多重要的小票,但是想打印出来保存或者报销,却发现安卓系统里的...
安卓10安装系统应用,轻松上手... 你有没有发现,你的安卓手机最近是不是有点儿“慢吞吞”的?别急,别急,今天就来给你支个招——升级安卓1...
美国不提安卓系统华为,迈向自主... 华为与美国:一场关于技术、市场与政策的较量在当今这个数字化的世界里,智能手机已经成为我们生活中不可或...
安卓系统怎么打开ppt,选择文... 你有没有遇到过这种情况:手里拿着安卓手机,突然需要打开一个PPT文件,却怎么也找不到方法?别急,今天...
谷歌退回到安卓系统,探索创新未... 你知道吗?最近科技圈可是炸开了锅,谷歌竟然宣布要退回到安卓系统!这可不是一个简单的决定,背后肯定有着...
安卓系统待机耗电多少,深度解析... 你有没有发现,手机电量总是不经用?尤其是安卓系统,有时候明明没怎么用,电量就“嗖”的一下子就下去了。...
小米主题安卓原生系统,安卓原生... 亲爱的手机控们,你是否曾为手机界面单调乏味而烦恼?想要给手机换换“衣服”,让它焕然一新?那就得聊聊小...
voyov1安卓系统,探索创新... 你有没有发现,最近你的手机是不是变得越来越流畅了?没错,我要说的就是那个让手机焕发青春的Vivo V...
电脑刷安卓tv系统,轻松打造智... 你有没有想过,家里的安卓电视突然变得卡顿,反应迟钝,是不是时候给它来个“大保健”了?没错,今天就要来...
安卓系统即将要收费,未来手机应... 你知道吗?最近有个大消息在科技圈里炸开了锅,那就是安卓系统可能要开始收费了!这可不是开玩笑的,这可是...
雷凌车载安卓系统,智能出行新体... 你有没有发现,现在的汽车越来越智能了?这不,我最近就体验了一把雷凌车载安卓系统的魅力。它就像一个聪明...
怎样拍照好看安卓系统,轻松拍出... 拍照好看,安卓系统也能轻松搞定!在这个看脸的时代,拍照已经成为每个人生活中不可或缺的一部分。无论是记...
安卓车机系统音频,安卓车机系统... 你有没有发现,现在越来越多的汽车都开始搭载智能车机系统了?这不,咱们就来聊聊安卓车机系统在音频方面的...
老苹果手机安卓系统,兼容与创新... 你手里那台老苹果手机,是不是已经陪你走过了不少风风雨雨?现在,它竟然还能装上安卓系统?这可不是天方夜...