C语言与C++:一场编程的对比分析

发表时间: 2020-12-25 11:31

这个问题最权威的回答看看C++语言之父Bjarne Stroustrup是如何讲解的。

由于引进了C++ 这个名字,写出了C++ 的参考手册 [Stroustrup,1984],与C语言的兼容性问题就变成了一个最重要的问题,而且也成为争论的焦点。

还有,到1983年后期,贝尔实验室里负责开发和支持UNIX、生产AT&T的3B系列计算机的分支机构开始对C++ 感兴趣,它已经希望为C++ 工具的开发投入一些资源。不幸的是对于使C++ 的发展由一个人独舞转变为一个公司的支持关键性项目所用的语言,这种发展情况确实非常有必要。然而,这同时意味着在开发管理层也要考虑C++ 了。

开发管理层发出的第一个命令就是要求与C的100%兼容性。与C语言兼容的想法非常明显,也很合理。但程序设计的现实则不那么简单。作为第一步,C++ 到底应该与哪个C兼容?到处都是C语言的方言,虽然ANSI C已开始出现,但是得到它的稳定版本还需要时日。ANSI C的定义也同样允许方言存在。我记得那时计算过——不过是作为玩笑——存在342个严格符合ANSI C标准的方言。得到这个数字的基本方法,就是列出所有未定义的或要求实现去定义的方面,用它作为算式的指数,底则采用不同可能性的平均数。

很自然,一个普通用户所希望的与C兼容,指的是C++ 与其使用的局部C方言兼容。这是很重要的实际问题,也是我和我的朋友特别关注的。业界的经理或者销售商对这方面的关心就差多了,他们或是对技术细节不甚了了,或者不过是想用C++ 把用户绑到自己的软件和/或硬件上。而贝尔实验室的C++ 开发者们则不同,他们独立于自己为之工作的机构,“把从感情上承担起兼容性的义务作为一个观念 [Johnson,1992]”,努力抵抗着管理层的压力,设法把一种特殊的C方言隐藏在C++ 的定义中。

兼容性问题的另一个方面更紧迫:“C++ 应该以什么方式与C不同,以便能达到自己的目标?”还有“C++ 应该以什么方式与C兼容,才能达到其目标?”问题的这两个方面同样重要,在从C with Classes转变到C++ Release 1.0的过程中,这两个方向上都做了一些修正。很缓慢的,也是充满痛苦的,一个共识逐渐浮现:在C++和ANSI C(当它成为标准后)之间不应该存在无故的不兼容性 [Stroustrup,1986],而确实应该有一些不兼容性,只要它不是无故的。很自然,“无故的不兼容性”这个概念成为许多争论的话题,它耗费了我太多太多的时间和精力。这个原则后来被广泛理解为“C++:尽可能地与C靠近,但又不过分近”,这是到了Andrew Koenig和我一篇以此为名的文章之后。这种策略是成功的,一个标志就是K&R2 [Kernighan,1988] 里的所有例子都是用C++ 的C子集写出来的。Cfront就是做K&R2里例子代码的基本测试时所使用的编译器。

关于模块化,如何通过组合起一些分别编译部分的方式做出一个程序,在最初的C++参考手册 [Stroustrup,1984] 里已经有明确的反映:

[a] 名字是私用的,除非显式将其声明为公用的;

[b] 名字局部于其所在的文件,除非显式地从文件里导出;

[c] 总是做静态的类型检查,除非显式地抑制这种检查;

[d] 一个类是一个作用域(这意味着类可以完美地嵌套)。

观点 [a] 不会影响与C的兼容性,但是 [b]、[c] 和 [d] 却隐含着不兼容性:

[1] 按默认方式,C的非局部函数和变量可以在其他编译单位里访问;

[2] 在使用之前不必有C函数的声明,按默认方式,C函数调用不检查类型;

[3] 在C语言里,结构的名字不能嵌套(即使它们在词法上嵌套)。

此外,

