C++17常用新特性
std::visit(visitor,var)传入一个访问者visitor,它会根据variant当前存储的实际类型,调用对应的处理函数。绑定到std::pair和std::tuple。只有结构体/类中的共有数据成员才可以被绑定。
目录
一、结构化绑定
允许将一个复合类型(数组、结构体/类、元组等)直接解包绑定到多个变量,极大的简化代码,更加高效。
1.绑定到数组
int main()
{
int arr[3] = { 1,2,3 };
auto& [a, b, c] = arr;
cout << a; //1
return 0;
}
2.绑定到结构体/类
只有结构体/类中的共有数据成员才可以被绑定。
绑定到std::pair和std::tuple
int main()
{
//处理像std::map::insert返回的pair时,结构化绑定很有用
map<int, string> myMap;
const auto& [iter, success] = myMap.insert({ 1,"blue" });
if (success) {
cout << "Insertion successful!" << endl;
}
//绑定到std::tuple
tuple<int, float, string> tup(61, 1.1, "blue");
auto [num, fl, msg] = tup;
cout << num << "," << fl << "," << msg << endl;
return 0;
}
3.与范围for结合遍历关联式容器
//C++17结构化绑定,清晰明了
map<int, string> myMap = { {1,"blue"},{2,"blue"} };
for (const auto& [key, value] : myMap) {
cout << "Key: " << key << ", Value: " << value << endl;
}
二、if和switch中的初始化语句
允许在if/switch的条件判断之前,定义并初始化一个局部变量,该变量的作用域仅限于这个if/switch语句块内。
1.if初始化语句用法
if (init-statement; condition) {
// 如果 condition 为 true,执行这里的代码
} else { // 可选的 else
// 如果 condition 为 false,执行这里的代码
}
vector<int> v = { 1,2,3,4,5,6 };
int target = 3;
if (auto iter = std::find(v.begin(), v.end(), target);iter != v.end()) {
cout << "Found." << *iter << endl;
}
else {
cout << "Not found." << endl;
}
2.switch初始化语句用法
switch (init-statement; expression) {
case value1:
// ...
break;
case value2:
// ...
break;
default:
// ...
}
map<int, string> colers = { {1,"Blue"},{2,"Green"} ,{3,"Write"} };
int colerId = 1;
switch (auto it = colers.find(colerId);it != colers.end() ? it->second[0] : ' ') {
case 'B':
cout << "Found: " << it->second << endl;
break;
case 'G':
cout << "Found: " << it->second << endl;
break;
default:
cout << "Not found." << endl;
}
三、if constexpr
编译期if是一种在编译时进行条件判断的机制,它允许在模板代码中根据常量表达式来选择不同的代码路径,不会编译被剔除的代码路径,从而避免一些语法或类型错误。
如果使用的是普通的if,那么编译器会尝试编译所有分支,可能导致类型不匹配的错误。
1.普通函数中使用
#include <iostream>
void check(int x) {
if constexpr (sizeof(int) == 4) { // 编译期常量
std::cout << "int is 4 bytes on this platform\n";
} else {
std::cout << "int is not 4 bytes\n";
}
}
int main() {
check(10); // 输出:int is 4 bytes on this platform(在大多数平台上)
}
2.模板函数中使用
template <typename T>
void printTypeInfo(const T& value)
{
if constexpr (std::is_integral_v<T>) {
cout << "Integral type: " << value << endl;
}
else if constexpr (std::is_floating_point_v<T>) {
cout << "Floating point type: " << value << endl;
}
else if constexpr (std::is_same_v<T,std::string>) {
cout << "String type: " << value << endl;
}
else {
cout << "Unknow type" << endl;
}
}
编译期常量布尔表达式
C++17标准库<type_traits>中提供的类型特性工具,用于在编译期查询某个类型T的属性。
- std::is_integral_v<T>:判断类型T是不是整型,比如:int、short、long、unsigned、bool等(会在编译期判断类型T是不是整型类型返回true或false)。
- std::is_floating_point_v<T>:判断类型T是不是浮点型,比如:float、double、long double。
- std::is_same_v<T, U>:判断类型T和U是否是完全相同的类型。
四、内联变量
允许在头文件中定义(而不仅仅是声明)一个变量,并且保证这个变量在整个程序中只有一个定义,避免了在多个翻译单元中包含同一个同文件时导致的 "重复定义" 链接错误。
//test.h(C++17之前错误的写法)
const string str = "blue";
- 如果a.cpp和b.cpp都包含了test.h头文件,那么在链接时,str这个全局变量会有两个定义(一个在a.obj,一个在b.obj),导致链器报错。
- 传统的解决方法(C++17之前):使用extern关键字在test.h中声明变量,然后在一个仅且一个.cpp文件中定义这个变量,但这比较麻烦。
使用inline关键字:
//test.h(C++17的正确写法)
inline string str = "blue";
- 在多个源文件中包含test.h时,链接器会知道这些str变量都指向同一个实体,不会发生重复定义的错误。
五、类模板参数推导
不需要制定模板参数,编译器会根据构造函数参数自动推导。简化模板类实例化,特别是vector、map、pair、tuple等类型。
std::pair p(1, 3.14); //推导为 std::pair<int,double>
std::tuple t(1, 3.14, "blue"); //推导为 std::tuple<int,double,const char*>
std::vector v = { 1,2,3 }; //推到为 std::vector<int>
六、编译期Lambda
C++17允许Lambda表达式在constexpr上下文中使用,Lambda表达式本身也可以是constexpr,提升运行时效率。
constexpr int calculator()
{
auto lam = [](int x) {
return x * x;
};
return lam(3);
}
constexpr auto lam = [](int x) {return 2 * x;};
七、std::optional<T>
optional<T>是C++17引入的一个类模板,用于表示一个可能有值,也可能没有值(即”空“)的对象,它的主要目的是用来替代传统通过一些方式,比如:使用nullptr、-1或bool类型的变量,来表示某个值可能有或没有的情况,这种传统的方式是不安全也不太优雅。
C++17中的optional<T>更安全,更现代,不需要依赖特殊值nullptr、-1等避免误用这些特殊值。
struct User
{
string name;
int age;
};
std::optional<User> find_user_by_id(const int& id)
{
if (id == 99) {
return User{ "张三",20 };
}
else {
return nullopt;
}
}
int main()
{
User user{ "张三",20 };
auto o = find_user_by_id(55);
if (o.has_value()) {
cout << "name: " << o->name << " age: " << o->age << endl;
}
else {
cout << "Not found." << endl;
}
return 0;
}
- 判断std::optional<T>类型对象o是否有值:if(o.has_value)或if(o)。
- 访问内部数据:o.value()(无值会抛出std::bad_optional_access异常)或o.value_or(def)(较安全,无值返回设置的默认值def)。
- std::nullopt:表示空值。
- std::reset():清空o中的值。
- std::emplace(args...):重新构造o中的新值。
- *o/o->:访问o中的值。
八、std::variant<T...>和std::visit
C++17中的variant是安全类型的联合体,相较于union具有类型安全、现代、功能丰富的优点。
使用传统的union存储多个数据时,需要我们自己跟踪记录当前存的是哪个类型的数据,防止错误的类型会导致未定义行为,variant配合visit则解决了这个问题。
使用std::variant时需要包含头文件<variant>。
std::variant<int, string> var;
var = string("hhh");
try {
cout << std::get<string>(var) << endl;
}
catch (const bad_variant_access& exception) {
cout << exception.what() << endl;
}
- 使用std::get<T>获取variant对象当前存储的数据时,该数据类型必须和T的类型一致,负责会抛出std::bad_variant_access异常。
std::visit(visitor,var)传入一个访问者visitor,它会根据variant当前存储的实际类型,调用对应的处理函数。
1.使用普通函数对象(仿函数)
using MyVariant = std::variant<int, double, string>;
class MyVisitor
{
public:
void operator()(int i) const
{
cout << "Is int: " << i << endl;
}
void operator()(const string& s) const
{
cout << "Is string" << s << endl;
}
};
int main()
{
MyVariant v1 = 18;
MyVariant v2 = string("blue");
MyVisitor visitor;
std::visit(visitor, v1);
std::visit(visitor, v2);
reuturn 0;
}
2.使用Lambda表示式(C++17支持)
using MyVariant = std::variant<int, double, string>;
int main()
{
MyVariant var = 8888;
std::visit([](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, int>) {
cout << "Is int: " << arg << endl;
}
else if constexpr (std::is_same_v<T, string>) {
cout << "Is string: " << arg << endl;
}
}, var);
return 0;
}
std::decay_t<T>
- std::decay_t<T>是C++11中的类型转换工具,定义在<type_traits>中,他是std::decay<T>::type的简写形式(C++14开始支持_t后缀的快捷方式)。
- std::decay_t<T>主要对类型做了:移除引用、移除const,volatile属性、将数组和函数类型转换为对应的指针类型。
九、std::string_view
string_view是非拥有式字符串视图,提供对字符串数据的只读访问,使用时需包含头文件#include <string_view>
关键点:
- 非拥有式:string_view对象部管理内存,只保存指向字符串的指针和字符串的大小。
- 性能优异:避免了不必要的字符串拷贝和内存分配(底层只是复制指针和大小)。
- 只读属性:不能通过string_view对象修改底层数据。
- 生命周期:必须确保底层数据的生命周期长于string_view对象,防止悬空引用。
string_view几个常见的陷阱:
string_view create_dangerous_view()
{
string str("temporary str");
string_view dangerous_view = str;
return dangerous_view;
//Error! str底层管理的数据空间已销毁
//dangerous_view仍然指向被销毁的无效空间
}
void dangerous_lifetime()
{
string_view dangerous_view;
{
string str = "temporary str";
dangerous_view = str;
cout << "在作用域内:" << dangerous_view << endl;
}
//Error 未定义行为!
cout << "在作用域外:" << dangerous_view << endl;
return;
}更多推荐


所有评论(0)