C++进阶学习(三)constexpr关键字、值类别与decltype关键字、lambda表达式
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
五、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<int, num> arr1(); /*error*/
constexpr auto n2 = 4;
constexpr array<int, n2> 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<decltype(lambda)>(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 = []<typename T>(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<any> v = {ptr1, ptr2};
any_cast<decltype(ptr1)>(v[0])(1);
any_cast<decltype(ptr2)>(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的