c++14并没有太大的改动,就连官方说明中也指出,c++14相对于c++11来说是一个比较小的改动,但是在很大程度上完善了c++11,所以可以说c++14就是在c++11标准上的查漏补缺。
C++14在2014年8月18日正式批准宣布,同年12月15日正式发布release版本。本文中将就变动部分做一个总结,有需要改进和提升的地方希望大家批评指正。
一、C++14新特性
1.1新的语言特性
- 变量模板
- 泛型 lambda
- lambda 初始化捕获
- 新建/删除省略
- 放宽对 constexpr 函数的限制
- 二进制文字
- 数字分隔符
- 函数的返回类型推导
- 具有默认非静态成员初始值设定项的 聚合类。
1.2新库功能
- std::make_unique
- std::shared_timed_mutex和std::shared_lock
- std::整数序列
- 标准::交换
- std::引用
- 以及对现有图书馆设施的许多小改进,例如
- 某些算法的两范围重载
- 类型特征的类型别名版本
- 用户定义的basic_string、持续时间和复杂的文字
- ETC。
1.3变量模板
在C++11及之前,我们只有针对类和函数的模板。C++14中,新增了变量模板:
template<class T> constexpr T pi = T(3.1415926535897932385L); template<class T> T circular_area(T r) { return pi<T> *r * r; }
变量模板同样可以在类变量中使用:
template<class T> class X { static T s; }; template<class T> T X<T>::s = 0;
类似函数和类模板,当变量模板被引用时,则会发生实例化。
1.4lambda 表达式的新增功能
1)泛化
支持在 lambda 表达式中使用 auto 定义变量类型:
#include <iostream> #include <algorithm> using namespace std; int main() { auto glambda = [](auto& a) { cout << a << " "; }; int a[] = { 4, 2, 6, 3, 7, 5 }; for_each(a, a + sizeof(a) / sizeof(int), glambda); cout << endl; }
2)对捕获的变量和引用进行初始化
include <iostream> using namespace std; int main() { int x = 4; auto y = [&r = x, x = x + 1]()->int { r += 2; return x * x; }(); cout << "x = " << x << " y = " << y << endl; }
1.5constexpr 函数可以包含多个语句
在C++11中,如果想使用constexpr方法,只能包含一个返回语句。C++14中,放宽了此要求,允许constexpr函数中声明变量,使用循环和条件语句等:
#include <iostream> #include <cmath> using namespace std; constexpr bool isPrimitive(int number) { if (number <= 0) { return false; } for (int i = 2; i <= sqrt(number) + 1; ++i) { if (number % i == 0) { return false; } } return true; } int main() { cout << boolalpha << isPrimitive(102) << " " << isPrimitive(103); return 0; }
在C++11中,我们一般需要通过递归来实现相同的功能:
constexpr bool isPrimitive(int number, int currentFactor, int maxFactor) { return currentFactor == maxFactor ? true : (number % currentFactor == 0 ? false : isPrimitive(number, currentFactor + 1, maxFactor)); } constexpr bool isPrimitive(int number) { return number <= 0 ? false : isPrimitive(number, 2, sqrt(number) + 1); }
1.6整型字面量
1)二进制字面量
支持使用 0b 开头的一串数字作为二进制表示的整型:
int a = 0b10101001110; // 1358
2)数字分割符
支持在数字中使用单引号进行分割(便于阅读)。在编译时,这些单引号会被忽略。
int a = 123'456'789; // 123456789
1.7返回类型自动推导
在C++14中,我们可以使用 auto 作为函数返回值并且不需要指明其返回类型的推导表达式
int x = 1; auto f() { return x; } /* c++11 auto f() -> decltype(x) { return x; } */
这种类型推导有一些限制:
- 一个函数中所有返回字句必须推导出相同的类型;
- 使用 {} 包裹的数据作为返回值时,无法推导其类型;
- 虚函数和 coroutine 不能被推导;
- 函数模板中可以使用类型推导,但是其显式实例化和特化版本必须使用相同的返回类型描述符。
1.8exchange
exchange 用于移动语义,可以使用指定的新值替换掉原值,并返回原值。其定义在C++20中被简单修改如下:
template<class T, class U = T> constexpr // since C++20 T exchange(T& obj, U&& new_value) { T old_value = std::move(obj); obj = std::forward<U>(new_value); return old_value; }
其使用如下:
#include <iostream> #include <vector> #include <utility> using namespace std; int main() { vector<int> v = {5, 6, 7}; std::exchange(v, { 1,2,3,4 }); std::copy(begin(v), end(v), ostream_iterator<int>(cout, " ")); cout << endl; }
1.9quoted
该类用于字符串转义的处理。使用 out << quoted(s, delim, escape) 的形式,可以将字符串 s 的转义格式写入输出流中;使用 in >> quoted(s, delim, escape) 可以将输入流去除转义格式后写入字符串 s 中。其中,delim 指明了需要转义的字符,escape 指明了修饰该转移字符的字符:
#include <iostream> #include <iomanip> #include <sstream> using namespace std; int main() { stringstream ss; string in = "String with spaces, and embedded \"quotes\" too"; string out; auto show = [&](const auto& what) { &what == &in ? cout << "read in [" << in << "]\n" << "stored as [" << ss.str() << "]\n" : cout << "written out [" << out << "]\n\n"; }; ss << quoted(in); show(in); ss >> quoted(out); show(out); ss.str(""); in = "String with spaces, and embedded $quotes$ too"; const char delim{ '$' }; const char escape{ '%' }; ss << quoted(in, delim, escape); show(in); ss >> quoted(out, delim, escape); show(out); }
【文章福利】小编推荐自己的Linux C++技术交流群:【1106675687】整理了一些个人觉得比较好的学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!前100名进群领取,额外赠送大厂面试题。
二、C++14经常考到的知识点
2.1C++14引入了哪些新特性?
C++14引入了一些新特性,包括但不限于以下内容:
- 通用Lambda表达式:允许在lambda函数中使用auto关键字来推导参数类型。
- 自动返回类型推导:允许使用auto关键字自动推导函数返回值类型。
- 初始化列表的泛型支持:可以使用auto关键字在初始化列表中推导元素类型。
- 带有二进制分隔符的整数字面量:可以在整数常量中使用单撇号作为分隔符,提高可读性。
- constexpr函数的扩展:constexpr函数可以包含更多操作,例如循环和条件判断。
- 变长参数模板(Variadic Templates)的改进:支持递归处理变长参数模板的展开。
- 返回void类型的lambda表达式:允许定义返回void类型的lambda函数。
2.2C++14中auto关键字的用法和限制是什么?
在C++14中,auto关键字用于自动类型推导,可以根据初始化表达式的类型来确定变量的类型。它的使用和限制如下:
- 自动类型推导:使用auto关键字声明变量时,编译器会根据初始化表达式的类型自动推导出变量的类型。
auto x = 42; // 推导为int型 auto name = "John"; // 推导为const char*型 - 声明时必须初始化:使用auto声明变量时,必须进行初始化。因为编译器需要根据初始化表达式来推导出变量的类型。
auto y; // 错误,未初始化 - 可与引用结合使用:auto关键字可以与引用一起使用,从而推导出引用的类型。
int a = 10; auto& ref = a; // 推导为int&型,ref是a的引用 - 不支持数组或函数指针:auto不能直接用于数组或函数指针的声明。但可以通过decltype结合auto来实现对数组或函数指针类型进行推导。
int arr[] = {1, 2, 3}; auto arrRef = arr; // 错误,无法推导arr的数组类型 decltype(arr) arrType; // 使用decltype获取arr的数组类型并声明arrType void foo(); auto funcPtr = foo; // 错误,无法推导foo的函数指针类型 decltype(foo)* funcPtrType; // 使用decltype获取foo的函数指针类型并声明funcPtrType
需要注意的是,auto在C++14中的用法和限制可能与之后的标准(如C++17、C++20等)有所不同,具体取决于编译器和所使用的标准版本。
2.3C++14中如何使用Lambda表达式?有什么改进?
在C++14中,使用Lambda表达式的语法与之前的C++版本相似。Lambda表达式是一种可以在代码中内联定义匿名函数的方式。
下面是一个使用Lambda表达式的示例:
#include <iostream> #include <vector> #include <algorithm> int main() { std::vector<int> numbers = {1, 2, 3, 4, 5}; // 使用Lambda表达式进行遍历打印 std::for_each(numbers.begin(), numbers.end(), [](int num) { std::cout << num << " "; }); return 0; }
在Lambda表达式中,方括号 []
用于捕获外部变量(可选)。小括号 ( )
内指定参数列表(可选),箭头 ->
后面指定返回类型(可选)。
C++14对于Lambda表达式有一些改进,其中最显著的改进是可以自动推导返回类型。这意味着你不需要显式地指定返回类型,编译器会根据表达式体来推断返回类型。
以下是一个示例:
auto lambda = [](int a, int b) { return a + b; };
在上述示例中,我们没有显式指定返回类型,但编译器会自动推断出返回类型为整数(因为a和b都是整数)。
此外,在C++14中还引入了泛型lambda,使得可以在lambda函数中使用auto关键字作为参数类型,更加灵活和方便。
2.4C++14对于constexpr关键字有何改进?
C++14对于constexpr
关键字进行了一些改进,使得其更加灵活和强大。在C++11中,constexpr
只能用于表示常量表达式的函数和构造函数,而在C++14中,它还可以用于一些额外的情况。
首先,在C++14中,constexpr
函数可以包含一些非常量表达式的逻辑,只要这部分逻辑在运行时不会执行即可。这意味着我们可以在constexpr
函数内使用循环、条件语句等非常量表达式的控制流程。
其次,C++14引入了对变量模板(Variable Templates)的支持,并且允许将变量声明为constexpr
。这样我们就可以定义并初始化一个编译期间可计算的常量变量。
此外,在C++14中,对于某些标准库类型(如数组、字符串等),它们也提供了更多的支持以便于使用在编译期间计算出来的常量值。
2.5C++14中提供了哪些新的标准库组件和功能?
C++14引入了一些新的标准库组件和功能,以下是其中的一些主要特性:
- std::make_unique:提供了在堆上创建 unique_ptr 对象的便捷方式。
- std::integer_sequence:支持编译时整数序列的操作,用于元编程。
- std::user_defined_literals:允许用户定义自己的字面量后缀,扩展了语言的表达能力。
- 通用 lambda 表达式:允许使用 auto 参数声明参数类型,使得 lambda 表达式更加灵活。
- 变长模板参数折叠(Variadic template parameter packs expansion):可以将多个参数打包传递给模板函数或类,并且可以对它们进行展开操作。
- std::experimental 命名空间:引入了一些实验性质的标准库组件,如 optional、any、string_view 等。
2.6在C++14中,变长参数模板是如何使用的?
在C++14中,可以使用变长参数模板(Variadic Templates)来处理可变数量的函数参数。通过使用递归展开参数包的方式,可以灵活地处理任意数量的参数。
下面是一个示例:
#include <iostream> // 递归终止条件:当没有剩余参数时停止递归 void printArgs() { std::cout << "All arguments have been printed." << std::endl; } // 可变参数模板:展开第一个参数并调用自身处理剩余参数 template<typename T, typename... Args> void printArgs(T first, Args... args) { std::cout << "Argument: " << first << std::endl; printArgs(args...); // 递归调用自身处理剩余参数 } int main() { printArgs(1, "Hello", 3.14, 'A'); return 0; }
输出结果:
Argument: 1 Argument: Hello Argument: 3.14 Argument: A All arguments have been printed.
在上述代码中,printArgs
是一个可变参数模板函数。它首先处理第一个传入的参数 first
,然后递归地调用自身处理剩余的 args
参数。当所有参数都被展开并打印完毕后,最终会到达递归终止条件。
这种方式使得我们能够在编译时处理不同数量和类型的函数参数,并且可以灵活地进行操作。
2.7在C++14中,是否允许在lambda函数内定义其他函数或类?
在C++14中,lambda函数内是不允许定义其他函数或类的。Lambda函数是一个匿名的函数对象,它通常用于简化代码,提供一种在局部范围内编写小型函数的方式。Lambda函数本质上是一个闭包,它可以捕获外部作用域中的变量,并且具有与普通函数相似的行为。
然而,在C++17中引入了嵌套lambda的概念,使得在lambda函数内定义其他lambda函数成为可能。在这种情况下,内层的lambda函数可以访问外层lambda函数的变量。所以如果你想要在C++14中定义其他函数或类,建议将其定义在lambda之外的范围内。
2.8C++14是否支持原始字符串字面量(raw string literals)?如何使用它们?
是的,C++14支持原始字符串字面量(raw string literals)。
原始字符串字面量可以用来表示包含特殊字符(例如转义序列和引号)的字符串,而无需使用转义符号。它们由R"delim(raw_characters)delim"的语法表示,其中delim可以是任何非空字符序列,并且在开始和结束位置上必须匹配。
以下是一个示例:
#include <iostream> int main() { const char* str1 = R"(Hello \n World!)"; std::cout << str1 << std::endl; // 输出:Hello \n World! const char* str2 = R"###(This is a "quoted" string.)###"; std::cout << str2 << std::endl; // 输出:This is a "quoted" string. return 0; }
在上面的示例中,我们使用了原始字符串字面量来创建包含特殊字符的字符串,而不需要使用额外的转义符号。
2.9在C++14中,std::make_unique和std::make_shared这两个函数的作用是什么?
在C++14中,std::make_unique
和std::make_shared
是用于创建智能指针的函数模板。
std::make_unique
:用于创建一个std::unique_ptr
对象,它拥有独占所有权的动态分配对象。这个函数接受参数并返回一个std::unique_ptr
,它会自动管理内存释放。示例:
auto ptr = std::make_unique<int>(42);
std::make_shared
:用于创建一个std::shared_ptr
对象,它可以被多个指针共享的动态分配对象。这个函数接受参数并返回一个std::shared_ptr
,它使用引用计数来管理内存释放。示例:
auto ptr = std::make_shared<int>(42);
这两个函数可以减少手动进行资源管理的工作量,并提供了更安全、更简洁的方式来处理动态分配对象。
2.10C++14引入了统一初始化语法(uniform initialization syntax),具体有哪些变化?
C++14引入了统一初始化语法(uniform initialization syntax),它允许使用一种更统一和一致的方式进行初始化。具体的变化包括以下几个方面:
- 初始化列表(initializer list):可以使用花括号
{}
来初始化对象,无论是简单类型还是复杂类型。例如:
int num{ 42 }; std::vector vec{ 1, 2, 3 }; - 自动类型推导:在使用统一初始化语法时,编译器可以自动推导出变量的类型。
auto value{ 3.14 }; // 推导为 double 类型 auto str{ "Hello" }; // 推导为 const char[6] 类型 - 统一构造函数调用语法:通过统一初始化语法,可以直接调用类的构造函数进行对象的创建。
class MyClass { public: MyClass(int value) { /* 构造函数实现 */ } // ... }; MyClass obj{ 42 }; // 调用构造函数创建对象 - 空初始化:可以使用
{}
或()
进行空初始化,不再需要显式地指定默认值。
int num{}; // 初始化为0 std::string str{}; // 初始化为空字符串
这些变化使得初始化更加灵活和一致,并且提供了更强大的类型推导能力。注意,在使用统一初始化语法时,要注意类型的精确匹配和可能的隐式转换。
三、C++14 新特性总结
C++14 这一继 C++11 之后的新的 C++ 标准已经被正式批准,正在向ISO 提交,将于年内发布。 C++ 之父 Bjarne Stroustrup 说道,尽管与 C++11 相比,C++14 的改进“有意做的比较小”,但是仍然为用户“带来了极大的方便”,是实现使C++“对新手更为友好”这一目标的步骤之一。
在C++ 的时间表中, C++14 按计划是一个小版本,完成制定 C++11 标准的剩余工作,目的是使 C++ 成为一门更清晰、更简单和更快速的语言。新的语言特性留到了未来的 C++17 标准中。
C++14 的主要特性可以分为三个领域:Lambda 函数、constexpr 和类型推导。
3.1Lambda 函数
C++14 的泛型 Lambda 使编写如下语句成为可能:
auto lambda = [](auto x, auto y) {return x + y;};
而另一方面,C++11 要求 Lambda 参数使用具体的类型声明,比如:
auto lambda = [](int x, int y) {return x + y;};
此外,新标准中的 std::move 函数可用于捕获 Lambda 表达式中的变量,这是通过移动对象而非复制或引用对象实现的:
std::unique_ptr ptr(new int(10)); auto lambda = [value = std::move(ptr)] {return *value;};
(1)lambda参数auto
在c++11中,lambda表达式参数需要使用具体的类型声明。
auto f = [] (int a) { return a; }
在c++14中,lambda表达式参数可以直接为auto。
auto f = [] (auto a) { return a; }; cout << f(1) << endl; cout << f(2.3f) << endl;
3.2constexpr
在 C++11 中,使用 constexpr 声明的函数可以在编译时执行,生成一个值,用在需要常量表达式的地方,比如作为初始化模板的整形参数。C++11 的 constexpr 函数只能包含一个表达式,C++14 放松了这些限制,支持诸如 if 和 switch 等条件语句,支持循环,其中包括基于区间(range)的 for 循环。
(1)变量模板
c++14支持变量模板。
template<class T> constexpr T pi = T(3.1415926535897932385L); int main() { cout << pi<int> << endl; // 3 cout << pi<double> << endl; // 3.14159 return 0; }
(2)别名模板
c++14支持别名模板。
template<typename T, typename U> struct A { T t; U u; }; template<typename T> using B = A<T, int>; int main() { B<double> b; b.t = 10; b.u = 20; cout << b.t << endl; cout << b.u << endl; return 0; }
(3)constexpr的限制
c++14相较于c++11减少了限制:c++11和c++14中constexpr函数均可以使用递归。
constexpr int factorial(int n) { // C++14 和 C++11均可 return n <= 1 ? 1 : (n * factorial(n - 1)); }
c++14还可以使用局部变量和循环。
constexpr int factorial(int n) { // C++11中不可,C++14中可以 int ret = 0; for (int i = 0; i < n; ++i) { ret += i; } return ret; }
c++11中constexpr函数中必须把所有东西放在一个单独的return语句中。
constexpr int func(bool flag) { // C++14 和 C++11均可 return 0; }
c++14中constexpr函数没有上述限制。
constexpr int func(bool flag) { // C++11中不可,C++14中可以 if (flag) return 1; else return 0; }
3.3类型推导
C++11 仅支持 Lambda 函数的类型推导,C++14 对其加以扩展,支持所有函数的返回类型推导:
auto DeducedReturnTypeFunction();
因为 C++14 是强类型语言,有些限制需要考虑:
- 如果一个函数的实现中有多个返回语句,这些语句一定要推导出同样的类型。
- 返回类型推导可以用在前向声明中,但是在使用它们之前,翻译单元中必须能够得到函数定义。
- 返回类型推导可以用在递归函数中,但是递归调用必须以至少一个返回语句作为先导,以便编译器推导出返回类型。
C++14 带来的另一个类型推导方面的改进是 decltype(auto) 语法,它支持使用与 auto 同样的机制计算给定表达式的类型。auto 和 decltype 在 C++11 中就已经出现了,但是它们在推导类型时使用了不同的机制,这可能会产生不同的结果。
C++14 中的其他改变包括可以声明变量模板,支持使用 0b 或 0B 前缀来声明二进制字面常量。InfoQ 已经介绍过 C++14 中可能破坏 C++11 程序的其他小型修改。
主流 C++ 编译器对新语言特性的支持正在有条不紊地开发: Clang “完全实现了当前草案的所有内容”; GCC 和 Visual Studio 也对 C++14 的新特性提供了一些支持。
(1)函数返回值类型推导
c++14对函数返回类型推导规则做了优化:
auto func(int i) { //C++11编译非法,c++14支持auto返回值类型推导 return i; } int main() { cout << func(4) << endl; return 0; }
支持函数模块返回值类型推导:
template<typename T> auto func(T t) { return t; } int main() { cout << func(4) << endl; cout << func(3.4) << endl; return 0; }
auto返回值用例:
// 编译失败,多个return语句必须返回相同类型。 auto func(bool flag) { if (flag) return 1; else return 2.3; } // 编译失败,不能返回初始化列表 auto func1() { return {1, 2, 3}; } //虚函数不能返回类型推导 class A{ virtual auto func() { return 1; } }; //返回值类型推导可以提前前声明,但使用前必须进行函数定义。 auto f(); // declared, not yet defined auto f() { return 42; } // defined, return type is int // 回类型推导可以用在递归函数中,但是递归调用必须以至少一个返回语句作为先导,以便编译器推导出返回类型。 auto sum(int i) { if (i == 1) return i; // return int else return sum(i - 1) + i; // ok } int main() { cout << f() << endl; return 0; }
3.4C++14其他
(1)[[deprecated]]标记
c++14中增加deprecated标记,修饰类、变量、函数等。编译时产生警告,提醒用户该标记修饰的内容未来可能会被丢弃。
struct [[deprecated]] A { }; int main() { A a; return 0; }
(2)二进制字面量与字面量分隔符
c++14引入了二进制字面量和字面量分隔符。
int a = 0b0001'0011'1010; double b = 3.14'1234'1234'1234;
(3)std::make_unique
c++11中有std::make_shared,c++14增加了std::make_unique。
struct A {}; std::unique_ptr<A> ptr = std::make_unique<A>();
(4)std::shared_timed_mutex与std::shared_lock
c++14通过std::shared_timed_mutex和std::shared_lock来实现读写锁,保证多个线程可以同时读,但是写线程必须独立运行,写操作和读操作不可同时进行,这种情况下才能从shared_mutex中获取性能优势。
c++11 中互斥量
c++14互斥量管理类-锁
- shared_lock是read lock。搭配std::shared_mutex使用,被锁定后允许其它线程执行同样被shared_lock的代码。
- lock_gurd和unique_lock是write lock。被锁定后,不允许其它线程执行被share_lock或unique_lock的代码。
通常这样定义:
typedef std::shared_lock<std::shared_mutex> ReadLock; typedef std::lock_guard<std::shared_mutex> WriteLock;
实现方式:
struct ThreadSafe { mutable std::shared_timed_mutex mutex_; int value_; ThreadSafe() { value_ = 0; } int get() const { std::shared_lock<std::shared_timed_mutex> lock(mutex_); return value_; } void increase() { std::unique_lock<std::shared_timed_mutex> lock(mutex_); value_ += 1; } };
(5)std::integer_sequence
表示一个编译时的整型序列。
(6)std::exchange
以 new_value 替换 obj 的值,并返回 obj 的旧值。注意与std::swap的区别。
vector<int> v{4,5,6,7}; vector<int> x = std::exchange(v, {1,2,3,4}); cout << v.size() << endl; for (int a : v) { cout << a << " "; } cout<<endl; for (int a : x) { cout << a << " "; }
(7)std::quoted
c++14引入std::quoted用于给字符串添加双引号。
string str = "hello world"; cout << str << endl; cout << std::quoted(str) << endl; /* 输出 * hello world * "hello world" */
精品文章推荐阅读: