前言
C++ 可算是一种声名在外的编程语言了。这个名声有好有坏,从好的方面讲,C++ 性能非常好,哪个编程语言性能好的话,总忍不住要跟 C++ 来单挑一下;从坏的方面讲,它是臭名昭著的复杂、难学、难用。当然,这样一来,熟练的 C++ 程序员也就自然而然获得了 “水平很高” 的名声,所以这也不完全是件坏事。
不管说 C++ 是好还是坏,不可否认的是,C++ 仍然是一门非常流行且非常具有活力的语言。继沉寂了十多年,并终于发布语言标准的第二版 —— C++11,再那之后,C++ 以每三年一版的频度发布着新的语言标准,每一版都在基本保留向后兼容性的同时,提供着改进和新功能。
1. 为什么难学
每次提到 C++ 编程,无论你是使用 C++ 的开发者,还是使用其他编程语言和开发环境的开发者,我们对 C++ 的评价往往都是 “复杂且难学”。为什么 C++ 会留下这样的口碑?追根溯源,主要有两个原因。
第一个原因是 C++ 的包容性,即向前兼容。
C++ 类似 Objective-C,是 C 语言的超集,它希望尽量向下兼容 C 的一切语法和特性(在 C99 标准之前甚至是完全兼容),因此足够接近硬件底层。但这是把双刃剑。
虽然 C99 之前语法足够简单,但实际使用的复杂性并不低,而 C++ 为了兼容 C 语言的语法付出了很大的代价,并在此基础上设计并发展出了多范式的编程模型,这意味着可以继续采用面向过程的编程模式,也可以转向面向对象。与此同时,现代 C++ 还提供了一组函数式编程工具。
因此,在现代 C++ 得到发展以前,实际开发时到底要选用何种范式或者如何合理组合,一直让我们很头痛。
C++ 兼容 C 有什么代价呢?比如,C 的指针类型声明就备受 C++ 之父 Bjarne Stroustrup 诟病,但是为了向前兼容,不得不在这种声明模式下继续扩展。
第二个原因是 C++ 的设计哲学,“不为任何抽象付出不可接受的多余运行时性能损耗”。
纵观 C++40 多年的演进历程,可以发现每一次演进所支持的都是和编译时相关的新特性,而相对来说,运行时特性非常少,除了在面向对象的编程模型基础上提出的多态以外,几乎再无运行时特性(其他的均以库的形式提供)。这是因为 C++ 是零成本抽象,也就是说,开发者在使用 C++ 表达抽象概念时,无需忍受多余的运行时性能开销。
因此,虽然 C++ 具备很多高级抽象的语法特性,但在设计与具体使用过程中,我们仍然需要考虑各种各样的问题,包括基础对象内存模型、虚函数的设计、基于模板的泛型系统、基于模板的静态反射体系,以及到目前为止都是由编译器决定可选的垃圾回收(在其他现代语言中可以说是必备的特性了),这就让我们学习和使用 C++ 变得更复杂了。
的确,这真够复杂的。一门编程语言必定有其局限性,这也是为什么 “更为现代” 的 Go 和 Rust 出现了,试图解决一些问题,特别是安全性方面。
不过作为语言的使用者,你肯定会问,那今后的 C++ 学习和使用会有哪些变化呢?这个问题,有人曾经问过 C++ 之父 Bjarne Stroustrup。
诸如 Go 和 Rust 编程语言新贵,它们在发力解决安全性和易用性方面的问题,规避缓冲区溢出这样的漏洞,甚至 Linux kernel 也开始考虑或采纳对 Rust 的支持,您是否觉得这会成为 C++ 的一个潜在的巨大威胁和挑战?
他的回答简单明了。
“每隔几年,就会出现 C++ 的挑战者,我相信它们一定会有支持者。但是,C++ 的独特的语言特性、应用场景,以及 C++ 标准发展的方向,会让 C++ 继续茁壮成长。”
我特别喜欢这个回答。是啊,劣势固然存在,但 C++ 经过历史的检验,在高性能计算、低延迟处理、图形学领域以及机器学习等前沿技术领域有着难以替代的优势。
C++ 的 “复杂且难学” 一定程度上取决于向前兼容的能力和设计哲学,但正因如此,维护多年的系统仍然能与全新开发的系统友好地对接和集成,C++ 的包容性和多样性也让它极具发展力。
自 C++11 标准诞生以来,我们正式迈入现代 C++ 世界,而 C++20 及后续演进标准作为继 C++11 之后的又一次重大变革,给我们带来了新思想、新工具,让我们从容面对以往难以解决的问题。
2. C++的意义
C++ 程序员应该都听到过下面这种说法:
- C++ 是一门多范式的通用编程语言。
多范式,是因为 C++ 支持面向过程编程,也支持面向对象编程,也支持泛型编程,新版本还可以说是支持了函数式编程。同时,上面这些不同的范式,都可以在同一项目中组合使用,这就大大增加了开发的灵活性。因此,C++ 适用的领域非常广泛,小到嵌入式,大到分布式服务器,到处可以见到 C++ 的身影。
下面是一些著名的用到 C++ 的场合:
- 大型桌面应用程序(如 Adobe Photoshop、Google Chrome 和 Microsoft Office)
- 大型网站后台(如 Google 的搜索引擎)
- 游戏(如 StarCraft)和游戏引擎(如 Unreal 和 Unity)
- 编译器(如 LLVM/Clang 和 GCC)
- 解释器(如 Java 虚拟机和 V8 JavaScript 引擎)
- 实时控制(如战斗机的飞行控制和火星车的自动驾驶系统)
- 视觉和智能引擎(如 OpenCV、TensorFlow)
- 数据库(如 Microsoft SQL Server、MySQL 和 MongoDB)
有些同学可能会觉得,这些应用场景似乎和平时的开发场景有点远啊!你的感觉是对的。有些传统上使用 C++ 的场合现在已经不一定使用 C++,最典型的是个人电脑上的桌面应用。以前 Windows 下开发桌面应用常常用 MFC,微软的 C++ 框架。目前很流行的 Visual Studio Code 主要是用 TypeScript 写的,不是 C++。
C++ 的传统领域有被侵蚀的风险,那是因为和它相竞争的语言远远不止一个,可以说是上下夹攻。
- 如果专注性能和最小内存占用的话,C 仍然是首选——嵌入式领域用 C 非常多,而 Linux 也是用纯 C 写的。
- 如果专注抽象表达和可读性的话,那 Python 之类的脚本语言则要方便得多。
- 图形界面(GUI)编程传统上是 C++ 的地盘,但近年来 C# 和 JavaScript 占领了很大一部分市场。
- 游戏算是 C++ 的经典强项了,但有了 C++ 写的游戏引擎,游戏用 C# 写也没啥问题了——你可能不一定知道,Unity 游戏引擎上的首选开发语言是 C#,而王者荣耀是用什么游戏引擎呢?答案正是 Unity —— 所以王者荣耀可以认为是用 C# 开发的。
- 还有,Go 和 Rust 也加入了战团,对 C++ 形成了一定的竞争……
目前,跟 C++ 定位差不多、能有直接竞争关系的,也就是既支持高度抽象、又追求高性能的通用编程语言,其实只有 Rust 一种。而 Rust 远没有达到跟 C++ 一样的成熟和普及程度。这也可以从 TIOBE 的排名看出来:C++ 是第 4 位,而 Rust 是第 25 位。
另外,和 C 的兼容性,也是 C++ 的一大优势。虽然现在很多大型程序都混杂了多种语言,但在小项目里,减少语言的数量可以简化开发和部署。
3. 什么时候该用C++
C++ 既然性能又好,又支持抽象,为什么没有更流行呢?
C++ 比起 C 来,要更安全,更不容易出现缓冲区溢出这类漏洞,但跟没有指针概念的语言比起来,它仍然是一种“不安全”的语言。我的个人经验,完成同样的功能,C++ 需要的代码行数一般是 Python 的三倍左右,而性能则可以达到 Python 的十倍以上。
那么问题来了:你在开发上额外付出的时间,能从性能上省回来吗?
显然,这取决于你开发软件的用途和开发时间。举个例子,如果你用 Python 开发需要一天,运行需要十秒,并且不需要反复运行;那么,转用 C++ 开发就意味着开发费用也许要增加两倍,开发加运行的总时间增加两天,大亏。
反之,如果用 Python 开发还是需要一天,单次运行需要十秒,但是软件会作为服务长时间运行、每天被调用十万次。在这种情况下,明显你就需要多台服务器来支撑其使用了。这时,如果用 C++ 开发会需要额外的两天,但跟 Python 相比,部署上有望节约十分之九的硬件和电费 —— 那就很值了。
简言之,当你的软件属于运算密集或者内存密集型,你需要性能、且愿意为性能付出额外代价的时候,应该考虑用 C++,特别在你的代码需要部署在多台服务器或者移动设备的场合。反之,如果性能不会成为你开发的软件的瓶颈,那 C++ 可能就不是一个最合适的工具。
此外,在嵌入式应用的场景,那就根本不是值不值、而是行不行的问题。如果程序完成一个功能不能在指定的若干毫秒、甚至微秒内完成,那产品根本是失败、不可用的。在这种场合,能和 C++ 竞争的只有 C,但 C 是一种开发效率更低、更需要堆人力的语言了。在嵌入式开发使用 C++ 的最大障碍可能不是技术,而是人力资源——搞嵌入式开发的程序员可能大多都习惯使用纯 C 了。
由于 C++ 是解决性能问题的利器,短时间里在市场上没有真正的竞争对手,对 C++ 的需求会在相当长的时间里一直存在,尤其在大公司和像金融机构一样对性能渴求的地方。
顺便提一句,C++ 之父 Bjarne Stroustrup 目前就职的地方便是摩根斯坦利。
4. 如何学习C++
作为很多聪明人使用过的语言,C++ 在某些场合也可能被用来炫技,写出除了本人之外谁都看不懂的高抽象代码。这恰恰是 Bjarne 想努力抵制的方向。他想让 C++ 对初学者变得更为友好,也明确提出过,他不希望 C++ 是一种让人们耍机灵的语言,而是一种让人们更易于使用的语言。
学习 C++ 语言就像学一门活跃使用中的外语,你不要期望能够掌握所有的单词和语法规则 —— 那对于世界上 99.999999% 的人来说是不可能的。但语言是服务于人的,语法规则也是服务于人的,是为了让人们能够更好地沟通和表达。虽然 C++ 的每一个新标准都是让语言从定义和规则的角度变得更复杂,但从用法上来说,新标准允许人们能够更简单地表达自己的计算意图。跟学外语一样,我们需要的是多看多写,掌握合适的 “语感”,而不是记住所有的规则。
Bjarne 有一个洋葱理论: 抽象层次就像一个洋葱,是层层嵌套的。如果想用较低的抽象层次表达较高的概念,就好比一次切过了很多层洋葱,你会把自己的眼泪熏出来的。与这个思路相反,教 C++ 往往有一种不好的倾向,从那些琐碎易错的底层教起,自底向上,使得很多人常常在尚未领悟到抽象的真谛之前就已经被 C++ 的复杂性吓翻,从入门到放弃;或者,在学了基本的 C 语法和 class 之后就满足了,错过了高级抽象带来的全新境界。他主张学习应当自顶向下,先学习高层的抽象,再层层剥茧、丝丝入扣地一步步进入下层。如果一次走太深的话,挫折可能就难免了。
最后,分享一个c/c++后端开发的学习知识图谱(摘自零声教育的大纲:对标腾讯T9职级技术栈,腾讯认证的)
c++后端开发是一个庞杂的技术栈,因为没有统一的开发框架并且应用行业非常广泛。所有涉猎广泛,这里就把c/c++后端开发的技术点进行整理总结,看完以后,不会让你失望的。
- 精进基石
- 高性能网络设计
- 基础组建设计
- 中间件开发
- 开源框架
- 云原生
- 性能分析
- 分布式架构
- 上线实战
部分试听视频
6种epoll的设计方法(单线程epoll、多线程epoll、多进程epoll)
5000道C++“八股文”,还需要死记硬背吗?90分钟梳理清晰
C++后端必读7个开源项目源码(redis、mysql、nginx、protobuf...)
免费学习地址:c/c++ linux服务器开发/后台架构师
需要C/C++ Linux服务器架构师学习资料加qun812855908获取(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享
1、精进基石,分为四个方面(数据结构,设计模式,c++新特性,Linux工程管理)
数据结构部分
设计模式
C++新特性
linux工程管理
2. 高性能网络设计(网络编程,网络原理,协程ntyco,用户态协议栈ntytcp)
网络编程
网络原理
自研框架: 纯c实现的协程(2000行代码)
自研tcp协议栈
高性能异步io机制io_uring
3. 基础组建设计,分为3部分, 池式组件,高性能组件,开源组件
池式结构
高性能组件
开源组件
4、中间件开发专栏
redis
mysql
kafka
gRPC
nginx
5. 开源框架
游戏后端开源框架 skynet
分布式API网关
DPDK
高性能计算CUDA
6、云原生专栏
docker
kubernetes
7、性能分析专栏
性能与测试工具
观测技术bpf与ebpf
内核源码机制
8、分布式架构专栏
rocksdb
TiDB
分布式服务
9、上线项目实战(可以写入简历的两个实战项目,让面试不再为没有项目发愁)
1、图床共享云存储
2、微服务即时通讯