当前位置: 首页 > news >正文

跨越十年的C++演进:C++14新特性全解析

跨越十年的C++演进系列,分为5篇,本文为第二篇,后续会持续更新C++17、C++20、C++23~

第一篇:C++11新特性:跨越十年的C++演进:C++11新特性全解析

C++14是 C++ 编程语言的又一次重要更新,于 2014 年正式发布。它是对 C++11 的一次小幅但非常实用的增强,延续了现代 C++ 的演进方向,在保持语言一致性的基础上进一步提升了开发效率和代码可读性。

跨越十年的C++演进系列,分为5篇,本文为第二篇,后续会持续更新C++17、C++20、C++23~

第一篇:C++11新特性:

跨越十年的C++演进:C++11新特性全解析

C++14是 C++ 编程语言的又一次重要更新,于 2014 年正式发布。它是对 C++11 的一次小幅但非常实用的增强,延续了现代 C++ 的演进方向,在保持语言一致性的基础上进一步提升了开发效率和代码可读性。

相较于 C++11,C++14 引入了约 20 多个新特性,并修复和完善了大量已有的语言设计问题。这些改进虽然整体上不如 C++11 那样具有革命性,但在实际开发中极大地增强了语言的表达能力和易用性。

C++14 被广泛认为是一个“成熟版”的现代 C++ 标准,它在工业界得到了快速普及,成为许多项目推荐使用的默认标准之一。

话不多说,开始聊聊C++14的新特性~


1、变量模板

变量模板是C++中一种实现泛型编程的机制,它允许定义一个模板化的变量,类似于函数模板可以根据不同的类型生成对应的函数,变量模板也可以根据类型参数生成特定类型的变量实例。

这种机制在编写需要处理多种数据类型的通用代码时非常有用。通过变量模板,可以避免为每种类型重复定义相同的变量,从而提高代码的复用性和可维护性。

示例代码:

#include <iostream>
template<typename T>
constexpr T pi = T(3.1415926535897932385);
int main() {std::cout << "int类型的pi值: " << pi<int> << std::endl;std::cout << "double类型的pi值: " << pi<double> << std::endl;return 0;
}

在上述示例中,定义了一个变量模板 pi,它接受一个类型参数 T。根据传入的不同类型,该模板会生成相应类型的 pi 常量值。

在 main 函数中,分别使用了 pi<int> 和 pi<double> 来实例化整型和双精度浮点型的 pi 值。这种方式能够以统一的形式访问不同类型下的常量值,体现了变量模板在泛型编程中的灵活性与实用性。


2、泛型 Lambda 表达式

C++11 引入了 Lambda 表达式,极大地简化了匿名函数的定义和使用,使开发者能够在代码中就地书写简洁的函数对象。然而,在 C++11 中,Lambda 表达式的参数类型必须在编写时明确指定,这在处理多种类型的数据时显得不够灵活。

为了解决这一限制,C++14 对 Lambda 表达式进行了增强,引入了泛型 Lambda 表达式的支持。通过使用 auto 作为参数类型,编译器可以自动推导传入参数的实际类型,从而使得同一个 Lambda 表达式可以适用于不同的数据类型,具备了类似模板函数的通用性。

这种机制不仅提升了代码的复用率,还增强了程序的灵活性和可读性,尤其适用于需要对不同类型容器执行相同逻辑操作的场景。

示例代码:

#include <iostream>
#include <vector>
#include <algorithm>
int main() {std::vector<int> vec = {1, 3, 2, 4};std::vector<double> dvec = {1.1, 3.3, 2.2, 4.4};// 使用泛型lambda表达式对整数向量进行排序std::sort(vec.begin(), vec.end(), [](auto a, auto b) { return a < b; });// 使用相同的泛型lambda表达式对双精度向量进行排序std::sort(dvec.begin(), dvec.end(), [](auto a, auto b) { return a < b; });// 输出排序结果for (int num : vec) {std::cout << num << " ";}std::cout << std::endl;for (double num : dvec) {std::cout << num << " ";}std::cout << std::endl;return 0;
}

在这个示例中,定义了一个泛型 Lambda 表达式:

[](auto a, auto b) { return a < b; }

这里的 auto 关键字允许编译器根据调用上下文自动推导出参数的实际类型。当该 Lambda 被用于排序 std::vector<int> 时,参数被推导为 int 类型;而用于 std::vector<double> 时,则被推导为 double 类型。

