浅论C++的复杂性

C++语言已经有了30多年的历史。作为一门影响广泛的编程语言,它所受到的关注和争论恐怕是任何一门其他的语言所不能比拟的。十几年前,Java等新生语言的出现曾导致“C++信任危机”,但最终C++以自身非凡的品质屹立于主流编程语言的行列。在有着众多编程语言可以选择的今天,到底还有没有必要学习C++?怎样学习C++?怎样使用C++?对于广大的程序员,特别是对于刚刚接触编程的学习者,这些问题都是至关重要的。

C++语言遭受批评最多的是它的复杂性。对于这个话题,已经有很多的文献讨论过。在这里,不想提出什么新的观点,或者根本就提不出什么新的观点,只是想把自己的感触和大家进行简要的谈谈。

1. C++真的很复杂吗

这个问题的答案是肯定的。从C++语言本身的发展和组成来看,C++语言并不是一种单一、“纯粹”的编程语言,他有着复杂的内部结构。

最初,C++仅仅是在C的基础上附加了一些object-oriented(面向对象)的特性。C++最初的名字是“C with Class”。以后C++不断的创新和发展,融入了procedural(过程化),object-oriented(面向对象),functional(函数化),generic(泛型)以及metaprogramming(元编程)特性。这些能力和弹性使C++成为强大而又复杂的工具。

面对如此复杂强悍的编程语言,我们该如何理解它和学习它呢?最简单的方法就是将C++视为一个由相关子语言组成的联合体。在每一个特定的子语言中,它的特性趋向于直截了当,简单易记。但当你从一个子语言跳转到另一个子语言的时候,它的规则就会发生变化。C++的子语言有4个。