[4] C++ 只有一个名字空间,而C语言中“结构标志”有独立的名字空间(2.8.2节)。

这种“有关兼容性的战争”现在看起来是琐碎而无趣的,但还是留下一些基本问题,至今仍未解决,我们还在ANSI/ISO标准化委员会里为它们而斗争。我非常执着地认为,使兼容性战争发生而且使它出奇地范围广泛,原因就在于我们从来没有直面关于C和C++ 语言不同目标的深刻内涵,而一直把兼容性看作一些单独的需要个别解决的问题。

作为一个典型情况,在最不具根本性的问题 [4]“名字空间”上花了最多的时间,最后是通过 [ARM] 的一种妥协解决了。

在把类作为作用域的观念([3])上,我也不得不做了一些折中,在发布Release 1.0时接受了C的“解决办法”。我原来一直没认识到一个实际问题——在C语言里,一个struct并不构成一个作用域,因此下面的例子:

struct outer {    struct inner {        int i ;    };    int j ;};struct inner a = { 1 };

在C语言里完全合法。不仅如此,这样的代码甚至可以在标准的UNIX头文件里找到。在有关兼容性的斗争接近结束时,这个问题被提了出来,而我已经没有时间再去领会有关的C语言“解决方案”到底会带来什么,表示同意比与之斗争要容易得多。后来,在遇到了许多技术问题,并且接到来自用户的许多不满之后,嵌套的类作用域才在1989年被重新引进C++ [ARM](13.5节)。

经过了许多激烈的辩论之后,C++ 对于函数调用的强类型检查才被接受了(没有修改)。对静态类型系统的隐含破坏是C/C++ 之间不兼容性的一种根本性例子,但是这绝不是无故的。ANSI C委员会在这个问题上采纳了一种比C++规则和概念稍微弱一点方式,其中声明说:那些不符合C++ 的用法都是过时的用法。

我不得不接受C的规则:在默认情况下,全局名字在其他编译单位里也是可以访问的。因为没人支持更严格的C++ 规则。这也就意味着在C++里(与C类似),在类和文件的层次之上,再也没有有效的模块化描述机制了。这导致了一系列的指责,直到ANSI/ISO委员会接受名字空间(第17章)作为避免名字污染的机制。Doug McIlroy和其他人争辩说,不管怎样,如果在一个语言里,为了让一个对象或函数可以从其他编译单元里访问,就必须显式声明,C程序员不可能接受这个语言。他们在当时可能是对的,也使我避免了一个大错误。我现在也认识到,原来的C++ 解决方案也不是足够优美的。

与兼容性有关的另一问题是,在这里总能看到两条战线,两条战线的人都对自己的观点坚信不疑,认为必须为自己的情况辩护。一条战线要求100% 的兼容性——通常并没有理解这样做究竟意味着什么。例如,许多要求100% 兼容性的人在了解到这实际上意味着与现存C++ 的不兼容,并将导致千百万行C++ 代码无法编译时,会感到大吃一惊。在许多情况下,强调100% 兼容性的基本假设是C++ 只有不多的用户。另一种情况也常常见到,强调100% 兼容性的背后隐藏的是一些人对C++ 的忽视和对新特征的反感。

另一条战线也可能同样使人烦恼,他们声称与C的兼容性根本不是一个应该考虑的问题。他们为一些新特征辩护,而对那些希望能混合使用C和C++ 代码的人而言,这些特征将带来严重的不便。很自然,每条战线中提出的极端论点都使另一战线绷紧神经,更害怕损害了自己最关心的语言部分。在这里——几乎总是——冷静的头脑占了上风,在认真考虑过所涉及的实际需要和使用C/C++ 的实际情况之后,争论通常收敛到对折中细节的更具建设性的考察上。在X3J16 ANSI委员会的组织会议上,原ANSI C委员会的编辑Larry Rosler对抱有怀疑态度的Tom Plum解释说,“C++ 就是我们想做但却一直无法做成的那个C语言”。这可能有点夸大其词,但对于C和C++ 的共同子集而言,这个说法与真理相距不远。

