分享 | 现代 C++ 特性与标准库 —— C++14 篇
2285
发布于 未知归属地

C++14 新特性探幽

目录
[TOC]

那不勒斯.jpg

一. 前言

C++14 作为重磅更新 C++11 之后的一个“小”更新,虽然其暂时还不被大多数公司的开发环境支持,但是学习其引入的一些新特性,可以让我们更好地理解现代 C++ 的发展趋势和技术细节。就好像你买了一套拥有两百个房间的大 house ,虽然平时可能只会用到 10 个房间,但是你至少得了解一遍所有房间的位置和用途。
相比于 C++11 和 C++20 , 14 更多地侧重于编码的便捷性,因为内容并不多,因此就压缩在一篇文章中。

二. variable templates : 变量模板

变量模板,和函数模板与类模板本质上相同,

变量模板定义了一组变量或静态数据成员。

syntax : template< parameter-list > variable-declaration
相当于把一个变量的类型变为模板参数,下面看一看 cppreference 官方的例子。

template<class T>
constexpr T pi = T(3.1415926535897932385L);  // variable template
 
template<class T>
T circular_area(T r) // function template
{
    return pi<T> * r * r; // pi<T> is a variable template instantiation
}

三. generic lambdas : 泛型 lambda 表达式

Lambda 表达式大家一定不陌生,这一特性在 C++11 引入后,极大地方便了代码的编写。而 14 中增强了 Lambda 的功能,使得其支持入参的自动推导。

auto lam = [](auto x, auto y)// 自动推导入参类型
{
	return x + y;
};

下面是使用该 Lambda 表达式的例子。

    auto a = lam(3, 4);		    //a为int
    auto b = lam(4.8, 9.5);		//b为double

对于 C++ 比较熟悉的读者可能已经看出来了,这不是和函数模板长得差不多吗!是的没错,编译器就是用函数模板的推导规则来进行推导的,因此上面的 Lambda 表达式大致相当于:

struct
{
	template<typename T1, typename T2>
	auto operator() (T1 x, T2 y) -> decltype(x + y)
	{
		return x + y;
	}
} lam;

当然我们需要注意,普通的函数是不支持 auto 形参的!!千万不要混淆。

C++14 的泛型 Lambda, 能够被看做 C++11 的 Lambda 的升级版。单态 Lambda 相当于普通函数对象,而泛型 Lambda 则相当于带模板参数的函数对象,或者说相当于带状态的函数模板。两者相比,能够推出下面结果:
  1. 单态 Lambda 在函数内使用,可以捕获外围变量形成闭包,作用相当于局部函数,泛型 Lambda 强化了这一能力,其作用相当于局部函数模板。
  2. 单态 Lambda 可以服务于高阶函数(參数为函数的函数),作用相当于回调函数,泛型 Lambda 强化了这一能力,使得泛型回调成为可能。
  3. 单态 Lambda 可以作为函数返回值,形成柯里化函数(闭包),用于 Lambda 演算,泛型 Lambda 强化了这一能力,使得泛型闭包成为可能。

什么?你说你不熟悉 Lambda 表达式?没关系!下期我将从概念到用法,详细介绍贯穿 C++11 到 C++20 几个版本的重磅特性: Lambda 表达式。

四. relaxed restrictions on constexpr functions : 更宽松的 constexpr 限制

constexpr 是 C++11 标准引入的关键字。首先介绍一下什么是常量表达式:

⚠️表达式中所有的成员都是常量,那么这就是一个常量表达式,这也意味着常量表达式声明后,其值就无法修改。

constexpr 提供了一种强大的能力,就是使指定的表达式在程序编译截断计算出结果,而不必像普通表达式一样在运行期计算结果。constexpr 关键字可以修饰普通变量、函数及模板函数、构造函数。
  1. constexpr 最常用的用法就是修饰函数
    如下所示,用 constexpr 来修饰一个函数。C++11 中的 constexpr 指定的函数返回值和参数必须要保证是字面值,而且必须有且只有一行 return 代码,这给函数的设计者带来了更多的限制,通常只能通过 return 三目运算符 + 递归来计算返回值,如下例所示。
constexpr int factorial(int n) { // C++14 和 C++11均可
    return n <= 1 ? 1 : (n * factorial(n - 1));
}
  1. C++14 对于 constexpr 的改进
    而 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;
}

五. return type dedute : 返回值自动类型推导 ( auto )

✨C++11 标准中引入了自动类型推导(decltypeauto),但是 C++11 中尚未支持函数返回类型的自动推导,如下例在 C++11 中编译会报错。
auto func(int i) {
    return i;
}

int main() {
    cout << func(4) << endl;
    return 0;
}

编译器这时会很人性地提示 :

note: deduced return type only available with -std=c++14 or -std=gnu++14

正如编译器的输出信息,返回值类型推导是 C++14 的新特性。返回值类型推导同样也可以用在模板函数中,这里不再赘述。使用这一特性时需要注意的是,函数内如果有多个 return 语句,那么必须返回相同的类型,否则会引起编译失败。

六. binary-literal integer-suffix : 二进制字面量

C++14 中引入了直接声明二进制量的方法,同时也为了可读性引入了分隔符。
下面就是一个典型的二进制字面量声明。

int a = 0b0001'0011'1010;

可读性分隔符,是为了在声明长整型的时候增加可读性的符号,并不会影响变量实际值,下面的几种声明方式得到的 l1 , l2 , l3 , l4 具有相同值。

unsigned long long l1 = 18446744073709550592ull; // C++11
unsigned long long l2 = 18'446'744'073'709'550'592llu; // C++14
unsigned long long l3 = 1844'6744'0737'0955'0592uLL; // C++14
unsigned long long l4 = 184467'440737'0'95505'92LLU; // C++14

结语

到这里,C++14 的几大新特性就介绍完毕了,感谢大家的阅读!
笔者从今天开始,应该会陆续把之前深入学习 C++ 语言过程中写的一些随笔整理并发布出来。本文作为第一篇正式发布的文章,想必有非常多不足之处,欢迎各位在评论区交流。那么我们下期再见!

评论 (0)