(1)C 归根结底,C++仍然是基于C的。blocks(模块)、statements(语句、preprocessor(预处理器)、built-in data types(内建数据类型)、arrays(数组)、pointers(指针)等,全都是来自于C。在很多方面,C++提出了比相应C版本更高级的解决问题的方法,例如内联函数、引用、函数和操作符重载等。这些特性能够和传统的C很好地结合在一起,可以视对C的扩充体现了C++的“A better C”的初衷。

(2)Object-Oriented C++ 面向对象的C++就是C with Classes涉及到的全部:classes(类)、encapsulation(封装)、inheritance(继承)、polymorphism(多态)、virtual functions(虚函数)等。C++这一部分直接用于object-oriented design(面向对象设计)的经典规则。

(3)Template C++ 这是C++ generic programming(泛型编程)部分,大多数程序员对此缺乏经验。Template(模板)的考虑已遍及C++,而且好的编程规则中包含特殊的template-only(模板专用)条款已经不再不同寻常。实际上,tempalate(功能)极为强大,它提供了一种全新的programming paradigm(编程范式)——template metaprogramming(TMP,模板元编程)。

(4)STL(Standard Template Library,标准模板库) STL是个template程序库,看名称就知道,但它是非常特殊的一个。它对容器(container)、迭代器(iterator)、算法(algorithm)以及函数对象(function objects)的规约有极佳的紧密配合与协调。但是templates即程序库也可以以其他的方式建立起来。STL有很多独特的处理方法,使用STL编程时,需要遵循它的规则。

C++的四种子语言(sublanguages)紧密地结合在一起,但它们的确又有各自鲜明的风格。当从一种子语言转到另一种时,为了搞笑编程时需要改变编程的策略,这是C++程序员可能遇到的情形,对此必须有心里准备。例如,使用built-in(内建)类型时,pass-by-value(传值)通常比pass-by-reference(传引用)更高效。但当从C++的C部分转移到Object-Oriented C++(面向对象C++),由于传值传值调用会导致建立参数的副本,调用用户自定义的构造函数和析构函数会降低效率,所以更好的做法是传const引用。在Template C++中工作时,这一点更加重要。因为在这种情况下,你甚至不知道你的操作涉及到的对象的类型。然而,当你进入STL,由于iterator(迭代器)和function objects(函数对象)以C的pointers(指针)为原型塑造出来的,所以对STL的迭代器和函数对象而言,旧式的C中的pass-by-value(传值)规则又重新生效。

因此,C++不是使用一套规则的单一语言,而是由上面四个子语言组成的联邦语言。每一种都有自己的规则。有了这样的理解,就能更清楚地了解C++的内部结构,并能根据不同的应用需求使用不同的子语言,充分发挥C++语言的长处。

C++的的复杂性可以体现在以下三个方面: (1)学习周期长。C++由于其内在的复杂机制,要想成为一名合格的C++程序员,业界的规律是3到5年。

(2)开发效率低。主要是历史原因,C++诸多库都停留在“很低”的层次上,使用的便利性无法与RAD工具(Rapid Application Develop,快速应用开发)相提并论。有人提议将C++库的层次提高,但是这是一件非常苦难的工作,原因是这与C++语言的设计理念是有冲突的,C++希望最大限度地保持通用性和底层性。

(3)容易犯错,维护难度大。C++是一种功能强大且自由度极大的语言,使用C++的过程中一不小心就犯下错误,留下代码漏洞,特别对于初学者,要能够自如高效的使用C++语言需要很长时间的磨练。

2. C++语言复杂的原因

C++复杂的真正原因是什么?对此,仁者见仁智者见智。因为是学院派的东西吗?不,学院派的出来的东西就一定复杂吗?这个理由站不住脚。

经历三十多年的发展,C++的触角已经遍及了当今世界学术、工业界的方方面面,体积虽然庞大,但结构却很清晰,C++并不因此而复杂。

C++是因为支持的编程范式太多了吗?也不是,新生的语言几乎都在走C++的成功范式,Python和Ruby等新型动态语言的范式甚至更多,然而它们却以简单和开发效率高而著称、其实C++正真复杂的原因在于,坚守三大原则决不妥协。一是对C的完全兼容,而是静态类型检查,三是最高性能。而其中最高性能又是这三大原则中的重点。既要发展新的特性,同时又要保持最高的性能,这是C++语言复杂性的根本原因。

C++没有采用一些可能会降低程序性能的做法,如采用来及回收机制等。而这些做法是有可能降低C++的复杂性的。Bjarne Stroustrup教授(C++之父)在多种场合下表示,对C++的设计没有大的后悔支出,原因在于对三大原则的坚持首先是正确的,然后,若坚守三大原则,即使重新设计一遍C++,结果也与今日相差不远。

3.需要学习和使用C++吗

既然C++如此复杂,那么有必要学习和使用C++吗?

对于这个问题,无法给出强制性的回答。在这个世界上,一定存在从来不用C++编程能够出色完成特定编码工作的程序员,也许他们所使用的语言就是Java、C#或者其他的编程语言。但是,我的建议是需要就用,不需要就不用学。但是,C++是一门优秀且值得学习的语言。原因是C++具有如下特性。

(1)C++是一门贯通低级到高级的语言 C++语言向下兼容C语言,能够直接通计算机的硬件和底层打交道,甚至能够直接使用内联汇编。向上,C++语言是4中子语言的而结合体,它所能支持的特性的丰富程度也是其他语言所难以企及的。对于一个能够静下心来,能够持续持续不断努力提升自己对计算机系统理解程度(计算机体系结构、硬件、操作系统、应用开发、软件项目和过程管理)的程序员来说,C++语言是一个绝佳的选择。

(2)C++是一种高效的语言 C++程序的执行效率与C语言相当,同时又提供了诸多的高级特性。这样,C++语言为程序员创造了这样一种可能:在利用各种高级特性(面向对象方法、泛型编程等)充分表达设计思想、解决各种复杂问题的同时,保持应用程序的高效运行。这也是其他编程语言难以做到的。

(3)C++是一门复杂的语言 这个观点听起来有些怪异。C++语言的复杂性往往是造成人们放弃C++的原因,但同时,C++语言的复杂性也有可能成为人们选择C++语言的原因。C++的先去大师Andy Koenig在他的《C++沉思录》里回击了对C++复杂的攻击。他认为,选择什么样的编程语言,取决于要解决的问题。世上没有万灵药,要解决复杂的问题,必要要依赖于复杂的工具。C++程序员是实用主义者,他们首先保证问题能被解决,其次才能谈得上其他。实际上,要解决的问题是复杂的,计算机系统使不完美的,人类的自然语言体系和表达习惯就更是不完美的。而一门成熟的通用编程语言,要在这三极之间保持平衡,谈何容易。Java语言通过削减矛盾(用虚拟机代替真实机器),削减表达能力来获得简单性,这也同时限制了它在实时性高计算密集的领域里得到应用。无论是调度仿真、实时控制还是媒体编辑,一旦触及重量型的关键应用,除了C++你别无选择。C++的复杂性源于对其高效解决问题的承诺。这就好比,现实生活中,思想简单的人不能委以重任。

(4)C++是一门成熟的编程语言。这并不是说其他的编程语言不成熟。成熟是一种相对的概念。C++语言在其30多年的发展和使用过程中,开发了无数成功的软件系统,积累了丰富的成功案例和可重用资源。其数量之大,应用之广,影响之深,也是首屈一指。有兴趣的读者可以光临Bjarne Stroustrup教授的主页,了解一下C++语言在业界创造的辉煌战绩。

4.如何应对C++的复杂性

尽管C++的复杂性有其产生的深刻背景,但复杂性确实是个问题。在实践上最突出的表现就是开发效率的降低,毕竟简单易用的工具能带来生产率的提高。但是C++的复杂性导致了开发效率的降低只是一种表象,它是没有对复杂性进行有效控制而产生的后果。换句话说,问题不在于C++的复杂性,而在于使用C++的人有没有有效控制这种复杂性。

那么,如何应对C++的复杂性,下面给出几点建议。

(1)用沉稳的心态去学习C++ 学习编程语言,掌握语法,能上手实践,不过是万里长征迈出了第一步。更何况想C++这样的语言,要做到掌握各个子语言的基本内容,都不是一件容易的事情。所以,心态一定要平稳,着急不来,更不可轻狂。要真正掌握语言,非得集中精力学习实践一两年,将该语言所擅长领域的应用问题熟悉过一遍,才有可能。若论精通,啊那是一个没有止境的过程,Henry Spencer用了30年C,仍乐此不疲。Pragmatic Programmer中评价Ruby说,学上四个小时就可以用它解决实际问题,但是10年之后还为它层出不穷的心意感到惊讶。真正掌握C++语言之后,再熟悉一两门层次不同,思维不同的语言,那就是更高层次的追求了。

要注意的是,这也是一个心理学的问题。C++语言中总是存在着一些新奇的特性,它会引起你强烈的兴趣,将你的注意力从真正有用的事情中分离出来。这些被称之为“奇技淫巧”的东西即使能短暂给你带来自豪感,但是不应该成为你学习C++的主流。要注意,不要为了使用每一种特性而去使用,要根据实际问题和项目的需求去应用C++的特性。

(2)采用科学的学习方法 全面掌握C++固然重要,但是那不等于说,只有掌握了C++全部你爱能用来它解决问题。你可以把你对C++的理解限制于一个相对简单的程度,只要你需要解决问题的复杂度不超过你所掌握的工具的复杂度。初学者要把C++分为逻辑层次上、难度比较独立的部分,根据自己的需要循序渐进地的学习,利用每一部分所学解决能够解决的问题。只有这样,才能学得扎实。不要怕碰到问题,从某种角度来说,遇到问题是好事,因为这是弥补自己在某方面的无知的机会。自己不懂得东西太多了,只是还未暴露出来。解决了问题,你就学到了东西。

(3)正确的使用C++ C++被错误地使用是一种很普遍的现象,这也是C++遭受“过于复杂”的抱怨的真正原因。C++语言由4个子语言组成,C++语言提供了如此丰富的特性和自由度。如何选这些特性体现了C++程序员的真正“功力”和成熟度。

首先,要小心选择你所使用的子语言。例如,C++是向下兼容C的。那么,是不是在任何场合下,都要使用C++的面向对象的特性呢?或者无论在什么情况下,都选择C,因为C更简单?这是一刀切的思维实不可取的。显然C有自己擅长的领域,比如设备驱动开发、操作系统的大部分工作都不需要OOP/GP( Object Oriented Programming/Generic Programming)。然而,在更多领域,抽象与效率是并重的,这些正是C++的面向对象的特性适用的场合。

其次,充分利用现有的、经过实践检验的资源。代码重用是现代软件工程提倡的一种做法,不仅因为它可以提高开发效率,还因为它可以降低程序的复杂程度。如果一个高效的容器(或智能指针)能把你从无聊的手动内存管理中解放出来,为啥还要用那原始的malloc/free呢?如果一个好的string类或正则表达式类能把你从繁琐的字符串处理中解脱出来,那么为啥要手动去做这些是呢?如果一个transform(或for-each)能够用一行代码把事情漂亮搞定,为啥还要手写一个for循环呢?

再次,控制你代码的复杂程度。C++语言不是为了复杂而复杂,而是因为要解决复杂的问题而引入了复杂的机制。问题的关键在于,程序有时是自己把问题搞复杂了。例如在C++中,一个普通程序员很可能会写出一堆高度耦合的类,很快情况就变得一团糟。但这不是C++的问题,这种情况很可能发生在任何一门面向对象语言中,因为总有程序员在还没有弄懂什么是has-a和is-a之前,就敢于在类上再写类,就这样一层一层的堆砌上去。它们学会了在一门特性语言中如何定义类,如何继承类的语法,然后就认为自己已经掌握了OOP的精髓了。

由于C++是如此灵活,很多问题在C++中都有好几中解决办法,于是在这些选择中进行权衡本身就成了一个困难这也是得程序员犯错误的可能性增加了。所以掌握一门优秀的设计思想(比如说优先使用组合而不是继承),或者遵循C++社群这些年积攒下来的只会,或者说干脆只使用C++语言中C with Class部分以规避复杂性的风险,都是程序员需要不断学习和不断实践的。 总之,正确使用C++所应遵循的原则是:了解C++的高级特性,用简单的方法解决简单的问题,用简单的形式解决复杂的问题,即将复杂的解决方案包装在简单的形式之下,重用前人的劳动成果,遵循最佳的实践。


参考文献

[1]陈刚.C++高级进阶教程[M].武汉:武汉大学出版社,2008[P419-P423] [2]Scott Meyers(著),侯捷(译).Effective C++[M].北京:电子工业出版社,2011:11-13

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏程序员互动联盟

【编程牛人】C++之父

本贾尼·斯特劳斯特卢普 1982年,美国AT&T公司贝尔实验室的Bjarne Stroustrup博士在c语言的基础上引入并扩充了面向对象的概念,发明了—种新的...

42360
来自专栏程序员互动联盟

编程自学成才要多久?

java自学需要2个月左右就能写点小东西,半年就能上手做一些东西,一年就该知道的都知道了,该会的也基本会了,易上手好找工作。 C语言时间会长点,如果你英语好,数...

39370
来自专栏程序员互动联盟

学会了C语言,能开发什么?

看招聘职位要C语言的比例真不多了,是不是C语言真的过时了? 看看很多招聘网站有关找纯粹的C语言开发的比例真的不是很多,都被Java,php,python等等语言...

63160
来自专栏Albert陈凯

2018-11-20 老码农教你在 StackOverflow 上谈笑风生

作为一个高大上的码农,你肯定用到过 StackOverflow,必须的。会有人否定这个断言么?那他恐怕不是真正的码农,或者说还没入门。StackOverflow...

64720
来自专栏BestSDK

C++是程序界的“屠龙刀”,但是握刀的人经常用成“杀猪刀”

C++的初衷 1)早期C++许多的工作是对于C的强化和净化,并把完全兼容C作为强制性要求。C89、C99中许多的改进正是从C++中所引进。可见,Stroustr...

30340
来自专栏LET

我的面试心得:面试官视角

15760
来自专栏企鹅号快讯

Python为什么这么火?聆听Python之父诠释Python的精神和文化

最近EETOP先后发过几篇关于Python的相关文章: 用Python给头像戴圣诞帽 Python实例代码:ADC拟合、频谱计算 用Python设计芯片 基于P...

24560
来自专栏张善友的专栏

十年学会程序设计

这里分享一篇 Peter Norvig的 《十年学会程序设计》 (Peter Norvig  系Google研究院主任、美国计算机协会(ACM)资深会员(Fel...

25550
来自专栏编程

你应当如何学习C加加以及编程

Javascript是世界上最受误解的语言,其实C++何尝不是。坊间流传的错误的C++学习方法一抓就是一大把。我自己在学习C++的过程中也走了许多弯路,浪费了不...

23990
来自专栏程序员互动联盟

作为大三的学生,需要为找工作做点什么准备?

疑惑一 数据结构很难嘛? 很多小伙伴在微信后台问,数据结构为啥学起来这么难,数据结构其实就是在c语言的基础上对数据进行抽象的处理,其实就是在基础语言的基础上进一...

34580

扫码关注云+社区

领取腾讯云代金券