一个牛逼C++程序员的编程素养(整理版)

本文由博览网整理出品,已获得高博老师本人授权。

一个牛逼C++程序员的编程素养(整理版)_第1张图片
Paste_Image.png

首先介绍一下我自己和这门课。我会对在博览网开设的这门课程做一个简介。然后开始今天的议题,它包括四个模块:

  • C++是怎么演化的,演化的基本规律
  • C++里哪些特性是被废弃的,以及为什么会被废弃
  • 新的习惯用法和赏析
  • 没有银弹(没有完美的解决办法)

最后一个是QA环节。


一个牛逼C++程序员的编程素养(整理版)_第2张图片
Paste_Image.png

那么我们现在就开始。
我叫高博,英文名叫Daniel,原先是EMC的首席工程师,当然我已经离开了这家公司,开始自己的business.
除了职业经历这一块,还有一个与众不同的业余爱好,就是做翻译。我最近出的一本书,叫做《C++覆辙录》,它可以说是C++98时代的教训总结吧,里面列举了99种常犯的一些错误。其他的一些我翻译过的,包括《编程格调》( The Elements of Programming Style),《研究之美》(Surreal Numbers)以及《信息简史》。你可以看到我是按照C++的相关度来排序的。(笑)

一个牛逼C++程序员的编程素养(整理版)_第3张图片
Paste_Image.png

好,讲一下这门课,我在博览网开设的这门课的受众。
第一种呢,是之前掌握过一门编程语言,现在想转到c++。不管是从哪门语言转过来,只要你有一定的编程经验。
还有一种是,之前学过C++98,现在想转型到C++11/14,他可能会涉及到一些遗留代码的处理,自己写过的也好,看别人写过的也好。
当然如果没有任何编程基础的话,我们也非常欢迎,但是需要你非常认真地听课,有比较不错的悟性。如果说是直接跨越式地学习C++11/14,就把它当做你的第一门语言,并不是没有可能的,我之前也有过成功案例。
更何况C++之父Bjarne Stroustrup,我曾采访过他,学C++是否非要先从98学起,他的回答是非常斩钉截铁的,完全没有必要,C++是一门可以从任何版本——当然是C++的标准版本——开始学起,并且能够学得非常好的一门语言。
所以如果你完全没有一点基础的话,也是可以来听这门课,我觉得完全没有必要做这样的限制了。

前面我也有提到过,我在做翻译,我这门课程的基础,就是最近我在翻译的一本书,《Effective Modern C++》。我看到有很多网友在问,什么时候能出版。因为实在比较忙,这本书一拖再拖,但是我会争取本月之内截稿,根据出版规律,一般会等两个月左右上市,也就是10月。

报名这门课程的同学,也就是有一个幸运的机会哈,能跟我一起,把这本书详细地看一遍。

这本书可以看作是,从作坊时代到工程时代的飞跃,它就是这么有影响力。大家如果学了这本书,一定会跟我一样,非常有收获。


一个牛逼C++程序员的编程素养(整理版)_第4张图片
Paste_Image.png

好,闲话休提,我们开始来说一些干货。
C++是非常博大精深的,二十四史,从何说起?那我这里呢,想先从一个小例子,跟大家探讨一下,我在面试时也经常出这道题。

class Student{
char *name;
int age;
};

假如说我们现在有这么一个class,我想请大家用三分钟时间看看,能不能实现这个东西:

operator =
它的赋值运算符

如果能够正确实现的话,是有一个小奖品的:)
那么大家一边继续往下写,我继续往下讲。

一个牛逼C++程序员的编程素养(整理版)_第5张图片
Paste_Image.png

我们可以看到,C++是一门非常复杂的语言,原来就有三种范式在工作:

  • 面向过程,把C++当作更好的C编译器来使用;
  • 基于对象,把数据表现成抽象数据结构;
  • 继承抽象,把一部分class视作接口,把一部分实现放到passion类里面去。

