1. 枚举初始化在 C++17 之前,给作用域枚举(强类型枚举)变量赋值有时会很麻烦,甚至不得不写强转。C++17 修复了这个痛点,让枚举的使用变得更像普通的整数变量一样自然。
如果我们定义了一个作用域枚举(C++11),想要给它初始化一个值,必须小心翼翼。假设我们有一个代表颜色的枚举:
123456enum class Color : unsigned int { Red = 1, Green = 2, Blue = 3};
如果我们想在代码中临时定义一个灰度值(比如 0),这样做是不行的:
12// 错误!C++17 之前不允许用整数直接初始化 enum classColor c = 0; // 编译报错!类型不匹配
以前必须显式地告诉编译器:“我知道我在干什么,请把这个整数强转成 Color。”
12// 麻烦的旧做法:必须写 static_castColor c = static_cast<Color>(0); // 即使是0,也得写这么长一串
这种写法非常繁琐,尤其是在处理底层硬件寄存器、网络协议或者位掩码时,枚举可能代 ...
1. C++17 中的聚合类聚合类就是一种可以直接用初始化列表 {} 进行初始化的类(像 struct 或数组一样)。它通常用来表现“数据集合”,没有太复杂的封装逻辑。
在 C++17 之前,一个类要成为聚合类,必须满足以下严苛条件:
没有用户声明的构造函数。
没有私有或保护的非静态数据成员(必须是 public 的)。
没有虚函数。
没有虚基类。
在 C++17 之前,如果想让一个类继承自另一个类,同时又想保持聚合初始化的特性(即直接用 {} 赋值),是做不到的。一旦继承了基类,这个类就不再是聚合类了,必须写构造函数来转发参数给基类。
C++17 放宽了限制,允许聚合类拥有基类(前提是基类本身也是非虚的聚合类)。这意味着我们可以更方便地扩展数据结构。现在的规则是:
只要基类也是非虚的、没有私有/保护成员的,就可以继承它。
派生类依然可以不写构造函数,直接使用 {} 初始化。
初始化顺序: 先初始化基类部分,再初始化派生类成员。
综上所述,在C++17中对聚合类的要求如下:
必须是公有继承(private ...
1. C++17 的主要改进Lambda 表达式是 C++11 引入的一种匿名函数(没有名字的函数)。它可以让我们在需要函数的地方快速定义一个小型函数,特别适合用于算法、回调函数等场景。
C++17 对 Lambda 表达式进行了改进,让我们用起来更方便!
2. constexpr Lambda在 C++17 之前,Lambda 表达式默认是不能在编译期常量表达式(如 constexpr 函数或模板参数)中使用的。虽然编译器经常在内部做一些优化,但标准层面并不允许显式地将 Lambda 用于编译期计算。
C++17 规定,Lambda 表达式默认就是隐式的 constexpr(前提是它的函数体满足 constexpr 的要求)。这意味着我们可以在编译期调用 Lambda,比如在 constexpr 函数里使用 Lambda、把 Lambda 当作模板参数传递。这极大地增强了 C++ 的元编程能力。
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950#in ...
1. 什么是属性在 C++ 中,属性 是一种给编译器下指令的机制。它允许程序员告诉编译器某些额外的信息,比如“不要警告我”、“这段代码可能会失败”或者“把这个函数放到某个特定的段”。
属性的标准写法是用 两个方括号 [[ ... ]] 括起来:
1[[标签]] 代码
在 C++17 之前,大家最常使用的属性是 [[deprecated]],用来标记某个函数或变量已经被弃用,如果别人还在用,编译器就会发出警告。
假设你写了一个旧版本的函数,后来发现更好的实现方法,但为了兼容性又不能立刻删除旧函数。
123456789101112131415161718192021222324#include <iostream>// 告诉编译器:这个函数过时了, 不带文字描述[[deprecated]] void oldFunc() {}// 旧的实现方式(被标记为过时)[[deprecated("请使用新的 FastCalculate() 函数")]]int CalculateOldWay() { return 100;} ...
1. 什么是命名空间在 C++ 中,为了避免变量名、函数名冲突(比如你也写了个 max 函数,库里也有个 max),我们通常使用“命名空间”来把它们圈在不同的地盘里。
传统的“嵌套”写法是这样的:
12345678910111213namespace A { namespace B { namespace C { void func() { // 我是函数 } } }}
调用的代码是这样的:****使用 ::(两个冒号)来连接各个命名空间层级。
1A::B::C::func();
看上面的定义代码,是不是像俄罗斯套娃一样一层套一层?不仅代码显得很长,而且每一层都要写一对花括号 {},缩进层级太多,代码很难看,还浪费屏幕空间。
2. C++17 带来的改变C++17 允许我们用一种更直观、更扁平的方式来写嵌套命名空间。新写法: ...
1. 前言在开始学习 C++17 的新字面量特性之前,我们先要理解什么是字面量。简单来说,字面量就是在代码中直接写出来的值。例如:
123 是整型字面量
3.14 是浮点数字面量
'A' 是字符字面量
"Hello" 是字符串字面量
字面量就像是烹饪中的原材料,程序员可以直接在代码中使用它们,而不需要先定义变量。在 C++17 中,标准委员会对字面量进行了两项非常实用的增强:一是让 UTF-8 字符字面量 的类型变得更规范,二是引入了 十六进制浮点数字面量。
2. UTF-8字面量2.1 语法格式想象一下我们要开发一个国际化的应用:
中国用户想看到中文”你好”
小鬼子想看到鬼子文”こんにちは”
俄罗斯用户想看到俄文”Привет”
还有各种表情符号😀🎉🎈
在计算机里,所有东西最终都是 0 和 1。比如字母 'A',计算机存的是 01000001(十进制的 65)。但是,中文、英文、Emoji 表情这么多,计算机怎么知道哪个 01 代表哪个字呢?这就需要一本字典,我们叫它字符编码。
GBK(本地编码): 以前的老办法。在 ...
1. 折叠表达式折叠表达式是 C++17 引入的新特性,它允许在编译时对参数包中的所有参数应用二元运算符。这使得处理可变参数模板变得更加简洁和直观。
1.1 为什么需要折叠表达式对于初学者来说,可变参数模板本身的语法就比较晦涩,而展开参数包往往需要写大量的递归代码。折叠表达式的出现,就是为了解决这个痛点。折叠通常指的是将一个列表中的元素通过某种操作(比如加法、逻辑与)两两结合,最终得到一个单一结果的过程。例如,有一排数字:1, 2, 3, 4,如果你想要它们的和,会这样计算:
1(((1 + 2) + 3) + 4)
这就是折叠。C++17 允许我们在编译期,对模板参数包进行这种操作。下面是一个典型的 C++ 可变参数模板递归求和 实现。让我详细解释它的工作原理。
123456789101112// 1. 终止递归的基准函数int sum() { return 0;}// 2. 递归展开参数包的函数模板template <typename T, typename... Args>int sum(T first, Args... rest) ...
1. 类型萃取1.1 核心概念类型萃取这是 C++11 引入的特性(并在 C++14/17/20 中得到了极大的扩展),是进行 模板元编程 和编写通用泛型代码的基础工具。它提供了一系列编译时的模板类和模板别名,用于在编译期间检查、修改和操作类型。这些功能主要集中 C++ 标准库的 <type_traits> 头文件中。
在 C++ 中文技术社区和文献中,<type_traits> 通常有以下几种常见的称呼,根据上下文略有不同:
官方翻译:类型萃取,意为从类型中提取信息。
在 C++ 标准库中文版文档以及许多经典书籍(如《C++ Primer》译本)中,通常使用这个词来描述这种在编译期获取类型特性的机制。
编程术语:类型特性
<type_traits> 头文件提供的正是各种“类型”的“特性”(比如是否有 const 修饰符、是否是整数、是否是类等)。
通俗称呼:类型工具 或 类型辅助
在日常开发交流中,程序员很少专门说出一个学术名词,通常会说用一下 type_traits或用类型工具判断一下。
类型萃取允许在编 ...
1. 什么是 constexpr if简单来说,constexpr if 是 C++17 引入的一个编译期判断语句。它的作用是告诉编译器:在编译代码的时候,就根据这个条件决定保留哪一段代码,扔掉哪一段代码。
打个通俗的比方,想象你在打包行李(编译代码):
普通的 if 语句:就像把两把伞都放进了箱子,一把是遮阳伞,一把是雨伞。等到了目的地(程序运行时),看天气决定用哪一把。两把伞都得背着,箱子很重。
constexpr if 语句:就像在打包前看了一下天气预报。如果目的地是大晴天,就只把遮阳伞放进去,雨伞直接扔一边不带了。这样箱子(可执行文件)更轻,而且在目的地绝对不会误拿雨伞。
在 C++17 之前,如果用了模板(泛型),编写函数时往往需要写很多在这个特定模板类型下根本用不到的代码,或者会因为类型不同而导致编译错误。
constexpr if 允许我们将那些对当前类型来说非法的代码直接在编译阶段剔除掉,于是编译器就不会检查那些被剔除的代码的语法正确性。
它的写法和普通 if 几乎一样,只是开头加了一个 constexpr 关键字:
12345678if constexpr (条件 ...
1. 为什么需要内联变量1.1 缘起1.1.1 全局变量的问题在开始讲解内联变量之前,我们先来看一个让很多 C++ 初学者头疼的问题。假设你想在头文件中定义一个常量,你会怎么写?
12// my_constants.hconst int MAX_USERS = 100; // 这样做会有什么问题吗?
如果你在不同的源文件中包含了这个头文件,每个源文件都会得到自己的 MAX_USERS 副本。在 C++ 中,有一个重要的规则叫做 ODR(One Definition Rule,单一定义规则)。简单来说:
每个变量/函数在整个程序中只能有一个定义
可以有多个声明(告诉编译器这个变量/函数存在)
问题来了:如果我们在头文件中定义了一个变量,并且这个头文件被多个源文件包含,那么我们就违反了 ODR!
在 C++17 之前,我们通常这样解决这个问题:
12345// my_constants.hextern const int MAX_USERS; // 只是声明,不是定义// my_constants.cppconst int MAX_USERS = 100; ...









































