1
第十五讲
面向对象程序设计与 C++
2
内 容
?程序设计方法概述
?面向对象程序设计方法
?C++语言
3
一、程序设计方法概述
4
程序设计方法
?早期的程序设计方法
?结构化程序设计方法
?面向对象程序设计方法
5
早期的程序设计方法追求程序的高效率,
编程过份依赖技巧,而不注重所编写程序的结
构,也就是 没有固定程序设计方法 的时期。程
序的可读性、可重用性都很差。其中一个典型
问题是频繁使用 goto语句。
虽然这种方法存在很多问题,但对于单人
完成较为简单的任务,事实上还是经常被采用
的。
早期的程序设计方法
6
结构化方法出现在 70年代中期,我们可以
这样理解它,
结构化程序设计方法是从程序要实现的 功
能 的角度出发的。一般按照 自顶向下, 逐步求
精 的方式,将程序要完成的功能逐级划分成许
多小的功能模块,象搭积木一样搭起来。这些
小的功能模块最终都可以转化成三种基本控制
结构的组合。
所谓的功能可以理解为对数据的操作。在
程序实现中,特定的功能或功能模块一般用 函
数 来实现,它们要对特定的 数据 进行操作。
结构化程序设计方法
7
结构化设计方法的特点
?结构化程序设计方法的主要技术是 自顶
向下, 逐步求精,采用 单入口, 单出口
的控制结构
?自顶向下 是一种分解问题的技术,逐步
求精 指结构化程序的连续分解,最终成
为下面三种基本控制结构的组合
?三种基本控制结构,顺序、分支、循环
8
分支结构
语句 1
语句 2
语句 3
条件
语句 2 语句 1
语句 1
语句 2
顺序结构 循环结构
9
例,
从键盘输入一个学生的信息(包括姓名、
年龄、性别、学号等)和一个老师的信
息(包括姓名、年龄、性别、是否授课
等),然后将信息输出到屏幕。
一个简单的例子
10
分析,
根据需求(题目要求),我们可以把问
题划分为两个功能模块,一个是 输入模块,
一个是 输出模块,做完了输入模块,再做
输出模块。再具体考虑每个模块如何实现
(逐步求精)。
我们用 C语言来写,参看下面的代码,
11
// ……
void main() // 主函数开始
{
// 声明用于存储学生信息的变量
char strStudentName[20]; // 学生姓名
int nStudentAge; // 学生年龄
char cStudentSex; // 学生性别
int nStudentNumber; // 学生学号
// 声明用于存储老师信息的变量
char strTeacherName[20]; // 老师姓名
int nTeacherAge; // 老师年龄
char cTeacherSex; // 老师性别
int nIsTeaching; // 是否授课
// 输入模块
GetStudentInfo(…); // 输入学生信息
GetTeacherInfo(…); // 输入老师信息
// 输出模块
PrintStudentInfo(…); // 输出学生信息
PrintStudentInfo(…); // 输出老师信息
}
描述学生的数据
描述老师的数据
函数
函数
12
上面的例子中,我们可以进一步将属于学生和
老师的变量放入 结构 中 。这样可以在一定程度上完
成 对数据的封装 。但 在结构化程序设计中,数据与
对其进行操作的函数仍是分离的。
// 声明学生结构 Student
struct Student
{
char strStudentName[20]; // 学生姓名
int nStudentAge; // 学生年龄
char cStudentSex; // 学生性别
int nStudentNumber; // 学生学号
};
// 声明老师结构 Teacher
struct Teacher
{
char strTeacherName[20]; // 老师姓名
int nTeacherAge; // 老师年龄
char cTeacherSex; // 老师性别
int nIsTeaching; // 是否教书
};
13
程序=算法+数据结构
算法 数据结构
描述问题 解决问题
结构化程序设计方法
14
问题,
函数用于完成一定的功能,它们都是针
对特定的数据进行操作的。那么我们能
不能以特定的数据为中心,将数据与对
其进行操作的函数封装起来呢?
15
面向对象程序设计方法
?面向对象程序设计出现在 80年代中后期
?面向对象程序设计是建立在结构化程序
设计基础上的,但它不再是从功能入手,
而是从 对象 (人、地方、事情等)入手
?面向对象程序设计以 类 作为构造程序的
基本单位,它具有封装、数据抽象、继
承、多态等特点
16
简单地说,对象就是现实世界中的各
种实体,包括人、地点和事物等。例如,
桌子、椅子、教室、学生、老师、电话、
汽车等等。一般都要从 属性 和 行为 两个
方面来对它们加以描述。在这里,我们
认为对象和对象的实例是同一个概念。
什么是对象?
17
对象具有的一些特征称为属性,以 一个
人 为例,他的姓名、年龄、身高、体重等
可以作为他的属性。 这些属性会有其对应
的值,一般至少会有一项区别于其它对象,
它们在程序设计中对应的是一定的 数据 。
为了达到目的,对象 必须提供的功能
(或必须提供的服务)称为对象的 行为,
在程序设计中对应一定的方法( 函数 )。
属性和行为
18
类描述了一组具有相同属性(数据
元素)和相同行为(函数)的对象。
类的数据成员是对对象属性的抽象,
类的函数成员是对对象行为的抽象,
而类本身就是对对象的抽象。
什么是类?
19
class Student // Student类的声明
{
public,// 公有成员
Student(); // 构造函数
~Student(); // 析构函数
char* GetName(); // 查询姓名
int GetAge(); // 查询年龄
char GetSex(); // 查询姓名
int GetNumber(); // 查询学号
bool SetName(char* n); // 设置姓名
bool SetAge(int age); // 设置年龄
bool SetSex(char* s); // 设置性别
bool SetNumber(int num);// 设置学号
protected,// 保护成员
char m_strName[20]; // 姓名,字符串数组
int m_nAge; // 年龄,整型
char m_cSex; // 性别,字符型
int m_nNumber; // 学号,整型
};
例,C++中类的声明 ——Student类
成员函数
成员函数
成员变量
20
……
Student A; // 声明 Student的对象 A
A.SetName(“张三” ); // 设置 A的名字
A.SetAge(20); // 设置 A的年龄
……
例,C++中类使用
21
程序=对象+对象+……
对象 1
描述问题 解决问题
面向对象程序设计方法
……
数据 算法
对象 n
数据 算法 ……
22
总的来说,
?结构化程序设计方法是一种模块化程序设计
方法,它在解决问题时是 以功能为中心 的,
一定的功能模块虽然也作用于特定的数据,
但它们并没有被封装在一起。
?面向对象程序设计方法则是 以对象为中心 来
解决问题的。属于同种对象的属性(数据)
和服务(功能)被抽象出来封装到一起。
23
二、面向对象程序设计方法
24
? 数据抽象
? 封装
? 继承
? 多态
? 动态绑定
面向对象方法的主要特点
25
数据抽象
对 象 类 抽象数据类型
抽象 抽象
具体 具体
类是一组相似对象的抽象描述,它
抽取了这些对象的共性组成了一个共同
的概念。抽象数据类型 ( Abstract Data
Type,ADT) 是一组相似的类的抽象,
而一个类又是 ADT的具体实现。
26
封装是指软件的组成部分(模块、子
程序、方法等)应该 互相独立,或者 隐
藏设计的细节 。在传统的方法中,封装
通常局限于将功能和数据分开封装;而
在面向对象方法中,封装将功能和数据
同时装入对象中。
参看 Student类的例子
封 装
27
class Student // Student类的声明
{
public,// 公有成员
Student(); // 构造函数
~Student(); // 析构函数
char* GetName(); // 查询姓名
int GetAge(); // 查询年龄
char GetSex(); // 查询姓名
int GetNumber(); // 查询学号
bool SetName(char* n); // 设置姓名
bool SetAge(int age); // 设置年龄
bool SetSex(char* s); // 设置性别
bool SetNumber(int num);// 设置学号
protected,// 保护成员
char m_strName[20]; // 姓名,字符串数组
int m_nAge; // 年龄,整型
char m_cSex; // 性别,字符型
int m_nNumber; // 学号,整型
};
例,C++中类的声明 ——Student类
28
如果类与类之间有 is-a(是一种)的
关系,那么可以采用继承机制来表示。
子类可以自动继承父类中的一些属性和
行为,而不必再进行定义,从而实现了
代码的复用 。同时,继承也是产生 新类
的方法之一。
继 承
29
从上图可以看出,学生和教师都是人
的一种,所以,学生类和教师类可以从
人类继承而来,从而实现了代码的共享。
人
学 生 教 师
30
class People // People类的声明
{
public,// 公有成员
People(); // 构造函数
~People(); // 析构函数
char* GetName(); // 查询姓名
int GetAge(); // 查询年龄
……
bool SetName(char* n); // 设置姓名
bool SetAge(int age); // 设置年龄
……
private,// 私有成员
protected,// 保护成员
char m_strName[20]; // 姓名,字符串数组
int m_nAge; // 年龄,整型
char m_cSex; // 性别,字符型
……
};
类的声明举例 ——People类
31
class Teacher,public People // Teacher类的声明
{
public,// 公有成员
Teacher(); // 构造函数
~Teacher(); // 析构函数
bool IsTeaching(); // 查询是否授课
……
private,// 私有成员
……
protected,// 保护成员
bool m_bIsTeaching // 是否授课
……
};
类的声明举例 ——Teacher类
32
class Student, public People
{
public,
Student(); // 构造函数
~Student(); // 析构函数
int GetNumber(); // 查询学号
bool SetNumber(int n); // 设置学号
……
private,
……
protected,
int m_nNumber; // 学号
……
};
类的声明举例 ——Student类
33
在程序中同一符号或名字在不同情况
下具有不同解释的现象称为多态性
( Polymorphism)。在面向对象程序设
计语言中,由程序员设计的多态性由两
种基本形式,编译时多态性 和 运行时多
态性 。许多程序设计语言都或多或少地
支持多态性,但 运行时多态性是面向对
象程序设计语言的一大特点 。
多 态
34
编译时多态性 是指在程序编译阶段即可
确定下来的多态性,主要通过使用重载
( Overloading)机制获得,重载机制包括
函数重载 和 运算符重载 两大类。
举一个 C++中的例子,
int Abs(int x);
double Abs(double x);
cout<<Abs(-4)<<endl; //调用 int Abs…
cout<<Abs(3.2)<<endl;//调用 double Abs…
35
运行时多态性 是指必须等到程序动态
运行时才可确定的多态性,主要通过 继
承 结合 动态绑定 获得。
动态绑定也称晚绑定,它也是面向对
象的重要特点之一。动态绑定的使用可
以提高程序的可用性和可扩展性。
36
八十年代末以来,由 Booch,Coad,
Yourdon,OMT,Jacobson等人提出的多种
面向对象 方法在 已经 得到了广泛的 应用 。面
向对象方法中的三种基本活动就是,
(1) 识别对象和类
(2) 描述对象和类之间的关系
(3) 通过描述类的功能定义对象的行为
面向对象方法
37
科德将对象模型分为下面四个部件,也就是将
对象分为了四组,
( 1)问题域 PD( problem domain)
( 2)人机交互 HI( human interaction)
( 3)数据管理 DM( data management)
( 2)系统交互 SI( system interaction)
科德( Coad)方法
38
?问题域问题( PD) 通常最先考虑,因为用
户往往最关心为自己的商业问题建模。问题
域部件包含与需要建模的问题直接有关的对
象。问题域部件的对象技术上呈现中型,它
们几乎不了解或完全不了解人机交互、数据
管理和系统交互部件的对象。
?人机交互部件( HI) 包含为问题域对象和人
们之间提供界面的对象。在对象模型中,人
机交互部件的对象通常对应具体的窗口和报
表。
39
?数据管理部件( DM) 包含为问题域对象和
数据库系统或文件管理系统之间提供界面的
对象。在对象模型中,数据管理部件的对象
通常对应某些需要保存及搜索的问题域对象 。
?系统交互部件( SI) 包含为问题域对象和其
它系统或设备提供界面的对象。系统交互对
象封装了通信协议,使得问题域对象不需要
了解底层的实现细节。
40
41
三,C++语言
?C与 C++的比较
?C++中的数据类型
?C++程序框架
?类和对象
?类的声明、实现、继承、多态性
?举例
42
C 语 言
C语言的优点,
?与硬件无关,可移植性强
?语言简洁,使用方便
?丰富的运算符和数据类型
?可直接访问内存地址
?能进行位操作
?目标代码质量高,运行效率高
43
C语言的弱点,
?检查机制弱,编译时不能发现编程错误
?面向过程的语言,没有支持代码复用的
机制
?很难控制大规模程序的复杂性
44
C++ 语 言
?是 C的超集,保持了与 C的 兼容 性
?继承了 C语言 高效性, 灵活性 的特点
?扩充了对 面向对象程序设计 和 高层次问题抽象
方法的支持,是面向对象程序设计语言之一
?完善了 C语言的 类型检查, 代码重用, 数据抽
象 机制,便于大规模软件开发
?既反映了 结构化 程序设计方法,又反映了 面向
对象 程序设计方法
45
#include <iostream.h> // 预编译命令
void main() // 主函数开始
{
cout <<,Hello the world!” << endl;
} // 主函数结束
一个简单的 C++程序的例子
C++中提供的 iostream库可以完成对输入输出
流的操作,“流”( stream)就是简单的字
符序列。在 C++中用 cin,cout和 cerr来定义
键盘输入类、屏幕输出类和错误信息输出类。
endl用于换行并清空流。如,
cin >> a; cout <<,输入的数据为” << a << endl;
46
基本数据类型
构造数据类型
整型 ( int)
实数型
字符型 ( char)
空类型 ( void)
布尔型 ( bool)
引申数据类型
结构化数据类型
指针 ( *)
引用 ( &)
数组 ( [])
结构 (struct)
联合 (union)
枚举 ( enum)
类 (class)
浮点型 ( float)
双精度型 ( double)
C++中的数据类型
47
引用 &
一般用于参数传递 。 &可以认为是取地址。
引用类型的变量并不真正创建新的对象,而
是作为另一个对象的别名。例如
void swap(int& a,int& b) // 用于交换 a,b
{
int temp;
temp = a; a = b; b = temp;
}
对形参 a,b的操作等价与对实参的操作。
48
C++程序结构
C++源代码一般都由若干类或函数组成。为
了便于管理,一般把不同的类放在不同的文
件中,对于类的声明和实现也分别放在对应
的,h(或,hpp)和,cpp文件中。
由于文件较多,所以为了便于管理,一般的
集成开发工具都会提供工程( Project)管理
功能来管理这些文件,对源文件进行编译和
链接。
49
类的声明(,h )
目标文件(,o bj )
类的实现(,c pp )
编译
类的声明(,h )
目标文件(,o bj )
类的实现(,c pp )
编译 ……
库函数
可执行文件(,e x e )
链接
目标文件(,o bj )
主程序(,c p p )
编译
50
为防止 重复包含 (多次包含同一头文件,造
成重复定义)和 嵌套包含 (互相包含)造成
的错误,应该在头文件里加上如下预编译命
令。例如,对于 test1.h
#ifndef __INCLUDE_TEST1_H__
#define __INCLUDE_TEST1_H__
// …
// test1.h的内容
……
#endif
说 明
51
class 类名
{
public,
公有数据和函数
protected,
受保护数据和函数
private,
私有数据和函数
};
类的声明
注意要有分号
52
class People // People类的声明
{
public,// 公有成员
People(); // 构造函数
People(char* name,int age,char sex);
~People(); // 析构函数
char* GetName(); // 查询姓名
int GetAge(); // 查询年龄
char GetSex(); // 查询性别
bool SetName(char *n); // 设置姓名
bool SetAge(int age); // 设置年龄
bool SetSex(char sex); // 设置性别
private,// 私有成员
protected,// 保护成员
char m_strName[20]; // 姓名,字符串数组
int m_nAge; // 年龄,整型
char m_cSex; // 性别,字符型
};
类的声明举例 ——People类
53
? 类名是有效的标识符,不能是关键字
(保留字)
? 数据成员在声明时不能进行初始化
? 数据成员不能是正在声明的类,但可以
用指针
? 访问控制 public,private,protected可以
以任意次序出现,并可重复多次
有关类的声明
54
? 类的成员函数中有两个函数是特殊的,就是 构
造函数 和 析构函数,它们没有返回值,且一般
不直接调用这些函数,而是在对象建立和撤销
时分别被 隐式调用 的。
?构造函数的函数名要与类名相同,在创建对象
时被调用。可以不定义构造函数,编译程序会
自动生成一个缺省的构造函数。
?析构函数的函数名是在类名前加上一个,~”。
同上,也可以不定义析构函数,编译程序会自
动生成一个缺省的析构函数。
构造函数和析构函数
55
? 在 类的构造函数 中,一般要完成一些 初始化 的
任务。例如将一些指针赋值为 NULL,给一些
变量赋初值,或者为一些指针分配空间等。还
可以利用函数的重载,声明并实现多种构造函
数,以完成不同的初始化任务。(参见 People
类)
?在 类的析构函数 中,一般要进行一些收尾工作,
如 空间的释放 。如对于为指针分配的空间,要
加以释放,等。
构造函数和析构函数
56
?public,公有成员, 可以被任何函数访
问 ( 即可以从类的外部进行访问 ) 。
?private,私有成员, 只有类成员函数
和友员类可以访问 。
?protected,保护成员, 只有类成员函
数, 派生类, 友员类可以访问 。
?缺省方式为 private
类成员访问控制
57
People::People() // 构造函数
{ // 函数体开始
// 初始化名字为空串
memset(m_strName,0,sizeof(m_strName));
// 初始化年龄为 0
m_nAge=0;
// 初始化性别为 ?0?
m_cSex='0';
} // 函数体结束
People::~People(){} // 析构函数
类的实现 ——People类
58
People::People(char* name,int age=0,char sex=?0?)// 构造函数
{ // 函数体开始
if (name!=NULL) // 初始化 m_strname
strcpy(m_strName,name);
else // 如果 name为空,则初始化 m_strname为空
memset(m_strName,0,sizeof(m_strName));
if((age>=0) && (age<=200)) // 初始化年龄 m_nAge
{
m_nAge=age;
}
else
m_nAge=0; // age不合法,则将 m_nAge初始化为 0
switch (sex) // 初始化性别 m_cSex
{
case ?M?,m_cSex=?M?; break; // ?M?表示男
case ?F?,m_cSex=?F?; break; // ?F?表示女
default,m_cSex=?0?; // ?0?表示未赋值
}
}
59
// 查询姓名的成员函数
char* People::GetName()
{
return m_strName; // 返回姓名
}
// 查询年龄的成员函数
int People::GetAge()
{
return m_nAge; // 返回年龄
}
// 查询性别的成员函数
char People::GetSex()
{
return m_cSex; // 返回性别
}
// 用于设置的成员函数的实现不在此列出
// ……
60
void main() // 主函数
{
People A; // 声明类 People的对象 A
People B("Li Si");// 声明类 People的对象 B,参数 "Li Si"
// 声明类 People指针,并构造实例
People* C = new People("Zhang San",20,'M');
A.SetName(“Wang Wu”); // 对对象 A设置名字
cout <<,A?s name:” << A.GetName() << endl;// 输出 A的信息
cout << "A's age:" << A.GetAge() << endl;
cout << "A's sex:" << A.GetSex() << endl << endl;
cout <<,B?s name:” << B.GetName() << endl;// 输出 B的信息
cout << "B's age:" << B.GetAge() << endl;
cout <<,B?s sex:” << B.GetSex() << endl << endl;
cout <<,C?s name:” << C->GetName() << endl;// 输出 C的信息
cout << "C's age:" << C->GetAge() << endl;
cout << "C's sex:" << C->GetSex() << endl << endl;
delete C; // 释放指针 C所指的空间
}
61
A's name:Wang Wu
A's age:0
A's sex:0
B's name:Li Si
B's age:0
B's sex:0
C's name:Zhang San
C's age:20
C's sex:M
程序运行结果
62
// 声明类 People的对象 A,调用没有参数的构造函数
People A;
// 声明类 People的对象 B,参数 "Li Si"
People B("Li Si");
// 声明类 People指针,并 动态 构造实例
People* C = new People("Zhang San",20,'M');
或,People *C;
C = new People("Zhang San",20,'M');
类对象的声明
63
People A(“Zhang San”),B; // 声明 People的对象 A和 B
B = A; // 用 A初始化对象 B
对象的复制
说明,
这种对象复制可以将 A的所有数据成员的值赋给 B,
如果对象的数据成员中不存在指针,则没有问题,但
如果其中含有指针,则这种方式只是简单地把指针所
指向的内容的地址复制过来,并没有复制其所指的内
容。这可能并不是用户所期望的。这要通过自定义的
复制策略来实现,拷贝构造函数 和 重载运算符,=”。
64
继 承
继承是面向对象程序设计的重要特点之
一。它描述了类之间的 is-a的关系。如
果类 B继承类 A,则称类 A是类 B的 基类
(或 父类 ),类 B是类 A的 派生类 (或 子
类 )。
65
class Student, public People
{
public,
Student(); // 构造函数
~Student(); // 析构函数
int GetNumber(); // 查询学号
bool SetNumber(int n); // 设置学号
private,
protected,
int m_nNumber; // 学号
};
举例 ——Student类
类声明第一行后面的,,public People” 指明了
Student类继承自 People类。 public是继承访问控制,
用于指明继承成员在子类中的访问控制 。这样,类
People中的一些成员就会被 Student类自动继承了。
66
继承成员的访问控制
父类访问控制 继承访问控制 子类访问控制
public
protected
private
public
public
protected
不可访问
public
protected
private
protected
protected
protected
不可访问
public
protected
private
private
private
private
不可访问
67
继承成员的访问控制
?父类中的 私有成员不能继承
?继承访问控制为 public时, 继承父类的访
问控制
?继承访问控制不是 public时, 子类中继承
成员的访问控制 与继承访问控制相同
68
类型兼容性
C++语言中,允许且 仅允许公有派生类兼容
基类 。这就是说,子类的对象可以赋值给父
类的对象, 指向父类的指针可以指向子类 。
反之则不正确。
例,class Child,public Father
Father* father; // 声明 Father对象指针
Child* child; // 声明 Child对象指针
// 动态创建对象
// …
father = child; // 正确
child = father; // 错误
69
多态性
如前所述,在程序中同一符号或名字在
不同情况下具有不同解释的现象称为多
态性。在面向对象程序设计语言中,由
程序员设计的多态性由两种基本形式:
编译时多态性 和 运行时多态性 。 运行时
多态性是面向对象程序设计语言的一大
特点 。
70
编译时多态性
编译时多态性是指在 程序编译阶段即可确
定下来的多态性,主要通过使用 重载 机制获
得,重载机制包括 函数重载 和 运算符重载 两
大类。
在声明函数原型时只要 形式参数的个数或
类型不同,就可以使用 相同的函数名 。只有
返回值的不同同名函数,并不认为是不同的
函数。 运算符重载 允许程序员重新定义 C++
语言已有的运算符。
71
函数重载
int Add(int a,int b); // 整数加法
float Add (float a,float b); // 单精度加法
double Add (double a,double b);// 双精度加法
int Abs(int a); // 整数的绝对值
float Abs(float a); // 单精度数的绝对值
double Abs(double a); // 双精度数的绝对值
对于普通函数和类成员函数都可以进行函数
的重载,构造函数也可以重载。
72
运算符重载
运算符可以理解为函数,一元、二元运算符
分别认为是具有一个、两个参数的函数。那
么,对一个 n维矢量类,我们可以重载, +,,
,-, 运算符,以实现矢量的加减运算。
例 class Vector,
Vector operator+(const Vector& Vec);// 加法
Vector operator-(const Vector& Vec);// 减法
Vector operator-( ); // 求反
Vector operator=(const Vector& Vec);// 赋值
73
运行时多态性是指必须等到 程序动态
运行时才可确定的多态性,主要通过 继
承 结合 动态绑定 获得。这与类的继承密
切相关。因为存在类型的兼容性,所以
有些函数只有在运行时才能确定是调用
父类的还是子类的函数。在 C++中,使
用虚函数 ( Virtual Functions) 来实现。
运行时多态性
74
虚成员函数主要用于要在子类(派生类)
中重新定义此函数。 virtual只需要在父
类中定义,子类中对应的函数会默认为
虚函数。子类中重新定义的函数必须与
父类中的函数参数和返回值相同。不过,
只有返回值不同,不会被认为与原函数
不同。
虚成员函数 virtual
75
class A // 声明类 A
{
public,// 公有成员
virtual ShowData(); // 虚函数,显示数据
protected,// 保护成员
int m_nData; // 数据成员
};
class B,public A // 声明类 B,继承 A
{
public,// 公有成员
virtual ShowData(); // 虚函数,显示数据
};
76
void main()
{
A* a = new A(),*c;//声明 A类指针 a,c,并创建 a对象
B* b = new B(); //声明 B类指针 b,并创建对象
c = a;
c->ShowData(); // 调用 A类的函数
c = b;
c->ShowData(); // 调用 B类的函数
}
如果 ShowData()不是虚函数,则不管 c指向父类还是
子类的对象,所调用的都是父类的函数。
77
?1 虚函数仅适用于有继承关系的类对象,所
以只有类的成员函数才能说明为虚函数。
?2 静态成员函数不能是虚函数
?3 内联( inline)函数不能是虚函数
?4 构造函数不能是虚函数
?5 析构函数可以是虚函数
虚函数的限制
78
【 1】, 面向对象系统分析与设计, Ronald J,Norman
著,周之英等译,2000
【 2】, 面向对象程序设计基础,,李师贤,李文军,
周晓聪 编著,高等教育出版社,1998
【 3】 C++语言和面向对象程序设计(第二版),宛延
闿
【 4】 C++讲义,徐玉华、殷人昆老师提供
【 5】 http://www.cbi.pku.edu.cn/Doc/teach/cpp/
【 6】 面向对象方法综述,陈小群
参考资料
79
结 束
第十五讲
面向对象程序设计与 C++
2
内 容
?程序设计方法概述
?面向对象程序设计方法
?C++语言
3
一、程序设计方法概述
4
程序设计方法
?早期的程序设计方法
?结构化程序设计方法
?面向对象程序设计方法
5
早期的程序设计方法追求程序的高效率,
编程过份依赖技巧,而不注重所编写程序的结
构,也就是 没有固定程序设计方法 的时期。程
序的可读性、可重用性都很差。其中一个典型
问题是频繁使用 goto语句。
虽然这种方法存在很多问题,但对于单人
完成较为简单的任务,事实上还是经常被采用
的。
早期的程序设计方法
6
结构化方法出现在 70年代中期,我们可以
这样理解它,
结构化程序设计方法是从程序要实现的 功
能 的角度出发的。一般按照 自顶向下, 逐步求
精 的方式,将程序要完成的功能逐级划分成许
多小的功能模块,象搭积木一样搭起来。这些
小的功能模块最终都可以转化成三种基本控制
结构的组合。
所谓的功能可以理解为对数据的操作。在
程序实现中,特定的功能或功能模块一般用 函
数 来实现,它们要对特定的 数据 进行操作。
结构化程序设计方法
7
结构化设计方法的特点
?结构化程序设计方法的主要技术是 自顶
向下, 逐步求精,采用 单入口, 单出口
的控制结构
?自顶向下 是一种分解问题的技术,逐步
求精 指结构化程序的连续分解,最终成
为下面三种基本控制结构的组合
?三种基本控制结构,顺序、分支、循环
8
分支结构
语句 1
语句 2
语句 3
条件
语句 2 语句 1
语句 1
语句 2
顺序结构 循环结构
9
例,
从键盘输入一个学生的信息(包括姓名、
年龄、性别、学号等)和一个老师的信
息(包括姓名、年龄、性别、是否授课
等),然后将信息输出到屏幕。
一个简单的例子
10
分析,
根据需求(题目要求),我们可以把问
题划分为两个功能模块,一个是 输入模块,
一个是 输出模块,做完了输入模块,再做
输出模块。再具体考虑每个模块如何实现
(逐步求精)。
我们用 C语言来写,参看下面的代码,
11
// ……
void main() // 主函数开始
{
// 声明用于存储学生信息的变量
char strStudentName[20]; // 学生姓名
int nStudentAge; // 学生年龄
char cStudentSex; // 学生性别
int nStudentNumber; // 学生学号
// 声明用于存储老师信息的变量
char strTeacherName[20]; // 老师姓名
int nTeacherAge; // 老师年龄
char cTeacherSex; // 老师性别
int nIsTeaching; // 是否授课
// 输入模块
GetStudentInfo(…); // 输入学生信息
GetTeacherInfo(…); // 输入老师信息
// 输出模块
PrintStudentInfo(…); // 输出学生信息
PrintStudentInfo(…); // 输出老师信息
}
描述学生的数据
描述老师的数据
函数
函数
12
上面的例子中,我们可以进一步将属于学生和
老师的变量放入 结构 中 。这样可以在一定程度上完
成 对数据的封装 。但 在结构化程序设计中,数据与
对其进行操作的函数仍是分离的。
// 声明学生结构 Student
struct Student
{
char strStudentName[20]; // 学生姓名
int nStudentAge; // 学生年龄
char cStudentSex; // 学生性别
int nStudentNumber; // 学生学号
};
// 声明老师结构 Teacher
struct Teacher
{
char strTeacherName[20]; // 老师姓名
int nTeacherAge; // 老师年龄
char cTeacherSex; // 老师性别
int nIsTeaching; // 是否教书
};
13
程序=算法+数据结构
算法 数据结构
描述问题 解决问题
结构化程序设计方法
14
问题,
函数用于完成一定的功能,它们都是针
对特定的数据进行操作的。那么我们能
不能以特定的数据为中心,将数据与对
其进行操作的函数封装起来呢?
15
面向对象程序设计方法
?面向对象程序设计出现在 80年代中后期
?面向对象程序设计是建立在结构化程序
设计基础上的,但它不再是从功能入手,
而是从 对象 (人、地方、事情等)入手
?面向对象程序设计以 类 作为构造程序的
基本单位,它具有封装、数据抽象、继
承、多态等特点
16
简单地说,对象就是现实世界中的各
种实体,包括人、地点和事物等。例如,
桌子、椅子、教室、学生、老师、电话、
汽车等等。一般都要从 属性 和 行为 两个
方面来对它们加以描述。在这里,我们
认为对象和对象的实例是同一个概念。
什么是对象?
17
对象具有的一些特征称为属性,以 一个
人 为例,他的姓名、年龄、身高、体重等
可以作为他的属性。 这些属性会有其对应
的值,一般至少会有一项区别于其它对象,
它们在程序设计中对应的是一定的 数据 。
为了达到目的,对象 必须提供的功能
(或必须提供的服务)称为对象的 行为,
在程序设计中对应一定的方法( 函数 )。
属性和行为
18
类描述了一组具有相同属性(数据
元素)和相同行为(函数)的对象。
类的数据成员是对对象属性的抽象,
类的函数成员是对对象行为的抽象,
而类本身就是对对象的抽象。
什么是类?
19
class Student // Student类的声明
{
public,// 公有成员
Student(); // 构造函数
~Student(); // 析构函数
char* GetName(); // 查询姓名
int GetAge(); // 查询年龄
char GetSex(); // 查询姓名
int GetNumber(); // 查询学号
bool SetName(char* n); // 设置姓名
bool SetAge(int age); // 设置年龄
bool SetSex(char* s); // 设置性别
bool SetNumber(int num);// 设置学号
protected,// 保护成员
char m_strName[20]; // 姓名,字符串数组
int m_nAge; // 年龄,整型
char m_cSex; // 性别,字符型
int m_nNumber; // 学号,整型
};
例,C++中类的声明 ——Student类
成员函数
成员函数
成员变量
20
……
Student A; // 声明 Student的对象 A
A.SetName(“张三” ); // 设置 A的名字
A.SetAge(20); // 设置 A的年龄
……
例,C++中类使用
21
程序=对象+对象+……
对象 1
描述问题 解决问题
面向对象程序设计方法
……
数据 算法
对象 n
数据 算法 ……
22
总的来说,
?结构化程序设计方法是一种模块化程序设计
方法,它在解决问题时是 以功能为中心 的,
一定的功能模块虽然也作用于特定的数据,
但它们并没有被封装在一起。
?面向对象程序设计方法则是 以对象为中心 来
解决问题的。属于同种对象的属性(数据)
和服务(功能)被抽象出来封装到一起。
23
二、面向对象程序设计方法
24
? 数据抽象
? 封装
? 继承
? 多态
? 动态绑定
面向对象方法的主要特点
25
数据抽象
对 象 类 抽象数据类型
抽象 抽象
具体 具体
类是一组相似对象的抽象描述,它
抽取了这些对象的共性组成了一个共同
的概念。抽象数据类型 ( Abstract Data
Type,ADT) 是一组相似的类的抽象,
而一个类又是 ADT的具体实现。
26
封装是指软件的组成部分(模块、子
程序、方法等)应该 互相独立,或者 隐
藏设计的细节 。在传统的方法中,封装
通常局限于将功能和数据分开封装;而
在面向对象方法中,封装将功能和数据
同时装入对象中。
参看 Student类的例子
封 装
27
class Student // Student类的声明
{
public,// 公有成员
Student(); // 构造函数
~Student(); // 析构函数
char* GetName(); // 查询姓名
int GetAge(); // 查询年龄
char GetSex(); // 查询姓名
int GetNumber(); // 查询学号
bool SetName(char* n); // 设置姓名
bool SetAge(int age); // 设置年龄
bool SetSex(char* s); // 设置性别
bool SetNumber(int num);// 设置学号
protected,// 保护成员
char m_strName[20]; // 姓名,字符串数组
int m_nAge; // 年龄,整型
char m_cSex; // 性别,字符型
int m_nNumber; // 学号,整型
};
例,C++中类的声明 ——Student类
28
如果类与类之间有 is-a(是一种)的
关系,那么可以采用继承机制来表示。
子类可以自动继承父类中的一些属性和
行为,而不必再进行定义,从而实现了
代码的复用 。同时,继承也是产生 新类
的方法之一。
继 承
29
从上图可以看出,学生和教师都是人
的一种,所以,学生类和教师类可以从
人类继承而来,从而实现了代码的共享。
人
学 生 教 师
30
class People // People类的声明
{
public,// 公有成员
People(); // 构造函数
~People(); // 析构函数
char* GetName(); // 查询姓名
int GetAge(); // 查询年龄
……
bool SetName(char* n); // 设置姓名
bool SetAge(int age); // 设置年龄
……
private,// 私有成员
protected,// 保护成员
char m_strName[20]; // 姓名,字符串数组
int m_nAge; // 年龄,整型
char m_cSex; // 性别,字符型
……
};
类的声明举例 ——People类
31
class Teacher,public People // Teacher类的声明
{
public,// 公有成员
Teacher(); // 构造函数
~Teacher(); // 析构函数
bool IsTeaching(); // 查询是否授课
……
private,// 私有成员
……
protected,// 保护成员
bool m_bIsTeaching // 是否授课
……
};
类的声明举例 ——Teacher类
32
class Student, public People
{
public,
Student(); // 构造函数
~Student(); // 析构函数
int GetNumber(); // 查询学号
bool SetNumber(int n); // 设置学号
……
private,
……
protected,
int m_nNumber; // 学号
……
};
类的声明举例 ——Student类
33
在程序中同一符号或名字在不同情况
下具有不同解释的现象称为多态性
( Polymorphism)。在面向对象程序设
计语言中,由程序员设计的多态性由两
种基本形式,编译时多态性 和 运行时多
态性 。许多程序设计语言都或多或少地
支持多态性,但 运行时多态性是面向对
象程序设计语言的一大特点 。
多 态
34
编译时多态性 是指在程序编译阶段即可
确定下来的多态性,主要通过使用重载
( Overloading)机制获得,重载机制包括
函数重载 和 运算符重载 两大类。
举一个 C++中的例子,
int Abs(int x);
double Abs(double x);
cout<<Abs(-4)<<endl; //调用 int Abs…
cout<<Abs(3.2)<<endl;//调用 double Abs…
35
运行时多态性 是指必须等到程序动态
运行时才可确定的多态性,主要通过 继
承 结合 动态绑定 获得。
动态绑定也称晚绑定,它也是面向对
象的重要特点之一。动态绑定的使用可
以提高程序的可用性和可扩展性。
36
八十年代末以来,由 Booch,Coad,
Yourdon,OMT,Jacobson等人提出的多种
面向对象 方法在 已经 得到了广泛的 应用 。面
向对象方法中的三种基本活动就是,
(1) 识别对象和类
(2) 描述对象和类之间的关系
(3) 通过描述类的功能定义对象的行为
面向对象方法
37
科德将对象模型分为下面四个部件,也就是将
对象分为了四组,
( 1)问题域 PD( problem domain)
( 2)人机交互 HI( human interaction)
( 3)数据管理 DM( data management)
( 2)系统交互 SI( system interaction)
科德( Coad)方法
38
?问题域问题( PD) 通常最先考虑,因为用
户往往最关心为自己的商业问题建模。问题
域部件包含与需要建模的问题直接有关的对
象。问题域部件的对象技术上呈现中型,它
们几乎不了解或完全不了解人机交互、数据
管理和系统交互部件的对象。
?人机交互部件( HI) 包含为问题域对象和人
们之间提供界面的对象。在对象模型中,人
机交互部件的对象通常对应具体的窗口和报
表。
39
?数据管理部件( DM) 包含为问题域对象和
数据库系统或文件管理系统之间提供界面的
对象。在对象模型中,数据管理部件的对象
通常对应某些需要保存及搜索的问题域对象 。
?系统交互部件( SI) 包含为问题域对象和其
它系统或设备提供界面的对象。系统交互对
象封装了通信协议,使得问题域对象不需要
了解底层的实现细节。
40
41
三,C++语言
?C与 C++的比较
?C++中的数据类型
?C++程序框架
?类和对象
?类的声明、实现、继承、多态性
?举例
42
C 语 言
C语言的优点,
?与硬件无关,可移植性强
?语言简洁,使用方便
?丰富的运算符和数据类型
?可直接访问内存地址
?能进行位操作
?目标代码质量高,运行效率高
43
C语言的弱点,
?检查机制弱,编译时不能发现编程错误
?面向过程的语言,没有支持代码复用的
机制
?很难控制大规模程序的复杂性
44
C++ 语 言
?是 C的超集,保持了与 C的 兼容 性
?继承了 C语言 高效性, 灵活性 的特点
?扩充了对 面向对象程序设计 和 高层次问题抽象
方法的支持,是面向对象程序设计语言之一
?完善了 C语言的 类型检查, 代码重用, 数据抽
象 机制,便于大规模软件开发
?既反映了 结构化 程序设计方法,又反映了 面向
对象 程序设计方法
45
#include <iostream.h> // 预编译命令
void main() // 主函数开始
{
cout <<,Hello the world!” << endl;
} // 主函数结束
一个简单的 C++程序的例子
C++中提供的 iostream库可以完成对输入输出
流的操作,“流”( stream)就是简单的字
符序列。在 C++中用 cin,cout和 cerr来定义
键盘输入类、屏幕输出类和错误信息输出类。
endl用于换行并清空流。如,
cin >> a; cout <<,输入的数据为” << a << endl;
46
基本数据类型
构造数据类型
整型 ( int)
实数型
字符型 ( char)
空类型 ( void)
布尔型 ( bool)
引申数据类型
结构化数据类型
指针 ( *)
引用 ( &)
数组 ( [])
结构 (struct)
联合 (union)
枚举 ( enum)
类 (class)
浮点型 ( float)
双精度型 ( double)
C++中的数据类型
47
引用 &
一般用于参数传递 。 &可以认为是取地址。
引用类型的变量并不真正创建新的对象,而
是作为另一个对象的别名。例如
void swap(int& a,int& b) // 用于交换 a,b
{
int temp;
temp = a; a = b; b = temp;
}
对形参 a,b的操作等价与对实参的操作。
48
C++程序结构
C++源代码一般都由若干类或函数组成。为
了便于管理,一般把不同的类放在不同的文
件中,对于类的声明和实现也分别放在对应
的,h(或,hpp)和,cpp文件中。
由于文件较多,所以为了便于管理,一般的
集成开发工具都会提供工程( Project)管理
功能来管理这些文件,对源文件进行编译和
链接。
49
类的声明(,h )
目标文件(,o bj )
类的实现(,c pp )
编译
类的声明(,h )
目标文件(,o bj )
类的实现(,c pp )
编译 ……
库函数
可执行文件(,e x e )
链接
目标文件(,o bj )
主程序(,c p p )
编译
50
为防止 重复包含 (多次包含同一头文件,造
成重复定义)和 嵌套包含 (互相包含)造成
的错误,应该在头文件里加上如下预编译命
令。例如,对于 test1.h
#ifndef __INCLUDE_TEST1_H__
#define __INCLUDE_TEST1_H__
// …
// test1.h的内容
……
#endif
说 明
51
class 类名
{
public,
公有数据和函数
protected,
受保护数据和函数
private,
私有数据和函数
};
类的声明
注意要有分号
52
class People // People类的声明
{
public,// 公有成员
People(); // 构造函数
People(char* name,int age,char sex);
~People(); // 析构函数
char* GetName(); // 查询姓名
int GetAge(); // 查询年龄
char GetSex(); // 查询性别
bool SetName(char *n); // 设置姓名
bool SetAge(int age); // 设置年龄
bool SetSex(char sex); // 设置性别
private,// 私有成员
protected,// 保护成员
char m_strName[20]; // 姓名,字符串数组
int m_nAge; // 年龄,整型
char m_cSex; // 性别,字符型
};
类的声明举例 ——People类
53
? 类名是有效的标识符,不能是关键字
(保留字)
? 数据成员在声明时不能进行初始化
? 数据成员不能是正在声明的类,但可以
用指针
? 访问控制 public,private,protected可以
以任意次序出现,并可重复多次
有关类的声明
54
? 类的成员函数中有两个函数是特殊的,就是 构
造函数 和 析构函数,它们没有返回值,且一般
不直接调用这些函数,而是在对象建立和撤销
时分别被 隐式调用 的。
?构造函数的函数名要与类名相同,在创建对象
时被调用。可以不定义构造函数,编译程序会
自动生成一个缺省的构造函数。
?析构函数的函数名是在类名前加上一个,~”。
同上,也可以不定义析构函数,编译程序会自
动生成一个缺省的析构函数。
构造函数和析构函数
55
? 在 类的构造函数 中,一般要完成一些 初始化 的
任务。例如将一些指针赋值为 NULL,给一些
变量赋初值,或者为一些指针分配空间等。还
可以利用函数的重载,声明并实现多种构造函
数,以完成不同的初始化任务。(参见 People
类)
?在 类的析构函数 中,一般要进行一些收尾工作,
如 空间的释放 。如对于为指针分配的空间,要
加以释放,等。
构造函数和析构函数
56
?public,公有成员, 可以被任何函数访
问 ( 即可以从类的外部进行访问 ) 。
?private,私有成员, 只有类成员函数
和友员类可以访问 。
?protected,保护成员, 只有类成员函
数, 派生类, 友员类可以访问 。
?缺省方式为 private
类成员访问控制
57
People::People() // 构造函数
{ // 函数体开始
// 初始化名字为空串
memset(m_strName,0,sizeof(m_strName));
// 初始化年龄为 0
m_nAge=0;
// 初始化性别为 ?0?
m_cSex='0';
} // 函数体结束
People::~People(){} // 析构函数
类的实现 ——People类
58
People::People(char* name,int age=0,char sex=?0?)// 构造函数
{ // 函数体开始
if (name!=NULL) // 初始化 m_strname
strcpy(m_strName,name);
else // 如果 name为空,则初始化 m_strname为空
memset(m_strName,0,sizeof(m_strName));
if((age>=0) && (age<=200)) // 初始化年龄 m_nAge
{
m_nAge=age;
}
else
m_nAge=0; // age不合法,则将 m_nAge初始化为 0
switch (sex) // 初始化性别 m_cSex
{
case ?M?,m_cSex=?M?; break; // ?M?表示男
case ?F?,m_cSex=?F?; break; // ?F?表示女
default,m_cSex=?0?; // ?0?表示未赋值
}
}
59
// 查询姓名的成员函数
char* People::GetName()
{
return m_strName; // 返回姓名
}
// 查询年龄的成员函数
int People::GetAge()
{
return m_nAge; // 返回年龄
}
// 查询性别的成员函数
char People::GetSex()
{
return m_cSex; // 返回性别
}
// 用于设置的成员函数的实现不在此列出
// ……
60
void main() // 主函数
{
People A; // 声明类 People的对象 A
People B("Li Si");// 声明类 People的对象 B,参数 "Li Si"
// 声明类 People指针,并构造实例
People* C = new People("Zhang San",20,'M');
A.SetName(“Wang Wu”); // 对对象 A设置名字
cout <<,A?s name:” << A.GetName() << endl;// 输出 A的信息
cout << "A's age:" << A.GetAge() << endl;
cout << "A's sex:" << A.GetSex() << endl << endl;
cout <<,B?s name:” << B.GetName() << endl;// 输出 B的信息
cout << "B's age:" << B.GetAge() << endl;
cout <<,B?s sex:” << B.GetSex() << endl << endl;
cout <<,C?s name:” << C->GetName() << endl;// 输出 C的信息
cout << "C's age:" << C->GetAge() << endl;
cout << "C's sex:" << C->GetSex() << endl << endl;
delete C; // 释放指针 C所指的空间
}
61
A's name:Wang Wu
A's age:0
A's sex:0
B's name:Li Si
B's age:0
B's sex:0
C's name:Zhang San
C's age:20
C's sex:M
程序运行结果
62
// 声明类 People的对象 A,调用没有参数的构造函数
People A;
// 声明类 People的对象 B,参数 "Li Si"
People B("Li Si");
// 声明类 People指针,并 动态 构造实例
People* C = new People("Zhang San",20,'M');
或,People *C;
C = new People("Zhang San",20,'M');
类对象的声明
63
People A(“Zhang San”),B; // 声明 People的对象 A和 B
B = A; // 用 A初始化对象 B
对象的复制
说明,
这种对象复制可以将 A的所有数据成员的值赋给 B,
如果对象的数据成员中不存在指针,则没有问题,但
如果其中含有指针,则这种方式只是简单地把指针所
指向的内容的地址复制过来,并没有复制其所指的内
容。这可能并不是用户所期望的。这要通过自定义的
复制策略来实现,拷贝构造函数 和 重载运算符,=”。
64
继 承
继承是面向对象程序设计的重要特点之
一。它描述了类之间的 is-a的关系。如
果类 B继承类 A,则称类 A是类 B的 基类
(或 父类 ),类 B是类 A的 派生类 (或 子
类 )。
65
class Student, public People
{
public,
Student(); // 构造函数
~Student(); // 析构函数
int GetNumber(); // 查询学号
bool SetNumber(int n); // 设置学号
private,
protected,
int m_nNumber; // 学号
};
举例 ——Student类
类声明第一行后面的,,public People” 指明了
Student类继承自 People类。 public是继承访问控制,
用于指明继承成员在子类中的访问控制 。这样,类
People中的一些成员就会被 Student类自动继承了。
66
继承成员的访问控制
父类访问控制 继承访问控制 子类访问控制
public
protected
private
public
public
protected
不可访问
public
protected
private
protected
protected
protected
不可访问
public
protected
private
private
private
private
不可访问
67
继承成员的访问控制
?父类中的 私有成员不能继承
?继承访问控制为 public时, 继承父类的访
问控制
?继承访问控制不是 public时, 子类中继承
成员的访问控制 与继承访问控制相同
68
类型兼容性
C++语言中,允许且 仅允许公有派生类兼容
基类 。这就是说,子类的对象可以赋值给父
类的对象, 指向父类的指针可以指向子类 。
反之则不正确。
例,class Child,public Father
Father* father; // 声明 Father对象指针
Child* child; // 声明 Child对象指针
// 动态创建对象
// …
father = child; // 正确
child = father; // 错误
69
多态性
如前所述,在程序中同一符号或名字在
不同情况下具有不同解释的现象称为多
态性。在面向对象程序设计语言中,由
程序员设计的多态性由两种基本形式:
编译时多态性 和 运行时多态性 。 运行时
多态性是面向对象程序设计语言的一大
特点 。
70
编译时多态性
编译时多态性是指在 程序编译阶段即可确
定下来的多态性,主要通过使用 重载 机制获
得,重载机制包括 函数重载 和 运算符重载 两
大类。
在声明函数原型时只要 形式参数的个数或
类型不同,就可以使用 相同的函数名 。只有
返回值的不同同名函数,并不认为是不同的
函数。 运算符重载 允许程序员重新定义 C++
语言已有的运算符。
71
函数重载
int Add(int a,int b); // 整数加法
float Add (float a,float b); // 单精度加法
double Add (double a,double b);// 双精度加法
int Abs(int a); // 整数的绝对值
float Abs(float a); // 单精度数的绝对值
double Abs(double a); // 双精度数的绝对值
对于普通函数和类成员函数都可以进行函数
的重载,构造函数也可以重载。
72
运算符重载
运算符可以理解为函数,一元、二元运算符
分别认为是具有一个、两个参数的函数。那
么,对一个 n维矢量类,我们可以重载, +,,
,-, 运算符,以实现矢量的加减运算。
例 class Vector,
Vector operator+(const Vector& Vec);// 加法
Vector operator-(const Vector& Vec);// 减法
Vector operator-( ); // 求反
Vector operator=(const Vector& Vec);// 赋值
73
运行时多态性是指必须等到 程序动态
运行时才可确定的多态性,主要通过 继
承 结合 动态绑定 获得。这与类的继承密
切相关。因为存在类型的兼容性,所以
有些函数只有在运行时才能确定是调用
父类的还是子类的函数。在 C++中,使
用虚函数 ( Virtual Functions) 来实现。
运行时多态性
74
虚成员函数主要用于要在子类(派生类)
中重新定义此函数。 virtual只需要在父
类中定义,子类中对应的函数会默认为
虚函数。子类中重新定义的函数必须与
父类中的函数参数和返回值相同。不过,
只有返回值不同,不会被认为与原函数
不同。
虚成员函数 virtual
75
class A // 声明类 A
{
public,// 公有成员
virtual ShowData(); // 虚函数,显示数据
protected,// 保护成员
int m_nData; // 数据成员
};
class B,public A // 声明类 B,继承 A
{
public,// 公有成员
virtual ShowData(); // 虚函数,显示数据
};
76
void main()
{
A* a = new A(),*c;//声明 A类指针 a,c,并创建 a对象
B* b = new B(); //声明 B类指针 b,并创建对象
c = a;
c->ShowData(); // 调用 A类的函数
c = b;
c->ShowData(); // 调用 B类的函数
}
如果 ShowData()不是虚函数,则不管 c指向父类还是
子类的对象,所调用的都是父类的函数。
77
?1 虚函数仅适用于有继承关系的类对象,所
以只有类的成员函数才能说明为虚函数。
?2 静态成员函数不能是虚函数
?3 内联( inline)函数不能是虚函数
?4 构造函数不能是虚函数
?5 析构函数可以是虚函数
虚函数的限制
78
【 1】, 面向对象系统分析与设计, Ronald J,Norman
著,周之英等译,2000
【 2】, 面向对象程序设计基础,,李师贤,李文军,
周晓聪 编著,高等教育出版社,1998
【 3】 C++语言和面向对象程序设计(第二版),宛延
闿
【 4】 C++讲义,徐玉华、殷人昆老师提供
【 5】 http://www.cbi.pku.edu.cn/Doc/teach/cpp/
【 6】 面向对象方法综述,陈小群
参考资料
79
结 束