然后在现代C++里,又多了一种,叫做函数式编程。函数甚至可以是匿名的,我们把它称作lamda表达式,它可以即时定义,即时执行。现在C++就有了四种编程范型,这四种又相互影响,让它变得更复杂。
但是所谓的C++,还是有一些核心价值观,我们可以从《C++的设计与演化》里看到一些。当然这里面也有一些过时的东西,仍然成立的,有这些:

一个牛逼C++程序员的编程素养(整理版)_第6张图片
Paste_Image.png

C++实际上非常讲究抽象性,它不仅是可以对数据本身,对数据的实现有比较强的抽象,它对数据类型也会有一定的抽象。其他语言也有一些类似模板的东西。
你看在C++11里面,这个其实就引入了一种新的语义,叫做移动语义。你只需要把旧的搬到新的里面中去就行,它的代码就会变得非常简洁。

那么实际上,C++另外一个比较大的核心价值在于,它可以极其精确地分配内存和代码的执行,在冯诺依曼的体系结构里,所有的代码,不是数据就是执行。很多语言提供了高层的抽象,但是你没有办法知道它的底下是如何运作的。但是C++的话,如果你想,完全可以自己来实现。

当然,你也可以不去自己实现所有东西,而是理解所有东西,然后去用。这是一个更高的层次,我也会在自己的课程里反复强调,能不要自己做的事情,千万不要自己去做,你要去理解,不要去重新发明轮子。

C++第三个比较大的核心价值,你只为你用的东西付费。你没有必要去申请你不需要的资源。如果你不用的话,编译器不会为你生成一大段不用的代码。C++之父有一句著名的论断,garbage cracktion之所以高效运作,是因为它产生的垃圾代码很少。嗯,可能是最少的。(笑)

跨平台,跨编译器,跨操作系统,关注性能,这都是C++在语言层面就提供的支持。很多语言需要你具备高明的编程技巧,才能够去实现这些东西。

除了了解C++的核心价值,我们更要了解它的趋势是什么,我们可以看到C++从11到14到17,几乎是三年一变,我们可以从中可以看到很多趋势。第一个,它提供了更强有力的语法。第二个,它引入了更多的饰词,这样你可以更清楚表达你的意思,不再需要用含含糊糊地,隐式的句子来表达自己,更少地犯错。

第三点,越来越多的库支持。一般来说,语言是由两部分组成的,一部分是语法部分,比如for应该怎么写,如何声明变量;另一部分,组成部分,许许多多的标准库,C++的标准库里面有很多的容器和算法,让你可以很容易地编译各种程序。

一个牛逼C++程序员的编程素养(整理版)_第7张图片
Paste_Image.png

我觉得这三点,是我总结出来的,比较准确的C++的演变趋势。
那么现在我们就举几个例子。
这很C++,你为了学习新的知识,总是需要先清除旧的东西。

一个牛逼C++程序员的编程素养(整理版)_第8张图片
Paste_Image.png

我们会越来越少地会有机会来写出这样的代码:

int x;
double d;
MyObject * o = new MyObject();

像这样的一些代码,都是在教科书里反复出现,但是在新标准里,是不提倡的。比如说上面一段,我们没有初始化它,声明完了我们可能就忘了它,然后就引起目录引擎编译错误,或者其他的问题。因此我们提倡,去写让类型自动来推导,类似这样:

auto x = 5;
auto y = 3.0;

这样子的话,你就绝对不会忘记初始化,因为只写auto没有初始化,编译是通不过的。
还有一些,比如lamda表达式里,一些形参,用旧方法是没办法表达的,但是用auto就可以进行表达,这是我们非常基本基本的东西,教科书里写了十多年的东西,但是现在就要被颠覆了,要用auto来表达。
过去我们做类型转换,会这样写,把一个隐式转换成一个实数。

auto x = 5;
double d = x;

