C++对C语言的改进

Author: 北京大学计算中心 吕凤翥 Date: 1993-11-05

        c++是c语言的一个超集,它继承了c语言的所有特点,同时对c语言作了很多改进并且有重大突破。
        c++对c语言的改进表现在c++增加了一些新的功能并对c语言中的一些不足之处作了修正,使之更加完善,更加灵活。
        c++对c语言的突破表现在c++是一个面向对象的程序设计语言,它具有面向对象的程序设计语言的三大特性:封装性、继承性和多态性。
        下面分几个问题讲述c++对c语言的改进和突破。
        c++对c语言的改进较多,这里仅列举几处稍加说明。
        1、增加了一些新的运算符。例如:作用域运算符::用来说明当前作用域被隐藏的数据项。存储管理运算符new和delete相当于c语言中存储管理函数malloc()和free()的功能。
        2、c语言的函数体和分程序中规定说明语句放在执行语句之前;而c++突破了这一限制,不限制说明语句和执行语句出现的顺序。
        3、c++中对不同类型的转换要使用强制转换运算符,这便增加了安全性。
        4、c++中引进入“引用”(reference)的概念,用来实现直接存取。引用本身不是变量,但它可用作函数的参数和返回值。程序中对引用的存取实际上是对它所引用的变量的存取,因此,引用实际上是另一个标识符的别名。
        5、c++中说明函数要采用函数的原型,既说明函数的类型又说明函数参数的类型。这种做法可以保证函数参数的匹配,即做类型检查,又可对同名函数用参数类型加以选择。
        6、c++中引进了“内置”函数(Inline)的概念。这种函数不被编译为单独一段可调用的代码,而是将该函数插入到每一调用处。这样,既减少调用函数的额外开销,又保持程序的结构化方式。
        7、c++的函数原型说明中,可以直接给出参数的数值作为缺省值。
        8、c++中允许函数和运算符重载。重载是指同一个函数名(或运算符)对应不同功能的函数(或运算操作)。对重载函数或运算符按指定规则进行选择。
        以上列举出c++和c语言的部分改进。
        C++的封装性
        c++的封装性主要表现在类和对象的使用上。
        类是一种类型,它是对一组对象的抽象概括,也是对于对象的一组描述。
        对象是一种客观世界的客体,它是由数据和对数据的操作构成的。对象比一般意义上变量含有更丰富的内容。
        类和对象迪蟮墓剜似于结构名和结构变量的关系。所说的对象是指某个类的对象,这意味着它包含着某个类的说明中给定的该类对象的内部数据结构和有关操作。
        1、类的说明
        类的说明格式如下:
        class<类名>
        {
        <数据成员说明>;
        <成员函数说明>;
        };
        其中,<类名>同标识符,class是类的关键字。
        数据成员可是c语言中规定的各种类型数据,可以是另一类的对象及自身类的指针或引用,但不得用auto,register,extern说明的变量。
        成员函数又称方法,用来描述对数据成员的某种操作,成员函数的定义格式同一般函数一样,可定义在类内,也可以定义在类外。
        2、对象的描述
        对象的定义格式如下:
        <类名><对象名表>;
        对象的成员是指该对象所属类的数据成员和成员函数。对象和对象引用的成员用表示,指向对象的指针的成员用→表示。
        3、类的作用域规则
        类的作用域规则规定:类的所有成员都在该类的作用范围内存取,一个类的任何成员可以引用该类的其它成员。这就是说,类的成员函数可对该类的数据成员进行存取,而对该类作用域之外的成员或变量的存取由程序员控制。  为进一步控制类中成员的可访问性又规定三种不同的存取说明符,它们分别是:
        ①私有的(private):该成员只能在类的作用域内存取。
        ②公有的(public):该成员可被类作用域外的任何程序部分存取。
        ③保护的(protected):该成员具有两重性,对它的派生类中成员函数来说是公有的;而对类之外定义的函数来说是私有的。
        4、对象的赋值
        c++中的对象可以赋值也可以赋初值。赋初值的方示可以用构造函数对对象初始化,也可以用初始值表以赋初值。前者用的较多,即通过构造函数给对象初始化。
        构造函数是一种特殊的成员函数,它的名字与该类的类名相同,可以有参数,也可以无参数,无返回值。构造函数在定义类时定义,可定义在类体内,也可定义在类体外。一个类可以定义多个构造函数,调用时按其参数的不同来选择。在程序中说明一个对象时,若该对象所属的类中有构造函数,则程序自动调用构造函数给对象初始化。
        与构造函相对应的是析构函数,该函数也是一种特殊的构造函数,其名字同类名,无参数,无返回值。该函数的功能是用来释放已定义对象的内存空间的。
        给对象赋值要通过一个赋值运算符函数,它也是一种成员函数,该函数的定义格式如下:
        void<类名>::operator=(const<类名>&c){<函数体>}
        其中,operator是定义运算符函数的关键字。=是被定义的运算符,该函数有一个具有常数性质的参数,它是某个类的对象的引用。
        把一个对象赋给另一个对象是将这个对象的各个成员逐一地赋值给另一个对象的对应成员。一般地,类中的数据成员被说明为私有的,而成员函数被说明为公有的(或者部分为公有的),公有的成员函数提供了类的对外接口,而私有的数据成员是被隐藏的,这就体现了类的封装性,即类中的数据结构的改变不影响整个的程序,这便是封装性的特点
        C++的继承性
        继承性是面向对象的程序设计语言的重要特性,只有类而无继承性的语言不是面向对象的语言,只能是依赖于对象的语言。
        继承性分为单继承性和多继承性两类。
        1、派生类和基类的关系从一个或多个基类中派生出来的类称为派生类。派生类继承了基类的特性称为继承性。从一个基类派生出来的派生类具有单继承性,从多个基类派生出来的派生类具有多继承性,即一个类可以是多个派生类的基类,一个派生类又可以有多个基类。
        在实际生活中,存在有很多具有基类和派生类关系的事物,它们之间具有继承性。下面举一个例子说明基类与派生类的继承关系。
        该例中小说与文艺作品之间具有派生类与基类之间的继承关系。文艺作品中包含有小说、诗歌、剧本……小说具有文艺作品的特性,即小说继承了文艺作品的特性。将文艺作品作为基类,小说便是它的派生类,同样,诗歌、剧本都是文艺作品的派生类。另外,小说又可以作为基类,而长篇小说、中篇小说、短篇小说分别是它的派生类。即长篇小说具有小说的特性。由于基类与派生类之间有继承关系,即派生类继承基类的特性,在基类中定义的功能在派生类可以使用,这将避免冗余性。
        2、派生类的定义格式class<派生类名>:<基类名表>{<数据成员和成员函数的说明>};其中,<派生类名>同标识符,<基类名表>是由若干个基类名组成的,多个基类名由逗号(,)分隔。基类名前可加存取说明符(private和public),用来规定基类成员在派生类中的存取权限。使用public说明时,表明基类中成员的存取权限在派生类中不变;使用private说明时,表明基类中成员的存取权限在派生类中一律视为私有的。这样就进一步规定了在派生类中被继承的基类成员的存取权限。  3、派生类对基类的继承。
        (1)派生类对基类成员的继承。
        基类的成员也是它的派生类的成员,这里包括数据成员和成员函数。
        (2)派生类对基类成员访问权限的继承。派生类中定义的成员函数对基类成员的访问权限规定如下:
        ①任何情况况下,派生类的成员函数可以直接访问基类中定义的公有成员或从基类继承下来的公有成员。
        ②基类中由protected说明的保护成员可被派生类访问。
        (3)派生类对基类的构造函数的继承。在派生类中,对基类成员初始化由派生类的构造函数调用基类的构造函数来实现。派生类中如果包含其他类的对象成员,则该对象成员的初始化由该对象所属类的构造函数实现。于是,派生类的构造函数的格式如下:
        <派生类名>:<派生类构造函数名>:(<总参数表>):<成员初始化表>{函数体}
        其中,〈派生类构造函数名〉与该派生类名相同,〈总参数表〉包含基类构造函数的参数和对象成员构造函数的参数以及派生类物构函数的参数。〈成员初始化表〉包含:〈基类构造数名〉(参数表),〈对象成员构造函数〉(参数表)。其中,〈对象成员构造函数名〉同象标识符。
        派生类对象初始化的具体实现机制是先调用基类构造函数,对基类成员进行初始化,如果有对象成员时,再调用对象成员所属类的构造函数,对对象成员进行初始化,最后调用派生类构造函数,对派生类的成员初化。如果基类仍是派生类,则按递归进行。
        C++的多态性
        多态性又称重载性。多态性的含义是一个名字对应多个不同的实现,这里的名字可以是函数名也可以是运算符。多态性定义我为相的函数调用作为不同的对象接收时会引起不同行为的实现。多态性常出现在类的层次结构中,例如,在一个图形类的层次结构中,Circle类用来画圆,Ellipse类用来画椭圆,在它们各自的类中分别有一个显示图形的函数show(),它在不同类中分别用来绘制不同的图形,但是,它们的功能却相似,名字相同。这样就造成了相同名字的函数对应于不同的对象将有着不同的实现,即画出不同的图形,这就是多态性。按其实现机制多态性可分为编译多态性和运行多态性两种。
        1、编译多态性
        又称静态联编支持的多态性。所谓静态联编是指编译系统在编译时根据调用函数所用的参数来确定所调的重载函数。静态联编对重载函数选择的原则如下: ①由参数的特性加以区别。例如,参数的个数或类型的不同。②由作用域分辩符:指出那个对象的成员函数。③ 由对象成员的表示加以区别。例如,<对象名>@<函数名>或<指向对象的指针名>-><函数名>。静态联编的最大特点是在编译时确定函数的操作,即做出了同名函数的选取。
        2、运行多态性
        运行多态性又称为动态联编支持的多态性。动态联编的特点是在运行进选择同名函数的操作。动态联编选择重载函数的原则是根据调用函数的所包含的对象的类型进行选择。实现动态联编要使用虚函数的方法。虚函数定义格式如下:virtual<函数名>(<参数表> ){<函数体>}虚函数是一种特殊的成员函数,它用在不同类之间相同函数名不同实现的情况下,用来标识动态联编。虚函数的特性有:①虚函数是一种成员函数,它的函数体可定义在类体内,也可以定义在类体外。在第一次说明时函数名前加关键字virtual。②在派生类中,如果用基类中相同形参和相同返回值说明的函数,则自动认为是虚函数,可以不必加virtual说明。③派生类的虚函数覆盖基类中的虚函数,基类和派生类中虚函数无法继 承。④虚函数比普通成员函数执行时间要长些。