深度好文:How to get started in C++!
Datawhale干货
作者:zclll,推荐:卢雨畋,Datawhale成员
在大家的不懈催更下(hhh),这篇文章终于和大家见面了。对于程序设计、软件开发而言,我都只是一个入门水平而已(也许C++略多一点,但也并不很多)。对任何知识的掌握,都不足以保证完美无缺。那么有什么可以和大家说的呢?我想,只有对一些事物的理解与认知。如何快速地养成一位普通的C++工程师,是培训班应该去做的任务。而我们,应该为成为一名优秀的软件工程师打好基础。
这些内容显然不会是无上真理,因为它受限于笔者的有限认知。但至少,做到这些内容,成为比笔者更优秀的C++工程师,还是非常容易的。
最重要的
对于学编程的新手来说,最重要的事情是——先把代码写起来。这件事情对于各个行业来说都很重要,但也许对coding尤为重要。如果说程序员这个行业一定需要一些天分的话,那么最重要的一个,就是有“先把事情搞起来”的执行力。
这意味着,也许你还在犹豫“我应该学什么,用哪本书、学谁的课”,什么时候学,要不要沐浴更衣,要不要择良辰吉日——都不必。就用你手头有的资源,写你想到的东西,先写起来。如果没有合适的,那么在这篇文章中,我会提供一些——但它不会是本篇文章的重点。
C++需要学什么
被问最多的问题是:“有没有推荐的学习路线?”
要解答这个问题,必须要解决几个前置问题:
你得明确,学C++是为了什么为了赶紧找一份不错的工作?为了做有价值的东西?为了做很酷的东西让自己开心?
自己要去做哪个方面的开发是hpc,服务端,嵌入式,量化……还是什么?让我们一个一个来说。
第一点,你的动机决定了你要去做什么。因为人的精力是有限的,你不可能对C++这么辽阔的版图面面俱到。特别是,C++的世界中有一类特别的人——语言律师,他们对复杂的语法规则——你绝不可能了解的那些——如数家珍,但不一定是非常优秀的软件工程师。你需要决定自己要不要成为这样的人。这并没有优劣之分,只是不同的选择。
就像“茴字有几种写法”一样。当然需要有人知道(比如计算机史学家),但如果你希望成为一名优秀的coder的话,显然不是你需要知道的事情。
那么,掌握一门语言需要到什么程度呢?
对语言的掌握
C++是一门非常复杂的语言,与其他语言最大的不同在于,它有一套被称之为“标准”的东西3 。这套标准非常详尽,事无巨细地规定了C++世界中的每一个角落该长什么样子。也正因为如此,C++世界中有一个经典的笑话,关于 “面试者在简历上写了精通C++结果面试官出了一堆元编程打击得面试者无地自容的那些事儿”。
这意味着我们永远都不能够说自己“精通C++”了么?我看并不是。
很多群友都知道,我在自己的简历上的确写了“精通C++”。因为在我看来,自己已经完全满足这个要求了。我能保证做出cpp quiz中的hard题吗?我不能。给我几个超复杂的候选函数,我能搞懂重载决议会pick哪个吗?我不能。
但让这些炫技的东西出现在一个正经项目的代码里,比“不会C++”可怕一万倍。
我能保证的是,对于我们的项目中所涉及到以及可能涉及到的C++知识,我已经掌握了至少99%了。我的精力应该放到如何写出更简洁、低耦合、可复用的代码上去,应该研究如何让我的代码对CPU、缓存更加友好,应该研究各个组件的架构应该如何设计才能从宏观角度提高性能和稳定性……
——而不是让那些超复杂且有害的东西来占据我本就有限的时间。
如果一个机制需要非常复杂的语法去实现它,那么相信我,一定有更好的办法。如果你在cr的时候看到了一个完全看不懂的写法,那么相信我,give it a -1.
这是第一点:你必须清楚自己学C++是为了什么。花一些时间做cool的事情来取悦自己没什么不好,但长期来看,你得大体坚持在正确的道路上,才能写出真正很酷的东西。它会是一个从性能、软件工程等角度上都很酷的东西,而并不因为你采取了很先进或很复杂的语法。
先进的语法只有在让代码更简单的时候才是真的先进;复杂的代码只有让更多的代码更简单/性能更高的时候才是合适的复杂。
所以——
比语言更重要的
专业能力
语言永远只是一个工具。
我想大多数人学C++的目的,是因为它容易写出可掌控、高性能的代码,这就意味着语言不是第一性的,它是为了功能而服务。
如果对业务确实有利,那么明天去写Rust又怎么样呢?任何人都不应该抵触在合适的时机选择更合适的语言。C++也许的确更难学,但不要因为学了它而拥有可悲的壁垒。你的领域知识远比语言更重要。
所以,你应该去考虑:我要成为一名hpc开发?还是嵌入式开发?还是后端开发?……
只不过你喜欢C++,那么你可以选一些用C++比较频繁的领域,把C++作为你的核心竞争力之一,而不是“一棵树上吊死”。
当然,语言是这一切的基础。所以你首先得把C++学到一个“能写出各类功能代码”的程度,然后就要尽快让领域知识驱动着你去学习和coding。而这并不难。我们预期花多少时间呢?如果你慢慢学,每天学一点,两三个月总够了;如果你把这作为你的主业,那么顶多需要半个月~一个月的时间。
学习方式
关键点就在于,不要把做一件实事的bar想象地太高。
最基本的语法都掌握了吗?很好,去学你想要做的那个领域的知识吧!
思考能力
抓住问题的本质
我想,选择C++作为主要语言的人,往往都对高性能有一些执念吧?那么“抓住问题的本质”这件事情简直太重要,太重要了!
比如有些acmer第一次接触到编译期计算,会想到——我靠,那我直接把运行时算的东西挪到编译期不就得了?TLE再也与我无关!
——当然我们知道这是天方夜谭。但我看到过不少人曾有过这样的想法。问题不在于他们没有掌握元编程的正确知识,而是他们未能建立正确的思考模型。
比如说, 下列代码段中,虚函数调用是不是必须的?
struct A{ virtual void f(){ ... } };
struct B : A { void f() override { ... } };
{
A* a = new B{};
a->f();
}
如果你C++学得多一点,可能会很快反应过来:我可以用CRTP优化掉这个虚调用嘛!
但这件事的本质并不是CRTP,这件事的本质是“运行前就已经有足够的信息”,即使没有CRTP, 也会有ARTP, BRTP,只要是一个图灵完备的语言,就总有技巧消除掉这个开销。
对你来说,最重要的东西不是学会用CRTP,而是“知道什么是理论上可以实现的”。
就像网络工程师常用的“零拷贝优化”一样,即使没有人总结出来这种优化,它也应该是你有能力在业务优化中思考出来的。你不是掌握了一堆兵器,然后在项目上挨个按上去,看看卡不卡得上。而是:如果这个地方理论上可以更好,那么我总能有个技术把它搞了。
我认为, 从这个角度——也就是事物的本质分析问题的能力, 比编程能力要重要很多倍。掌握很多技术可能能让你成为一名还算不错的软件工程师,而掌握从本质角度思考问题的能力,你才有可能成为一名架构师。
在恰当的抽象层上思考问题
这也是非常重要的一种能力,同时也是对软件工程的理解中的一环。就像我们经常说到,XXX对程序员来说是透明的。CPU有乱序发射,编译器有各种优化,这意味着你需要考虑这些东西会不会搞砸你的代码吗?No。所有的优化与变换,首先得是透明的、条件保持的。
如果你决定了用 swap 去交换对象,就不用担心它是否因为应用了针对PIMPL的优化、针对POD的优化,而搞砸你的代码。
如果你需要用或者提供一个接口,你所需要关注的全部就是接口的前条件和后条件,不要做跨层的思考。(除非你正打算从数据流的角度优化算法)
成长路径
原则性的(也是最重要的)东西讲完了,让我们来讲讲成长路径。所谓“成长路径”,简单讲就是“我在各个阶段应该学些什么?”,说直白点,就是“我应该往简历上写些什么?”。
首先第一部分,是你的基础能力,这主要是语言能力——毕竟你得先把想到的东西写出来,才有别的东西可谈。
所以,开始把代码写起来吧。
书籍、参考与练习
对于入门来说,有很多书籍是不错的,但它们都比不上找到自己想做的东西,然后一边写一边查。当然,参考书也是有必要的,我会推荐这本:
《CPP Primer Plus》
这本书的质量据说不错,但我没看过。叶神说过相比之下还不错,我信叶神的(但他好像也没看过)。
然后呢?把这本书(或者其他你觉得不错的)看个差不多,就已经足够去写代码了。现在对你来说最重要的事情,就是找到你的目标领域当中适合入门的toy,写起来。
请坚决摒弃上学时期张嘴等老师“喂饭”的思维,真正的学习绝不是把书一本一本的读下去就有效果的!
在这个过程中,你需要同步提高自己的C++水平,那么有这两本非常不错的书:
《现代C++32讲》
《Effective Modern C++》
后者我更推荐互联网上的gitbook版本,看着更舒服。
如果你需要检验一下自己对C++语法的掌握如何,那么你可以在这里找到很多练习:
Cpp Quiz
请注意,不要痴迷。你不需要花费时间在这里的Hard难度上。
而如果你需要确定地查找一个关于C++语法与库的正确答案,请到这个对于Cpper最重要的网站:
cppreference.com
这时候,你就开始需要接触一些C++的最佳实践了:
StackOverflow —— 这个地球上最高质量的程序员问答平台之一
CppCon —— C++的前沿发展汇报
总结来说,对于绝大多数新生代的C++ coder(或是其他门类的软件工程师)来说,在学习过程中最需要树立的一个观点是:绝对不是已经掌握了对应的技能才去做东西,你要做一个东西,正是因为你还不会它。
当然,这时候,写出更好的代码就已经不能只局限于使用更合适的语法了,而是必须要从软件工程的角度重新思考你的代码。
重构
最好的方法,就是定期重构自己的代码。重新去思考能不能用更好的抽象,能不能更好地复用?更好的解耦合?
隔一段时间回头看,总会觉得自己的代码像依托答辩,那么,这就是你提升的时候了!
请不要忘记……
能够实际做一些东西,总比纸上谈兵要好,不是吗?
所以一定不要忘了,学会Linux、Cmake、gtest、、git、perf、valgrind、benchmark……这些工具的使用!
那么,我该怎么去学它们呢?在什么时机去接触呢?
答案是当你意识到你需要的时候。 请再次提醒自己,写代码已经不是中学时期等老师来喂你知识的时候了,你必须自己进行思考。当你思考如何改进自己的代码的时候,这些工具就会自然进入到你的世界。——这种学习的过程,将会伴随你的整个职业生涯。
如果你说,我写了很多代码(xcpc coding除外),但是仍然好像没有接触到这些工具的使用?那么,这就是你的问题了,其他人无能为力。的确有的人不具备主动思考和学习的能力,那么你要做的应该是关闭这篇文章,去找个培训班让他们想办法帮你物色一个好的工作。
项目
哦,到了大家(中的很多人)最为关心的部分了!所以我把这一小项提升为一个章节来说——
其实人的眼界和机遇,比我们想象当中重要太多了对于绝大多数人来说,无论你找到的工作是什么样的,这个世界上几乎一定存在着更轻松、待遇更优渥的另一个,只是你无法发现。所以多去拓展消息渠道,多接触更优秀的人,对你的意义比想象中的大。至少对我来说,如果大一就能有这样一个QQ群的话,我想自己一定能成长到另一个高度。
所以,好好利用这些资源,去决定自己要做什么。
还是那句话,领域知识才是根本。你要成为一名后端/前端/嵌入式/区块链……工程师,而不是“C++工程师”。
名校Lab
如果说接触商业项目相对困难的话,那么去做lab(课程附带实验)很容易成为你写实际代码的第一步!
这些国外顶尖名校的lab与我们普通大学的实验课有天壤之别,它们往往要求你完成一个非常系统且颇具难度的软件,这其中覆盖了几乎全部这个课程的关键基础知识。因此,它们很适合作为新手入门的第一步。例如以下Lab:
CMU 15-445 数据库
CMU 15-721 数据库系统进阶
MIT 6.824 分布式系统
MIT 6.828/s.081 操作系统
MIT 6.S191 深度学习
……
当然,随着互联网卷度增加,lab也有“泛滥”之嫌。但相信我,如果你真的搞懂了一个lab,它已经足以让你在对应领域成为一名合格的(校招)工程师了。
开源项目
实际参与开源项目的含金量,我认为不需要我多说了。它远超你的Lab、xcpc牌子、绩点、奖学金……
这对你来说会不会很遥远?我可以告诉你的是,完全不。开源社区中的人们,大多是一群非常友好的家伙,而且非常多的项目都有一些容易让新人上手的case,而且会有很友好的mentor去对应的协助你完成。
所以,你只需要摆脱胆怯的情绪,就可以很容易地去参与其中。例如1我们的大型分布式数据库DORIS,就给新手提供了非常友好的Good First Issues栏目,你可以非常容易地选择一个自己想要去完成的内容,然后接受导师的指导。
之后你会发现,原来参与一个开源项目并不是那么困难嘛~
作者网址:https://zclll.com/