编程语言 cpp C++14 Standards 星野暗涌 2026-05-08 2026-07-04 1. C++14 的介绍
在这里我们可以发现 c++14 是·c++11的这要版本之后的一个次要版本,主要包括了改进和缺陷修复,更新的语法和并发库不是很多。他是在2014年8月18日获得批准,2015年12月15日首发。
那么就让我们开始学习 c++14 标准的新特性和语法吧。 🌈 🌈
2. 变量模板 c++14 允许使用变量模板,还可以适用于变量。
一个简单的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #include <iostream> template <class T >constexpr T pi = T (3.1415926535897932385L ); template <class T>T circular_area (T r) { return pi<T> *r * r; } template <std::size_t N>constexpr std::size_t factorial = N * factorial<N - 1 >;template <>constexpr std::size_t factorial<0 > = 1 ;template < class T >constexpr bool is_const_v = is_const<T>::value;int main () { std::cout.precision (6 ); std::cout << "float π: " << pi<float > << std::endl; std::cout.precision (10 ); std::cout << "double π: " << pi<double > << std::endl; std::cout.precision (6 ); float radius1 = 2.5 ; std::cout << "半径为 " << radius1 << " 的圆⾯积: " << circular_area (radius1) << std::endl; std::cout.precision (10 ); double radius2 = 2.5 ; std::cout << "半径为 " << radius2 << " 的圆⾯积: " << circular_area (radius2) << std::endl; std::cout << factorial<5 > << std::endl; std::cout << factorial<10 > << std::endl; return 0 ; }
这样的操作使得一些特殊场景更加的简便! 😋
3. 泛型 lambda 表达式
在 C++14 中允许 lambda 表达式使用auto 作为参数类型,使其成为泛型和前面的语法模板高度相似, auto& 代表左值引用的形参,auto&& 代表万能引用的形参, auto&&... 代表可变参数的模板参数的万能引用.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 #define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> #include <vector> #include <algorithm> int main () { auto getMax = [](const auto & a, const auto & b) { return a > b ? a : b; }; std::cout << "最⼤整数: " << getMax (10 , 20 ) << std::endl; std::cout << "最⼤字符串: " << getMax (std::string ("apple" ), std::string ("banana" )) << std::endl; auto func = [](auto && x, auto & y) { x += 97 ; y += 97 ; }; int i = 0 , j = 1 ; func (10 , i); func (j, i); std::string s1 ("hello worldxxxxxxxxxxxx" ) ; func (move (s1), i); func (s1, i); std::vector<std::string> v; auto f1 = [&v](auto &&... ts) { v.emplace_back (std::forward<decltype (ts)>(ts)...); }; f1 (move (s1)); f1 ("1111111" ); f1 (10 , 'y'); for (auto & e : v) { std::cout << e << " " ; } std::cout << std::endl; return 0 ; }
在 C++14 中 lambda 捕获红可以直接使用表达式初始化啦捕获变量,这个变量可以是当前域定义的,也可以是没有定义的.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <iostream> #include <vector> #include <algorithm> #include <memory> int main () { std::vector<int > numbers = { 1 , 2 , 3 , 4 , 5 }; auto p = std::make_unique <int >(10 ); auto lambda1 = [value = 5 , ptr = std::move (p), &numbers]() { std::cout << "捕获的值: " << value << std::endl; std::cout << "捕获的智能指针值: " << *ptr << std::endl; std::cout << "捕获的vector大小: " << numbers.size () << std::endl; }; lambda1 (); int x = 10 ; auto lambda2 = [y = x * 2 ]() { std::cout << "y = x * 2 = " << y << std::endl; }; lambda2 (); return 0 ; }
4. 函数的返回类型推导
在c++11 中普通的函数使用auto 做返回来类型推导时,一般要求配合尾置类型使用,星队来说比较有局限性.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <iostream> using namespace std;int x = 1 ;auto f1 () -> int { return x; } auto f2 () -> int & { return x; } auto f3 (int x) -> decltype (x * 1.5 ) { return x * 1.5 ; } int main () { cout << f1 () << endl; int & ret = f2 (); cout << f3 (3 ) << endl; ret++; cout << x << endl; cout << ret << endl; return 0 ; }
C++14 直接使用auto 进行推导,如果函数声明的声明说明符序列包含auto ,那么尾随返回类型可以省略,且编译器讲舍弃的返回一句中所使用的操作数的类型推到他,如果有多条返回语句,那么就必须推导出相同的类型!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <iostream> using namespace std;int x = 1 ;auto f1 () { return x; } auto & f2 () { return x; } auto f3 (int x) { return x * 1.5 ; } auto f4 (int x) { if (x > 0 ) return 1.0 ; else return 2 ; } int main () { cout << f1 () << endl; int & ret = f2 (); cout << f3 (3 ) << endl; ret++; cout << x << endl; cout << ret << endl; return 0 ; }
如果返回的类型没有使用decltype(auto) ,那么推导遵循模板实参推导的规则进行. 如果返回的类型是decltype(),那么返回类型是将返回语句中的所有的操作数包裹在decltype 中时所具有的类型.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <iostream> using namespace std;int x = 1 ;decltype (auto ) f1 () { return x; } decltype (auto ) f2 () { return (x); } template < typename F, typename ... Args>decltype (auto ) call (F&& f, Args&&... args) { return std::forward<F>(f)(std::forward<Args>(args)...); } int main () { cout << f1 () << endl; int & ret = f2 (); ret++; cout << x << endl; cout << ret << endl; cout << call ([](int a, int b) { return a + b; }, 1 , 2 ) << endl; return 0 ; }
5. 二进制自变量
十进制字面量是一个非0十进制数字(1,2,3,4,5,6,7,8,9)后面随0或者是多十进制数字
八进制字面量是数字0后面随零或者多个八进制数字(0, 1, 2, 3, 4, 5, 6, 7).
十六进制数字就是在字符序列0x或字符序列0x后随一或是多个十六进制数字(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, A, b, B, c, C, d, D, e, E, f, F)
二进制字面量就是字符序列0b或字符序列oB后随一个或者是多个二进制数字(0, 1),二进制字面量是C++14才开始支持的.
下面是代码的示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include <iostream> #include <bitset> using namespace std;int main () { int d = 42 ; int o = 052 ; int x = 0x2a ; int X = 0X2A ; int b = 0b101010 ; cout << d << endl; cout << o << endl; cout << x << endl; cout << X << endl; cout << b << endl; const int FLAG_A = 0b0001 ; const int FLAG_B = 0b0010 ; const int FLAG_C = 0b0100 ; const int FLAG_D = 0b1000 ; int flags = FLAG_A | FLAG_C; std::cout << "标志位: " << std::bitset <4 >(flags) << std::endl; if (flags & FLAG_A) { std::cout << "FLAG_A 被设置" << std::endl; } if (flags & FLAG_B) { std::cout << "FLAG_B 被设置" << std::endl; } else { std::cout << "FLAG_B 未被设置" << std::endl; } return 0 ; }
6. 数字分隔符
c++14 允许在数字字面量中使用单引号作为分隔符, 提高可读性.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <iostream> #include <bitset> int main () { int million = 100 '0000 ; long hexValue = 0xDEAD 'BEEF; double pi = 3.141 '592 '653 '59 ; unsigned long long bigBinary = 0b1010 '1010 '1010 '1010 ; std::cout << "一百万: " << million << std::endl; std::cout << "十六进制值: 0x" << std::hex << hexValue << std::dec << std::endl; std::cout << "π: " << pi << std::endl; std::cout << "二进制值: " << std::bitset <16 >(bigBinary) << std::endl; const int MAX_USERS = 1 '000 '000 ; const double EARTH_RADIUS_KM = 6 '371.0 ; const long long BIG_NUMBER = 123 '456 '789 '123 ; std::cout << "最大用户数: " << MAX_USERS << std::endl; std::cout << "地球半径(km): " << EARTH_RADIUS_KM << std::endl; std::cout << "大数字: " << BIG_NUMBER << std::endl; return 0 ; }
7. 使用默认非静态成员初始化器的聚合类 聚合类的定义
聚合类是指满足以下的条件的类(包括结果体):
没有用户提供的构造函数
没有时候或者是受到保护的非静态数据成员
没有基类
没有虚函数
关键点
默认成员初始化器: 档使用默认构造或者是值初始化时,成员会使用这些默认值
聚合初始化: 任然使用花括号初始化列表,未指定的成员使用默认值.
初始化顺序: 初始化列表中的值按成员进行声明顺序应用于成员
聚合类的定义和初始化方式的变化
在c++11之前,聚合类不能有任何的初始化器,但在c++14的时候这个限制被放宽了.
C++14允许聚合类宝娟默认的非晶态成员初始化器(default member initializers ), 这使得聚合类的使用更加的灵活.
在后续的 C++17/C++20对嵌套类定义在不断的放宽,并且初始化的方式也再进一步优化,具体的文档在下面,感兴趣的uu可以深入学习了解一下.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include <iostream> #include <string> struct Employee { std::string name = "Unknown" ; int id = -1 ; double salary = 0.0 ; void print () { std::cout << "Name: " << name << ", ID: " << id << ", Salary: " << salary << std::endl; } }; int main () { Employee e1 = { "xxx" , 1 , 1.1 }; e1. print (); Employee e2{ "John" }; Employee e3{ "Alice" , 123 }; Employee e4{}; Employee e5{ .id = {1 }, .salary{1.1 } }; struct Inner { int a; int b; }; struct Outer { Inner i; int c; }; Outer o1{ .i{1 ,2 }, .c = 3 }; Outer o2{ .i = {.a = 1 , .b = 2 }, .c = 3 }; return 0 ; }
8. 标准库的新增功能std::exchange
std::exchange 是C++14 标准库在<utility> 头文件中引入的一个实用的模板,特提供了一种简洁高效的方式来替换一个对象的值并返回其旧值.
相比与我们自己实现的替换而言 std::exchange在使用上个国家的简介,并且在c++20之后库里面将这个函数实现了 constexpr,效率更高!
这个更能非常的实用,下面展示一下他的具体使用代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #include <iostream> #include <iterator> #include <utility> #include <vector> class stream { public : using flags_type = int ; public : flags_type flags () const { return flags_; } flags_type flags (flags_type newf) { return std::exchange (flags_, newf); } private : flags_type flags_ = 0 ; }; void f () { std::cout << "f()" ; }int main () { stream s; std::cout << s.flags () << 'n'; std::cout << s.flags (12 ) << 'n'; std::cout << s.flags () << "nn" ; std::vector<int > v = { 10 ,20 ,30 }; std::vector<int > ret = std::exchange (v, { 1 , 2 , 3 , 4 }); for (auto e : v) std::cout << e << " " ; std::cout << "n" ; for (auto e : ret) std::cout << e << " " ; std::cout << "n" ; std::cout << "nn斐波那契数列: " ; for (int a{ 0 }, b{ 1 }; a < 100 ; a = std::exchange (b, a + b)) std::cout << a << ", " ; std::cout << "...n" ; return 0 ; }
注意使用 std::exchange 的对象一定是支持移动构造的才可以!
9. 标准库新增功能std::make_unique
std::make_unique是c++14的一个只智能指针的创建工具函数,用于安全的创建和管理std::unique_ptr对象, 他是现代c++,推荐的对象创建的方式之一,类似于c++11的std::make_shared
std::make_unique相比于直接构造std::unique_ptr对象而言更加的安全,当构造函数或者是初始化过程中抛出了异常时, std::make_unique能确保已经分配的资源可以正确的释放. 相比于直接使用new可能导致智能指接管前发生异常,造成内存泄漏.
所以在现代c++中更推荐使用std::make_shared和std::make_unique这类的工具常见智能指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 #include <iostream> #include <memory> class TestClass {public : TestClass () { std::cout << "TestClass 无参构造函数调用n" ; } TestClass (int num, const std::string& str) : num_ (num), str_ (str) { std::cout << "TestClass 带参构造函数调用:num=" << num_ << ", str=" << str_ << "n" ; } ~TestClass () { std::cout << "TestClass 析构函数调用(对象被自动释放)n" ; } void showInfo () const { std::cout << "当前对象信息:num=" << num_ << ", str=" << str_ << "n" ; } private : int num_ = 0 ; std::string str_ = "" ; }; int main () { std::cout << "----- 创建独占智能指针(make_unique) -----n" ; auto uPtr1 = std::make_unique <TestClass>(); auto uPtr2 = std::make_unique <TestClass>(100 , "hello unique_ptr" ); uPtr2->showInfo (); std::cout << "n----- 创建共享智能指针(make_shared) -----n" ; auto sPtr1 = std::make_shared <TestClass>(); auto sPtr2 = std::make_shared <TestClass>(200 , "hello shared_ptr" ); sPtr2->showInfo (); auto sPtr3 = sPtr2; std::cout << "sPtr2 引用计数:" << sPtr2. use_count () << "n" ; std::cout << "sPtr3 引用计数:" << sPtr3. use_count () << "n" ; std::cout << "n----- 函数结束,智能指针自动释放对象 -----n" ; return 0 ; }
10. 标准库新增功能 std::integer_sequence
所属标准
头文件
作用
C++14
#include<utility>
在编译期生成固定的整数序列 ,解决模板参数包无法直接遍历 / 展开的问题,是模板元编程编译时计算的一个得力工具
这个涉及到模板元编程,到了后面会更加详细地展开讲解.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 template < class T , T... Ints >class integer_sequence ;#include <cstddef> #include <iostream> #include <tuple> #include <utility> template < typename T, T... ints>void print_sequence (int id, std::integer_sequence<T, ints...> int_seq) { std::cout << id << " 大小为 " << int_seq.size () << " 的序列: " ; ((std::cout << ints << ' '), ...); std::cout << 'n'; } int main () { print_sequence (1 , std::integer_sequence<unsigned , 9 , 2 , 5 , 1 , 9 , 1 , 6 >{}); print_sequence (2 , std::make_integer_sequence<int , 12 >{}); print_sequence (3 , std::make_index_sequence<10 >{}); print_sequence (4 , std::index_sequence_for<std::ios, float , signed >{}); return 0 ; }
大家简单看一下这个即可, 在实际的开发应用中使用相对较少一些.
11. 标准库新增功能std::quoted
所属标准
头文件
用处
C++14
#include<iomanip>
一个IO操作器用于简化引号字符串的输入输出操作
输出时: 自动为字符串添加引号;
输出时:自动去除字符串周围的引号
处理转义字符: 自动处理引号内的转义序列
基本的用法示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <iostream> #include <sstream> #include <iomanip> #include <string> int main () { std::string text = "Hello, World!" ; std::cout << "Without quoted: " << text << std::endl; std::cout << "With quoted: " << std::quoted (text) << std::endl; std::istringstream input ("" Hello, World!"" ) ; input >> std::quoted (text); std::cout << "Extracted: " << text << std::endl; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <iostream> #include <iomanip> #include <string> #include <sstream> int main () { std::string text = "It's a test" ; std::cout << "Default: " << std::quoted (text) << std::endl; std::cout << "Single quotes: " << std::quoted (text, ''') << std::endl; std::string complex = R"(He said "Hello" and left)" ; std::cout << "Complex string: " << std::quoted (complex) << std::endl; std::stringstream ss1; ss1 << std::quoted (complex); std::string extracted; ss1 >> std::quoted (extracted); std::cout << "Extracted: " << extracted << std::endl; std::stringstream ss2; text = "Line1nLine2tTabbed" ; ss2 << std::quoted (text); std::cout << "Original: " << text << std::endl; std::cout << "Quoted: " << ss2. str () << std::endl; ss2 >> std::quoted (extracted); std::cout << "Extracted: " << extracted << std::endl; }
12. 字面量后缀 字面量后缀是附加在字面量后面的标识符,用于明确指出该字面量的具体类型.这在避免歧义、确保精度、控制转换和提升代码可读性等方面有很大的作用。
1 2 3 4 5 6 7 8 9 10 11 auto a = 10 ; auto b = 10u ; auto c = 10l ; auto d = 10ul ; auto e = 10ll ; auto f = 10ull ; auto g = 3.14 ; auto h = 3.14f ; auto i = 3.14l ;
从C++11开始,允许浮点数、证书、字符串和字符串字面量通过定义用户定义后缀生成用户定义类型的对象。 这是通过operator""实现的。它允许程序员为字面量(数字、字符、字符串)定义自己的后缀,从而使这些“裸的”字面量转换为具有特定类型的和语义的对象。 在c++14/17/20里面定义了一些实用的时间、字符串字面量后缀。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #include <string> #include <string_view> std::string operator "" _s(const char * str, size_t len) { return std::string (str, len); } std::string_view operator "" _sv(const char * str, size_t len) { return std::string_view (str, len); } float operator "" _e(const char * str){ return std::stof (std::string (str)); } constexpr long double operator "" _km(unsigned long long int x) { return x * 1000.0 ; } constexpr long double operator "" _pi(long double x) { return x * 3.14159265358979323846L ; } int main () { auto s1 = "hello" _s; auto s2 = "HelloWorld" _s; auto sv1 = "hello" _sv; auto distance = 5 _km; auto angle = 2.0 _pi; auto x = 12.3 _e; }
C++14起,标准库就提供了很多开箱即用的字面量,他们定义在内联命名空间之中 std::literals中。
后缀
例子
转换后的类型
头文件
s
"hello"s
std::string
<string>
sv
"hello"sv
std::string_view
<string_view>
h
24h
std::chrono::hours
<chrono>
min
30min
std::chrono::minutes
<chrono>
s
10s
std::chrono::seconds
<chrono>
ms
100ms
std::chrono::milliseconds
<chrono>
us
100us
std::chrono::microseconds
<chrono>
ns
100ns
std::chrono::nanoseconds
<chrono>
i
2.0 + 3.0i
std::complex<double>
<complex>
if, i, il
1.0if
std::complex<float>
<complex>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <iostream> #include <string> #include <chrono> #include <thread> using namespace std::literals;int main () { auto str = "Hello" s; std::cout << str.size () << std::endl; std::this_thread::sleep_for (std::chrono::milliseconds (500 )); std::this_thread::sleep_for (500 ms); return 0 ; }