学习C或C++的几个共性问题:

我对C或C++都不了解,我是不是应该先学习C?

我想做OOP,那么,是不是应该在学习C++ 之前先学Smalltalk? 我在一开始应该把C++ 作为一种OOPL,还是作为一个更好的C? 学习C++ 需要花多少时间?

我不是想说自己有对这些问题的唯一答案。正如前面所言,‘正确的’回答依赖于环境情况,大部分C++ 教科书的作者、教师和程序员都有他们自己的回答。我的回答是基于自己多年用C++ 和系统程序语言做程序设计,教授短的C++ 设计和编程课程(主要是给职业程序员),做C++ 入门和C++ 使用的咨询,讨论C++ 语言,以及自己关于编程、设计和C++ 语言的一般性思考。

我对C或C++ 都不了解,是不是应该先学习C?不,首先学习C++。C++ 的C子集对于C/C++ 的新手是比较容易学的,又比C本身容易使用。原因是C++(通过强类型检查)提供了比C更好的保证。进一步说,C++ 还提供许多小特征,例如运算符new,与C语言对应的东西相比,它们的写法更方便,也更不容易出错。这样,如果你计划学习C和C++(而不只是C++),你不应该经由C那条迂回的路径。为能很好地使用C,你需要知道许多窍门和技术,这些东西在C++ 里的任何地方都不像它们在C里那么重要、那么常用。好的C教科书倾向于(也很合理)强调那些你将来在用C做完整的大项目时所需要的各种技术。好的C++ 教科书则不太一样,强调能引导你去做数据抽象、面向对象的程序设计的技术和特征。理解了C++ 的各种结构,而后学习它们在(更低级的)C里替代物将会很简单(如果需要的话)。

要说我的喜好:要学习C,就用 [Kernighan,1988];要学习C++,就用[2nd]。两本书的优点是都组合了两方面内容:一方面是关于语言特征和技术的指导性的描述,另一方面是一部完整的参考手册。两者描述的都是各自的语言而不是特定的实现,也不企图去描述与特定实现一起发布的特殊程序库。

现在有许多很好的教科书和许多各种各样风格的材料,上面只是我对理解有关概念和风格的喜好。请仔细选择至少两个信息来源,以弥补可能的片面性甚至缺陷,这样做永远是一种明智之举。

我想做OOP,那么,是不是应该在学习C++之前先学Smalltalk?不。如果你计划用C++,那就学C++。各种语言,像C++、Smalltalk、Simula、CLOS和Eiffel等,各有自己对于抽象和继承等关键概念的观点,各语言以略微不同的方式支持着这些概念,也支持不同的设计概念。学习Smalltalk当然能教给你许多有价值的东西,但它不能教给你如何在C++ 里写程序。实际上,除非你有充分时间学习和消化Smalltalk以及C++ 的概念和技术,否则用Smalltalk作为学习工具将导致拙劣的C++ 设计。

当然,如果同时学了C++ 和Smalltalk,能使你取得更广泛领域中的经验和实例,那当然是最理想的。但是那些不可能花足够时间去消化所有新概念的人们常常最后是‘在C++ 里写Smalltalk’,也就是说,去用那些并不能很好适应C++ 的Smalltalk设计概念。这样写出的程序可以像在C++ 里写C或Fortran一样,远不是最好的东西。

常见的关于学习Smalltalk的理由是它‘很纯’,因此会强迫人们去按‘面向对象的’方式思考和编程。我不想深入讨论‘纯’的问题,除提一下之外。我认为一个通用程序设计语言应该而且也能够支持一种以上的程序设计风格(范型)。

