作者:邹德虎
经济学领域的“不可能三角”挺火,类似这种提法,编程语言的三个需求不可能同时满足:
很高的性能要求;
较低的学习和开发成本(包括无代码/低代码的快速搭建方式);
自主可控,不依赖国外的商业平台。
其实我也幻想过,有没有可能存在某种技术方案,可以把这三条都满足了。但是,自从学习了计算机系统体系结构有关的知识后,发现确实是不可能。当然,如果基于某个强有力的商业平台,例如MATLAB,确实可以实现较高的性能和较低的开发成本,可以同时在工业界和学校使用,但是短期内,国内不可能有替代的方案。
下面依次介绍电力系统中的常用编程语言。
C和C++确实是两种完全不同的语言,不能认为C++是C的超集。但是这两者的联系很紧密,我就放到一起了。电力系统首先主要是工业,既然是工业,那自然是嵌入式的装置研发比较多,嵌入式装置以C语言为主。就算是现在工业物联网、主站信息系统的发展,云计算、大数据、人工智能在电力系统应用也越来越多了,但这些云上的东西,C++的成分依然比较多。特别是电网调度控制的一区,不允许使用国外开源软件,大家基本上用C++开发控制类型的应用(如SCADA、AVC、AGC)。另外,电力系统的界面,目前大多数是基于Qt开发的(Qt是基于C++写的库),虽然有往 Html5 的方向发展。
JAVA是国内应用最广泛的语言,从业者最多。大数据平台、分布式计算机系统的开发以JAVA为主。网上有种说法:“你如果不懂JAVA,那就与平台架构师无缘了”。这个说法最初可能是来自于阿里巴巴公司的。当然,很牛的公司,例如谷歌、腾讯,还是以C++为基础构建大数据平台的,但这样的公司不多。
人工智能的发展很快,大多初学者都用Python。Python是对初学者最友好的语言,现在中学生也逐渐开始学习编程和人工智能技术了,我翻看了他们的教材,都是使用Python的。Python的问题是性能不好,很难适应工业级程序的开发,但是它作为胶水语言,写一写调试的小工具、小脚本是最好不过的。我调试部分协议的socket接口,或者研究过程中绘制一些波形曲线图,用的是Python语言。
Go语言是替代JAVA的候选,其上手容易,并发性能也很好。Go语言在电力系统中的应用主要集中在以下几个方面:SCADA系统;电力管理平台。如果没有历史包袱,Go语言优于JAVA。
C#是微软旗下的语言,上手容易,特别是桌面的开发非常方便。
Fortran是最古老的语言,适合科学计算。我刚工作那会,还调试了不少Fortran语言呢,当时的电力系统潮流、状态估计程序都是用Fortran写的,后文会详细讨论Fortran语言。
Rust是唯一与C++对标的语言,其内存安全性得到了很好保证,但目前用户不多。是否能够代替C++,还需要观察一段时间,但现在已经可以着手学习了。
Juila是科学计算的语言,性能比Python高,是Python的强劲竞争对手。
总结:从我的经验来看,C/C++仍然是电力系统最主要的编程语言,学习难度也最大。对于研发人员,除了C/C++,建议再学至少一种语言作为互补,这对于思维方式的开拓很有好处,具体哪个就不强求了。
Fortran语言是最早的高级编程语言,专门为科学计算和工程计算设计。我刚工作那会,还写过不少Fortran77代码,而且还写过对这些古早代码的解析文档,不过现在都找不到了。那些代码最早可以上溯到1988年,可能比我很多同事的年龄还大。后来,南瑞把这些Fortran代码都重构为C++代码,也就是说,现在南瑞的工程师已经很难接触到Fortran代码了。
Fortran语言目前仍然在许多领域保留,特别是工业软件。在数值天气预报和气候模拟等领域,Fortran仍然是主要的编程语言。据了解,中国电科院的部分软件(如PSD-BPA);以及加拿大的PSCAD/EMTDC的仿真计算内核仍然是Fortran。PSCAD已经20多年没有大的更新了。有人认为:Fortran语言仍然有保留的价值,其编译器经过多年的优化,能够生成高效的机器代码,特别适用于矩阵运算和线性代数等计算密集型任务。对于这一观点,本文做明确的澄清。
Fortran语言在数值计算方面的高效性,其部分原因除了编译器的流水线优化之外(这点C语言也可做到),在于Fortran数组大多数情况下默认具有非别名(non-aliasing)属性,这类似于 C 语言中使用 restrict 关键字的效果。Fortran 语言规范规定,数组和数组切片在传递给子程序时,通常被视为独立的内存区域,不会与其他数组或变量重叠。这使得编译器能够安全地进行优化。
下面列举一段C语言代码,这是矩阵向量乘法,如下:
void transform (float * __restrict dest, float * __restrict src,
float * __restrict matrix, int n)
{
int i;
for (i=0; i<n; i++)
{
dest[0] = src[0] * matrix[0] + src[1] * matrix[1] +
src[2] * matrix[2] + src[3] * matrix[3];
dest[1] = src[0] * matrix[4] + src[1] * matrix[5] +
src[2] * matrix[6] + src[3] * matrix[7];
dest[2] = src[0] * matrix[8] + src[1] * matrix[9] +
src[2] * matrix[10] + src[3] * matrix[11];
dest[3] = src[0] * matrix[12] + src[1] * matrix[13] +
src[2] * matrix[14] + src[3] * matrix[15];
src += 4;
dest += 4;
}
}
这个函数 transform 的功能是对源数组 src 中的向量进行矩阵乘法变换,将结果存储到目标数组 dest 中。具体来说,每次循环处理一组 4 个元素,将它们视作一个 4 维向量,并用一个 4x4 变换矩阵对其进行线性变换。变换后的结果存储在 dest 数组中。对于C语言编译器来说,当加上 restrict 关键字时,编译器可以假设 dest、src 和 matrix 指针指向的内存区域是独立的,不会重叠。具体可以从生成的汇编语言看出来。
这允许编译器进行更多的优化,内存访问指令减少,更多的计算指令可以直接在寄存器或者缓存中完成。因为编译器可以缓存 src 和 matrix 的值,可以假定这些值在每次迭代中不会被其他指针修改。学过计算机系统结构都知道,内存搬移数据需要消耗几十个时钟周期,而寄存器或者缓存的速度是内存访问速度的10倍以上。
由此我们可以得出结论:Fortran语言与优化的C语言相比,数值计算性能是相当的。事实上有人做过测试,Fortran语言总体上,比不加优化的C语言速度只高出20%左右。
在高度优化的领域,Fortran语言远远比不上汇编语言。事实上,如果你看OpenBLAS的代码,它针对每种CPU,都有专门的汇编实现。很多数值领域(如矩阵),保留Fortran语言接口仅仅是历史惯性,其高度优化的实现早已经切换为别的语言。
即使是纯数值计算,C++的潜力可能也已经超过Fortran语言。比如说表达式模板等技术,现在AI等领域已经应用了很多C++库(如Eigen)。更重要的是,现在的数值计算趋势是:CPU单核的性能几乎不增长,需要充分发挥并行计算、异构计算的潜力,例如充分利用GPU的浮点加速能力,或者采用FPGA加快实时性。这些最新的高性能计算技术,与计算机硬件技术、体系结构、操作系统、现代软硬件技术、高性能通信等,完全不可分离。在现代数值计算应用中,往往需要极其复杂的系统支持,因此C++是很好的代替方案。在AI领域,C++成为了主要编程语言之一。TensorFlow、PyTorch等框架的核心部分使用C++编写,并利用了CUDA和OpenCL进行GPU加速。这些框架在数值计算和并行计算中的表现已经超过了传统的Fortran应用。
在现代软件技术方面,尽管Fortran在后续版本中引入了面向对象编程(OOP)、模块和并行编程支持,但其特性和灵活性仍不及C++。Fortran的生态系统相对薄弱,缺乏现代软件工程工具和库的支持 。Fortran代码在结构化方面做的不好,goto满天飞,缺少现代调试与代码静态检测工具。
我认为现代软件技术,应该是多种编程范式的灵活组合,包括:基于对象、面向对象、函数式、泛型等,这点Fortran是做不到的。随着时间的推移,熟悉Fortran的工程师数量逐渐减少,新一代开发者更倾向于学习和使用Python、C++、Rust等现代编程语言。因此,现有的Fortran语言,至少在电力系统这个领域,有必要全部重构为现代编程语言。
对于20年以上的历史代码,尽管其中蕴含了丰富的知识和经验,这并不意味着这些代码本身就是财富。相反,这些代码在很多情况下更像是负债。因为计算机体系结构和软件工程发展迅速,以前的许多通行做法如今显得落伍,例如缺乏清晰的结构、添加新功能变得困难,同时很难应用最新的高性能计算技术。继承这些历史知识和经验的更好方式是重构成更先进、更主流的编程语言。当然,重构也不能过于激进,需要综合考虑成本和新语言的生命周期,这很考验技术领导者的判断力。可以考虑分阶段的重构和改进策略,先重构一些关键模块或性能瓶颈,逐步进行全面重构。在保留核心逻辑的基础上,通过封装接口逐步引入新技术和新模块。通过自动化测试确保每一步重构不会破坏现有功能。
谈论信息技术与编程语言的发展趋势时,程序员习惯从技术本身出发,较少考虑外部环境和社会的发展变迁。比如说,经常看到Java程序员说:”程序员比机器更贵,因此节省程序员开发时间的Java语言必将长盛不衰“。如果以发展的眼光会发现这种观点很有问题,程序员长期比其他脑力工作者收入高的现状很难维持,未来的极大可能趋势是程序员收入的相对下降,这在经济学中很容易解释的。一方面,AI大模型的发展会使得程序员的马太效应更加显著,随着AI的发展和自动化水平的提升,某些编程工作可能会减少,从而影响程序员的需求;另一方面,信息技术与其它传统领域的结合(数字化转型),会使得程序员供给增加,各个行业都会着力培养自己行业的程序员。考虑这种趋势,会发现程序员比机器更贵的趋势将来可能会扭转。
与此同时,自然环境的约束,会使得人类社会更加重视节能减排,以满足碳排放的控制。这种趋势是强有力的,在电力行业已经产生了几乎颠覆性的影响,很快也要在计算机行业产生更大的影响。在传统上,信息技术工作者较少考虑供电的问题。比如说,在5G研发之前,通信行业的工程师几乎没有人预测出5G基站的耗电竟然如此之大(虽然从物理学上分析出5G更耗电是不困难的)。根据网上的一些报道,全国5G基站电费增加数百亿元人民币,几乎占据利润的大部分。
根据国外的统计数字,数据中心和数据传输网络分别占全球用电量的 1-1.5%。对于个人和工商业办公用到的个人电脑、手机的耗电量一直缺乏细分的数据,这部分都算在工商业用电和居民用电内部了,包括暖气、冷却、照明、烹饪、家电,等等。可以认为,分散的个人信息设备、嵌入式设备,总用电量应该超过数据中心,因为数据中心的集中管理会使得能效更高。因此,全球的总信息设备用电量,可能会接近5%,这已经是非常巨大的电力消耗了。在未来,人工智能应用如果进一步发展,可能会有更大的比例。
事实上,在信息技术方案设计方面,能效已经是影响很大的因素。比如在手机领域ARM芯片的快速推广;在数值计算领域,GPU不仅更利于浮点计算,而且能效超过CPU,这些都是显著的技术趋势。
下面从软件开发的角度来分析能效因素。关于不同编程语言的能源消耗,2017年有篇论文影响很大,题目是《Energy Efficiency Across Programming Languages》,作者通过标准化算例分析了不同编程语言的性能、能耗、内存占用。最重要的指标当然是能耗。
根据论文的数据,总体上,编程语言可以分为三类,分别为编译型语言(代表是C/C++/Rust);基于虚拟机的编译型语言(代表是Java/C#);基于虚拟机的解释型语言(代表是Python/PHP)。假定C语言的相对能耗是1.0,则编译型语言的能耗接近1;基于虚拟机的编译型语言能耗为2~3;基于虚拟机的解释型语言能耗大约为30~80。
由于是标准化测试,各种指标是接近的。对于真实的大型工程项目,不同类型的编程语言可能差距会更大。这项研究表明,编程语言的选择可以显着影响能源消耗,C、C++ 和 Rust 等编译类型语言通常比在虚拟机上运行的 Java 等语言或 Python 和 Perl 等解释语言更节能 。
上述结果是符合计算机体系结构原理的,计算机的耗能80%由CPU消耗。编译型语言便于控制内存布局、进行性能方面的优化,提高CPU的运行效率。C语言已经成为事实上的低层语言标准,如果我们进行系统级编程,那么零开销抽象就非常重要,这方面C++ 和 Rust是候选。
除了编程语言,信息技术在以下方面也要注意能效:
在数据中心方面,需要优化数据中心的冷却系统和能源使用效率,使用更多的可再生能源,包括使用更高效的通信协议和网络设备。在寒冷地区,数据中心可以利用外界低温来提高冷却系统的效率,因为外界的低温空气可以直接用于降低内部设备的温度,从而减少机械制冷所需的能量。这是为什么很多大型数据中心会选择在气候较冷的地区建设,以此来降低冷却成本和提高能效。当然,数据中心靠近水电站也是一个可行的想法,可以减少长距离电能传输过程中的损耗,同时有充足的水资源进行直接或间接冷却。
最后谈一谈网络通信的能耗影响,由于现代交换设备和网卡工作的原理,CPU需要付出大量资源进行数据包的快速接收处理,这方面的能耗是不可忽略的。同时,带宽也是资源,也有很多建设费用。因此,在数据产生的地方(如边缘计算)进行更多的数据预处理和分析,减少数据传输需求,实现信息处理的本地化,也是一个趋势。
1)在计算机系统中,硬件和软件是相互依存的两个方面。无论是最简单的个人计算机还是最复杂的超级计算机,它们都需要这两个组件紧密协作,才能完成各种任务。例如,操作系统软件协调硬件资源的使用,应用软件则提供特定的功能,如文字处理、图像处理或数据库管理。即使是FPGA,似乎是纯粹的硬件,它的设计是离不开EDA软件工具的。
这一点与电力系统是一致的,硬件是电力设备和二次设备中的装置平台,同样需要软件的组织,与管理体系和经济社会连接,才可以作为整体运行。
2)曾经由软件执行的大量计算任务,渐渐转移到专为这些任务设计的硬件上。显著的例子是图形处理单元(GPU)。最初,计算机图形主要由CPU处理,但随着图形处理要求的提高,GPU应运而生,专门用于处理复杂的图形和图像计算。这不仅极大地提高了处理效率和性能,还减轻了CPU的负担,使其可以处理其他任务。这种“硬件化”的趋势也出现在了网络处理、数据加密、音视频处理等领域。但是新的需求往往是先在软件层次实现的,直到成规模后才考虑硬件化。硬件和软件是此消彼长的过程。在过去的10多年时间,软件技术更加活跃。但是近期,乃至未来的若干时间,反而是硬件发展更快,需要软件体系迅速改变(尤其是底层基础设施),跟上硬件的发展。
在电力系统方面,同样存在一次系统投资建设与二次设备(包括自动化和信息化系统)之间此消彼长的过程,不同的历史时期重点是不同的。
3)在计算机系统的发展中,协议扮演着至关重要的角色。协议定义了不同计算机组件、系统、网络及其它设备之间交流的规则和标准。这一点对于硬件和软件的协同工作尤为重要。协议确保了来自不同制造商的组件和系统能够无缝对接和交互,从而支持了一个多样化且互操作的计算生态系统。例如,网络协议如TCP/IP为不同类型和品牌的设备之间的通信提供了标准化的方法,而数据传输协议如USB和PCI-E则确保了各种类型的硬件设备能够有效地相互连接和通信。协议制定好后,不同的厂家都可以各自精益求精的专心研发,然后组合成完整的系统。
在传统上,电力系统的技术承包商竞争不激烈,因此协议的重要性显著不如计算机系统。但是未来,随着电改的深入推进,电力系统的开放性必将大幅度提升,未来的电力系统协议,以及不同技术厂商和甲方的交互,将更加显著。
当前C++的应用场合已经比较少,至少不是热门的技术。即使是重点大学的计算机专业学生,很多人也不会把精力用于学习C++语言。但是在目前的阶段,我们依然不能说C++已经属于被淘汰的技术,因为C++语言更多成为信息世界的基础设施。
计算机和信息技术为什么可以成为新一次的科技革命?首要原因是计算机技术大大推进了生产力的发展。回看历史,计算机和网络技术是先用于军事,然后用于工业生产,最后再应用于娱乐和消费端。你不能说消费比生产更重要,毕竟只有消费没有生产,人类立刻就要灭亡。但是中国的计算机行业由于发展阶段,以及国际分工等原因,主要只发展消费端和娱乐。对于计算机底层和应用于工业生产,是严重缺失的。按照我粗略的估计,工业界的程序员数量恐怕只有消费端的10%吧,收入也低。
如果软件工程只局限于消费、娱乐,那C++确实不是好的选择。但是如果是做工业软件,C++作为系统性编程语言,是非常合适的。拿我现在的工作来说(国产的实时仿真装置),C++几乎是唯一的选择。
首先,有硬实时的要求,这就直接排除python和JAVA了,Julia语言也达不到硬实时的要求。
其次,有大量的科学计算,哪怕是计算效率比竞争对手高10%,那都是了不起的优势,因为确实可以降低用户的成本。在高性能科学计算方面,似乎C语言、C++、Fortran都可以胜任。但是从软件工程方面,C++多范式的特点,用的好的话可以提高研发效率。比如说仿真某个装置,几十个型号都需要开发相应的模型,用C或者Fortran怎么行?很快代码就要变成一团乱麻了。Rust也是系统编程语言,但是Rust的优势是减少内存bug,在科学计算方面还未听说Rust也很强的。
有半实物仿真的需求,这就要求仿真软件能够无缝对接各种驱动,甚至深入到操作系统内核,而不是仅仅调用操作系统提供的接口。例如,连接FPGA装置需要走PCI总线,还有链路层的网络报文(无法使用TCP/IP协议,因为性能达不到要求)。由于操作系统和驱动都是C语言写的,因此在这方面C和C++都符合要求,其它语言都增加了间接的一层封装。
综上,对于工业软件来说,C++是非常合适的,很多时候也是唯一的选择。
C++的设计原则主要是一些个人的经验,只可以参考,不一定适用所有的场合。
我不止一次看到这样的案例:某个复杂业务系统,几十个人进行设计,设计的似乎完美无缺,无论是上千页的设计文档,还是精美的PPT,都说明这个系统会是尽善尽美的。但是经过开发、工程投运之后,用户却发现这个系统完全不好用,就算用的起来,那也是磕磕绊绊、投入大量的人力去维护。
这说明设计和工程实施出现了脱节。问题之一是设计人员中或许有业务专家、或许有计算机高手,但是没有兼具一线业务与计算机专业知识的人,造成设计想法的难以落地。其次是,设计是容易滥竽充数的,不像软件开发有那么多的测试工具、静态检查工具、代码审查等。有的人,连Python都不会写,就可以为复杂C++程序进行设计了,这肯定会出大问题啊。网上有很多讽刺“PPT架构师”的段子,建议大家去看看。
为了避免架构的脱离实际,我建议设计的产出物,至少是一个可运行的原型程序。这个原型程序可以没有界面和实质性功能,但是至少可以显示业务流程。这也是为后续实质性的研发,打下一个很好的基础。当然设计文档也是需要的,但是没必要对设计文档提出过多的形式化要求。把设计写在黑板上,用手机拍下来,如果研发人员都觉得看得懂、思路清晰,那就可以作为设计文档。关键是解决实际问题,解决问题是第一位的。
Don’t Repeat Yourself:当在两个或多个地方发现一些相似代码的时候,我们需要把它们的共性抽象出来形成一个唯一的新方法,并且改变现有地方的代码让它们以一些合适的参数调用这个新的方法。
最少知识原则:一个类对于其他类知道的越少越好,就是说一个对象应当对其他对象有尽可能少的了解,只和朋友通信,不和陌生人说话。反面教材:有人喜欢用全局变量,写的时候确实很爽,随心所欲。
单一职责原则:一个类,只做一件事,并把这件事做好,其只有一个引起它变化的原因。职责过多,可能引起它变化的原因就越多,这将导致职责依赖,相互之间就产生影响,从而极大地损伤其内聚性和耦合度。把模块间的耦合降到最低,而努力让一个模块做到精益求精。使用多个专门的接口比使用单一的总接口要好。反面教材:有的大型系统经常出现几万行代码甚至更多行的超级大类,这种类很快陷入不可维护的境地。如果作者转岗、辞职,其它同事对于这些代码只能干瞪眼,很多时候只能推倒重来。
开闭原则:模块是可扩展的,而不可修改的。也就是说,对扩展是开放的,而对修改是封闭的。对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。对修改封闭,意味着类一旦设计完成,就可以独立完成其工作,而不要对类进行任何修改。反面教材:很多程序加一个简单功能都需要修改几十个地方,用户或者领导非常困惑:加的功能太简单了,看样子几分钟就能开发出来,为什么他们说至少需要1个月呢?
关注点分离:问题太过于复杂,要解决问题需要关注的点太多,而程序员的能力是有限的,不能同时关注于问题的各个方面。实现关注点分离的方法主要有两种,一种是标准化(大家都按照标准行事,最后拼装成大系统),另一种是抽象与封装。
依赖倒置原则:高层模块不应该依赖于低层模块的实现,而是依赖于高层抽象。在依赖结构中不允许出现环(循环依赖)。这就需要对软件的层级结构很好的设计,绝不允许出现混乱。有时候临时一个需求,随手就开发出来,看样子工作能力强、业绩优秀。但是却破坏了整体架构,久而久之就不可收拾了。
除了上述要点,写大型C++程序,测试也是非常重要的。有的人甚至推荐测试代码要先于业务代码去编写。有的业务,测试代码的行数比业务代码还多。测试驱动有专门的方法论、工具与管理措施,我这里就不展开说了。
使用C++编程,很难回避与C语言的混合编程,这是因为这两种语言天然接近底层,而操作系统和驱动,以及许多高性能的库,是C语言编写的。C语言本身的范式属于直接实现设计思路,语言与汇编严格对应。编译器很少会做出让你大吃一惊的“优化”。C++编程在很多场合,例如与C语言的混合编程,以及某些特殊需求,是有必要采用C语言范式的。
我有个观点,不知道对不对,就是你用C++如果完全用不到指针,那真的不如转到Java等开发效率高的语言,指针是C语言最显著的标志之一。一旦与驱动、内核打交道,指针就是无论如何也绕不过去的关键概念了。你用python等语言,觉得没指针也好好的,那是因为某种中间层(JVM、CPython等)屏蔽了底层,这样也好,提高了很多开发效率。我有时候写高性能的仿真程序,要求时钟控制在微秒级别,很多时候不得不用裸指针,有时候也不得不改写Linux内核程序,虽然看起来这种做法严重违反了现代软件工程的推荐做法。
当然在用户态编程,如果不涉及操作系统内核,那还是有必要用智能指针包装一下的。有人提出操作系统内核其实也可以用智能指针。
基于对象更多的是用类进行封装,但类与类之间没有继承、多态等复杂关系。关于类,大约可以分为两种:1)一种类存在对资源的抽象封装和管理,例如动态内存、文件句柄、socket等,这种类需要仔细安排资源管理,定义或者禁用拷贝构造函数;编写析构函数,这就是著名的RAII(资源获取就是初始化)原则;2)另一种类并没有对资源进行管理的职责,可以直接复制,例如矩阵、字符串等,这种类就没有必要实现“深拷贝”,没必要写析构函数。
把类的作用分别搞清楚,就可以解决大多数关于类的业务问题。
面向对象是很多教科书,特别是设计模式方面的书详细介绍的。优点:大量的经验(设计模式),符合人的直觉,与真实世界对应。缺点:太多的封装,代码一层层套;重构困难;虚函数的运行效率会略有下降;使用虚函数会有二进制兼容性问题(与C语言不兼容,C++编译器会自动安排虚函数表及虚函数指针,这是用户无法看见的)。
有的人对面向对象严厉批评,比如陈硕、王垠等人,觉得是“花拳绣腿”;当然也有人,例如罗剑锋、吴咏炜等人持中立的态度。
我觉得,用不用面向对象,需要具体问题具体分析。例如设计模式中的组合模式,用基类加继承是合适的。但一定不能滥用,毕竟使用C++的首要保证高性能,至少不能像JAVA那样滥用面向对象。
顺便提一句,用C语言(包括嵌入式)也可以实现面向对象的特性,只需要把函数指针表处理好就可以。
如果说面向对象是运行时候的多态,模板就是编译时候的多态。其中STL对于数据结构和算法的解决,是泛型极其精彩的应用案例。STL的设计与面向对象是毫无关系的,有些初学者可能把C++当成面向对象的语言,其实不是的,C++是多范式语言。
泛型/模板、编译期编程,还可以用“模板元”的方式,这部分抽象程度太高,至今我也不会,就略过不谈。
C++11引入的lambda 表达式,使C++逐渐具备函数式风格。函数式可以理解为更高抽象层次的泛型,它可以把函数打包成变量,然后就可以实现“函数的函数”。在许多场合,函数式可以极其简化程序,例如设计模式中的行为模式,可以用函数式的风格,避免使用继承。对于STL的有些应用,使用函数风格,可以降低循环的出现次数。
函数式编程期望函数的行为像数学上的函数,要点在于:
会影响函数结果的只是函数的参数,没有对环境的依赖;
返回的结果就是函数执行的唯一后果,不产生其他影响;
函数就像普通的对象一样被传递、使用和返回;
代码为说明式而非命令式,可读性高。
但是,走到极端也是不行的,不能为函数式而函数式、滥用函数式。我觉得,函数式可以是其它范式的一种补充。
关于读技术书籍,尤其是C++方面的极端强调实践的技术书籍,有以下原则:
明确读书的目的是为了用于实践。好的技术书籍来源于工程实践,那么读者读完之后用于实践,才是一个最合理的闭环。如果读书的目的是为了考试得个好成绩,又或者想把论文包装的高大上,又或者想增加些炫耀的资本,那就有失偏颇,而且未必能有预想中的效果。即使是从掌握知识的角度来说,学以致用也是最高效的,死记硬背是低效的。有时候,因为条件所限,暂时找不到合适的应用实践环境,怎么办呢?其实把所学的知识整理之后传授给他人也是一种实践活动。如果没有人愿意来学,自己整理并系统化,也比被动的学习效果好的多。
分清主次。把知识点详略程度和重要性划分一个坐标系,那么会存在四种区域。区域一:重要且简略的知识。例如面向对象设计的几个设计原则(如里氏替换、多用组合少用继承等)。这部分知识当然是重要的,但是过于强调则容易陷入教条主义,脱离实际。区域二:重要且详细的知识。主要是涉及到重要的实操、案例、说明、推导等。这部分知识当然要详细掌握,但不能只见树木不见森林。区域三:不重要但详细的知识,包括许多细节,随着技术进步可能会被淘汰的部分。这部分最好是随着实践自动掌握的,不值得花脱产的时间去学习。有的人特别喜欢深究这部分知识,美其名曰抓细节、性格仔细认真,其实陷入牛角尖而不自知。区域四:不重要也不详细的知识,例如花絮、人物生平、补充说明之类的,但如果想深入进阶,有的地方还是不能略过不看。读书时要会分辨知识是属于哪个区域的。
及时复盘。我读《深入理解计算机系统》,其第一章讲的是C语言hello world程序执行所发生的背后的事情。第一次看的时候觉得好神奇(特别是对于我这样的非计算机专业的人来说)。但是这本书仔细读过一遍重新看,只觉得第一章写的简直酣畅淋漓,真的是高屋建瓴,非常自然(看山还是山,看水还是水,hello world还是hello world),对作者的佩服更加深了一层。
下面是具体推荐的C++书目。首先是基础类:
BjarneStroustrup 著.A Tour of C++, 3nd ed 中文版:pansz(潘诗竹)译,《C++ 语言导学》第三版。电子工业出版社,2023。这是一本小册子,简明扼要的介绍了现代C++(C++17标准,后面应该会再更新)。篇幅不长,但是极其权威。
Bjarne Stroustrup 著.《C++程序设计原理与实践》。这是C++之父专门为大一新生写的教科书。当然难度比真正的“教科书”难得多。但不管怎样,C++之父百忙之中为初学者写书,是非常难能可贵的。这本书明显看出来大师跟普通的C++教师的差距,即使是面向初学者,书里面也涵盖了矩阵计算、嵌入式编程、GUI编程、测试等实用性内容。这本书对于C++教师也是很有参考意义的。
《C++ Primer》(第五版)。这可能是销量最大的C++教科书。问题是只支持到C++11标准,但是作者已经去世了,看来再版是很困难了。读这本书,不用担心作者讲错了,可以说几乎毫无瑕疵。里面的案例很值得揣摩,比如文本查询程序。几乎每个章节的示例程序,都值得敲到电脑里面执行、揣摩。我在工作中看到,很多程序员被一些bug卡死,有的bug几天都解决不了,仔细读《C++ Primer》往往直接就可以避免。
《C++ 标准程序库》。C++标准库是一个宝库,虽然很多人诟病C++标准库的内容还是不够多(例如没有网络库)。但不管怎么说,C++标准库是世界顶级水准的代码,用C++,当然是优先使用C++标准库的设施。这本书专门讲解C++标准库,篇幅很长,是非常好的工具书,我办公桌常年就摆着这本书。
下面是C++进阶类或者专题类:
《Effective C++》、《Effective Modern C++》。这两本书的作者是同一个人,而且是大师。他把专家经验,深入浅出的写出来。熟读C++教科书,并不能让你可以上手干活,该踩的坑还是会踩。读专家经验是一个很好的过渡过程。
《Effective STL》、《STL源码剖析》。这两本书是对STL进行深度挖掘或者剖析的,但要注意这两本书出版时间都很久了,有些条款已经不适合C++11以后的标准。
《C++程序设计语言》。Bjarne Stroustrup 著,这是最权威的、最全面的C++专著。内容丰富、几乎覆盖整个C++标准。但是我个人觉得这本书更适合查缺补漏。从头到尾读实在是一个挑战。
《Linux多线程服务端编程》,作者是陈硕。这本书其实主要是介绍作者开发的开源软件,这个软件对TCP服务器通信进行了封装。由于C++程序员几乎都会涉及网络通信,因此读一本这样的书是有必要的。除此以外,陈硕分享了他的一些C++工程经验,其中关于多线程编程、编译过程、日志等都有独到的见解。
还有一本书我之前经常推荐,那就是《深入理解计算机系统》,虽然与C++没有直接关系,但是这本书详细全面介绍了计算机软硬件系统。对于想要写出高性能C++程序来说,仅仅学习C++本身是不够的,《深入理解计算机系统》是非常好的进阶教程。
把上面的书读完需要很多时间,1年的时间肯定不够,但是是值得的。有的人追着热点走,什么流行就做什么,浮光掠影,他们到底有多少东西在工业现场真正发挥着基础设施的作用,是可以打一个问号的。把基础打好,做一些有深度的事情,虽然不光鲜,可能也没荣誉,但终究会有长远的回报。