目录

一、结构化绑定

1.绑定到数组

2.绑定到结构体/类

3.与范围for结合遍历关联式容器

二、if和switch中的初始化语句

1.if初始化语句用法

2.switch初始化语句用法

三、if constexpr

1.普通函数中使用

2.模板函数中使用

四、内联变量

五、类模板参数推导

六、编译期Lambda

七、std::optional

八、std::variant 和std::visit

1.使用普通函数对象(仿函数)

2.使用Lambda表示式(C++17支持)

std::decay_t

九、std::string_view


 

一、结构化绑定

允许将一个复合类型(数组、结构体/类、元组等)直接解包绑定到多个变量,极大的简化代码,更加高效。

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;
}
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