这里的问题是,适合Smalltalk并得到它很好支持的风格并不一定适合C++。特别是模仿性地追随Smalltalk风格,将会在C++ 里产生低效、丑陋,而且难以维护的C++ 程序。个中理由很简单,好的C++ 程序所需要的设计应该能很好地借助C++ 静态类型系统的优势,而不是与之斗争。Smalltalk(只)支持动态类型系统,把这种观点翻译到C++ 将导致广泛的不安全性和难看的强制转换。

我把C++ 程序里的大部分强制转换看作是设计拙劣的标志。有些强制转换是很基本的,但大部分都不是。按我的经验,传统C程序员使用C++,通过Smalltalk理解OOP的C++ 程序员是使用强制转换最多的人,而所用的那些种类的转换,完全可以通过更仔细的设计而得以避免。

进一步说,Smalltalk鼓励人们把继承看作是唯一的,或者至少是最基本的程序组织方式,并鼓励人们把类组织到只有一个根的层次结构中。在C++ 里,类就是类型,并不是组织程序的唯一方式。特别的是,模板是表示容器类的最基本方法。

我也极端怀疑一种论断,说是需要强迫人们去采用面向对象的风格写程序。如果人们不想去学,你就不可能在合理的时间内教会他们。按我的经验,确实愿意学习的人从来也不短缺,最好还是把时间和精力用到他们身上。除非你能把握住如何表现隐藏在数据抽象和面向对象的程序设计后面的原理,否则你能做的不过是错误地使用支持这些概念的语言特征,而且是以一种不适当的‘巴罗克’形式[2]——无论在C++、Smalltalk或者其他语言里。

参看《C++ 程序设计语言》(第2版) [2nd],特别是第12章,那里有关于C++ 语言特征和设计之间关系的更多讨论。

我在一开始应该把C++ 作为一种OOPL,还是作为一个更好的C语言?看情况。为什么你想开始用C++?对这个问题的回答应该能确定你走近C++ 的方式,在这里,没有某种放之四海而皆准的道理。按照我的经验,最安全的方式是自下而上地学习C++,也就是说,首先学习C++ 所提供的传统的过程性程序设计特征,也就是那个更好的C子集;而后学着去使用和遵循那些数据抽象特征;再往后学习使用类分层去组织相互有关的类的集合。

按照我的观点,过快地通过早期阶段是很危险的,这样会使忽视某些重要概念的可能性变得非常之大。

例如,一个有经验的C程序员可能会认为C[3]的更好的C子集是‘很熟悉的’,因此跳过了教科书中描述这方面的前100页或多少页。但在这样做时,这个C程序员可能就没看到有关函数的重载能力,有关初始化和赋值之间差异的解释,用运算符new做存储分配,关于引用的解释,或许还有其他一些小特征。在后面阶段它们会不断地跳出来缠住你,而在这时,一些真正的新概念正在复杂的问题中发挥着作用。如果在更好的C中所用的概念都是已知的,读过这100页可能也就只要几个小时的时间,其中的一些细节又是有趣的,很有用的。如果没有读,后面花的时间可能更多。

有些人表达了一种担心,害怕这种‘逐步方式’会引导人们永远去写C语言风格的东西。这当然是一种可能的后果,但是从百分比看,与在教学中采用‘更纯的’语言或者强迫的方式相比,很难说这样做就一定更不值得信任。关键是应该认识到,要把C++ 很好地用作数据抽象和/或面向对象的语言,应该理解几个新概念,而它们与C或者Pascal一类语言并不是针锋相对的。

C++ 并不只是用新语法表述一些老概念——至少对于大部分程序员而言不是这样。这也就隐含着教育的需要,而不仅仅是训练。新概念需要通过实践去学习和掌握。老的反复试验过的工作习惯需要重新评价。不再是按照‘老传统的方式’向前冲,而是必须考虑新方式——通常,与按照老方式相比,以新方式做事情,特别是第一次这样做时,一定更困难,也更费时间。

