X

曜彤.手记

随记,关于互联网技术、产品与创业

  1. 1. “auto” 关键字
  2. 2. “decltype” 关键字
  3. 3. 基于范围的 “for” 循环

C++11 好用的新语法特性

C++ 语言自1983年发展至今已经过去了36年之久。从 C++98 标准到2017年的第五个标准也就是 C++17,C++ 语言的发展逐渐“脱离”了人们能够熟练驾驭它的速度。不仅如此,由于 C++ 语言在它设计之初,就秉承着这样一个设计理念:即要成为一种“能够直接和广泛地支持多种程序设计风格的语言,比如函数式编程、OOP 范式等”,并且在此之上还需要同时尽可能地与 C 语言的语法特性进行兼容。由于这些种种约束,导致 C++ 语言体系变得较为复杂和混乱,当然上述提到的只是造成这种问题的一部分原因。虽然 C++ 标准中有很多不是那么容易理解且学习路线陡峭的语法特性,但相对的,也有很多十分好用且易学的语法特性。今天,我们就来介绍几个在 C++11 标准中新增的,简单易用的语法特性。

1. “auto” 关键字

第一个我们要介绍的语法特性是 C++11 中新增的类型自动推导关键字 “auto”。C++ 语言一直以来都被人们称为是一种静态类型的语言,这主要是因为在 C++ 中,我们使用到的所有变量都需要在这些变量被使用前提前声明为具体的某种数据类型。而诸如 Python 以及 JavaScript 等语言由于他们语言体系中的变量在使用前不需要提前进行类型声明,因此被视为动态类型语言的代表。但实际上,静态类型和动态类型的主要区别在于对变量进行类型检查的具体时间点。对于静态类型语言来说,其类型检查主要发生在编译阶段;而动态类型语言则发生在代码的执行阶段。因此 C++ 在其2011年的版本中,为了弥补语言本身在“类型推导”上的不足,便引入了 “auto” 关键字。我们来看一段简单的代码,这里展示了该关键字的具体用法。

#include <iostream>

int main (int argc, char **argv) {
    auto i = 10;  // int;
    std::cout << ++i << std::endl;

    return 0;
}

可以看到在主函数中,我们使用了 “auto” 关键字来标记变量 “i” 的具体类型,而没有向以往一样使用具体的值类型关键字,比如常见的 “int” 整型或者 “char” 字符型等等。通过使用 “auto” 关键字,编译器会根据变量值的初始化表达式来自动推断该值的具体类型,这里上述代码中的变量 “i” 最后便会被推导为 “int” 类型。最后我们需要知道的是,在 C++11 标准中提到,实际上 “auto” 关键字它本身只是一个用于类型推导的类型占位符。

2. “decltype” 关键字

OK,第二个我们要介绍的语法特性便是 C++11 中新引入的 “decltype” 关键字。通过使用该关键字,我们可以推导一个变量值或字面量值的具体类型,并返回相应的类型修饰符。这段代码展示了该关键字的具体用法。

#include <iostream>

int main (int argc, char **argv) {
    auto i = 10;

    using iType = decltype(i);
    iType j = 20;
    std::cout << j << std::endl;

    return 0;
}

可以看到,这里我们首先使用 “auto” 关键字声明了一个变量 ”i“,这时该变量会被编译器推导为具体的 “int” 类型。而接下来的 “decltype” 语句则会根据变量 “i” 的值来反向推导出一个类型说明符,所以这里的 “iType” 其实代表了类型声明符 “int”。然后我们便可以在后续代码中直接使用这个 “iType” 来作为对应的类型说明符。

不仅如此,“decltype” 的强大之处在于它还可以推导匿名枚举类、匿名结构体以及匿名联合体的具体类型,并可以在后续代码中进行类型重用。但实际上,匿名对象一般都有它们被匿名的理由,因此这种重用匿名函数类型的做法其实并不太常见。除此之外,通过使用 “decltype” 我们还可以增强模板泛型的能力,可以看下面这段代码。

#include <iostream>

template<typename T1, typename T2>
auto sum(T1 &&x, T2 &&y) -> decltype(x + y) {
    return x + y;
}

int main (int argc, char **argv) {
    std::cout << sum(10, 20) << std::endl;

    return 0;
}

这里在函数 “sum” 中,我们直接使用 “decltype” 指定了该模板函数的实际返回值类型。通过结合使用 “auto” 关键字的类型推导占位,以及基于 “decltype” 关键字的追踪返回值类型,我们实现了函数返回值类型可以根据函数实际参数进行自动推导的特性。

3. 基于范围的 “for” 循环

最后一个我们要介绍的是 C++11 中新增的基于范围的 “for” 循环。以往我们在 C++98 中想要去遍历一个数组时,便需要知道该数组存储的具体元素类型以及数组的整体大小,并根据这些信息来找到数组的循环边界后,才能够进行循环操作。而通过使用 C++11 提供的基于范围的 “for” 循环,我们便能够很好地解决这个问题。我们来看这段代码。

#include <iostream>

int main (int argc, char **argv) {
    int arr[4] = { 1, 2, 3, 4 };

    for (auto &i : arr) {
        std::cout << i << std::endl;
    }

    return 0;
}

基于范围的 “for” 循环一共分为两个部分,冒号前的部分表示用于进行迭代的具体变量,该变量的具体值代表着容器每一轮迭代所取到的值;而冒号后的部分则代表将要被迭代值的具体范围。借助于 C++11 提供的基于范围的 “for” 循环,使得我们在遍历数组或者 STL 容器时变得更加的方便和容易。

最后总结一下,这里我们只列举出了三个最简单、最基本但也是最常用的 C++11 新特性来供大家了解。但实际上,C++11 相较于之前的 C++98,它所新增加的易学又好用的语法特性及标准库方法,远远不止这些。比如我们经常使用的 Lambda 表达式、“constexpr” 关键字以及智能指针等等。除此之外,简单好用也并不意味着万事俱备。比如我们今天介绍的 “auto” 关键字,它在某些特定的场景下也会产生一些奇怪的 BUG,这还需要我们在使用的过程中多加小心并且时常对照着官方标准文档来了解更多的特殊使用场景,以应对 C++ 复杂多变的代码环境。




评论 | Comments


Loading ...