面向对象程序设计的三大特征是封装、继承和多态。其中继承体现的是一种持续发展的、开放的编程态度。继承和派生可以在不破坏原有类的封装特性的情况下获得原有类的数据和功能,并在此基础上增加新的数据和功能。基类是为解决以前的老问题而设计的,但在遇到新问题时其功能就有局限,这是在继承的基础上进行派生则能够在基类的基础上添加功能来解决新问题。因此通过继承和派生可提高代码的可重用性和持续性。
继承与派生概念:保持已有类的个性而构造新类的过程称为继承,在已有类的基础上新增自己的特性而产生新类的过程称为派生。其中被继承的是父类(也称基类),派生出来的新类是子类(也称派生类)。派生类拥有基类的所有除构造函数和析构函数外的所有成员,并且拥有新的功能。
继承关系:根据继承关系的层次和父类的数量可分为单继承、多继承和多重继承。只由基类A派生出子类B称为单继承。由基类A、B等派生出子类C称为多继承。由基类A、B等派生出子类C,在由C派生出子类D,称为多重继承。
继承的三种方式有:公有继承,私有继承和保护继承。无论是哪种继承方式,基类中的私有成员都无法直接访问。公有public 继承 :基类中的所有成员访问属性不发生变化,即基类中的公有成员成为派生类的公有成员,基类中的保护成员成为基类的保护成员。保护 protect 继承:基类中的公有公有成员变为保护成员,而保护成员访问权限保持不变。私有 private 继承:基类中的所有成员全部成为派生类成员中的私有成员,这样派生类的所有成员都是私有成员,因此私有继承不能进行多重继承。
派生类的定义:
派生类定义时在派生类名后添加继承方式和基类名(基类名必须是已存在的类名)然后在大括号内添加新增成员。派生类可以继承多个基类,多个基类必须依照 “继承方式 基类” 的形式声明,多个基类中以逗号隔开。如果没有写明继承方式,系统默认私有继承。
多继承:
class C:继承方式1 A,继承方式2 B, .........
{
public:新增公有类成员;
protect:新增保护类成员;
private:新增私有类成员;
};
多重继承:
class C1:继承方式1 B1,继承方式2 B2, .......
{ ......................//同上 }
class D2:继承方式 C1
{ .....................//同上 }
类在派生时不仅可以添加新成员,还可以对旧成员进行改造。派生类的新成员会屏蔽基类同名成员,派生类定义与基类同名成员时会对基类成员进行覆盖,在使用派生类对象时,默认访问派生类的新成员。如果想访问基类旧成员,可以以 ”基类名::基类成员名” 的形式对其进行访问。
派生类对象的构造和析构:计算机执行定义对象语句时将创造对象,为其分配内存空间,并自动调用对象所属类的构造函数来初始化对象,这个过程就是对象的构造。当对象生存期结束时,计算机将销毁对象。销毁时自动调用对象所属类的析构函数来清理内存,然后释放其所占用的内存空间,这个过程就是对象的析构。
派生类构造函数的定义:
由于派生类继承了基类的数据,因此派生类构造函数的形参表不仅包含对派生类数据成员初始化的参数,还要包含对基类数据成员初始化的参数。
派生类名 :: 派生类名(基类所需的形参,派生类所需的形参)
:基类名1(参数表),基类名2(参数表)........ //参数传递列表
{ 本类成员的初始化语句; }
对与一个类的数据成员的初始化只能由该类的构造函数来完成,因此对于基类的数据成员的初始化要由基类的构造函数来完成。派生类构造函数的参数传递列表的本质是对基类的构造函数的调用显示列表,它的作用与组合类的初始化列表相同。当基类有多个构造函数时,系统会根据参数传递列表自动完成基类构造函数的重载,调用最匹配的基类构造函数初始化继承的基类成员。
派生类构造函数执行的顺序:
调用基类的构造函数初始化继承的基类数据成员调用顺序和派生类定义时声明的继承顺序一致。
如果新成员中含有对象成员,调用对象成员类的构造函数调用顺序为它们在派生类中的定义顺序。
初始化其他数据成员,执行派生类构造函数的函数体中的内容。
派生类析构函数执行的顺序:
先执行派生类析构函数的函数体,清理新增成员;再调用基类析构函数,清理基类成员简单地说,对象地析构顺序与构造顺序相反,即先析构新增成员,再析构基类成员。
多态性的概念:
源程序中相同的程序元素可能会具有不同的语法解释,在c++中就称这些程序元素具有多态性。多态性有多种形式,包括:关键字多态、重载函数多态、运算符多态、对象多态、参数多态。
对具有多态性的程序元素作出最终明确的语法解释,这称为多态的实现。
多态形式根据不同实现时间的分为编译时多态和执行时多态。
运算符的多态和重载
同一个运算符在处理不同类型的数据时有不同的语法解释。如c++中整数加法和浮点数加法使用的都是“+” 运算符,但处理方式却不同。整数加法是通过cpu的定点计算器来进行计算,而浮点数加法运算器则是通过浮点计算器来完成的。
c++的运算符只能对基本类型的数据进行运算,而对于类这样的自定义数据类型,则需要对运算符进行重载。
运算符重载即重新定义c++已有运算符的运算规则,来使同一运算符作用与不同数据类型时执行不同的运算。重载运算符是以函数的形式来重新定义运算符的运算规则。
定义运算函数的一般语法形式为
函数类型 operate 运算符 (形式参数)
{ 运算符的定义语句 }
运算符重载的语法细则:
并不是所有运算符都能够进行重载,在c++中有五个运算符不能重载,它们是:条件运算符 “?:” 、sizeof运算符、成员运算符 “.” 、指针运算符 “*” 和作用域运算符 “::”。
重载后运算符的优先级和结合性不会改变,改变的只是运算符的运算规则。
重载后运算符的操作数个数不能改变,同时至少有一个操作数是自定义数据类型。
重载后运算符的含义应与原运算符相似。
对象的替换与多态
在面向对象的程序设计中,重用处理基类对象的程序代码来处理派生类对象,这是非常普遍的需求。
如果派生类对象能够与基类对象一起共用程序代码,它将极大地提高程序开发和维护的效率。
面向对象程序设计方法利用派生类和基类之间存在的特殊关系,提出了对象的替换和多态。
Liskov替换准则
为了让基类对象及其派生类对象之间可以重用代码,c++语言制定了如下的类型兼容语法规则:
派生类的对象可以赋值给基类对象;
派生类的对象可以初始化基类引用;
派生类对象的地址可以赋值给基类的对象指针,或者说基类的对象指针可以指向派生类对象。
应用类形兼容语法规则有1个前提条件和1个使用限制;
前提条件:派生类必须公有继承基类。
使用限制:通过基类对象、引用或对象指针访问派生类对象,只能访问其基类成员。
对象多态性:
面向对象程序设计借用拟人化的说法,将调用对象的某个函数成员称为向对象发送一条消息。
将执行函数成员完成某种程序功能称为对象响应该消息所表现出的行为。
不同对象接收相同的消息,但会表现出不同的行为,这就称为对象的多态性,或称对象具有多态性。
从程序角度,对象多态性就是:调用不同对象的同名函数成员,但所执行的函数不同,完成的程序功能不同。导致对象多态性的同名函数成员有三种不同的形式:
不同类之间的同名成员。类成员具有类作用域,不同类之间的函数成员可以重名,互不干 扰。
类中的重载函数。类中的函数成员之间可以重名,只要它们的形参个数不同,或类型不同。 重载函数成员导致的多态性在本质上属于重载函数多态。
派生类中的同名覆盖。派生类中新增的函数成员可以与从基类继承来的函数成员重名,但它 们不是重载函数。
实现对象多态性
首先,在定义基类时使用 “virtual” 关键字将函数成员声明成虚函数。
然后通过公有继承定义派生类,并重写虚函数成员,也就是新增1个与虚函数同名的函数成员。
使用基类引用或对象指针。
声明虚函数的语法细则
只能在类声明部分声明虚函数。在类实现部分定义函数成员时不能使用“virtual”关键字。
基类中声明的虚函数成员被继承到派生类后,自动成为派生类的虚函数成员。
派生类可以重写基类虚函数成员。如果重写后的函数原型与基类虚函数成员完全一致,则该函数自动成为派生类的虚函数成员,无论声明时加不加“virtual”关键字。
类函数成员中的静态函数、构造函数不能是虚函数。析构函数可以是虚函数。