1、版权所有版权所有 复制必究复制必究本章主要内容本章主要内容:l类和对象l类继承l多态性:虚函数,重载,模板 与传统的面向过程的程序设计语言相比,C+语言的最大特征是支持面向对象程序设计OOP(Object Oriented Programming),它引入了类、继承、多态和重载等面向对象的新机制。通过本章的学习,使我们系统地介绍C+面向对象设计的基本方法。结构化程序设计的特点:结构化程序设计的特点:l是一种自上而下、逐步细化的模块化程序设计方法。lWirth N的观点:算法+数据结构=程序l是一种面向过程程序设计方法,即一个程序是由多个过程(在C+中为函数)模块组成,过程之间通过函数参数和全局
2、变量进行相互联系。3.1 面向对象程序设计概述面向对象程序设计概述 3.1.1 结构化程序设计结构化程序设计l与非结构化程序相比,结构化程序在调试、可读性和可维护性等方面都有很大的改进。l代码重用性不高:以过程为中心设计新系统,除了一些标准函数,大部分代码都必须重新编写。l由于软、硬件技术的不断发展和用户需求的变化,按照功能划分设计的系统模块容易发生变化,使得开发出来的模块的可维护性欠佳。l面向过程模式将数据与过程分离,若对某一数据结构做了修改,所有处理数据的过程都必须重新修订,这样就增加了很多的编程工作量。结构化程序设计的特点:结构化程序设计的特点:结构化程序设计的特点:结构化程序设计的特点
3、:什么是对象:什么是对象:l现实世界是由各种各样的事物组成,包括真实的事物和抽象的事物。例如,人、动物、汽车(真实的事物)和程序、直线(抽象的事物)等。l每一类事物都有自己特定的属性(如大小、形状、重量等)和行为(如生长、行走、转弯、运算等),人们通过研究事物的属性和行为而认识事物。l在计算机科学中将这些现实世界中的事物称之为对对象象。对象是包含现实世界中事物特征的抽象实体,它反映了系统为之保存信息和与之交互的方法。l在程序设计领域,可以用如下公式表示:对象=数据+作用于这些数据上的操作 3.1.2 面向对象程序设计方法及特征面向对象程序设计方法及特征l为了描述属性和行为相同的一类对象,引入了
4、类(class)的概念。l类是具有相同数据结构(属性)和相同操作功能(行为)的对象的集合,它规定了这些对象的公共属性和行为方法。l对象是类的一个实例,例如,汽车是一个类,而行驶在公路上的一辆汽车则是一个对象。l对象和类的关系相当于程序设计语言中变量和变量类型的关系。什么是类:什么是类:什么是类:什么是类:lOOP围绕现实世界的概念来组织模块,采用对象描述问题空间的实体,用程序代码模拟现实世界中的对象,使程序设计过程更自然、更直观。lSP是以功能为中心来描述系统,而OOP是以数据为中心来描述系统。相对于功能而言,数据具有更强的稳定性。lOOP模拟了对象之间的通信。就象人们之间互通信息一样,对象之
5、间也可以通过消息进行通信。这样,我们不必知道一个对象是怎样实现其行为的,只需通过对象提供的接口进行通信并使用对象所具有的行为功能。面向对象程序设计的特点:面向对象程序设计的特点:面向对象程序设计的特点:面向对象程序设计的特点:lOOP把一个复杂的问题分解成多个能够完成独立功能的对象(类),然后把这些对象组合起来去完成这个复杂的问题。l一个对象可由多个更小的对象组成,如汽车由发动机、传送系统和排气系统等组成。这些对象(类)可由不同的程序员来设计,可在不同程序中使用,就象一个汽车制造商使用许多零部件去组装一辆汽车,而这些零部件可能不是自己生产的。l采用面向对象模式就象在流水线上工作,我们最终只需将
6、多个零部件(已设计好的对象)按照一定关系组合成一个完整的系统。面向对象程序设计的特点:面向对象程序设计的特点:面向对象程序设计的特点:面向对象程序设计的特点:class Time private:int hour;/数据成员,表示小时 int minute;/数据成员,表示分钟 int second;/数据成员,表示秒public:void setTime(int h,int m,int s)/成员函数,设置时间 hour=(h=0&h=0&m=0&s60)?s:0;void showTime()/成员函数,输出时间 couthour:minute:secondendl;一个简单例子:一个简单
7、例子:一个简单例子:一个简单例子:main()Time EndTime;/声明对象EndTime /设置对象EndTime的时间(属性,数据成员)EndTime.setTime(12,23,36);coutThe time is:;/显示对象EndTime的时间 EndTime.showTime();运行结果:运行结果:The time is:12:23:36面向对象程序设计方法的基本特征面向对象程序设计方法的基本特征面向对象程序设计方法具有四个基本特征:l抽象l封装l继承l多态性1.抽象抽象 抽象是人类认识问题的最基本手段之一。抽象是指对具体问题(对象)进行概括,抽出一类类对象的公共属性和行
8、为并加以描述的过程。抽象包括数据抽象和代码抽象(或行为抽象)。2.封装封装 封装是把每个对象的数据(属性)和操作(行为)包装在一个类中。一旦定义了对象的属性和行为,则必须决定哪些属性和行为只用于表示内部状态,哪些属性和行为在外部是可见的。一般限制直接访问对象的属性,而应通过操作接口访问,这样使程序中模块之间关系更简单、数据更安全。对程序的修改也仅限于类的内部,使得由于修改程序所带来的影响局部化。3.继承继承 继承是指一个新类可以从现有的类派生而来。新类继承了现有类的特性,包括一些属性和行为,并且可以修改或增加新的属性和行为,使之适合具体的需要。例如,所有的Windows应用程序都有一个窗口,它
9、们可以看作都是从一个窗口类派生出来的,但有的应用程序用于文字处理,有的应用程序用于绘图,这是由于派生出了不同的类,它们增加了不同的属性和行为。继承很好地解决了软件的可重用性问题。4.多态性多态性 多态性是指类中具有相似功能的不同函数使用同一个名称来实现,并允许不同类的对象对同一消息作出的响应不相同。例如,同样的“编辑|粘贴”操作,在字处理程序和绘图程序中有不同的结果;同样的加法,把两个时间值相加和把两个整数相加的要求肯定不同。多态性使程序设计灵活、抽象,具有行为共享和代码共享的优点,很好地解决了程序的函数同名问题。l为了支持面向对象程序设计,C+在C语言结构(struct)数据类型的基础上引入
10、了类类这种抽象数据类型。lC+面向对象编程实质上就是面向类类编程,只有定义和实现了类,才能声明属于这个类的对象,才能通过对象使用定义的成员。l传统C程序员把编程重点放在函数的编写上,而C+程序员把重点放在类类的定义和实现上。3.2 C+类类lC+类将对象的属性抽象为数据成员,将对象的行为抽象为成员函数,并对它们进行封装。数据成员又称成员变量,成员函数又称为方法。lC+类在形式上类似于C语言中用户自定义的结构类型,但定义类时规定了成员的访问控制权限。对象只能访问所属类的公有成员,而类的私有成员只能在类的成员函数中被访问。C+类定义的基本形式 3.2.1 类的定义与实现类的定义与实现class p
11、rivate:;public:;protected:;C+C+类定义的基本形式:类定义的基本形式:类定义的基本形式:类定义的基本形式:l类的定义由关键字class开始,其后为用户定义的类名,花括号括起来的部分称为类体。l关键字private、public和protected称为访问权限控制符,用来设置数据成员和成员函数的访问属性,其默认值为private。lprivate属性表示数据成员和成员函数是类的私有成员,它们只允许被本类的成员函数访问或调用,数据成员一般定义为private属性;说明:说明:说明:说明:lpublic属性表示数据成员和成员函数是类的公有成员,它们允许被本类或其它类的成员
12、函数(通过对象)访问或调用,是类的外部接口,成员函数一般定义为public属性;lprotected属性表示数据成员和成员函数是类的保护成员,它们允许被本类的成员函数和派生类的成员函数访问或调用。例:例:说明:说明:说明:说明:class Timeprivate:/最好不要省略private int hour;/数据成员,表示小时 int minute;/数据成员,表示分钟 int second;/数据成员,表示秒public:void setTime(int,int,int);/成员函数,设置时间 void showTime();/成员函数,输出时间;例例 定义类Time(表示时间)。私有数
13、据成员hour、minute和second只能在类的成员函数中被访问或赋值;公有成员函数setTime、showTime可在外部被调用,但必须通过一个对象作为对象的成员使用。l利用C+类进行面向对象编程,定义类的成员只是完成了工作的第一步,最重要的工作是实现定义的类。l类的实现实质上是类的成员函数的实现,即定义类的成员函数。l成员函数的定义形式与一般函数的定义形式基本相同,但必须在成员函数名前加上类名和作用域限定符(:)。l成员函数的定义也可放在类体内(该函数声明之处),这时成员函数将变成内联函数。例:例:类的实现:类的实现:void Time:setTime(int h,int m,int
14、s)hour=(h=0&h=0&m=0&s60)?s:0;void Time:showTime()couthour:minute:second”访问对象的公有成员,但不能访问对象的私有成员。例如,公有成员函数调用:t1.setTime();start.showTime();pt1-setTime();而任何形如t1.hour、t1.minute、start.second等私有成员变量的直接访问都是非法的。成员的访问:成员的访问:例:例:main()Time EndTime;/声明对象EndTime EndTime.setTime(12,23,36);/设置对象EndTime的时间 coutTh
15、e time is:;EndTime.showTime();/显示对象EndTime的时间例例例例 类类TimeTime的使用,声明对象并设置对象属性。的使用,声明对象并设置对象属性。l在定义类时不能对成员变量进行初始化,因为无法确定成员变量属于哪一个对象。l成员变量一般都定义为私有属性,也不能在声明对象后利用赋值运算对成员变量进行初始化。l成员变量的初始化一般是利用一个名为构构造造函函数数的成员函数来完成。3.2.2 构造函数和析构函数构造函数和析构函数如何进行成员变量的初始化?构造函数是一种特殊的成员函数,它是在创建对象时(声明或new动态创建)系统自动调用的成员函数。什么是构造函数:什么
16、是构造函数:析构函数也是一种特殊的成员函数,它是在对象生存期结束时系统自动调用的成员函数。什么是析构函数:什么是析构函数:构造函数的名称与类名相同,析构函数的名称必须在类名前加上“”符号。注意,构造函数和析构函数不能指定任何返回值类型,包括void返回类型。#include class Timeprivate:int hour;int minute;int second;public:Time(int,int,int);/构造函数 Time();/析构函数 .;例例 为类Time添加构造函数和析构函数。Time:Time(int h,int m,int s)hour=h;/对私有成员变量初始化
17、 minute=m;second=s;coutThe constructor be called:hour:minute:secondendl;构造函数和析构函数的实现:构造函数和析构函数的实现:功能与成员函数Time:setTime()类似Time:Time()coutThe destructor be called:hour:minute:secondendl;void main(void)Time t1(10,35,55);/自动调用构造函数自动调用构造函数 Time t2(16,53,9);/自动调用构造函数自动调用构造函数/退出退出main()主函数时自动调用析构函数主函数时自动调用
18、析构函数构造函数和析构函数的自动调用:构造函数和析构函数的自动调用:程序运行结果为:程序运行结果为:The constructor be called:10:35:55The constructor be called:16:53:9The destructor be called:16:53:9The destructor be called:10:35:55为什么是这个结果?当创建一个对象时,系统先根据类定义的成员变量为对象分配内存空间,然后自动调用对象的构造函数对这段内存空间进行初始化处理,从而完成对象的初始化。当撤消一个对象时,系统先自动调用对象的析构函数,然后释放对象所占内存空间。从
19、程序的运行结果可以看出,析构函数的调用顺序一般与构造函数的调用顺序相反。栈栈:后进先出表结果分析:结果分析:l与一般数据类型的变量相比,对象在它的生存期会有大量的操作,有时这些操作的结果必须在对象的生存期结束时加以清理。因此可以在析构函数中进行动态分配的内存清理工作。l如果定义类时没有提供构造函数和析构函数,编译系统将会自动为类分别添加一个缺省的构造函数和析构函数。如果用户加上自定义的构造函数和析构函数,编译系统将不会再添加缺省的构造函数和析构函数。l若构造函数无参数,则声明对象时也不能给出参数。补充说明:补充说明:3.2.3 this指针指针lthis指针是一个特殊的隐藏在对象中的指针,每一
20、个处于生存期的对象都有一个this指针,用于指向对象本身。l当类的某个非静态成员函数被调用时,系统通过this指针确定是哪一个对象的该成员函数被调用。实际上,this指针总是作为一个隐含参数传递给类的每一个成员函数。例:例:下面定义的成员函数并没有声明this参数:void Time:showTime()couthour:minute:secondendl;编译器会把this指针作为成员函数的参数:void Time:showTime(Time*this);couthour:minute:secondendl;调用时:调用时:当程序中调用某个成员函数时,编译器会把该对象的地址赋值给this指针
21、,并将该地址值加入到参数表中,如下所示:EndTime.showTime(&EndTime);作用:作用:在一个成员函数中经常需要调用其它函数(非本类的成员函数),而有时需要把对象本身(即对象的地址)作为参数传递给被调用函数,这时必须使用this指针。例:例:例例 this指针的使用。#include#include class Personpublic:/可在外部直接访问public属性的数据成员 char m_strName20;char m_ID18;public:Person(char*strName,char*ID)/内联构造函数 strcpy(m_strName,strName);
22、strcpy(m_ID,ID);void Show();void Display(Person*pObj)/非成员函数 coutName:m_strNameendlID:m_IDShow();/通过调用Show调用Display3.2.4 静态成员静态成员静态成员的概念:静态成员的概念:一般情况下,同一个类不同对象的数据成员所占用的内存空间是不同的(体现了不同对象具有不同的属性值)。在有些情况下,类的数据成员的值对每个对象都是相同的,如当前已创建对象的数量,这时可以将该数据成员声明为静态数据成员(占有相同的存储单元)。静态成员的声明:静态成员的声明:在声明成员时以关键字static开头,例如:
23、public:static int m_nCount;说明:说明:l静态成员分为静态数据成员和静态成员函数。l静态数据成员类似于一般的static静态变量,它具有全局性。静态数据成员属于整个类,为类的所有对象共享。l无论类的对象有多少,类的静态数据成员只有一份,存储在同一个内存空间。即使没有创建类的一个对象,类的静态数据成员也是存在的。l使用静态数据成员保证了该数据成员值的唯一性。静态成员的初始化:静态成员的初始化:放在类定义的外部 int Person:m_nCount=0;静态成员的访问:静态成员的访问:静态成员的访问:静态成员的访问:l公有静态成员:三种方式(1)通过对象访问,如:per
24、son1.m_nCount=100;(2)利用类名和作用域限定符(:)访问,如:int Person:m_nCount=100;/初始化(3)在成员函数中访问,如:m_nCount+;l私有和保护静态成员:只能在成员函数中访问静态成员函数:静态成员函数:静态成员函数:静态成员函数:l成员函数也可以是静态的,其声明方式与静态成员变量类似。如:public:static int GetCount();/获取静态数据成员l静态成员函数也与一个类相关联,而不只与一个特定的对象相关联。l区别非静态成员函数,静态成员函数没有this指针,因为类的静态成员函数只有一个运行实例。l成员函数一般是公有属性,可以
25、通过对象、类名和作用域限定符、在成员函数中三种方式调用静态成员函数。静态成员函数只能访问类的静态成员(成员变量和成员函数),而不能访问类的非静态成员。因为当通过类名和运算符“:”调用一个静态成员函数时,不能确定函数中所访问的非静态成员属于哪一个对象。解决方法:解决方法:将对象作为静态成员函数的参数,然后在静态成员函数中通过对象访问它的非静态成员。注注 意意例例例例 静态成员变量和静态成员函数的使用。#include#include class Personpublic:char m_strName20;long m_ID;static int m_nCount;/静态成员变量,表示已创建对象的
26、数量public:Person(char*,long);/构造函数 static int GetCount();/静态成员函数 static long GetID(Person);/对象作为静态成员函数的参数;Person:Person(char*strName,long ID)strcpy(m_strName,strName);m_ID=ID;m_nCount+;/对象数目加1int Person:GetCount()return m_nCount;/访问静态成员变量long Person:GetID(Person x)return x.m_ID;/不能直接访问非静态成员m_IDint Pe
27、rson:m_nCount=0;/初始化静态成员变量void main()Person e1(LiuJun,1101051);coutPerson:m_nCount,e1.m_nCount n;/通过类或对象访问静态成员变量 coutPerson:GetCount(),”Person:GetID(e1)n;/通过类调用静态成员函数 coute1.GetCount(),e1.GetID(e1)n;/通过对象调用静态成员函数 Person e2(WangXiaogang,1101058);coutPerson:GetCount(),”Person:GetID(e2)n;coute2.GetCoun
28、t(),e2.GetID(e2)n;coute1.GetCount(),e1.GetID(e1)n;/e1和e2共享静态成员变量m_nCount程序运行结果为:程序运行结果为:1,11,11010511,11010512,11010582,11010582,1101051作业:作业:P109-114,3-37(1),3-423.2.5 友元友元 类具有封装性,类的私有成员一般只能通过该类的成员函数访问,这种封装性隐藏了对象的数据成员,保证了对象的安全,但有时带来了编程的不方便。友元函数:友元函数:C+提供了一种函数,它虽然不是一个类的成员函数,但可以象成员函数一样访问该类的所有成员,包括私有成
29、员和保护成员。这种函数称为友元(friend)函数。一个函数要成为一个类的友员函数,需要在类的定义中声明该函数,并在函数声明的前面加上关键字friend。友元函数本身的定义没有什么特殊要求,可以是一般函数,也可以是另一个类的成员函数。为了能够在友元函数中访问并设置类的私有数据成员,一个类的友元函数一般将该类的引用作为函数参数。例例友元函数的声明:友元函数的声明:友元函数的声明:友元函数的声明:例如:例如:例如:例如:class A friend void display(A);/友元函数是一个一般函数 friend void B:BMemberFun(A&);/友元函数是另一个类B的成员函数p
30、ublic:.友元类:友元类:友元的另一种类型是友元类,一个类可以声明另一个类为其友元类,这个友元类的所有成员函数都可以访问声明其为友元的类的所有成员。由于访问权限控制符不影响友元声明,友元声明可放在类体中任何地方,建议把友元声明放在类体的开始位置。例例 友元(一般友元函数、友元成员函数和友元类)的声明和使用。P78-79,例3-7。说明:说明:l友元关系是单方向的,不具有交换性和传递性。l使用友元虽然简化了编程,并可避免调用成员函数的开销,但破坏了类的封装性,建议谨慎使用。作业:作业:P114,3-433.3 类的继承类的继承 l继承继承是面向对象程序设计方法的四个基本特征之一,是程序代码可
31、重用性的具体体现。l在C+面向对象程序设计中,所谓类的继承就是利用现有的类创建一个新的类。新类继承了现有类的属性和行为。l为了使新类具有自己所需的功能,它可以扩充和完善现有类的属性和行为,使之更具体。l微软基础类MFC就是通过类的继承来体现类的可重用性和可扩充性。继承继承发扬发扬3.3.1 基类和派生类基类和派生类 l在现实世界中,一类事物的对象常常也属于另一类事物。l在面向对象程序设计方法中,一个类的对象也常常是另一个类的对象,即一个类具有了另一个类的属性和方法。l在定义一个类时,根据类的继承性,我们能够且应尽可能地利用现有的类来定制新的类,而不必重新设计新的类。1.1.问题的提出问题的提出
32、问题的提出问题的提出 在继承关系中,新定义的类称为被继承类的派派生生类类或子子类类,而被继承的类称为新定义类的基基类类或父类父类。派生类继承了基类的所有成员。一个派生类也可以作为另一个派生类的基类。2.2.基类和派生类的概念基类和派生类的概念基类和派生类的概念基类和派生类的概念 class :./派生类新增加的成员声明列表;3.3.派生类的定义派生类的定义派生类的定义派生类的定义 l派生方式决定了基类的成员在派生类中的访问权限。派生方式共有三种:public、private和protected(缺省值为private)。l虽然派生类继承了基类的所有成员,但为了不破坏基类的封装性,无论采用哪种派
33、生方式,基类的私有成员在派生类中都是不可见的,即不允许在派生类的成员函数中访问基类的私有成员。说明:说明:说明:说明:l采用public派生,基类成员的访问权限在派生类中保持不变,即基类所有的公有或保护成员在派生类中仍为公有或保护成员。public派生最常用。(1)可以在派生类的成员函数中访问基类的非私有成员;(2)可通过派生类的对象直接访问基类的公有成员。l采用private私有派生,基类所有的公有和保护成员在派生类中都成为私有成员,只允许在派生类的成员函数中访问基类的非私有成员。private派生很少使用。l采用protected保护派生,基类所有的公有和保护成员在派生类中都成为保护成员,
34、只允许在派生类的成员函数和该派生类的派生类的成员函数中访问基类的非私有成员。三种派生方式的区别:三种派生方式的区别:三种派生方式的区别:三种派生方式的区别:例 定义类Point,然后定义类Point的派生类Circle。#include class Point/定义基类,表示点private:int x;int y;public:void setPoint(int a,int b)x=a;y=b;/设置坐标 int getX()return x;/取得X坐标 int getY()return y;/取得Y坐标;class Circle:public Point /定义派生类,表示圆privat
35、e:int radius;public:void setRadius(int r)radius=r;/设置半径 int getRadius()return radius;/取得半径 int getUpperLeftX()return getX()radius;/取得外接正方形左上角的X坐标 int getUpperLeftY()return getY()+radius;/取得外接正方形左上角的Y坐标;main()Circle c;c.setPoint(200,250);c.setRadius(100);coutX=c.getX(),Y=c.getY(),Radius=c.getRadius()
36、endl;coutUpperLeft X=c.getUpperLeftX(),UpperLeft Y=c.getUpperLeftY()endl;公有派生类的对象可以直接访问基类Point的公有成员程序运行结果:程序运行结果:X=200X=200,Y=250Y=250,Radius=100Radius=100UpperLeftUpperLeft X=100 X=100,UpperLeftUpperLeft Y=350 Y=350 l派生类Circle通过public派生方式继承了基类Point的所有成员(除私有成员外所有成员的访问权限不变),同时还定义了自己的成员变量和成员函数。l若将类Cir
37、cle的派生方式改为private或protected,则下述语句是非法的:c.setPoint(200,250);说明:说明:说明:说明:容易混淆l无论哪种派生方式,派生类都继承了基类的所有成员,包括私有成员。我们虽然不能在派生类Circle中直接访问私有数据成员x和y,但可以通过继承 的 公 有 成 员 函 数 getX()、getY()和 setPoint()访问或设置它们。利用类继承定义类可能带来一个问问题题:派生类会继承它不需要的基类中的数据成员和成员函数,这时,基类中不适合于派生类的成员可以在派生类中重新加以定义。最后一个问题:最后一个问题:最后一个问题:最后一个问题:例例例例 派
38、生类成员函数对基类成员函数的覆盖。派生类成员函数对基类成员函数的覆盖。#include class Apublic:void Show()coutA:Shown;class B:public Apublic:void Show()coutB:Shown;/在派生类中重新定义成员函数 void Display()Show();/调用派生类B的成员函数Show();void main()A a;B b;a.Show();/调用基类A的成员函数Show()b.Show();/调用派生类B的成员函数Show()b.Display();如果想调用基类A的成员函数Show(),可以使用作用域限定符“:”:
39、A:Show();从本例可以看出,虽然派生类继承了基类的所有成员函数,但如果派生类某个成员函数的名称和参数与基类成员函数一致(即在派生类中对该成员函数重新进行了定义),则在派生类中调用的成员函数是派生类的成员函数。请问:如果在派生类B中没有对成员函数Show()重新进行定义,程序运行结果如何?程序运行结果:程序运行结果:A:ShowB:ShowB:Show 为什么我们经常在现有类的基础上采用继承的方法来定制新类,而不通过直接修改现有类来设计自己的类?除了代码重用的优越性,其主要原因是可能得不到基类的实现源码。继承的重要性!在利用微软基础类MFC派生自己的类时,我们只需要MFC类声明的头文件(利
40、用#include指令将头文件包含)和含有成员函数目标代码的OBJ文件,并不需要整个MFC类库的实现源码。3.3.2 基类和派生类的构造函数基类和派生类的构造函数l一个派生类对象也属于其基类,因此当程序创建一个派生类对象时,系统首先自动创建一个基类对象。l在调用派生类的构造函数构建派生类对象时,系统首先调用基类的构造函数构建基类对象。当派生类对象的生存期结束时,首先调用派生类的析构函数,然后调用基类的析构函数。1.1.问题的提出问题的提出问题的提出问题的提出 编译器在对程序编译时,首先生成基类构造函数的调用代码,然后生成派生类构造函数的调用代码。隐式调用和显式调用两种方式:2.2.基类构造函数
41、的调用方式基类构造函数的调用方式基类构造函数的调用方式基类构造函数的调用方式 注注意意:除非基类有默认的构造函数,否则必须采用显式调用方式。(1)隐隐式式方方式式是指在派生类的构造函数中不指定对应的基类的构造函数,调用的是基类的默认构造函数(即含有缺省参数值或不带参数的构造函数)。(2)显显式式方方式式是指在派生类的构造函数中指定要调用的基类构造函数,并将派生类构造函数的部分参数值传递给基类构造函数。设类B是类A的派生类,则派生类B显式方式构造函数的定义形式如下:3.3.显式方式构造函数的定义显式方式构造函数的定义显式方式构造函数的定义显式方式构造函数的定义 B:B():A()./类B构造函数
42、的实现代码 形参声明中的部分参数,传递给基类构造函数 派生类构造函数形参的名称和类型 l派生类构造函数既初始化派生类的数据成员,又通过基类构造函数初始化其基类的数据成员。l参数表中参数的个数和类型要与基类某个构造函数的形参声明一致。PointCircle Cylinder 注意:注意:当基类有多个构造函数时,编译器根据派生类构造函数为基类构造函数提供的参数表来确定调用基类的哪一个构造函数。例例 首先定义类Point,然后定义类Point的派生类Circle,再定义类Circle的派生类Cylinder。(x,y)(x,y)r(x,y)hr#include class Point/定义基类Poi
43、ntprotected:int x,y;public:Point(int a=0,int b=0)/含有缺省参数值的构造函数也是默认的构造函数x=a;y=b;coutPoint constructor:x,yendl;Point()coutPoint destructor:x,yendl;class Circle :public Point/定义类Point的派生类protected:int radius;public:/显式调用基类的构造函数 Circle(int a=0,int b=0,int r=0):Point(a,b)radius=r;coutCircle constructor:r
44、adiusx,yendl;Circle()coutCircle destructor:radiusx,yendl;class Cylinder:public Circle/定义类Circle的派生类protected:int height;public:/显式调用基类的构造函数 Cylinder(int a=0,int b=0,int r=0,int h=0):Circle(a,b,r)height=h;coutCylinder constructor:heightradiusx,yendl;Cylinder()coutCylinder destructor:heightradiusx,yen
45、dl;main()Cylinder cylinder(200,300,100,400);/调用了类Point、Circle和Cylinder的构造函数Point constructor:200,300Circle constructor:100 200,300Cylinder constructor:400 100 200,300Cylinder destructor:400 100 200,300Circle destructor:100 200,300Point destructor:200,300程序运行结果:程序运行结果:构造函数的执行顺序:构造函数的执行顺序:析构函数的执行顺序:析构
46、函数的执行顺序:Point()Circle()Cylinder()当声明Cylinder对象时 Cylinder()Circle()Point()当程序结束时作业:作业:P110-1143-37(2),3-45(上机),3-463.3.3 多重继承多重继承1.1.单继承和多重继承的概念单继承和多重继承的概念单继承和多重继承的概念单继承和多重继承的概念class Aclass Bclass Cclass Aclass Bclass C每个派生类只有一个直接基类 单继承一个派生类同时从多个基类派生而来,即有多个直接基类 多重继承 2.2.多重继承派生类的定义多重继承派生类的定义多重继承派生类的定义
47、多重继承派生类的定义 设类B是类A1、A2、An的派生类,多重继承的派生类的定义形式为:class :,./派生类新增加的成员声明列表 ;多 重 继 承 的 派 生 方 式 也 有private、public和protected三种,各基类的派生方式可以不同 例例例例 定义一个派生类定义一个派生类MultiDerivedMultiDerived,它是类它是类BaseABaseA和和BaseBBaseB的派生类。的派生类。class BaseA /定义基类protected:int a;public:void setA(int);class BaseB /定义基类protected:int b;
48、public:void setB(int);定义两个基类classclass MultiDerivedMultiDerived :public :public BaseA BaseA,public,public BaseB BaseB/定义定义多重继承的派生类多重继承的派生类 public:public:int getAB int getAB();();/添加成员函数添加成员函数;void BaseA:setA(int x)a=x;void BaseB:setB(int x)b=x;int MultiDerived:getAB()return a+b;可以直接访问基类中protected属性成
49、员成员函数的实现main()main()MultiDerived md MultiDerived md;/声明派生类的对象声明派生类的对象 mdmd.setAsetA(30);(30);/调用基类调用基类BaseABaseA的成员函数的成员函数 mdmd.setBsetB(70);(70);/调用基类调用基类BaseBBaseB的成员函数的成员函数 coutcouta+b=a+b=mdmd.getABgetAB()()endlendl;/调用派生类调用派生类MultiDerivedMultiDerived自定义的成员函数自定义的成员函数 程序运行结果:a+b=1003.3.4 虚基类虚基类 1
50、.1.多重继承中的多重继承中的多重继承中的多重继承中的 二义性问题二义性问题二义性问题二义性问题 class C:class C:public Apublic A public:public:int int c;c;class D:public B,public Cclass D:public B,public C /类类D D派生于类派生于类B B和类和类C Cpublic:public:int int d;d;main()main()D d1;D d1;d1.a=100;d1.a=100;class Aclass A public:public:intint a;a;class B:cla