0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

C++变化很大!得重学这门语言了

CPP开发者 来源:InfoQ 2023-07-09 16:09 次阅读

Key Takeaways:

  • C++ 依然非常重要,而且将会永远如此。

  • 有许多资源可以帮助我们学习现代 C++,包括 Godbolt 的编译器探索器、ISOCpp 和 CppReference。

  • C++ 可以比以前更加简单。除了便利性相关的增强外,潜在的性能改善也是 C++11 和后续标准的驱动力之一。

  • 我们通过填充一个 vector 并输出其内容进行练习。我们通过在 vector 上使用算法、range 和 lambda,以便于寻找具有特定属性的元素,实现进一步的练习。

C++ 是一门古老但不断演进的语言。你几乎可以使用它来做任何事情,而且可以在很多地方找到它的身影。实际上,C++ 的发明者 Bjarne Stroustrup 将其描述为一切事物的隐形基础。有时,它可以深入到另外一门语言的库中,因为 C++ 可以用于性能关键的路径中。它可以在小型的嵌入式系统中运行,也可以为视频游戏提供动力。你的浏览器可能正在使用它。C++ 几乎无处不在!

C++ 为何如重要

迄今为止,C++ 已经存在了很长的时间,但是其变化也是非常大的,尤其是 2011 年之后。当时,推出了一个名为 C++11 的新标准,标志着一个频繁更新的时代正式开启。如果你从 C++11 就没有使用过 C++,那么你有很多东西需要补习,这要从哪里开始呢?

该语言是需要编译的,面向特定的架构,如 PC、大型机、嵌入式设备、定制硬件,或者你想到的其他东西。如果你需要代码在不同类型的机器上运行,那需要重新编译它。这有缺点也有优点。不同的配置会带来更多的维护工作,但编译到特定架构能够让你“因地制宜(down to the metal)”,从而获得速度方面的优势。

不管你的目标是哪种平台,均需要一个编译器。你还需要一个编辑器或集成开发环境(IDE)来编写 C++ 代码。ISOCpp 给出了一个资源清单,包括 C++ 编译器。Gnu 编译器集(Gnu compiler collection,gcc)、Clang 和 Visual Studio 均有免费版本。你甚至可以使用 Matt Godbolt 的编译器探索器,在浏览器上尝试基于各种编译器的代码。编译器可能支持不同版本的 C++,所以必须在编译器标记中说明你所需要的版本,例如 g++ 的 -std=c++23 或 Visual Studio 的 /std:c++latest。ISOCpp 网站上有一个 FAQ 区域,概述了最近的一些变化,包括 C++11 和 C++14,以及整体的概览。另外,还有多本关于 C++ 最近版本的图书。

使用 Vector 快速了解 C++11

如果你已经被落下了,那么大量的资源可能会让你不知所措。但是,我们可以通过一个小例子来理解一些基础知识。停下来,亲自动手试一试往往是最好的学习方法。因此,我们从简单基础的东西开始吧!

一个很有用(且简单)的起点是不太起眼的 vector,它位于 std 命名空间的 vector 头文件中。CppReference 提供了一个概述,告诉我们 vector 是一个序列容器,封装了动态大小的数组。因此,vector 包含了一个连续的元素序列,我们可以根据需要调整 vector 的大小。vector 本身是一个类模板,因此它需要一个类型,例如 std::vector。我们可以使用 push_back 将一个条目添加到 vector 的尾部。C++11 引入了一个名为 emplace_back 的新方法,该方法取值来构造一个新的条目。对于 int,代码看上去是一样的:

std::vector<int> numbers;
numbers.push_back(1);
numbers.emplace_back(1);

如果我们有比 int 更复杂的东西,那么就可能在 emplace 版本中获得性能方面的收益,因为 emplace 版本可以就地构造条目,从而避免对其进行复制。

C++11 引入了 r-value 引用和移动语义(move semantics)来避免不必要的复制。潜在的性能改善是 C++11 的驱动力之一,后续的版本都是在此基础上进行的。为了解释什么是 r-value 引用,我们可以考虑前面样例中的 push_back 方法。它有两个重载形式,其中一个会接受一个常量引用,即 const T& 值,另外一个接受一个 r-value 引用,即 T&& 值。第二个版本会将元素移动到 vector 中,这可以避免复制临时对象。与之类似,emplace_back 的签名通过 r-value 引用来获取参数,Args&&…,同样允许移动参数而无需复制。移动语义是一个很大的话题,我们只是接触到了它的皮毛。如果你想了解更多详情的话,Thomas Becker 在 2013 年撰写了一篇很好的文章,介绍了它的细节。