现在我们提倡这种写法,如果你真的需要类型转换的话,就需要明确地表达出来。

auto d = static_cast(x);

表面上这只是一种习惯的改变,实质上是思想的转变。
这样子有若干好处,可以避免初始化,它会让代码更容易重构,比如以前你要把所有的int变成double,在老旧的代码里,你需要挨个去判断是否需要改掉,但是用auto的话,这些你都不需要操心。甚至有些场合,你根本就不知道自己使用了错误的类型。
当然关于auto的形别推导过程和模板推导过程,会在课程中进一步展开,这里就讲到这里。

一个牛逼C++程序员的编程素养(整理版)_第9张图片
Paste_Image.png

那么对于我们的第三个语句来说,我们不会再使用裸指针。当然裸指针有太多的遗留代码,但是一个合格的程序员是后及时地去重构它。也许在面试中会有点用,但是如果你能用新的智能指针的语法,说不定会有更多的加分。
我举一个最简单的例子,过去你或许会写出这样的语句来:

一个牛逼C++程序员的编程素养(整理版)_第10张图片
Paste_Image.png

这样的语句它有很多问题,最大的问题是,它不是异常安全的,如果你发生异常的话,你申请的内存就不会被回收,你打开的文件就不会被收回。你可能会打开很多文件,就会发生资源的耗尽。尤其当你需要频繁地打开文件,打开文件,读写读写,多线程的情况下,你自己或许能保证,但是你不能保证其他线程也这样。

那么在新的语法规则下,我们就会这样来写:

Paste_Image.png

你看它把它整个写入了一个函数里,用作用域来决定资源的申请与回收。这种用法才是对的,才能吧保证异常安全。
我们说,在C++98里,有一种所谓智能指针,叫做Autopcr,自动指针,它有一个巨大的问题,会引起所有权的流转。比如你要把a的值赋值给b,它竟然会因被赋值,而自己会发生改变,这是一个很tricky的语法,所以在新标准里,就被废弃。
那么取而代之的呢,就是一系列的指针类型。如果它需要专属的所有权,你可以用nickptr,或者允许多个指针指向同一个目标,用shareptr.
围绕着这些指针的话,你可以在合适的时候使用恰当的指针。这样你就可以避免使用delete,保证了异常安全性。
你看,连最基本类型的赋值,和指针的操作,都完全不一样了。

一个牛逼C++程序员的编程素养(整理版)_第11张图片
Paste_Image.png

我们会注意到,被废弃的特性当中,不仅仅包括语法,也包含我们看不到的语义的改变。
比如说,特殊的成员函数,(默认构造函数,拷贝构造函数等)这种东西是属于语言层面上会提供一些支持,你虽然没有写,但是C++会自动帮你合成一些函数。
像这样的函数,有相当的部分是不应该替你合成的。大三律,析构函数,复制构造函数,复制赋值运算符,这三个东西,要有就都有,要没有就都没有。这个实际按成员进行赋值,
但是C++98并没有从语言层面支持这一点,你自己定义了一个析构函数,它会自动替你生成赋值运算符等东西,那该怎么办呢?你得用删除函数把它干掉,或者指定默认行为。
像这种,在新的c++标准里,虽然为了兼容旧的,予以保留,但实际已经是被废弃的行为了。

再说一点,常量迭代器。像find这种的,在98里是非常量迭代器,而且讨厌的一点是,不能隐式,即不能转换成常量迭代器。在现代C++里,已经做出了调整:

C++98:
begin() end()
Modern C++:
cbegin() cend()

有一些东西,已经从程序员应该管理的东西,变成语言应该管理的东西。
模板的话,是一个太大的话题,我们还是在课程里面讲。

好,接下来我们来讲一些全新的东西。

一个牛逼C++程序员的编程素养(整理版)_第12张图片
Paste_Image.png