通过这种方式,可以使用同一段 Lambda 代码处理不同类型的输入数据,无需重复编写多个版本,显著提高了代码的通用性和简洁性。


3、返回类型推导的扩展

C++11 已经引入了函数返回类型自动推导的功能,允许使用 auto 关键字作为函数的返回类型,由编译器根据函数内部的返回语句自动推断实际返回类型。然而,在 C++11 中,这种推导机制存在一定的限制:例如,函数体中必须只包含一个 return 语句,不能包含复杂的控制流程。

C++14 对这一特性进行了增强和扩展,放宽了对函数结构的限制。现在,即使函数体内包含多个语句、条件分支或循环等复杂逻辑,只要所有返回路径都能明确推导出相同的类型,就可以使用 auto 来让编译器自动推断函数的返回类型。

这一改进显著提升了编写泛型代码和复杂函数时的灵活性与便捷性,使代码更简洁、易读,也减少了手动指定返回类型的繁琐工作。

示例代码:

#include <iostream>
// 根据条件返回不同值的函数,利用C++14扩展的返回类型推导
auto get_value(bool condition) {if (condition) {return 5;       // 推导为 int 类型} else {return 3.14;    // 推导为 double 类型}
}
int main() {std::cout << "返回int类型: " << get_value(true) << std::endl;std::cout << "返回double类型: " << get_value(false) << std::endl;return 0;
}

在上面的示例中,定义了一个函数 get_value,其返回类型被声明为 auto。该函数根据传入的布尔参数 condition 的值,返回不同的常量值:

  • 当 condition 为 true 时,返回整数 5,此时编译器会将返回类型推导为 int;
  • 当 condition 为 false 时,返回浮点数 3.14,此时返回类型被推导为 double。

尽管函数体中包含了条件分支和多个返回语句,C++14 的返回类型推导机制依然能够正确地识别每条路径上的返回类型,并确保类型一致性(如果返回类型不一致,编译器会报错)。

这使得可以在保证类型安全的前提下,以更自然的方式编写函数逻辑,而不必拘泥于函数结构的限制。


4、二进制字面量(Binary Literals)

在 C++14 之前,若想在代码中直接表示二进制数值,通常需要借助八进制、十六进制等格式,或者通过字符串转换的方式实现,这不仅不够直观,也容易出错,尤其是在进行位操作、底层硬件控制或处理特定二进制标志位时。

为了解决这一问题,C++14 引入了对二进制字面量的支持。可以直接使用前缀 0b 或 0B 来表示一个二进制数,极大地提升了代码的可读性和编写效率。

这种特性特别适用于以下场景:

  • 位掩码(bitmask)操作
  • 硬件寄存器配置
  • 标志位(flag)设置
  • 其他涉及二进制数据处理的任务

示例代码:

#include <iostream>
int main() {int num = 0b1010;  // 使用二进制字面量表示数字,对应十进制的10std::cout << "二进制 1010 对应的十进制数: " << num << std::endl;return 0;
}

使用了 0b1010 表示一个二进制数值。编译器会自动将其转换为对应的十进制整数 10。

这种方式能够以更自然的形式表达二进制数据,无需手动转换进制或依赖其他辅助函数,从而显著提高了代码的清晰度和开发效率。

例如,如果想设置某个寄存器的特定几位,可以这样写:

unsigned char flags = 0b10100000;  // 设置第7位和第5位为1

相比使用十六进制 0xA0,二进制形式更便于理解每个位的具体含义。


5、数字分隔符

在编写代码时,我们经常会遇到非常长的整型或浮点型字面量,例如大额金融数值、科学计算中的高精度数据等。这类数字由于位数较多,在阅读和理解时容易出错,也不利于后期维护。

为了解决这一问题,C++14 引入了数字分隔符功能,允许使用单引号 ' 将数字按逻辑分组,从而显著提升其可读性。这种分隔符仅用于视觉上的辅助,不会对数值本身产生任何影响。

该特性尤其适用于以下场景:

  • 金融领域的大金额表示
  • 科学计算中的高精度常量
  • 内存地址、寄存器值等底层开发任务
  • 任何需要清晰表达多位数字的场合

示例代码:

#include <iostream>
int main() {long long big_num = 123'456'789'012'345;  // 使用分隔符提高可读性double big_float = 1.234'567'89;           // 浮点数同样支持数字分隔符std::cout << "大整数: " << big_num << std::endl;std::cout << "大浮点数: " << big_float << std::endl;return 0;
}

使用了数字分隔符来格式化两个较长的数值:

  • 123'456'789'012'345
  • :将一个超长整数按照千分位的方式分隔,便于快速识别其数量级;
  • 1.234'567'89
  • :将浮点数的小数部分按三位一组分隔,有助于更直观地理解其精度结构。

这些分隔符可以放在数字的任意位置(但不能出现在开头或结尾),并且可以连续使用多个。例如,以下写法都是合法且有效的:

int x = 1'000'000;     // 千分位风格
int y = 0b1010'1100;   // 二进制位分组
int z = 0x1A'FF;        // 十六进制分组

通过这种方式,可以在不影响程序行为的前提下,使数字更具可读性和逻辑性。


6、函数对象的改进:支持 constexpr的函数对象

在 C++14 中,对 constexpr 的支持得到了进一步扩展,不仅允许普通函数被标记为 constexpr,还允许用户自定义的函数对象(即重载了 operator() 的类实例)也能够声明为 constexpr。这意味着这些函数对象可以在编译期执行,并参与常量表达式的计算。

这一改进为开发者提供了更大的灵活性和更强的编译期计算能力,尤其适用于以下场景:

  • 编译期常量计算
  • 模板元编程(TMP)
  • 高性能关键路径中的内联优化
  • 在 constexpr 上下文中使用复杂的逻辑处理

通过将函数对象标记为 constexpr,可以在不牺牲可读性和封装性的前提下,实现高效的编译期求值。

示例代码:

#include <iostream>
class Add {
public:constexpr int operator()(int a, int b) const {return a + b;}
};
int main() {constexpr Add add_obj;                      // 声明一个 constexpr 函数对象constexpr int result = add_obj(3, 5);       // 在编译期完成加法运算std::cout << "编译期计算结果: " << result << std::endl;return 0;
}

在上述示例中,定义了一个名为 Add 的类,并重载了其 operator() 方法,使其成为一个函数对象。该方法被标记为 constexpr,表示它可以作为常量表达式的一部分,在编译阶段就被求值。

在 main 函数中:

  • constexpr Add add_obj;
  •  表示这是一个可在编译期使用的函数对象;
  • constexpr int result = add_obj(3, 5);
  •  则利用该函数对象在编译期完成了 3 + 5 的计算;
  • 最终输出语句会在运行时直接打印出这个已在编译期确定的结果。

这种方式避免了运行时额外的计算开销,提高了程序效率,同时也保证了类型安全和良好的抽象能力。

实际应用价值

这种特性在现代 C++ 编程中具有重要意义,特别是在需要高性能和编译期验证的项目中,例如:

  • 使用 constexpr 容器和算法进行编译期数据结构构建;
  • 构建通用的编译期工具库;
  • 提高模板元编程的可读性与可维护性。

7、聚合初始化的扩展

在 C++ 中,聚合初始化(Aggregate Initialization) 是一种简洁且直观的对象初始化方式,常用于数组、结构体等聚合类型。C++14 对其进行了增强,特别是在处理具有继承关系的聚合类时,放宽了原有的限制,使得开发者可以更自然地使用花括号 {} 初始化包含基类成员的派生类对象。

这一改进简化了代码书写,提高了可读性,并减少了对构造函数的依赖,尤其适用于需要快速初始化多个字段或嵌套结构的场景。

示例代码:

#include <iostream>
struct Base {int base_num;
};
struct Derived : public Base {int derived_num;
};
int main() {// 使用扩展的聚合初始化方式初始化派生类对象Derived d = {1, 2};std::cout << "Base成员值: " << d.base_num<< ", Derived成员值: " << d.derived_num << std::endl;return 0;
}

在上述示例中,Derived类公有继承自 Base类,并新增了一个成员变量 derived_num。通过 C++14 支持的扩展聚合初始化语法:

Derived d = {1, 2};

我们可以直接使用花括号列表初始化该派生类对象:

  • 第一个值 1 被用来初始化基类 Base 的成员 base_num;
  • 第二个值 2 则用于初始化派生类 Derived 自身的成员 derived_num。

这种初始化顺序遵循 C++ 成员变量声明的顺序,包括基类成员先于派生类成员进行初始化。

实际优势

  • 简洁直观
  • :无需显式定义构造函数即可完成多层级对象的初始化;
  • 提升可读性
  • :初始化值与成员变量一一对应,逻辑清晰;
  • 支持嵌套结构
  • :可用于嵌套的聚合结构,如结构体数组、联合体等;
  • 兼容旧有语法
  • :保留了传统聚合初始化的风格,同时增强了功能。

8、放宽的 constexpr 函数限制

C++11 引入了 constexpr 函数机制,允许在编译期执行函数调用,从而生成常量表达式。然而,在 C++11 中,constexpr 函数的实现受到诸多限制,例如函数体中只能包含单一的返回语句、不能使用局部变量、循环、条件分支等复杂逻辑。

C++14 对这一特性进行了显著增强,放宽了对 constexpr 函数的语法限制,使其可以支持:

  • 局部变量定义
  • 多条语句和多个返回语句
  • 简单的控制结构,如 if、for、while 等

这些改进使得开发者能够编写更为复杂的编译期计算逻辑,将更多原本只能在运行时完成的任务提前到编译阶段执行,从而提升程序性能、优化资源使用,并增强代码的通用性和类型安全性。

示例代码:

#include <iostream>
constexpr int factorial(int n) {int result = 1;for (int i = 2; i <= n; ++i) {result *= i;}return result;
}
int main() {constexpr int fact_5 = factorial(5);  // 在编译期计算5的阶乘std::cout << "5的阶乘: " << fact_5 << std::endl;return 0;
}

在这个示例中,定义了一个 constexpr 函数 factorial,用于计算一个整数的阶乘。该函数包含了以下非 C++11 原生支持的结构:

  • 局部变量 result
  • for
  •  循环控制结构
  • 多个语句组成的函数体

在 C++14 及以后的标准中,这些都是合法且可以在编译期求值的。因此,当在 main 函数中使用constexprint fact_5 = factorial(5);时,编译器会在编译阶段就完成 5! 的计算,并将其结果 120 直接嵌入到程序中,避免了运行时开销。

实际应用价值

这种增强的 constexpr 功能具有广泛的实用价值,尤其适用于以下场景:

  • 数学常量和公式计算
  • :如阶乘、斐波那契数列、三角函数表等;
  • 模板元编程替代方案
  • :相比复杂的 TMP 技术,constexpr 提供了更易读、更可维护的编译期计算方式;
  • 静态查找表构建
  • :可在编译期预计算并存储常用数据;
  • 断言与验证
  • :在编译期验证某些逻辑条件,提高代码可靠性。

注意事项

尽管 C++14 放宽了限制,但 constexpr 函数仍需满足一些基本要求:

  • 所有参数和返回类型必须是字面类型(literal type);
  • 函数体中的操作必须能够在编译期确定;
  • 如果函数无法在编译期求值,它仍然可以在运行时正常调用。

9、[[deprecated]]属性标记

C++14 引入了标准属性 [[deprecated]],用于标记那些已废弃但尚未移除的程序元素,例如类、变量、函数、枚举、成员函数等。当开发者在代码中使用了被 [[deprecated]] 修饰的内容时,编译器会在编译阶段发出警告,提示该内容可能在未来版本中被移除,建议避免继续使用。

这一特性有助于:

  • 提高代码维护性:明确标识哪些接口已不推荐使用;
  • 平滑过渡更新:为库作者提供一种优雅地弃用旧接口的方式;
  • 避免潜在错误:提醒开发者尽早替换为新的替代方案。

此外,[[deprecated]] 是 C++ 标准的一部分,相比之前依赖于编译器扩展(如 GCC 的 __attribute__((deprecated)))的做法,它具有更好的可移植性和标准化支持。

示例代码:

#include <iostream>
struct [[deprecated]] A {void foo() {std::cout << "This is an old class." << std::endl;}
};
int main() {A a;  // 使用了被废弃的类,编译时将产生警告a.foo();return 0;
}

使用 g++ 编译时(启用 C++14 标准):

g++ test.cpp -std=c++14 -Wall

输出结果如下:

test.cpp: In function ‘int main()’:
test.cpp:13:7: warning: ‘A’ is deprecated [-Wdeprecated-declarations]A a;^
test.cpp:6:23: note: declared herestruct [[deprecated]] A {

在这个示例中,结构体 A 被标记为 [[deprecated]]。当我们在 main() 函数中创建其对象 a 时,编译器会检测到这一使用行为,并生成相应的警告信息,提示开发者该类已被弃用。

下期预告:

C++17新特性

相较于 C++11,C++14 引入了约 20 多个新特性,并修复和完善了大量已有的语言设计问题。这些改进虽然整体上不如 C++11 那样具有革命性,但在实际开发中极大地增强了语言的表达能力和易用性。

C++14 被广泛认为是一个“成熟版”的现代 C++ 标准,它在工业界得到了快速普及,成为许多项目推荐使用的默认标准之一。

话不多说,开始聊聊C++14的新特性~


1、变量模板

变量模板是C++中一种实现泛型编程的机制,它允许定义一个模板化的变量,类似于函数模板可以根据不同的类型生成对应的函数,变量模板也可以根据类型参数生成特定类型的变量实例。

这种机制在编写需要处理多种数据类型的通用代码时非常有用。通过变量模板,可以避免为每种类型重复定义相同的变量,从而提高代码的复用性和可维护性。

示例代码:

#include <iostream>
template<typename T>
constexpr T pi = T(3.1415926535897932385);
int main() {std::cout << "int类型的pi值: " << pi<int> << std::endl;std::cout << "double类型的pi值: " << pi<double> << std::endl;return 0;
}

在上述示例中,定义了一个变量模板 pi,它接受一个类型参数 T。根据传入的不同类型,该模板会生成相应类型的 pi 常量值。

在 main 函数中,分别使用了 pi<int> 和 pi<double> 来实例化整型和双精度浮点型的 pi 值。这种方式能够以统一的形式访问不同类型下的常量值,体现了变量模板在泛型编程中的灵活性与实用性。


2、泛型 Lambda 表达式

C++11 引入了 Lambda 表达式,极大地简化了匿名函数的定义和使用,使开发者能够在代码中就地书写简洁的函数对象。然而,在 C++11 中,Lambda 表达式的参数类型必须在编写时明确指定,这在处理多种类型的数据时显得不够灵活。

为了解决这一限制,C++14 对 Lambda 表达式进行了增强,引入了泛型 Lambda 表达式的支持。通过使用 auto 作为参数类型,编译器可以自动推导传入参数的实际类型,从而使得同一个 Lambda 表达式可以适用于不同的数据类型,具备了类似模板函数的通用性。

这种机制不仅提升了代码的复用率,还增强了程序的灵活性和可读性,尤其适用于需要对不同类型容器执行相同逻辑操作的场景。

示例代码:

#include <iostream>
#include <vector>
#include <algorithm>
int main() {std::vector<int> vec = {1, 3, 2, 4};std::vector<double> dvec = {1.1, 3.3, 2.2, 4.4};// 使用泛型lambda表达式对整数向量进行排序std::sort(vec.begin(), vec.end(), [](auto a, auto b) { return a < b; });// 使用相同的泛型lambda表达式对双精度向量进行排序std::sort(dvec.begin(), dvec.end(), [](auto a, auto b) { return a < b; });// 输出排序结果for (int num : vec) {std::cout << num << " ";}std::cout << std::endl;for (double num : dvec) {std::cout << num << " ";}std::cout << std::endl;return 0;
}

在这个示例中,定义了一个泛型 Lambda 表达式:

[](auto a, auto b) { return a < b; }

这里的 auto 关键字允许编译器根据调用上下文自动推导出参数的实际类型。当该 Lambda 被用于排序 std::vector<int> 时,参数被推导为 int 类型;而用于 std::vector<double> 时,则被推导为 double 类型。

通过这种方式,可以使用同一段 Lambda 代码处理不同类型的输入数据,无需重复编写多个版本,显著提高了代码的通用性和简洁性。


3、返回类型推导的扩展

C++11 已经引入了函数返回类型自动推导的功能,允许使用 auto 关键字作为函数的返回类型,由编译器根据函数内部的返回语句自动推断实际返回类型。然而,在 C++11 中,这种推导机制存在一定的限制:例如,函数体中必须只包含一个 return 语句,不能包含复杂的控制流程。

C++14 对这一特性进行了增强和扩展,放宽了对函数结构的限制。现在,即使函数体内包含多个语句、条件分支或循环等复杂逻辑,只要所有返回路径都能明确推导出相同的类型,就可以使用 auto 来让编译器自动推断函数的返回类型。

这一改进显著提升了编写泛型代码和复杂函数时的灵活性与便捷性,使代码更简洁、易读,也减少了手动指定返回类型的繁琐工作。

示例代码:

#include <iostream>
// 根据条件返回不同值的函数,利用C++14扩展的返回类型推导
auto get_value(bool condition) {if (condition) {return 5;       // 推导为 int 类型} else {return 3.14;    // 推导为 double 类型}
}
int main() {std::cout << "返回int类型: " << get_value(true) << std::endl;std::cout << "返回double类型: " << get_value(false) << std::endl;return 0;
}

在上面的示例中,定义了一个函数 get_value,其返回类型被声明为 auto。该函数根据传入的布尔参数 condition 的值,返回不同的常量值:

  • 当 condition 为 true 时,返回整数 5,此时编译器会将返回类型推导为 int;
  • 当 condition 为 false 时,返回浮点数 3.14,此时返回类型被推导为 double。

尽管函数体中包含了条件分支和多个返回语句,C++14 的返回类型推导机制依然能够正确地识别每条路径上的返回类型,并确保类型一致性(如果返回类型不一致,编译器会报错)。

这使得可以在保证类型安全的前提下,以更自然的方式编写函数逻辑,而不必拘泥于函数结构的限制。


4、二进制字面量(Binary Literals)

在 C++14 之前,若想在代码中直接表示二进制数值,通常需要借助八进制、十六进制等格式,或者通过字符串转换的方式实现,这不仅不够直观,也容易出错,尤其是在进行位操作、底层硬件控制或处理特定二进制标志位时。

为了解决这一问题,C++14 引入了对二进制字面量的支持。可以直接使用前缀 0b 或 0B 来表示一个二进制数,极大地提升了代码的可读性和编写效率。

这种特性特别适用于以下场景:

  • 位掩码(bitmask)操作
  • 硬件寄存器配置
  • 标志位(flag)设置
  • 其他涉及二进制数据处理的任务

示例代码:

#include <iostream>
int main() {int num = 0b1010;  // 使用二进制字面量表示数字,对应十进制的10std::cout << "二进制 1010 对应的十进制数: " << num << std::endl;return 0;
}

使用了 0b1010 表示一个二进制数值。编译器会自动将其转换为对应的十进制整数 10。

这种方式能够以更自然的形式表达二进制数据,无需手动转换进制或依赖其他辅助函数,从而显著提高了代码的清晰度和开发效率。

例如,如果想设置某个寄存器的特定几位,可以这样写:

unsigned char flags = 0b10100000;  // 设置第7位和第5位为1

相比使用十六进制 0xA0,二进制形式更便于理解每个位的具体含义。


5、数字分隔符

在编写代码时,我们经常会遇到非常长的整型或浮点型字面量,例如大额金融数值、科学计算中的高精度数据等。这类数字由于位数较多,在阅读和理解时容易出错,也不利于后期维护。

为了解决这一问题,C++14 引入了数字分隔符功能,允许使用单引号 ' 将数字按逻辑分组,从而显著提升其可读性。这种分隔符仅用于视觉上的辅助,不会对数值本身产生任何影响。

该特性尤其适用于以下场景:

  • 金融领域的大金额表示
  • 科学计算中的高精度常量
  • 内存地址、寄存器值等底层开发任务
  • 任何需要清晰表达多位数字的场合

示例代码:

#include <iostream>
int main() {long long big_num = 123'456'789'012'345;  // 使用分隔符提高可读性double big_float = 1.234'567'89;           // 浮点数同样支持数字分隔符std::cout << "大整数: " << big_num << std::endl;std::cout << "大浮点数: " << big_float << std::endl;return 0;
}

使用了数字分隔符来格式化两个较长的数值:

  • 123'456'789'012'345
  • :将一个超长整数按照千分位的方式分隔,便于快速识别其数量级;
  • 1.234'567'89
  • :将浮点数的小数部分按三位一组分隔,有助于更直观地理解其精度结构。

这些分隔符可以放在数字的任意位置(但不能出现在开头或结尾),并且可以连续使用多个。例如,以下写法都是合法且有效的:

int x = 1'000'000;     // 千分位风格
int y = 0b1010'1100;   // 二进制位分组
int z = 0x1A'FF;        // 十六进制分组

通过这种方式,可以在不影响程序行为的前提下,使数字更具可读性和逻辑性。


6、函数对象的改进:支持 constexpr的函数对象

在 C++14 中,对 constexpr 的支持得到了进一步扩展,不仅允许普通函数被标记为 constexpr,还允许用户自定义的函数对象(即重载了 operator() 的类实例)也能够声明为 constexpr。这意味着这些函数对象可以在编译期执行,并参与常量表达式的计算。

这一改进为开发者提供了更大的灵活性和更强的编译期计算能力,尤其适用于以下场景:

  • 编译期常量计算
  • 模板元编程(TMP)
  • 高性能关键路径中的内联优化
  • 在 constexpr 上下文中使用复杂的逻辑处理

通过将函数对象标记为 constexpr,可以在不牺牲可读性和封装性的前提下,实现高效的编译期求值。

示例代码:

#include <iostream>
class Add {
public:constexpr int operator()(int a, int b) const {return a + b;}
};
int main() {constexpr Add add_obj;                      // 声明一个 constexpr 函数对象constexpr int result = add_obj(3, 5);       // 在编译期完成加法运算std::cout << "编译期计算结果: " << result << std::endl;return 0;
}

在上述示例中,定义了一个名为 Add 的类,并重载了其 operator() 方法,使其成为一个函数对象。该方法被标记为 constexpr,表示它可以作为常量表达式的一部分,在编译阶段就被求值。

在 main 函数中:

  • constexpr Add add_obj;
  •  表示这是一个可在编译期使用的函数对象;
  • constexpr int result = add_obj(3, 5);
  •  则利用该函数对象在编译期完成了 3 + 5 的计算;
  • 最终输出语句会在运行时直接打印出这个已在编译期确定的结果。

这种方式避免了运行时额外的计算开销,提高了程序效率,同时也保证了类型安全和良好的抽象能力。

实际应用价值

这种特性在现代 C++ 编程中具有重要意义,特别是在需要高性能和编译期验证的项目中,例如:

  • 使用 constexpr 容器和算法进行编译期数据结构构建;
  • 构建通用的编译期工具库;
  • 提高模板元编程的可读性与可维护性。

7、聚合初始化的扩展

在 C++ 中,聚合初始化(Aggregate Initialization) 是一种简洁且直观的对象初始化方式,常用于数组、结构体等聚合类型。C++14 对其进行了增强,特别是在处理具有继承关系的聚合类时,放宽了原有的限制,使得开发者可以更自然地使用花括号 {} 初始化包含基类成员的派生类对象。

这一改进简化了代码书写,提高了可读性,并减少了对构造函数的依赖,尤其适用于需要快速初始化多个字段或嵌套结构的场景。

示例代码:

#include <iostream>
struct Base {int base_num;
};
struct Derived : public Base {int derived_num;
};
int main() {// 使用扩展的聚合初始化方式初始化派生类对象Derived d = {1, 2};std::cout << "Base成员值: " << d.base_num<< ", Derived成员值: " << d.derived_num << std::endl;return 0;
}

在上述示例中,Derived类公有继承自 Base类,并新增了一个成员变量 derived_num。通过 C++14 支持的扩展聚合初始化语法:

Derived d = {1, 2};

我们可以直接使用花括号列表初始化该派生类对象:

  • 第一个值 1 被用来初始化基类 Base 的成员 base_num;
  • 第二个值 2 则用于初始化派生类 Derived 自身的成员 derived_num。

这种初始化顺序遵循 C++ 成员变量声明的顺序,包括基类成员先于派生类成员进行初始化。

实际优势

  • 简洁直观
  • :无需显式定义构造函数即可完成多层级对象的初始化;
  • 提升可读性
  • :初始化值与成员变量一一对应,逻辑清晰;
  • 支持嵌套结构
  • :可用于嵌套的聚合结构,如结构体数组、联合体等;
  • 兼容旧有语法
  • :保留了传统聚合初始化的风格,同时增强了功能。

8、放宽的 constexpr 函数限制

C++11 引入了 constexpr 函数机制,允许在编译期执行函数调用,从而生成常量表达式。然而,在 C++11 中,constexpr 函数的实现受到诸多限制,例如函数体中只能包含单一的返回语句、不能使用局部变量、循环、条件分支等复杂逻辑。

C++14 对这一特性进行了显著增强,放宽了对 constexpr 函数的语法限制,使其可以支持:

  • 局部变量定义
  • 多条语句和多个返回语句
  • 简单的控制结构,如 if、for、while 等

这些改进使得开发者能够编写更为复杂的编译期计算逻辑,将更多原本只能在运行时完成的任务提前到编译阶段执行,从而提升程序性能、优化资源使用,并增强代码的通用性和类型安全性。

示例代码:

#include <iostream>
constexpr int factorial(int n) {int result = 1;for (int i = 2; i <= n; ++i) {result *= i;}return result;
}
int main() {constexpr int fact_5 = factorial(5);  // 在编译期计算5的阶乘std::cout << "5的阶乘: " << fact_5 << std::endl;return 0;
}

在这个示例中,定义了一个 constexpr 函数 factorial,用于计算一个整数的阶乘。该函数包含了以下非 C++11 原生支持的结构:

  • 局部变量 result
  • for
  •  循环控制结构
  • 多个语句组成的函数体

在 C++14 及以后的标准中,这些都是合法且可以在编译期求值的。因此,当在 main 函数中使用constexprint fact_5 = factorial(5);时,编译器会在编译阶段就完成 5! 的计算,并将其结果 120 直接嵌入到程序中,避免了运行时开销。

实际应用价值

这种增强的 constexpr 功能具有广泛的实用价值,尤其适用于以下场景:

  • 数学常量和公式计算
  • :如阶乘、斐波那契数列、三角函数表等;
  • 模板元编程替代方案
  • :相比复杂的 TMP 技术,constexpr 提供了更易读、更可维护的编译期计算方式;
  • 静态查找表构建
  • :可在编译期预计算并存储常用数据;
  • 断言与验证
  • :在编译期验证某些逻辑条件,提高代码可靠性。

注意事项

尽管 C++14 放宽了限制,但 constexpr 函数仍需满足一些基本要求:

  • 所有参数和返回类型必须是字面类型(literal type);
  • 函数体中的操作必须能够在编译期确定;
  • 如果函数无法在编译期求值,它仍然可以在运行时正常调用。

9、[[deprecated]]属性标记

C++14 引入了标准属性 [[deprecated]],用于标记那些已废弃但尚未移除的程序元素,例如类、变量、函数、枚举、成员函数等。当开发者在代码中使用了被 [[deprecated]] 修饰的内容时,编译器会在编译阶段发出警告,提示该内容可能在未来版本中被移除,建议避免继续使用。

这一特性有助于:

  • 提高代码维护性:明确标识哪些接口已不推荐使用;
  • 平滑过渡更新:为库作者提供一种优雅地弃用旧接口的方式;
  • 避免潜在错误:提醒开发者尽早替换为新的替代方案。

此外,[[deprecated]] 是 C++ 标准的一部分,相比之前依赖于编译器扩展(如 GCC 的 __attribute__((deprecated)))的做法,它具有更好的可移植性和标准化支持。

示例代码:

#include <iostream>
struct [[deprecated]] A {void foo() {std::cout << "This is an old class." << std::endl;}
};
int main() {A a;  // 使用了被废弃的类,编译时将产生警告a.foo();return 0;
}

使用 g++ 编译时(启用 C++14 标准):

g++ test.cpp -std=c++14 -Wall

输出结果如下:

test.cpp: In function ‘int main()’:
test.cpp:13:7: warning: ‘A’ is deprecated [-Wdeprecated-declarations]A a;^
test.cpp:6:23: note: declared herestruct [[deprecated]] A {

在这个示例中,结构体 A 被标记为 [[deprecated]]。当我们在 main() 函数中创建其对象 a 时,编译器会检测到这一使用行为,并生成相应的警告信息,提示开发者该类已被弃用。

下期预告:

C++17新特性

欢迎点击下方关注【Linux教程】,获取编程学习路线、原创项目教程、简历模板、面试题库、编程交流圈子。

相关文章:

  • CNN不是一个模型?
  • [特殊字符] Windows 查看端口占用及服务来源教程(以 9018 端口为例)
  • Python网安-随机密码生成器
  • 架构轻巧的kokoro 文本转语音模型
  • tcpdump用法
  • 快速傅里叶变换(FFT)是什么?
  • 以太网基础与 VLAN 配置实验
  • HTML 按钮单击事件示例
  • OceanBase批量插入数据报错java.lang.ArrayIndexOutOfBoundsException:0
  • 华为云鸿蒙应用入门级开发者认证 实验(HCCDA-HarmonyOS Cloud Apps)
  • EMQ X Broker 配置HTTP 的外部鉴权接口
  • 力扣-合并区间
  • QT6实现软键盘的两种方法
  • 腾讯混元API调用优化实战:用API网关实现流量控制+缓存+监控
  • 2-深度学习挖短线股-3-训练数据计算
  • 【windows处理技巧】如何缩小PDF
  • 鸿蒙边缘智能计算架构实战:多线程图像采集与高可靠缓冲设计
  • LeetCode 2311.小于等于 K 的最长二进制子序列:贪心(先选0再选1)-好像还是比灵神写的清晰些
  • VUE3入门很简单(3)--- watch
  • SpringBoot项目快速开发框架JeecgBoot——Web处理!