打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
探索C++14新特性:更强大、更高效的编程
userphoto

2023.11.04 湖南

关注

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引入了一些新特性,包括但不限于以下内容:

  1. 通用Lambda表达式:允许在lambda函数中使用auto关键字来推导参数类型。

  2. 自动返回类型推导:允许使用auto关键字自动推导函数返回值类型。

  3. 初始化列表的泛型支持:可以使用auto关键字在初始化列表中推导元素类型。

  4. 带有二进制分隔符的整数字面量:可以在整数常量中使用单撇号作为分隔符,提高可读性。

  5. constexpr函数的扩展:constexpr函数可以包含更多操作,例如循环和条件判断。

  6. 变长参数模板(Variadic Templates)的改进:支持递归处理变长参数模板的展开。

  7. 返回void类型的lambda表达式:允许定义返回void类型的lambda函数。

2.2C++14中auto关键字的用法和限制是什么?

在C++14中,auto关键字用于自动类型推导,可以根据初始化表达式的类型来确定变量的类型。它的使用和限制如下:

  1. 自动类型推导:使用auto关键字声明变量时,编译器会根据初始化表达式的类型自动推导出变量的类型。
    auto x = 42; // 推导为int型auto name = "John"; // 推导为const char*型

  2. 声明时必须初始化:使用auto声明变量时,必须进行初始化。因为编译器需要根据初始化表达式来推导出变量的类型。
    auto y; // 错误,未初始化

  3. 可与引用结合使用:auto关键字可以与引用一起使用,从而推导出引用的类型。
    int a = 10; auto& ref = a; // 推导为int&型,ref是a的引用

  4. 不支持数组或函数指针:auto不能直接用于数组或函数指针的声明。但可以通过decltype结合auto来实现对数组或函数指针类型进行推导。
    int arr[] = {1, 2, 3}; auto arrRef = arr; // 错误,无法推导arr的数组类型decltype(arr) arrType; // 使用decltype获取arr的数组类型并声明arrTypevoid 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引入了一些新的标准库组件和功能,以下是其中的一些主要特性:

  1. std::make_unique:提供了在堆上创建 unique_ptr 对象的便捷方式。

  2. std::integer_sequence:支持编译时整数序列的操作,用于元编程。

  3. std::user_defined_literals:允许用户定义自己的字面量后缀,扩展了语言的表达能力。

  4. 通用 lambda 表达式:允许使用 auto 参数声明参数类型,使得 lambda 表达式更加灵活。

  5. 变长模板参数折叠(Variadic template parameter packs expansion):可以将多个参数打包传递给模板函数或类,并且可以对它们进行展开操作。

  6. 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_uniquestd::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),它允许使用一种更统一和一致的方式进行初始化。具体的变化包括以下几个方面:

  1. 初始化列表(initializer list):可以使用花括号 {}来初始化对象,无论是简单类型还是复杂类型。例如:
    int num{ 42 }; std::vector<int> vec{ 1, 2, 3 };

  2. 自动类型推导:在使用统一初始化语法时,编译器可以自动推导出变量的类型。
    auto value{ 3.14 }; // 推导为 double 类型auto str{ "Hello" }; // 推导为 const char[6] 类型

  3. 统一构造函数调用语法:通过统一初始化语法,可以直接调用类的构造函数进行对象的创建。
    class MyClass { public: MyClass(int value) { /* 构造函数实现 */// ...}; MyClass obj{ 42 }; // 调用构造函数创建对象

  4. 空初始化:可以使用 {}或 ()进行空初始化,不再需要显式地指定默认值。
    int num{}; // 初始化为0std::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"
*/
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
30分钟了解C++11新特性
C 中auto用法
C++:C++11新特性详解(1)
C|函数调用与参数(形参与实参)传递(传值与传址)
《C语言程序设计教程》第七章函数
第7章函数实现模块化程序设计
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服