简述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++的高级特性,用简单的方法解决简单的问题,用简单的形式解决复杂的问题,即将复杂的解决方案包装在简单的形式之下,重用前人的劳动成果,遵循最佳的实践。

以上就是简述C++的复杂性的详细内容,更多关于C++的复杂性的资料请关注我们其它相关文章!

(0)

相关推荐

  • 详解C++纯虚函数与抽象类

    1.虚函数 1.1虚函数简介 虚函数可以毫不夸张的说是C++最重要的特性之一,我们先来看一看虚函数的概念. 在基类的定义中,定义虚函数的一般形式为: virtual 函数返回值类型 虚函数名(形参表) { 函数体 } 为什么说虚函数是C++最重要的特性之一呢,因为虚函数承载着C++中动态联编的作用,也即多态,可以让程序在运行时选择合适的成员函数.虚函数必须是类的非静态成员函数(且非构造函数),其访问权限是public.那么:  (1)为什么类的静态成员函数不能为虚函数?  如果定义为虚函数,那么

  • 浅谈C++ IO流

    1.输入输出(IO)与流的概念 输入输出(IO)是指计算机同任何外部设备之间的数据传递.常见的输入输出设备有文件.键盘.打印机.屏幕等.数据可以按记录(或称数据块)的方式传递,也可以 流的方式传递. 所谓记录,是指有着内部结构的数据块.记录内部除了有需要处理的实际数据之外,还可能包含附加信息,这些附加信息通常是对本记录数据的描述. 流是一种抽象概念,它代表了数据的无结构化传递.按照流的方式进行输入输出,数据被当成无结构的字节序或字符序列.从流中取得数据的操作称为提取操作,而向流中添加数据的操作称

  • VS Code C/C++环境配置教程(无法打开源文件“xxxxxx.h” 或者 检测到 #include 错误,请更新includePath) (POSIX API)

    一.问题描述与分析 编辑C/C++程序,我推荐使用C/C++,VS Code相对于别的编译器来说有很多的优势.但是如果第一次使用的话,会觉得其不好用.因为如果不配置好的话,操作会比较麻烦. 注意:我这里是在windows下编写Linux程序. 例如在使用VS Code编辑C/C++程序在没有配置好的情况下,会出现如下图情况, 出现这种情况的原因是 在VS Code没有找到头文件.或者是VS Code没有配置好. 为了很好的解释上述的问题,请先了解下Cygwin.MinGW.POSIX等,并了解下

  • c++ 面向对象设计五大原则

    面向对象设计(OOD)是面向对象编程(OOP)必不可少的一个环节,只有好的设计,才能保障程序的质量.面向对象设计的主要任务就是类的设计,不少面向对象(OO)的先驱和前辈已经提出了很多关于类的设计原则,用于指导OOP,其中就包括类设计的五项基本原则. 1.单一职责原则(Single Resposibility Principle,SRP) 专注是一个人的优良品质,同样,单一职责也是一个类的优良设计.单一职责的核心思想:一个类只做好一件事情. 单一职责原则可以看作是高内聚.低耦合在面向对象原则上的引

  • 详解C++ sizeof(上)

    sizeof是C/C++中的一个操作符(operator),其作用是返回一个对象或者类型所占的内存字节数,使用频繁,有必须对其有个全面的了解. 1.sizeof的基本语法 sizeof有三种语法形式. (1)sizeof(object); //sizeof(对象); (2)sizeof(type_name); //sizeof(类型); (3)sizeof object; //sizeof对象; 第三种语法结构虽然简约,但并不常见,为简单统一,建议使用第一和第二种写法. int i; sizeo

  • C++枚举类型enum与enum class的使用

    一.关于枚举类型 1. 什么是枚举类型? 答:如果一个变量只有几种可能的值,那么就可以定义为枚举类型,比如:性别只有男和女,那么就可以将性别定义为一种枚举类型,其中男和女就是性别所包含的变量.所谓"枚举"是指将变量的值一一列举出来,变量的值只能在列举出来的值的范围内.在C++中,枚举类型分为不限定作用域(enum)和限定作用域(enum class). 2. enum与enum class的区别? (为什么需要限定作用域?) 答:枚举作用域是指枚举类型成员名字的作用域,起自其声明之处,

  • 详解C++ sizeof(下)

    sizeof作用于基本数据类型,在特定的平台和特定的编译器中,结果是确定的,如果使用sizeof计算构造类型:结构体.联合体和类的大小时,情况稍微复杂一些. 1.sizeof计算结构体 考察如下代码: struct S1 { char c; int i; }; cout<<"sizeof(S1)="<<sizeof(S1)<<endl; sizeof(S1)结果是8,并不是想象中的sizeof(char)+sizeof(int)=5.这是因为结构体或

  • c++禁止函数的传值调用的方法

    代码编译运行环境:VS2017+Debug+Win32 按照参数形式的不同,C++应该有三种函数调用方式:传值调用.引用调用和指针调用.对于基本数据类型的变量作为实参进行参数传递时,采用传值调用与引用调用和指针调用的效率相差不大.但是,对于类类型来说,传值调用和引用调用之间的区别很大,类对象的尺寸越大,这种差别越大. 传值调用与后面两者的区别在于传值调用在进入函数体之前,会在栈上建立一个实参的副本,而引用和指针调用没有这个动作.建立副本的操作是利用拷贝构造函数进行的.因此,要禁止传值调用,就必须

  • c++代码调试方式的几点建议

    1.代码调试的重要性 代码调试在程序开发阶段占有举足轻重的地位,可见代码调试的重要性.但是有一点必须强调:程序是设计出来的,而不是调试出来的.这是所有程序员必须牢记在心的一条准则.一个没有设计或者这几得很糟糕的程序,无论怎样调试,也不会成为一个合格的程序. 程序有着良好的设计的前提下,软件开发的过程中,编码错误在所难免.所有程序可能出现的错误可分为两类:语法错误和逻辑错误.调试通常是指在消除了语法错误之后,发现程序中的逻辑错误的过程.对C/C++程序进行调试,有这样集中常用的手段.它们既可以单独

  • C++ 使用new与delete需注意的原则

    C++的动态内存管理是通过new和delete两个操作来完成的,即用new来申请空间,用delete来释放空间.在使用new和delete时,注意以下原则. 1.new与delete需一一对应 用new操作申请空间,如果申请成功,必须在以后的某个时刻用delete释放该空间,既不能忘记释放,也不能多次释放.前者会引起内存泄露,后者会引起运行时错误.如下面的程序. #include <iostream> using namespace std; int main() { int *p; p=ne

随机推荐