APP下载

每个开发者都应该了解的一些 C++ 特性

消息来源:baojiabao.com 作者: 发布时间:2024-05-15

报价宝综合消息每个开发者都应该了解的一些 C++ 特性

编译:机器之心,作者:M Chowdhury

C++ 是一种强大的程式语言,但也因为其复杂性一直让使用者望而却步。后来,C++ 决定做出改变,然后发展至今,成了程式设计社群最受欢迎的语言之一。C++ 有一些新特性非常好用,本文对此进行了介绍,比如 auto、lambda、constexpr、tuple、智慧指标等。

作为一门程式语言,C++已经进化了很多。

当然,这些改变不是一夜之间发生的。曾几何时,C++缺乏活力,导致人们不太喜欢这门语言。

但是,当 C++标准委员会决定加快转变时,情况就不同了。

自 2011 年以来,C++已经成为一种不断发展的动态语言,而这正是很多人所期许的。

不要误以为是这门语言变得简单了,实际并没有。它仍然是被广泛使用的最难程式语言之一。但是相比于之前的版本,确实对使用者更加友好了。

今天,我们深入发掘一下每位开发者都应该了解的新特性(这些新特性从 C++11 时开始出现,距今已有八年历史了)。注意,本文略过了一些高阶特性,可能会在以后的内容中详细探讨。

auto 概念

当 C++11 第一次引入 auto,一切都变得更简单了。

auto 的概念是让 c++编译器在编译时自动推断资料的型别,而不是每次都要求你手动宣告型别。如果你的资料型别是 map>> 看一下第五行。没有 initializer 时你不能宣告某些东西,这不难理解。像第五行这样,编译器是无法推断资料型别的。

最初,auto 的使用是非常受限的。在之后的版本中,auto 变得更加强大!

第 7 和第 8 行中,我使用了花括号初始化。这个特性也是 C++11 中新加入的。

记住,当使用 auto 时,必须确保你的编译器可以通过某种方式推断资料型别。

现在问题来了,如果我写 auto a = {1, 2, 3} 会发生什么?会有编译错误吗?这是向量吗?

实际上,C++11 引入了 std::initializer_list,如果宣告为 auto,那么初始化列表会被认为是这种轻量级容器。

最后,就像前面提到的,当你使用复杂的资料型别时,编译器推断资料型别会非常有用。

不要忘记检视第 25 行!表示式 auto [v1,v2] = itr.second 是 C++17 的新特性。这被称为结构化系结。在之前的版本中,每个变数必须要分别进行提取,然而结构化系结会使这个过程方便很多。

另外,如果你想通过引用获取资料,只需要新增一个像 auto &[v1,v2] = itr.second 这样的符号,非常简洁。

lambda 表示式

C++11 引入了 lambda 表示式,该表示式和 JavaScript 中的匿名函式非常相似。它们是没有命名的函式物件,并且基于一些简洁的语法在不同的作用域捕获变数,它们还可以分配给变数。

当你想在程式码中快速实现一些小功能但并不想为此单独编写整个函式时,lambda 非常有用。另一种非常普遍的应用是将其作为比较函式。

上面的例子中有很多细节。

首先,要注意到列表初始化为你节省了多少程式码。然后是通用的 begin 和 end,它们同样也是 C++11 中新新增的。然后是作为资料比较器的 lambda 函式。lambda 函式的引数被宣告为 auto,这是 c++14 中新增的。在此之前,是不可以用 auto 作为函式引数的。

这里使用方括号[]作为 lambda 表示式的开始。它定义了 lambda 函式的作用域,即它对区域性变数和物件有多少许可权。

下面是一些现代 c++中的相关定义:

[]代表空。因此你不可以在 lambda 表示式中使用任何外部作用域的区域性变数。只可以使用引数。[=]代表可通过值获取作用域内的区域性物件(区域性变数和引数),即你只可以使用但不可修改。[&]代表可通过引用获取作用域内的区域性物件(区域性变数和引数),即你可以像下面例子中一样修改它。[this]代表可通过值获取 this 指标。[a,&b]代表通过值获取物件 a, 通过引用获取物件 b。因此,如果你想在 lambda 函式中将资料转换成其他形式,你可以像下面这段程式码一样,利用作用域来使用 lambda。

在上面的例子中,如果你在 lambda 表示式中使用 [factor] 取值的方式获取了局部变数,你就不能在第五行中修改 factor,因为你没有权利这样做。不要滥用你的许可权!

最后,注意这里 var 是引用。这保证了在 lambda 函式内的任何改变都会真正改变 vector。

if 或 switch 语句里的初始状态

当我了解了 c++17 的这个特性之后我非常喜欢。

显然,现在你可以在 if/switch 语句块内初始化变数并且进行条件检查了。这对保持程式码的紧凑和简洁是非常有帮助的。通常形式如下:

if( init-statement(x); condition(x)) {

// do some stuff here

} else {

// else has the scope of x

// do some other stuff

}

编译时执行 constexpr

constexpr 非常酷!

假设你有一些表示式要计算,并且它的值一旦初始化就不会改变。你可以预先计算该值并且作为宏来使用。或者像 C++11 中提供的,你可以使用 constexpr。

程式设计人员倾向于尽可能减少程式的执行时间。因此如果某些操作可以让编译器来做,就可以减轻执行时的负担,从而提高时间效率。

上面的程式码是 constexpr 的一个常见例子。

由于我们宣告 fibonacci 计算函式为 constexpr,编译器会在编译时预先计算 fib(20) 的值。所以编译结束后,它可以把 const long long bigval = fib(20) 替换为 const long long bigval = 2432902008176640000;

需要注意的是,传递的引数是 const 值。这是宣告为 constexpr 的函式非常重要的一点,传递的引数同样要是 constexpr 或者 const。否则,该函式会像普通函式一样执行,即不会在编译时预先计算。

变数也同样可以是 constexpr。这种情况下,你应该可以猜到,这些变数同样也是编译时计算的。否则,会出现编译错误。

有趣的是,在之后的 c++17 中,又引入了 constexpr-if 和 constexpr-lambda。

tuple

和 pair 非常相似,tuple 是一组各种资料型别的固定大小值的集合。

有时候,使用 std::array会比使用 tuple 更加方便。array 和普通 C 型别的 array 非常相似,但具有 C++标准库的一些特性。这种资料结构是 C++11 中新增的。

类模版引数推断

名字有点长。从 c++17 开始,引数推断也适用于标准类模版。此前,该特性只支援函式模版。

因此,

std::pair user = {"M", 25}; // previous

std::pair user = {"M", 25}; // C++17

型别推断是隐式完成的。这对 tuple 来说变得更加方便。

// previous

std::tuple user ("M", "Chy", 25);

// deduction in action!

std::tuple user2("M", "Chy", 25);

如果你不熟悉 C++模版,那么上述特性可能对你来说不是很好理解。

智慧指标

指标也可能并不好用。

由于 C++给程式设计人员提供了很大的自由度,有时这种自由可能反而会成为绊脚石。在多数情况下,都是指标在起反面作用。

幸运的是,C++11 引入了智慧指标,它比之前的原始指标更加方便,可以通过适当地指标释放帮助开发者避免内存泄漏,同时也提供了额外的安全机制。

2019-12-10 09:56:00

相关文章