C++现在引入了一些全新的东西。C++的遗留代码很多,C++之父说过,要引入任何新东西都要谨慎。那么在这样的条件下,引入新东西是为了什么呢?
第一个例子,我想强调C++11/14增加了很多字面常量。什么是字面常量?0啊,3.0啊,一个字符啊,一个字符串啊,这些东西都叫做字面量。这些字面量增加了很多种,比如{}。之前你定义一个vector,你是没有办法一次性指定它的值的。
比如你定义一个整型的vector,你要加一个1,2,3,过去你需要多次push back,现在你可以直接用大括号,{1,2,3}。当然不止于此,你还可以用来做初始化,在类的定义中,直接用{}进行初始化。
那么这个{},也会带来一些问题,我会在第四部分,no bullet silver里讲一讲。

第二个例子,
还有一个就是带范围限制的枚举量。从enum改成enum class.虽然只是加了一个class,但实际上完全不一样。欲知详情,还是一样,在课程里找答案哈。

然后,更多的饰词。


一个牛逼C++程序员的编程素养(整理版)_第13张图片
Paste_Image.png

第一点。
比如noexcept,它不会抛出异常。
大家可能还记得这个东西,throw(),作为函数饰词的一部分,它表征它修饰的函数不会抛出任何异常,如果你有指定类型异常的话,你可以把它枚举一下。它引发了无数的问题,以至于很多编译器的实现,异常规格就当没看到。
但是noexcept,它就是说,我不会抛出任何异常,那么你就可以依据它进行优化。
那么删除函数和deport函数,刚才也有提到,这里就不多说了。
第二点的话,constexpr,它允许你在编译期间进行一些运算,有些东西只有在编译期才能计算,过去C++是找不到任何语法来支持的,现在的话,已经可以通过这个实现。这是一个非常强大的功能。
第三点,特殊成员函数,多了移动构造函数和移动赋值运算符,把大三律进行了扩充。

在学习C++的过程中,你搞清楚初始化和对象构造的过程,你在掌握一些细节语法,包括一些看起来非常难懂的lamda表达式,或者一些智能指针啊,或者一些复杂的东西,比如forward之类,你会发现掌握起来非常简单。所以我们理解C++,要抓住静态布局,以及构造过程两点。

所以说学习你还是要抓住题眼。或者构造,或者赋值,或者析构,或者定义,一共无非就几个过程。只不过在C++里,表层规则有所改变。

一个牛逼C++程序员的编程素养(整理版)_第14张图片
Paste_Image.png

刚才说的是语言核心,现在我们来说库的部分。
时间有点紧了,这里就简要带过。
新的容器里,多了一个array,一个固定尺寸的vector。
包括它引入了一堆新的东西,比如unordered。

接下来,我们如果想对C++的未来预测一下的话,unordered这个可能就会被放弃掉,因为还是需要程序员来选择和维护的。最好还是自动地根据场景来选择哪个版本,这是完全有可能的。

再说算法,也是增加了一大堆,它有一些修改性的算法和非修改性的算法。这里我说的不严格,它并不都是新的算法,但是都有新的算法实现。
比较值得注意的是,多了一些实用的工具算法,比如minmax(),同时找最小和最大。
还有一个比较注意的,foreach(),在一些其他语言里,其实早就实现了。但是在C++里,这依然是个非常重大的进步。

一个牛逼C++程序员的编程素养(整理版)_第15张图片
Paste_Image.png

最后说一下没有银弹的问题,C++其实提供了很多保护层,它会引入更多的语言特性,来take care原来程序员take care的问题。但这是否就是解决了问题了呢,实际上,说难听一点的话呢,它只是把一些问题的难度作了一些规约。(笑)
所以说没有什么银弹,没有什么完美的工具,我们今天这个讲座呢,就是通过就是查看一些被废弃的特性,和一些新的特性,对一些思想作了一些总结,希望能帮到大家。

你可能感兴趣的:(一个牛逼C++程序员的编程素养(整理版))