我们创建一个 vector 并在其中放置几个条目,然后使用来自 iostream 头文件的 std::cout 展示其内容。我们使用流插入操作符<<来显示这些元素。我们基于 vector 的 size 编写一个 for 循环,并使用操作符 [] 来访问每个元素:

#include 
#include 
void warm_up()
{
    std::vector<int> numbers;
    numbers.push_back(1);
    numbers.emplace_back(1);
    for(int i=0; i
    {
        std::cout << numbers[i] << ' ';
    }
    std::cout << '
';
}
int main()
{
    warm_up();
}

该代码会显示两个 1。这段代码可以在编译器探索器上找到。

类模板参数推断

让我们做一些更有意思的事情,并学习一下现代的 C++。我们构建几个数字三角,会发现它们之间存在一个模式。数字三角的值是 1,3,6,10……它们分别由 1,1+2,1+2+3,1+2+3+4,……相加而成。如果我们这些斯诺克球架起来,就可以组成一个三角形,它也因此得名:

7fc13678-1e0c-11ee-962d-dac502259ad0.png

如果再增加一排,我们就会再增加六个斯诺克球。再加一排就会增加七个,以此类推。

为了得到数字 1,2,3 等,我们可以构建一个充满 1 的 vector,然后将这些数字相加。我们可以直接创建一个 vector,比如 18 个 1,而不必再增加另一个循环。我们说明想要多少个元素,然后再指明它的值:

   std::vector numbers(18, 1);

注意我们不需要再声明了。因为从 C++17 开始,类模板参数推断(CTAD)就已经实现了。编译器可以推断出我们指的是 int,因为我们要求的值是 1,这是一个 int。如果我们需要显示 vector,那么可以使用基于 range 的 for 循环。此时,我们不必使用基于 vector 索引的传统 for 循环,而是声明一个类型,甚至可以使用新的关键字 auto,告诉编译器判断类型,然后是冒号和容器:

   for (auto i : numbers)
    {
        std::cout << i << ' ';
    }
    std::cout << '
';

CTAD 和基于 range 的 for 循环是 C++11以来引入的一些便利特性。

Range

有了由“1”组成的 vector,我们就可以包含numeric头文件,并使用部分的和来填充一个新的vector,如 1,1+1,1+1+1……,这样就有了 1,2,3……我们需要声明新vector的类型,因为这里要从一个空的vector开始,如果没有任何值可供使用,那么编译器将无法推断其类型。partial_sum需要开头和结尾的数字,最后我们需要使用back_inserter,这样目标 vector 会根据需要增长:

    #include 

    std::vector numbers(18, 1);
    std::vector<int> sums;
    std::partial_sum(numbers.begin(), numbers.end(),
        std::back_inserter(sums));

这样我们就得到了 1 到 18 的数字,均包含边界值。我们已经完成了数字三角的部分工作,但是 C++ 现在可以让我们的代码更加简洁。C++11 引入了iota函数,也位于numeric头文件中,它能够用不断增加的值填充一个容器:

std::vector<int> sums(18);
std::iota(sums.begin(), sums.end(), 1);

实际上,C++23引入了一个range版本,它会为我们找到对应的begin和end:

  std::iota(sums, 1);

C++23 还没有得到广泛的支持,所以可能需要等到你的编译器提供 range 版本。numeric 和 algorithm 头文件中的很多算法都有两个版本,其中一个需要一对输入迭代器(即 first and last),另一个则是 range 版本,只需要接受容器即可。ranges 重载正在逐渐添加到标准 C++ 中。ranges 提供的功能远远超过我们这里避免声明两个迭代器的场景。我们可以过滤和转换输出,将这些东西连接在一起,并使用视图来避免复制数据。ranges 支持惰性计算,所以视图的内容会在需要的时候才评估计算出来。Ivan Čukić的 Functional Programming in C++ 一书在这方面提供了更多的细节(书中还包含更多的内容)。

我们需要做的最后一件事就是形成数字三角。查看 vector 的部分和:

   std::partial_sum(sums.begin(), sums.end(), sums.begin());

我们已经得到了想要的数字三角,即 1,3,6,10,15……171。

我们注意到,有些算法有 ranges 版本,那我们可以尝试一个。前两个三角数字是 1 和 3 是奇数,然后是两个偶数 6 和 10。这个模式是不是可持续的呢?如果我们对 vector 进行转换,用点号“.”来标记奇数,用星号“*”来标记偶数,就能看出最终结果。我们可以声明一个新的 vector 来存放转换结果。对于每个数字,仅需要一个字符,所以我们需要一个 char 类型的 vector:

std::vector<char> odd_or_even.

我们可以编写一个简短的函数,它会获取一个 int 并返回对应的字符:

char flag_odd_or_even(int i)
{
    return i % 2 ? '.' : '*';
}

如果 i % 2 的值不为零,这就是一个奇数,所以我们返回.,否则,返回 *。我们可以在来自 algorithm 头文件的 transform 函数中使用这个自己的函数。最初的版本需要一对输入迭代器(first 和 last)、一个输出迭代器和一个一元函数(unary function),该函数会接受一个输入,就像我们的 flag_odd_or_even 函数这样。C++20 引入了一个 ranges 版本,它能够接受一个输入源,而不是一对迭代器,另外还需要一个输出迭代器和一元函数。这意味着我们可以通过如下方式来转换先前生成的和:

   std::vector<char> odd_or_even;
    std::transform(sums,
        std::back_inserter(odd_or_even),
        flag_odd_or_even);

输出将会如下所示:

. . * * . . * * . . * * . . * * . .

看上去,我们确实是不断地得到两个奇数,然后是两个偶数。Stack Exchange 的数学网站阐述了出现这种现象的原因。

Lambdas

我们使用另一个新的 C++ 特性对我们的代码做最后的改进。如果我们想要看一下实际的转换代码的话,那需要要转移到另外一个地方才能看到这个一元函数都做了些什么。

C++11 引入了匿名函数或 lambda 表达式的特性。它们看起来与有名称的函数类似,将参数放在括号中,将函数主体放到花括号中,但是它们没有名字,不需要返回类型,并且有一个用 [] 表示的捕获组:

[](int i) { return i%2? '.':'*'; }

如果与有名称的函数进行对比,会看到两者的相似性:

char flag_odd_or_even(int i){ return i % 2 ? '.' : '*'; }

我们可以在捕获组中声明变量,这会给我们一个闭包。这些内容超出了本文的范围,但是在函数式编程中它们是非常强大和常见的。

如果我们将一个 lambda 分配给一个变量,

auto lambda = [](int i) { return i % 2 ? '.' : '*'; };

那么,我们就可以像调用有名称的函数那样调用它:

lambda(7);

这个特性允许我们使用 lambda 重写转换调用:

    std::transform(sums,
        std::back_inserter(odd_or_even),
        [](int i) { return i%2? '.':'*'; });

这样的话,我们就可以在一个地方看到转换函数,而不必再去查看其他的地方了。

总 结

将所有的内容组合在一起,就形成了如下的代码:

#include 
#include 
#include 
#include 
int main()
{
    std::vector<int> sums(18);
    std::iota(sums.begin(), sums.end(), 1);
    std::partial_sum(sums.begin(), sums.end(), sums.begin());
    std::vector<char> odd_or_even;
    std::transform(sums,
        std::back_inserter(odd_or_even),
        [](int i) { return i%2? '.':'*'; });
    for (auto c : odd_or_even)
    {
        std::cout << c << ' ';
    }
    std::cout << '
';
}

我们使用了 ranges、lambda 和基于 range 的 for 循环,浏览了移动语义,并练习了对 vector 的使用。对于首次重回 C++ 的人来说,这是一个不错的起点!

你可以在编译器探索器中尝试上述的代码。

关于作者

Frances Buontempo,Frances Buontempo 有多年的 C++ 经验,还有过使用 Python 和其他各种语言的经验。她曾发表过关于 C++ 的演讲,并且是 ACCU 的 Overload 杂志的编辑。她有数学背景,为 PragProg 写了一本关于遗传算法和机器学习的书,并且正在为 Manning 写一本名为 C++ Bookcamp 的 C++ 书,以帮助那些被现代 C++ 落下的人迎头赶上。


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • C++
    C++
    +关注

    关注

    22

    文章

    2104

    浏览量

    73480
  • 编辑器
    +关注

    关注

    1

    文章

    800

    浏览量

    31108
  • Vector
    +关注

    关注

    3

    文章

    55

    浏览量

    8553

原文标题:C++ 变化很大!得重学这门语言了

文章出处:【微信号:CPP开发者,微信公众号:CPP开发者】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    C语言C++的区别

    很大程度上,C++C的超集,这意味着一个有效的C程序也是一个有效的C++程序。
    发表于 09-16 10:20 1128次阅读

    C语言C++运用

    很多同学在大学都学过CC++,那么CC++就业怎么样?薪资高吗?小编今天就给大家详细解读一下。学c++ 好不好?
    发表于 11-25 10:47

    C++编程语言的应用领域有哪些

    软件的架构方法。从这个层面上讲,C++的影响已经不仅仅是一门编程语言这样简单。今天,我们来看一看C++语言在另一块群雄纷争的领域上的身影。嵌入式系统编程“嵌入式”是一个
    发表于 12-15 06:58

    c++程序设计语言(特别版)

    c++程序设计语言介绍标准C++以及由C++所支持的关键性编程技术和设计技术。标准C++较以前
    发表于 09-05 15:28 0次下载

    c++程序设计语言题解

    c++程序设计语言题解是与Bjarne Stroustrup的《C++程序设计语言》一书配套使用的习题解答,为从《C++程序设计
    发表于 09-05 16:00 121次下载
    <b class='flag-5'>c++</b>程序设计<b class='flag-5'>语言</b>题解

    CodeWarrior C and C++ and Assembly 语言参考设计

    本文是英文版的,介绍CodeWarrior C and C++ and Assembly 语言参考设计,在您的设计中或许有用:
    发表于 06-22 15:33 74次下载

    CC++语言参考手册

    CC++语言参考手册
    发表于 06-19 11:12 23次下载

    C/C++编程语言学习难点及语言

    一般学C++前都要学C,所以通常叫它们C/C++C/C++历经40余年,也是经久不衰的号称“永
    发表于 08-15 10:24 2257次阅读

    40年后C++为何仍然当今使用最广泛的编程语言之一?

    强大、灵活、复杂:C++ 的起源可以追溯到 40 年前,但它仍然是当今使用最广泛的编程语言之一。为了找出原因,TechRepublic 采访 C++ 之父 Bjarne Strous
    的头像 发表于 02-15 11:18 2811次阅读

    C语言C++相互调用

    接口的问题,比如最常用的C++调用C的接口来完成相应的外设驱动接口。 我们知道C++几乎是完全兼容C的,不同的编译语言都有其自身的一套编译规
    的头像 发表于 01-18 11:05 3277次阅读
    <b class='flag-5'>C</b><b class='flag-5'>语言</b>与<b class='flag-5'>C++</b>相互调用

    C语言C++到底是什么关系

    首先C++C语言本来就是两种不同的编程语言,但C++确实是对C
    的头像 发表于 06-20 11:28 4881次阅读

    C++语言的发展

    今年的CPP North C++大会上,谷歌宣布一门新的编程语言Carbon,并称其将是“C++的继任者”。
    发表于 08-15 14:22 590次阅读

    如何选择创建c语言c++

    选择创建 C 语言C++ 都需要综合考虑多个因素。在决定使用哪种语言之前,我们需要对这两种语言的特点、优缺点、适用场景、学习成本等进行全
    的头像 发表于 11-27 15:58 566次阅读

    vb语言c++语言的区别

    VB语言C++语言是两种不同的编程语言,虽然它们都属于高级编程语言,但在设计和用途上有很多区别。下面将详细比较VB
    的头像 发表于 02-01 10:20 2046次阅读

    c语言,c++,java,python区别

    C语言C++、Java和Python是四种常见的编程语言,各有优点和特点。 C语言
    的头像 发表于 02-05 14:11 2215次阅读