许多经验说明,对大部分程序员而言,花时间和精力去学习关键性的数据抽象和面向对象技术,是非常有价值。并不是必须经过很长的时间才能产生效益,一般在3到12个月就可以。不花这些精力,而只是使用C++,也会有效益,但最大的效益还是要在为学习新概念而花费精力之后——我的疑问是,如果什么人不想花这个精力,那么为什么还要转到C++ 来呢。

在第一次接触C++,或是在许多时间之后又第一次接触它,用一点时间去读一本好的教科书,或者几篇经过很好选择的文章(在The C++ ReportThe C++ Journal里有许多这样的文章)。你也可能想看看某些主要的库的定义和源代码,分析其中使用的概念和技术。对那些已经用了一段C++ 的人来说,这也是个好主意,在重温这些概念和技术的过程中可以做许多事情。自C++ 第一次出现以来,在C++ 语言以及与之相关的编程和设计技术方面已经发生过许多事情。将《C++ 程序设计语言》的第1版和第2版做一个简单对比,就足以使人相信这个说法。

学习C++ 需要花多少时间?同样要看情况。依赖于你的经验,也依赖于你所说的‘学习C++’的意思。对大部分程序员而言,学习语法和用更好的C的风格写C++,再加上定义和使用几个简单的类,只要一两周时间。这是最容易的部分。最主要的困难在于掌握新的定义和编程技术,这也是最有意思、最有收获的部分。曾经和我讨论过的大部分有经验的程序员说,他们用了半年到一年半时间,才真正觉得对C++ 适应了,掌握了它所支持的数据抽象和面向对象技术。这里假定他们是在工作中学习并维持着生产——通常在此期间也用着C++的某种‘不那么大胆’的风格做程序设计。如果你能拿出全部时间学C++,就可能更快地适应它。但是,在没有将新的思想和设计应用到真实的项目中之前,这个适应也很可能是骗人的。面向对象的编程和面向对象的设计,基本上是实践性的训练而不是理论训练。只是对一些玩具式的小例子使用或者不使用它,这些思想就很可能演化为一种危险的盲从倾向。

请注意,学习C++,最根本的是学习编程和设计技术,而不是语言细节。在做完了一本教科书的学习工作之后,我会建议一本有关设计的书,例如 [Booch,1991] [4],该书里有一些稍长的例子,用的是5种语言(Ada、CLOS、CLU、C++、Smalltalk和Object Pascal),这样就可能在某种程度上避免语言的偏狭性,而偏狭性已经弄糟了许多有关设计的讨论。在这本书里,我最喜欢的部分就是描述设计概念和例子的那几章。

关注设计方式,与非常仔细地关注C++ 的定义细节(例如ARM,其中包含许多有用的信息,但是没有关于如何用C++ 编程的信息)是截然不同的。把注意力集中到细节上,很容易把人搞得头昏脑涨,以至于根本就用不好语言。你大概不会试着从字典和语法去学习一种外国语吧?

在学习C++ 时,最根本的,应该是牢记关键性的设计概念,使自己不在语言的技术细节中迷失了方向。如果能做到这一点,学习和使用C++ 就会是非常有趣的和收效显著的。与C比较,用一点点C++ 就可能带来许多收获。在理解数据抽象和面向对象技术方面付出进一步努力,你将能得到更多的收获。”

这个观点也不是全面的,受到当前工具和库的状况的影响。如果有了保护性更强的环境(例如包括了广泛的自动的运行时检查)以及一个小的定义良好的基础库,你就可以更早地转到大胆使用C++ 的方面去。这些将能更好地支持从关注C++ 的语言特征到关注C++ 所支持的设计和编程技术的大转移。

分散一些兴趣放到语法上,把一些时间用到语言的技术细节上,也是非常重要的。有些老牌程序员喜欢翻弄这些细节。这种兴趣与不大情愿去学习新的程序设计技术,经常是很难分辨清楚的。

类似地,在每个课程和每个项目里总有这样的人,他们根本不相信C++ 的特征是可以负担得起的,因此在后来的工作中坚持使用自己更熟悉和信任的C子集。只有不多的有关个别C++ 特征和用C++ 写出的系统的执行效率方面的数据(例如,[Russo,1988]、[Russo,1990]、[Keffer,1992]),不大可能动摇这些人长期而牢固的,有关比C更方便的机制是不可能负担的观点。看到这种宣传的量,与语言或工具领域中未得到满足的允诺的量的比较,人们应该对这种说法持怀疑态度,并要求拿出更明显的证据。

在每个课程和项目里也总有另一种人,他们确信效率无关紧要,倾向于用更一般的方式去设计系统,结果是,即使在最先进的硬件上也产生了可观察到的延迟。不幸的是,这种延迟在人们学习C++ 的过程中写玩具程序时很难观察到,因此具有这种性质的问题常常被遗留下来,直到遇到真正的项目。我一直在寻找一个简单但又实际的问题,如果采用一种过分一般的方式去解决,它就能打倒一个很好的工作站。这样的问题将使我能证明带有倾向性的设计的价值,以抵制那些极端的乐观主义者;而通过仔细思考又能使性能大大改善,可以对付过于谨慎和保守的人们。


读到这里,小编觉着有必要重复一下这个资讯,祝大家在程序员的路上能够稳步前行,跟上时代变迁。

以下内容来自

https://www.oschina.net/news/123834/cpp20-published?fr=vx

近期C++20 标准 (ISO/IEC 14882:2020) 正式发布

C++20 是一次重大的更新,引入了许多新特性:

  • 模块 (Modules)
  • 协程 (Coroutines)
  • 范围 (Ranges)
  • 概念与约束 (Constraints and concepts)
  • 指定初始化 (designated initializers)
  • 操作符 <=> != ==
  • constexpr支持:new/ deletedynamic_casttry/ catch、虚拟
  • constexpr 向量和字符串
  • 计时:日历、时区支持
  • std::format
  • std::span
  • std::jthread

书籍推荐

C Primer Plus 第6版 中文版

C Primer Plus(第6版)中文版》是一本经过仔细测试、精心设计的完整C语言教程,它涵盖了C语言编程中的核心内容。《C Primer Plus(第6版)中文版》作为计算机科学的经典著作,讲解了包含结构化代码和自顶向下设计在内的程序设计原则。

与以前的版本一样,作者的目标仍旧是为读者提供一本入门型、条理清晰、见解深刻的C语言教程。作者把基础的编程概念与C语言的细节很好地融合在一起,并通过大量短小精悍的示例同时演示一两个概念,通过学以致用的方式鼓励读者掌握新的主题。

每章末尾的复习题和编程练习题进一步强化了*重要的信息,有助于读者理解和消化那些难以理解的概念。本书采用了友好、易于使用的编排方式,不仅适合打算认真学习C语言编程的学生阅读,也适合那些精通其他编程语言,但希望更好地掌握C语言这门核心语言的开发人员阅读。

《C Primer Plus(第6版)中文版》在之前版本的基础之上进行了全新升级,它涵盖了C语言*新的进展以及C11标准的详细内容。本书还提供了大量深度与广度齐备的教学技术和工具,来提高你的学习。

C++ Primer Plus 第6版 中文版

《C++ Primer Plus(第6版)中文版》分18章,分别介绍了C++程序的运行方式、基本数据类型、复合数据类型、循环和关系表达式、分支语句和逻辑运算符、函数重载和函数模板、内存模型和名称空间、类的设计和使用、多态、虚函数、动态内存分配、继承、代码重用、友元、异常处理技术、string类和标准模板库、输入/输出、C++11新增功能等内容。

《C++ Primer Plus(第6版)中文版》针对C++初学者,从C语言基础知识开始介绍,然后在此基础上详细阐述C++新增的特性,因此不要求读者有C语言方面的背景知识。《C++ Primer Plus(第6版)中文版》可作为高等院校教授C++课程的教材,也可供初学者自学C++时使用。