第 5章 程序的类层次结构
第 5章 程序的类层次结构
?类的继承与派生
?类层次中成员函数名的多态性
派生
?面向对象的程序设计的一个重要特点是允许
以既有类(也称基类),以其为基础导出
(定义)新的类(也称派生类)。这一过程
称为派生
?派生时不需要把既有类的相关代码重新书写
一遍,只需要指明是以哪个类为基类,便可
以将基类中的有关特征继承过来,实现了部
分代码的可重用。
类的继承与派生
?派生方式
?派生类的构造函数与释放函数
?多基派生
?虚基类
?类层次中成员名的作用域
?类层次中的类转换
派生方式
?public派生与 private派生
?Protected成员与 protected派生
public派生与 private派生
? C++允许程序员用下边的格式用一个类派生它的子
类,
class 派生类名, 派生方式 基类名
{
private,
新增私有成员声明语句表列
public,
新增公开成员声明语句表列
};
两种派生方式的特点
?1)无论哪种派生方式,基类中的 private成员
在派生类中都是不可见的。也就是说,基底
类中的 private成员不允许外部函数或派生类
中的任何成员访问。
?2) public派生时,基类中的 public成员相当
于派生类中的 public成员。
?3) private派生时,基类中的 public成员相当
于派生类中的 private成员
派生后基类成员访问性的变化
基类成员的
访问性
private public
派 生 方
式
private public private public
派 生 类 成
员
不可见 可见 可见
外 部 函
数
不可见 可见
定义 Location— Point类层次结构
class Location{ // 基类接口定义
public,
int mX_Pos,mY_Pos; // 位置的坐标,以像素点计
Location ( int x,int y); // 构造函数,初始化位置坐标
int getX ( ); // 返回当前位置的 x坐标
int getY ( ); // 返回当前位置的 y坐标
};
enum BOOLEAN { FALSE,TRUE }; // 定义一个布尔类型
class Point, private Location{ // 派生类接口定义
BOOLEAN mVisible; // 可见性变量
Public,
Point(int x,int y); // 构造函数,初始化点的位置
BOOLEAN isVisible ();// 返回当前点是否可见
Void show ( ); // 显示当前点
Void hide ( ); // 隐藏当前点
Void moveTo ( ); // 移动当前点
};
Protected成员与 protected派生
? protected成员是一种血缘关系内外有别的成员。它
只为它所在类中的方法和由它直接派生的类方法可
见
? private,protected,,public作为类成员的可见性修
饰符,将产生如下影响,
1) 在一个类中定义的方法函数,可以访问本类中的
任何成员,但只能访问基类中的 protected成员和
public成员;
2) 一个类对象,只能使用本类或其 public派生基类
中的 public成员
例 5.1.2
class b
{
protected,
int x1;
public,
int x2;
b() {x1 = x2 = 5;}
};
class d1:public b
{
public,
int fd11()
{
return x1; // ok,可以访问基类 protected成员
return x2; // ok,可以访问基类 public成员
}
int fd12()
{
b bb; // return bb.x1; 类对象不可使用本类 protected成员
return bb.x2; // ok,类对象可以使用基类 public成员
}
};
定义 Location— Point— Circle类层
次结构
class Location // 位置类
{
protected,// 保护类成员
int mX_Pos,mY_Pos; // 在 Location派生类中可访问
public,
Location ( int x,int y);
int getX ( );
int getY ( );
};
enum BOOLEAN { FALSE,TRUE };
class Point, public Location // 点类
{
protected,// 保护类成员
BOOLEAN mVisible; // 在 Point派生类中可访问
Public,
Point(int x,int y);
BOOLEAN isVisible ();
Void show ( );
Void hide ( );
Void moveTo ( );
};
class Circle,public Point // 圆类
{
protected,// 保护类成员
int mRadius; // 圆半径,在 Circle派生类中可访问
Public,
Circle(int x,int y,int r); // 初始化圆
void show ( ); // 画圆
void hide ( ); // 隐藏圆
void moveTo ( ); // 移动圆
void expand (int delta); // 放大圆,半径为( r + delta)
void contract (int delta); // 缩小圆,半径为( r - delta)
};
派生类中访问属性发生如下变化
? 1) 基类的 private成员在派生类中是不可见的
? 2) private派生使基类中的非 private成员都成为派生
类中的私有成员; protected派生使基类的非 private
成员在派生类中都变为 protected成员; public派生使
基类的非 private成员在派生类中访问属性保持不变
? 3) 不同的派生方式引起成员访问属性的改变,只能
降低,不能提高(如将 private成员提升为 protected
成员或 public成员)。但是,C++允许在派生类中用
访问属性修饰符,恢复一个成员原来的访问属性
例 5.1.4
class b
{
protected,
int x1;
int x2;
public,
int x3;
int x4;
…
};
class d:private b // x1,x2,x3,x4 将派生为 private成员
{
protected,
b::x1; // 将 x1恢复为 protected成员
public,
b::x3; // 将 x3恢复为 public成员
…
};
派生类的构造函数与释放函数
?类层次结构中数据成员的存储
?派生类中构造函数 /释放函数的定义与调用
?派生类对象的创建
类层次结构中数据成员的存储
?Location—Point—Circle类层次结构中数据成
员的存储
mX_Pos
mY_Pos
mX_Pos
mY_Pos
mVisible
mX_Pos
mY_Pos
mVisible
mRadius
构造函数 /释放函数的定义与调用
?首先通过派生类的构造函数调用基类的构造
函数,对基类成员进行初始化
?然后再执行对在派生类中新增的成员进行初
始化的操作
?因此可以说,派生类对象是由其所有先辈类
共同创建的
派生类的构造函数原型格式
Y::Y(argX1,argX2,…,argY1,argY2,…),X
(argX1,argX2,…)
?其中,X为 Y的直接基类名,Y为派生类名。
这一格式是递归的
类层次结构 Location—Point—Circle
中的构造函数
Location,,Location ( int x,int y)
{
mX_Pos = x;
mY_Pos = y;
}
Point,,Point ( int x,int y ), Location ( x,y)
// 先调用基类构造函数
{
visible = FALSE; // 缺省情况下是不可见的
}
Circle,,Circle ( int x,int y,int r ), Point ( x,y )
// 先调用基类构造函数
{
mRadius = r;
}
说明
? 1)创建一个 Circle类对象时,要自动调用 Point类的
构造函数,再由 Point类的构造函数调用其父类
Location构造函数
? 2)当一个派生类对象撤销时,释放函数调用的顺
序与构造函数相反
? 3)保护成员具有良好的继承性,对保护成员的初
始化最好在基类中进行
? 4)派生类的构造函数的构成形式,与包含对象成
员的类的构造函数相似但不相同
派生类对象的创建
?派生类对象可以用两种方式创建,
1)用常数参数表创建
2)由部分基类对象创建派生类对象
多基派生
?派生类只有一个基类时,称为单基派生。一
个派生类具有多个基类时,称为多基派生或
多重继承 (multiple inheritance),这时将继承
每个基类的部分代码。多基派生是单基派生
的扩展。与单基派生相比,既有同一性,又
有特殊性。单基派生则可以看成多基派生的
特例
例
?由 Hard(机器名)类与 Soft(软件,由 os与
Language组成)类派生出 System类
#include <iostream.h>
#include <string.h>
class Hard
{ protected,char bodyname[20];
public,Hard(char * bdnm) // 构造函数
{ cout<<"con H\n";
strcpy(bodyname,bdnm);
}
Hard(Hard & abody) // 复制构造函数
{ cout<<"copy H\n";
strcpy(bodyname,abody.bodyname);
}
void print()
{ cout<<"Body_name:"<<bodyname<<endl;
}
};
class Soft
{
protected,
char os[10];
char lang[15];
public,
Soft(char * o,char * lg) // 构造函数
{
cout<<"con F\n";
strcpy(os,o);
strcpy(lang,lg);
}
Soft(Soft & asoft) // 复制构造函数
{
cout<<"copy F\n";
strcpy(os,asoft.os);
strcpy(lang,asoft.lang);
}
void print()
{
cout<<"os:"<<os<<",language:"<<lang<<endl;
}
};
class System:public Hard,public Soft // 派生类 System
{
char owner[10];
public,
System(char * ow,char * bn,char * o,char * lg)
,Hard(bn),Soft(o,lg) // 调用基类构造函数
{
cout<<"con S\n";
strcpy(owner,ow);
}
System(Hard abody,Soft asoft,char * ow)
,Hard(abody),Soft(asoft) // 调用基类复制构造函数
{
cout<<"copy S\n";
strcpy(owner,ow);
}
void print()
{
cout<<"owner:"<<owner;
cout<<";\n hard:"<<bodyname;
cout<<";\n soft:"<<os<<","<<lang<<endl;
}
};
测试程序
void main()
{
System bsystem( "Wang",// 用常参数表创建派生类对象
"IBM PC",
"PC DOS",
"True BASIC");
bsystem.print();
cout<<"Ok!\n";[y1]
Hard abody("AST 386 sx/16");
Soft asoft("PC DOS","Borland C++");
System asystem( "AST 386sx/16",// 基类对象创建派生类对象
asoft,
"Zhang");
asystem.print();
}
虚基类
?在多基派生中,如果在多条继承路径上有一
个公共的基类(如图 5.5 (a) 中的 base0),
则在这些路径的汇合点(如图 5.5 (a) 中的
derived类对象),便会产生来自不同路径的
公共基类的多个拷贝,如图 5.5 (b) 所示。如
果只想保留公共基类的一个拷贝,就必须使
用关键字 virtual把这个公共基类定义为虚基类
在多条继承路径上公共基类的情形
base0
base1 base2
derived
base0{a} base0{a}
base1 base2
derived
在定义基类直接派生的类时说明格式
class 派生类名, virtual 派生方式 基类名 {
…
};
例 5.1.7
class base0
{
public,
int a;
…
};
class base1:virtual public base0
{
…
};
class base2:virtual public base0
{
…
};
class derived:public base1,public base2
{
…
};
几点说明
? 1) virtual也是“派生方式”中的一个关键字,它与
访问控制关键字( public或 private,protected)间
的先后顺序无关紧要
? 2)为了保证虚基类在派生类中只继承一次,就必
须将其直接派生类定义时都说明为虚拟派生;否则
除从用作虚基类的所有路径中得到一个拷贝外,还
从其它作为非虚基类的路径中各得到一个拷贝
? 3) 虚基类对象的初始化图 5.6a所示的类层次结构,
当 A与 B都是 C与 D的虚基类时,系统将要自左向右
按深度优先遍历算法对公共派生类 E进行初始化
虚基类对象的初始化
C
B A
D
E
A B A B
C D
E
类层次中成员名的作用域
? private成员的作用域只在本类对象内部
? public成员和 protected成员,在类层次结构中将形成成员名
多重作用域,
· 基类成员作用域和派生类新增成员作用域形成相包含的关
系,派生类新增成员作用域为内层,基类成员作用域在外层
· 内层声明的标识符可以覆盖外层声明的同名标识符,使其
不可见;对于多基派生类,如果其多个基类中拥有同名成员,
而在派生类中又新增了同名成员,则派生类的该同名成员将
覆盖所有基类的同名成员
· 如果在几个基类中具有同名成员,但在派生类中没有与它
们同名的新增成员名,则由于这些成员名具有相同的作用域,
使系统无法判定到底是调用哪个基类成员
有类层次结构:A ← B ← C
#include <iostream.h>
class A
{ public,
int x,a;
void fun(int i)
{ x = i;
cout << "A,,x = "<< x << endl;
cout << endl;
}
};
class B:public A
{ public,
int x,b;
void fun(int i)
{ x = i * a; // 引用非同名基类成员
cout << "B::x = "<< x << endl; // 引用本类成员
cout << "A::x = "<< A::x << endl; // 引用同名基成员
cout << "a = " << a << endl; // 引用非同名基成员
cout << endl;
}
};
class C:public B
{
public,
int x;
void fun(int i)
{
x = i * a * b; // 引用非同名基类变量
cout <<,C::x =, << x << endl; // 引用本类成员
cout <<,A::x =, << A::x <<,,B::x =, << B::x << endl;// 引用同名基类成员
cout <<,a =, << a <<,,b =, << b << endl; // 引用非同名基类成员
cout << endl;
}
};
测试程序
void main()
{
int i = 2;
C obj;
obj.a = 1; //引用非同名基成员
obj.A::fun(i); //引用同名基成员
obj.b = 2; //引用非同名基成员
obj.B::fun(i); //引用同名基成员
obj.fun(i); //引用派生类成员
}
例 5.1.9
class Base
{
public,
static void sb(); // 静态成员
void fb();
};
class D, private Base { }; // 全私有派生
class DD, public D
{
void fdd();
};
void DD,,fdd()
{
Base,,sb(); // 对
// fb(); 错
// sb(); 错
}
类层次中的类转换
?单基派生的情形
?多继承的情形
?含有公共虚基类的类层次结构
单基派生的情形
#include <iostream.h>
#include <string.h>
class Person
{ char * mName;
char mSex;
int mAge;
public,
Person (char *n="",int a=50,char s='m')
{ mName=new char[strlen(n)+1];
strcpy (mName,n);
mAge = a;
mSex = s;
}
~Person()
{ delete []mName;
}
void show ()
{ cout <<,\n name:” << mName;
cout <<,-age:” << mAge;
cout <<,-sex:” << mSex << endl;
}
Person& operator=(Person& p)
{
delete []mName;
mName=new char[strlen(p.mName)+1];
strcpy(mName,p.mName);
mAge=p.mAge;
mSex=p.mSex;
return *this;
}
};
class Employee, public Person
{
char * mDepartment;
float mSalary;
public,
Employee (char *,int,char,char *,float);
void show();
~Employee()
{
delete[]mDepartment;
}
};
Employee,,Employee (char * nm = "",int ag = 0,char sx = ' ',
char * dprt = "",float slr = 0)
, Person (nm,ag,sx)
{
mDepartment=new char[strlen(dprt)+1];
strcpy ( mDepartment,dprt);
mSalary = sly;
}
void Employee,,show()
{
Person,,print();
cout << "-department:" << department ;
cout << "-salary:" << salary << endl;
}
测试程序
void main()
{
Employee zh("Zhang",50,'m',"SHXIEMU",180);
Person c,* pP;
c =zh; // 1用派生类对象给基类对象赋值
c.show();
Person & y = zh; // 2用派生类对象初始化基类的引用
y.show();
pP = & zh; // 3把派生类对象地址赋值给基类指针
pP -> show();
((Employee *)pP) -> show(); // 4基类指针向派生类指针强制转换
}
多继承的情形
class base0
{
protected,
int b0;
};
class base1:public base0
{
protected,
int b1;
};
class base2:public base0
{
protected,
int b2;
};
class derived:public base1,public base2
{
float d;
public,
derived();
};
各层次数据成员在内存中的分布
Pb1
int b0
int b1
int b0
int b2
…
base0
base0
base1
base2
derived
Pb2
base0
base1 base2
derived
克服对基类成员访问的二义性
?对指针要显式地指明全路径
?将指针先强制转换到不会产生二义性的基类
?显式指明成员来自哪个类
含有公共虚基类的类层次结构
?使用虚基类,在它的几条派生路经的汇合处,
只产生其一个拷贝
?引用的都是同一个虚基类中的数据成员具有
相同的值。可以进行如下的类变换
1) 派生类对象的地址可以直接赋给间接公
基类的指针,并且不需进行强制类型转换
2) 一个虚基类的引用,可以引用一个派生
类的对象
类层次中成员函数名的多态性
?虚函数与动态绑定
?虚函数的访问
?纯虚函数与抽象类
?虚释放 函 数
?多基派生中虚函数的二义性
虚函数与动态绑定
?虚函数是用关键字 virtual修饰的某基类中的
protected或 public成员函数。它可以在派生
类中重定义,以形成不同的版本。只有在程
序执行过程中,依据指针具体指向哪个类对
象,或依据引用哪个类对象,才能确定激活
哪一个版本,实现动态绑定。即这种绑定方
式不是在编译时静态地进行,而必须在程序
运行过程中进行动态绑定
C++中静态多态性与动态多态性的主
要特征
形 式 绑定方式 各版本的函
数原型
绑定依据 特点
函数名重载 静态绑定 不同 参数数目及类型 高效
虚 函
数
动态绑定 相同 程序运行时引用或
指针指向
高灵活性、抽象性
和可扩充性
点 ─圆类层次结构 不使用虚函数
#include <iostream.h>
class Point // 基类
{ private,
float mX,mY;
public,
Point(){}
Point(float i,float j)
{ mX = i; mY = j; }
float area() // 非虚函数
{ return 0.0; }
};
const float Pi = 3.141593;
class Circle, public Point // 派生类
{ private,
float mRadius;
public,
Circle(float r)
{ mRadius = r; }
float area()
{ return Pi * mRadius * mRadius; }
};
点 ─圆类层次结构 使用虚函数
#include <iostream.h>
class Point
{ private,
float mX,mY;
public,
Point(){}
Point(float i,float j)
{ mX = i; mY = j; }
virtual float area() // 声明为虚函数
{ return 0.0; }
};
const float Pi = 3.141593;
class Circle:public Point
{ private,
float mRadius;
public,
Circle(float r)
{ mRadius = r; }
float area() // 虚函数的再定义
{ return Pi * mRadius * mRadius; }
};
虚函数声明与重定义的一般规则
? 1)在基类中,用关键字 virtual可以将其 public或 protected部
分的成员函数声明为虚函数
? 2)一个虚函数是属于它所在的类层次结构的,而不是只属
于某一个类,只不过它在该类层次结构中的不同类中具有不
同的形态
? 3) 如果派生类中没有对基类中说明的虚函数进行重定义,
则它继承基类中的虚函数
? 4) 由于有些编译器不能正确处理虚函数,因此要避免把构
造函数声明为虚函数
? 5) 虚函数只能是类的成员函数,不能把虚函数声明为静态
的或全局的;也不能把友元说明为虚函数。但是虚函数可以
是另一个类的友元函数
虚函数的访问
?用基指针访问与用对象名访问
?访问该类层次中的虚函数 ——要使用 this指针
?用构造函数/释放函数访问
用基指针访问与用对象名访问
?把一个函数声明为虚函数,保证了这个函数
被指向基类的指针(或引用)调用时,C++
系统对其进行动态绑定,向实际的对象传递
消息
?但是,通过一个对象名访问虚函数时,C++
系统将使用静态绑定
例 5.2.2
int main()
{
Circle c(6.4321);
Cout << c.area() << endl;
Cout << c.Point::area() << endl;
Cout << c.Circle::area() << endl;
}
这时,可以用作用域运算符指定使用的是哪个类的虚函数。这与非虚函数没有区别。
这里所说的“指向基类”是指指向最先将一个成员函数声明为虚函数的类。
例 5.2.3
#include <iostream.h>
class base0
{ public,void v() {cout << "base0\n";} // 非虚函数
};
class base1:public base0
{ public,
virtual void v() {cout << "base1\n";} // 声明虚函数
};
class A1:public base1
{ public,
void v() {cout << "A1\n";}
};
class A2:public A1
{ public,
void v() {cout << "A2\n";}
};
class B1:private base1
{ public,
void v() {cout << "B1\n";}
};
class B2:public B1
{ public,
void v() {cout << "B2\n";}
};
访问该类层次中的虚函数
#include <iostream.h>
class A
{
public,
virtual void v1(){cout << "v1 is called in A.\n";a1();}
void a1(){cout << "a1 is called in A.\n";v2();}
virtual void v2(){cout << "v2 is called in A.\n";}
};
class B:public A
{
public,
void b1(){cout << "b1 is called in B.\n";v2();}
virtual void v2(){cout << "v2 is called in B.\n";}
};
这里,this是一个指向基类 A的指针。由于 v2()是一个虚函数,因此 C++系统要通过
动态绑定实现,具体取决于所引用的对象
用构造函数/释放函数访问
#include <iostream.h>
class base
{ public,
base() {};
virtual void v1() { cout << "v1 is called in base.\n"; }
};
class A, public base
{ public,
A()
{ cout << " call v1 in A().\n";
v1(); // 调用本类中定义的虚函数
}
virtual void v1() {cout << "v1 is called in A.\n"; }
};
class B, public A
{ public,
B()
{ cout << "call v1 in B().\n";
A::v1(); // 调用基类中定义的虚函数
}
void v1() {cout << "v1 is called in B.\n";}
};
纯虚函数与抽象类
? 当在基类中不能为虚函数提供有实际意义的定义时,可以将
这个虚函数声明为纯虚函数
? 声明纯虚函数的形式为,
virtual 类型 函数名 (参数列表 ) = 0;
1)纯虚函数不能被直接调用,仅起提供一个与派生类相一致
的接口作用,用作派生类中的具有相同名字的函数存放处。
包含纯虚函数的类与把所有构造函数都声明为 protected成员
的类一样,也称为抽象类。一个抽象类只能作为基类来派生
新类,不能生成抽象类的对象。因此,当 Point被声明为抽象
类后,便不可生成 Point的对象
2)纯虚函数不可以被继承。当基类是抽象类时,在派生类中
必须给出基类中纯虚函数的定义,或在该类中再声明其为纯
虚函数
例 5.2.6
#include "figure.hpp"
const float Pi = 3.141593;
class circle:public figure
{ private,
float radius;
public,
circle(float r)
{ radius = r; }
float area() // 重定义
{ return radius * radius * Pi; }
};
class triangle:public figure
{ protected,
float high,wide;
public,
triangle(float h,float w)
{ high = h;
wide = w; }
float area() // 重定义
{ return high*wide*0.5; }
};
class rectangle:public triangle
{ public,
rectangle(float h,float w):triangle(h,w){} // tectangle类是 triangle类的派生类
float area() // 重定义
{ return high * wide; }
};
测试程序
#include <iostream.h>
#include "shape.h"
float total(figure *pf[],int n) // 求面积和
{
float sum = 0;
for(int i = 0; i < n; i ++)
sum += pf[i] -> area(); // 按实际对象调用 area()
return sum;
}
void main()
{
figure *pf[5]; // 指向抽象类的指针数组
//动态创建计算各种图形面积的对象元素
pf[0] = new triangle(3.0,4.0);
pf[1] = new rectangle(2.0,3.5);
pf[2] = new rectangle(5.0,1.0);
pf[3] = new rectangle(3.0,6.0);
pf[4] = new circle(10.0);
cout<<"total area:"<<total(pf,5)<<endl;
}
虚释放 函 数
?虽然构造函数不可以被声明为虚函数,但是
释放函数可以被声明为虚函数。一般说来,
如果一个类中定义了虚函数,释放函数也应
定义为虚释放函数,尤其是释放函数要完成
一些有意义的工作,如释放内存
例 5.2.7 通用迭代算子类
?对一种数据结构来说,迭代就是对其元素按
某种次序进行搜索的过程。这里,将按照标
准 C++技术,把迭代设计成为迭代算子类,
使其能具有通用性
?所谓通用性是指,
· 操作通用性
· 类型通用性
· 存储通用性
操作通用性
?对不同的数据结构有不同的迭代方法。操作
的通用性就是要抽象出共同方法。一般说来,
这些方法有,
· 构造函数,用以建立要被处理的数据元素;
· 请求所对应数据结构中的下一个元素;
· 判断迭代结束条件是否满足;
· 赋值运算符函数,复制构造函数和释放函数
例
class Int_iterator
{
int * mData; // 数组起始地址
int mLen; // 数组大小
public,
Int_iterator(int *,int); // 构造函数
int valid() const; // 是否可以请求下一个元素
int next(); // 取下一个元素
Int_iterator(const Int_iterator &);
Int_iterator &
Operator = (const Int_iterator &);
~Int_iterator();
};
构造函数中有两个参数:第一个用以指定数据结构的地址,第二个用以指定其中
元素的个数
类型通用性
template <class T>
class Iterator
{
T * mData;
int mLen;
public,
Iterator(T *p,int c)
,mData(p),mLen(c){}
int valid()const
{
return mLen > 0;
}
T next()
{
-- mLen;
return * mData ++;
}
};
sum函数模板
template <class T>
T sum(Iterator<T> ir)
{
T result = 0;
while (ir.valid())
result += ir.next();
return result;
}
存储通用性
?上面给出的 Iterator类只限于对存于数组中的
对象进行访问,因为其它的数据结构中的方
法函数的定义是不同的,数据元素也是不相
同的。但是,由于在( 1)中已经抽象出操作
方法的通用性,所不同的只是它们对不同数
据结构有不同的定义,因此可以把 Iterator类
定义成一个抽象基类,作为对不同数据结构
进行迭代的公共接口
定义如下
template <class T>
class Iterator
{
public,
virtual int valid() const = 0;
virtual T next() = 0;
virtual ~ Iterator() {}
};
多基派生中虚函数的二义性
?前面介绍过,多基派生中的多条路径具有公
共基类时,在这多条路径的汇合处就会因对
公共基类产生多个拷贝而产生同名函数调用
的二义性。消除这个二义性的办法是把公共
基类定义为虚基类,使由它派生的多条路径
的汇聚处只产生一个拷贝
例 5.2.8
#include <iostream.h>
class base
{
public,
virtual void a(){cout << "a() in base\n";}
virtual void b(){cout << "b() in base\n";}
virtual void c(){cout << "c() in base\n";}
virtual void d(){cout << "d() in base\n";}
virtual void e(){cout << "e() in base\n";}
virtual void f(){cout << "f() in base\n";}
};
class A:public base
{
public,
virtual void a(){cout << "a() in A\n";}
virtual void b(){cout << "b() in A\n";}
virtual void f(){cout << "f() in A\n";}
};
class B:public base
{
public,
virtual void a(){cout << "a() in B\n";}
virtual void b(){cout << "b() in B\n";}
virtual void c(){cout << "c() in B\n";}
};
class C:public A,public B
{
public,
virtual void a(){cout << "a() in C\n";}
virtual void d(){cout << "d() in C\n";}
};
例 5.2.9
#include "ex529.hpp"
void main()
{
C c;
base *pa = &c; // 指向公共基类的指针引用派生类对象
pa -> a();
pa -> b();
pa -> c();
pa -> d();
pa -> e();
pa -> f();
}
习题
第 5章 程序的类层次结构
?类的继承与派生
?类层次中成员函数名的多态性
派生
?面向对象的程序设计的一个重要特点是允许
以既有类(也称基类),以其为基础导出
(定义)新的类(也称派生类)。这一过程
称为派生
?派生时不需要把既有类的相关代码重新书写
一遍,只需要指明是以哪个类为基类,便可
以将基类中的有关特征继承过来,实现了部
分代码的可重用。
类的继承与派生
?派生方式
?派生类的构造函数与释放函数
?多基派生
?虚基类
?类层次中成员名的作用域
?类层次中的类转换
派生方式
?public派生与 private派生
?Protected成员与 protected派生
public派生与 private派生
? C++允许程序员用下边的格式用一个类派生它的子
类,
class 派生类名, 派生方式 基类名
{
private,
新增私有成员声明语句表列
public,
新增公开成员声明语句表列
};
两种派生方式的特点
?1)无论哪种派生方式,基类中的 private成员
在派生类中都是不可见的。也就是说,基底
类中的 private成员不允许外部函数或派生类
中的任何成员访问。
?2) public派生时,基类中的 public成员相当
于派生类中的 public成员。
?3) private派生时,基类中的 public成员相当
于派生类中的 private成员
派生后基类成员访问性的变化
基类成员的
访问性
private public
派 生 方
式
private public private public
派 生 类 成
员
不可见 可见 可见
外 部 函
数
不可见 可见
定义 Location— Point类层次结构
class Location{ // 基类接口定义
public,
int mX_Pos,mY_Pos; // 位置的坐标,以像素点计
Location ( int x,int y); // 构造函数,初始化位置坐标
int getX ( ); // 返回当前位置的 x坐标
int getY ( ); // 返回当前位置的 y坐标
};
enum BOOLEAN { FALSE,TRUE }; // 定义一个布尔类型
class Point, private Location{ // 派生类接口定义
BOOLEAN mVisible; // 可见性变量
Public,
Point(int x,int y); // 构造函数,初始化点的位置
BOOLEAN isVisible ();// 返回当前点是否可见
Void show ( ); // 显示当前点
Void hide ( ); // 隐藏当前点
Void moveTo ( ); // 移动当前点
};
Protected成员与 protected派生
? protected成员是一种血缘关系内外有别的成员。它
只为它所在类中的方法和由它直接派生的类方法可
见
? private,protected,,public作为类成员的可见性修
饰符,将产生如下影响,
1) 在一个类中定义的方法函数,可以访问本类中的
任何成员,但只能访问基类中的 protected成员和
public成员;
2) 一个类对象,只能使用本类或其 public派生基类
中的 public成员
例 5.1.2
class b
{
protected,
int x1;
public,
int x2;
b() {x1 = x2 = 5;}
};
class d1:public b
{
public,
int fd11()
{
return x1; // ok,可以访问基类 protected成员
return x2; // ok,可以访问基类 public成员
}
int fd12()
{
b bb; // return bb.x1; 类对象不可使用本类 protected成员
return bb.x2; // ok,类对象可以使用基类 public成员
}
};
定义 Location— Point— Circle类层
次结构
class Location // 位置类
{
protected,// 保护类成员
int mX_Pos,mY_Pos; // 在 Location派生类中可访问
public,
Location ( int x,int y);
int getX ( );
int getY ( );
};
enum BOOLEAN { FALSE,TRUE };
class Point, public Location // 点类
{
protected,// 保护类成员
BOOLEAN mVisible; // 在 Point派生类中可访问
Public,
Point(int x,int y);
BOOLEAN isVisible ();
Void show ( );
Void hide ( );
Void moveTo ( );
};
class Circle,public Point // 圆类
{
protected,// 保护类成员
int mRadius; // 圆半径,在 Circle派生类中可访问
Public,
Circle(int x,int y,int r); // 初始化圆
void show ( ); // 画圆
void hide ( ); // 隐藏圆
void moveTo ( ); // 移动圆
void expand (int delta); // 放大圆,半径为( r + delta)
void contract (int delta); // 缩小圆,半径为( r - delta)
};
派生类中访问属性发生如下变化
? 1) 基类的 private成员在派生类中是不可见的
? 2) private派生使基类中的非 private成员都成为派生
类中的私有成员; protected派生使基类的非 private
成员在派生类中都变为 protected成员; public派生使
基类的非 private成员在派生类中访问属性保持不变
? 3) 不同的派生方式引起成员访问属性的改变,只能
降低,不能提高(如将 private成员提升为 protected
成员或 public成员)。但是,C++允许在派生类中用
访问属性修饰符,恢复一个成员原来的访问属性
例 5.1.4
class b
{
protected,
int x1;
int x2;
public,
int x3;
int x4;
…
};
class d:private b // x1,x2,x3,x4 将派生为 private成员
{
protected,
b::x1; // 将 x1恢复为 protected成员
public,
b::x3; // 将 x3恢复为 public成员
…
};
派生类的构造函数与释放函数
?类层次结构中数据成员的存储
?派生类中构造函数 /释放函数的定义与调用
?派生类对象的创建
类层次结构中数据成员的存储
?Location—Point—Circle类层次结构中数据成
员的存储
mX_Pos
mY_Pos
mX_Pos
mY_Pos
mVisible
mX_Pos
mY_Pos
mVisible
mRadius
构造函数 /释放函数的定义与调用
?首先通过派生类的构造函数调用基类的构造
函数,对基类成员进行初始化
?然后再执行对在派生类中新增的成员进行初
始化的操作
?因此可以说,派生类对象是由其所有先辈类
共同创建的
派生类的构造函数原型格式
Y::Y(argX1,argX2,…,argY1,argY2,…),X
(argX1,argX2,…)
?其中,X为 Y的直接基类名,Y为派生类名。
这一格式是递归的
类层次结构 Location—Point—Circle
中的构造函数
Location,,Location ( int x,int y)
{
mX_Pos = x;
mY_Pos = y;
}
Point,,Point ( int x,int y ), Location ( x,y)
// 先调用基类构造函数
{
visible = FALSE; // 缺省情况下是不可见的
}
Circle,,Circle ( int x,int y,int r ), Point ( x,y )
// 先调用基类构造函数
{
mRadius = r;
}
说明
? 1)创建一个 Circle类对象时,要自动调用 Point类的
构造函数,再由 Point类的构造函数调用其父类
Location构造函数
? 2)当一个派生类对象撤销时,释放函数调用的顺
序与构造函数相反
? 3)保护成员具有良好的继承性,对保护成员的初
始化最好在基类中进行
? 4)派生类的构造函数的构成形式,与包含对象成
员的类的构造函数相似但不相同
派生类对象的创建
?派生类对象可以用两种方式创建,
1)用常数参数表创建
2)由部分基类对象创建派生类对象
多基派生
?派生类只有一个基类时,称为单基派生。一
个派生类具有多个基类时,称为多基派生或
多重继承 (multiple inheritance),这时将继承
每个基类的部分代码。多基派生是单基派生
的扩展。与单基派生相比,既有同一性,又
有特殊性。单基派生则可以看成多基派生的
特例
例
?由 Hard(机器名)类与 Soft(软件,由 os与
Language组成)类派生出 System类
#include <iostream.h>
#include <string.h>
class Hard
{ protected,char bodyname[20];
public,Hard(char * bdnm) // 构造函数
{ cout<<"con H\n";
strcpy(bodyname,bdnm);
}
Hard(Hard & abody) // 复制构造函数
{ cout<<"copy H\n";
strcpy(bodyname,abody.bodyname);
}
void print()
{ cout<<"Body_name:"<<bodyname<<endl;
}
};
class Soft
{
protected,
char os[10];
char lang[15];
public,
Soft(char * o,char * lg) // 构造函数
{
cout<<"con F\n";
strcpy(os,o);
strcpy(lang,lg);
}
Soft(Soft & asoft) // 复制构造函数
{
cout<<"copy F\n";
strcpy(os,asoft.os);
strcpy(lang,asoft.lang);
}
void print()
{
cout<<"os:"<<os<<",language:"<<lang<<endl;
}
};
class System:public Hard,public Soft // 派生类 System
{
char owner[10];
public,
System(char * ow,char * bn,char * o,char * lg)
,Hard(bn),Soft(o,lg) // 调用基类构造函数
{
cout<<"con S\n";
strcpy(owner,ow);
}
System(Hard abody,Soft asoft,char * ow)
,Hard(abody),Soft(asoft) // 调用基类复制构造函数
{
cout<<"copy S\n";
strcpy(owner,ow);
}
void print()
{
cout<<"owner:"<<owner;
cout<<";\n hard:"<<bodyname;
cout<<";\n soft:"<<os<<","<<lang<<endl;
}
};
测试程序
void main()
{
System bsystem( "Wang",// 用常参数表创建派生类对象
"IBM PC",
"PC DOS",
"True BASIC");
bsystem.print();
cout<<"Ok!\n";[y1]
Hard abody("AST 386 sx/16");
Soft asoft("PC DOS","Borland C++");
System asystem( "AST 386sx/16",// 基类对象创建派生类对象
asoft,
"Zhang");
asystem.print();
}
虚基类
?在多基派生中,如果在多条继承路径上有一
个公共的基类(如图 5.5 (a) 中的 base0),
则在这些路径的汇合点(如图 5.5 (a) 中的
derived类对象),便会产生来自不同路径的
公共基类的多个拷贝,如图 5.5 (b) 所示。如
果只想保留公共基类的一个拷贝,就必须使
用关键字 virtual把这个公共基类定义为虚基类
在多条继承路径上公共基类的情形
base0
base1 base2
derived
base0{a} base0{a}
base1 base2
derived
在定义基类直接派生的类时说明格式
class 派生类名, virtual 派生方式 基类名 {
…
};
例 5.1.7
class base0
{
public,
int a;
…
};
class base1:virtual public base0
{
…
};
class base2:virtual public base0
{
…
};
class derived:public base1,public base2
{
…
};
几点说明
? 1) virtual也是“派生方式”中的一个关键字,它与
访问控制关键字( public或 private,protected)间
的先后顺序无关紧要
? 2)为了保证虚基类在派生类中只继承一次,就必
须将其直接派生类定义时都说明为虚拟派生;否则
除从用作虚基类的所有路径中得到一个拷贝外,还
从其它作为非虚基类的路径中各得到一个拷贝
? 3) 虚基类对象的初始化图 5.6a所示的类层次结构,
当 A与 B都是 C与 D的虚基类时,系统将要自左向右
按深度优先遍历算法对公共派生类 E进行初始化
虚基类对象的初始化
C
B A
D
E
A B A B
C D
E
类层次中成员名的作用域
? private成员的作用域只在本类对象内部
? public成员和 protected成员,在类层次结构中将形成成员名
多重作用域,
· 基类成员作用域和派生类新增成员作用域形成相包含的关
系,派生类新增成员作用域为内层,基类成员作用域在外层
· 内层声明的标识符可以覆盖外层声明的同名标识符,使其
不可见;对于多基派生类,如果其多个基类中拥有同名成员,
而在派生类中又新增了同名成员,则派生类的该同名成员将
覆盖所有基类的同名成员
· 如果在几个基类中具有同名成员,但在派生类中没有与它
们同名的新增成员名,则由于这些成员名具有相同的作用域,
使系统无法判定到底是调用哪个基类成员
有类层次结构:A ← B ← C
#include <iostream.h>
class A
{ public,
int x,a;
void fun(int i)
{ x = i;
cout << "A,,x = "<< x << endl;
cout << endl;
}
};
class B:public A
{ public,
int x,b;
void fun(int i)
{ x = i * a; // 引用非同名基类成员
cout << "B::x = "<< x << endl; // 引用本类成员
cout << "A::x = "<< A::x << endl; // 引用同名基成员
cout << "a = " << a << endl; // 引用非同名基成员
cout << endl;
}
};
class C:public B
{
public,
int x;
void fun(int i)
{
x = i * a * b; // 引用非同名基类变量
cout <<,C::x =, << x << endl; // 引用本类成员
cout <<,A::x =, << A::x <<,,B::x =, << B::x << endl;// 引用同名基类成员
cout <<,a =, << a <<,,b =, << b << endl; // 引用非同名基类成员
cout << endl;
}
};
测试程序
void main()
{
int i = 2;
C obj;
obj.a = 1; //引用非同名基成员
obj.A::fun(i); //引用同名基成员
obj.b = 2; //引用非同名基成员
obj.B::fun(i); //引用同名基成员
obj.fun(i); //引用派生类成员
}
例 5.1.9
class Base
{
public,
static void sb(); // 静态成员
void fb();
};
class D, private Base { }; // 全私有派生
class DD, public D
{
void fdd();
};
void DD,,fdd()
{
Base,,sb(); // 对
// fb(); 错
// sb(); 错
}
类层次中的类转换
?单基派生的情形
?多继承的情形
?含有公共虚基类的类层次结构
单基派生的情形
#include <iostream.h>
#include <string.h>
class Person
{ char * mName;
char mSex;
int mAge;
public,
Person (char *n="",int a=50,char s='m')
{ mName=new char[strlen(n)+1];
strcpy (mName,n);
mAge = a;
mSex = s;
}
~Person()
{ delete []mName;
}
void show ()
{ cout <<,\n name:” << mName;
cout <<,-age:” << mAge;
cout <<,-sex:” << mSex << endl;
}
Person& operator=(Person& p)
{
delete []mName;
mName=new char[strlen(p.mName)+1];
strcpy(mName,p.mName);
mAge=p.mAge;
mSex=p.mSex;
return *this;
}
};
class Employee, public Person
{
char * mDepartment;
float mSalary;
public,
Employee (char *,int,char,char *,float);
void show();
~Employee()
{
delete[]mDepartment;
}
};
Employee,,Employee (char * nm = "",int ag = 0,char sx = ' ',
char * dprt = "",float slr = 0)
, Person (nm,ag,sx)
{
mDepartment=new char[strlen(dprt)+1];
strcpy ( mDepartment,dprt);
mSalary = sly;
}
void Employee,,show()
{
Person,,print();
cout << "-department:" << department ;
cout << "-salary:" << salary << endl;
}
测试程序
void main()
{
Employee zh("Zhang",50,'m',"SHXIEMU",180);
Person c,* pP;
c =zh; // 1用派生类对象给基类对象赋值
c.show();
Person & y = zh; // 2用派生类对象初始化基类的引用
y.show();
pP = & zh; // 3把派生类对象地址赋值给基类指针
pP -> show();
((Employee *)pP) -> show(); // 4基类指针向派生类指针强制转换
}
多继承的情形
class base0
{
protected,
int b0;
};
class base1:public base0
{
protected,
int b1;
};
class base2:public base0
{
protected,
int b2;
};
class derived:public base1,public base2
{
float d;
public,
derived();
};
各层次数据成员在内存中的分布
Pb1
int b0
int b1
int b0
int b2
…
base0
base0
base1
base2
derived
Pb2
base0
base1 base2
derived
克服对基类成员访问的二义性
?对指针要显式地指明全路径
?将指针先强制转换到不会产生二义性的基类
?显式指明成员来自哪个类
含有公共虚基类的类层次结构
?使用虚基类,在它的几条派生路经的汇合处,
只产生其一个拷贝
?引用的都是同一个虚基类中的数据成员具有
相同的值。可以进行如下的类变换
1) 派生类对象的地址可以直接赋给间接公
基类的指针,并且不需进行强制类型转换
2) 一个虚基类的引用,可以引用一个派生
类的对象
类层次中成员函数名的多态性
?虚函数与动态绑定
?虚函数的访问
?纯虚函数与抽象类
?虚释放 函 数
?多基派生中虚函数的二义性
虚函数与动态绑定
?虚函数是用关键字 virtual修饰的某基类中的
protected或 public成员函数。它可以在派生
类中重定义,以形成不同的版本。只有在程
序执行过程中,依据指针具体指向哪个类对
象,或依据引用哪个类对象,才能确定激活
哪一个版本,实现动态绑定。即这种绑定方
式不是在编译时静态地进行,而必须在程序
运行过程中进行动态绑定
C++中静态多态性与动态多态性的主
要特征
形 式 绑定方式 各版本的函
数原型
绑定依据 特点
函数名重载 静态绑定 不同 参数数目及类型 高效
虚 函
数
动态绑定 相同 程序运行时引用或
指针指向
高灵活性、抽象性
和可扩充性
点 ─圆类层次结构 不使用虚函数
#include <iostream.h>
class Point // 基类
{ private,
float mX,mY;
public,
Point(){}
Point(float i,float j)
{ mX = i; mY = j; }
float area() // 非虚函数
{ return 0.0; }
};
const float Pi = 3.141593;
class Circle, public Point // 派生类
{ private,
float mRadius;
public,
Circle(float r)
{ mRadius = r; }
float area()
{ return Pi * mRadius * mRadius; }
};
点 ─圆类层次结构 使用虚函数
#include <iostream.h>
class Point
{ private,
float mX,mY;
public,
Point(){}
Point(float i,float j)
{ mX = i; mY = j; }
virtual float area() // 声明为虚函数
{ return 0.0; }
};
const float Pi = 3.141593;
class Circle:public Point
{ private,
float mRadius;
public,
Circle(float r)
{ mRadius = r; }
float area() // 虚函数的再定义
{ return Pi * mRadius * mRadius; }
};
虚函数声明与重定义的一般规则
? 1)在基类中,用关键字 virtual可以将其 public或 protected部
分的成员函数声明为虚函数
? 2)一个虚函数是属于它所在的类层次结构的,而不是只属
于某一个类,只不过它在该类层次结构中的不同类中具有不
同的形态
? 3) 如果派生类中没有对基类中说明的虚函数进行重定义,
则它继承基类中的虚函数
? 4) 由于有些编译器不能正确处理虚函数,因此要避免把构
造函数声明为虚函数
? 5) 虚函数只能是类的成员函数,不能把虚函数声明为静态
的或全局的;也不能把友元说明为虚函数。但是虚函数可以
是另一个类的友元函数
虚函数的访问
?用基指针访问与用对象名访问
?访问该类层次中的虚函数 ——要使用 this指针
?用构造函数/释放函数访问
用基指针访问与用对象名访问
?把一个函数声明为虚函数,保证了这个函数
被指向基类的指针(或引用)调用时,C++
系统对其进行动态绑定,向实际的对象传递
消息
?但是,通过一个对象名访问虚函数时,C++
系统将使用静态绑定
例 5.2.2
int main()
{
Circle c(6.4321);
Cout << c.area() << endl;
Cout << c.Point::area() << endl;
Cout << c.Circle::area() << endl;
}
这时,可以用作用域运算符指定使用的是哪个类的虚函数。这与非虚函数没有区别。
这里所说的“指向基类”是指指向最先将一个成员函数声明为虚函数的类。
例 5.2.3
#include <iostream.h>
class base0
{ public,void v() {cout << "base0\n";} // 非虚函数
};
class base1:public base0
{ public,
virtual void v() {cout << "base1\n";} // 声明虚函数
};
class A1:public base1
{ public,
void v() {cout << "A1\n";}
};
class A2:public A1
{ public,
void v() {cout << "A2\n";}
};
class B1:private base1
{ public,
void v() {cout << "B1\n";}
};
class B2:public B1
{ public,
void v() {cout << "B2\n";}
};
访问该类层次中的虚函数
#include <iostream.h>
class A
{
public,
virtual void v1(){cout << "v1 is called in A.\n";a1();}
void a1(){cout << "a1 is called in A.\n";v2();}
virtual void v2(){cout << "v2 is called in A.\n";}
};
class B:public A
{
public,
void b1(){cout << "b1 is called in B.\n";v2();}
virtual void v2(){cout << "v2 is called in B.\n";}
};
这里,this是一个指向基类 A的指针。由于 v2()是一个虚函数,因此 C++系统要通过
动态绑定实现,具体取决于所引用的对象
用构造函数/释放函数访问
#include <iostream.h>
class base
{ public,
base() {};
virtual void v1() { cout << "v1 is called in base.\n"; }
};
class A, public base
{ public,
A()
{ cout << " call v1 in A().\n";
v1(); // 调用本类中定义的虚函数
}
virtual void v1() {cout << "v1 is called in A.\n"; }
};
class B, public A
{ public,
B()
{ cout << "call v1 in B().\n";
A::v1(); // 调用基类中定义的虚函数
}
void v1() {cout << "v1 is called in B.\n";}
};
纯虚函数与抽象类
? 当在基类中不能为虚函数提供有实际意义的定义时,可以将
这个虚函数声明为纯虚函数
? 声明纯虚函数的形式为,
virtual 类型 函数名 (参数列表 ) = 0;
1)纯虚函数不能被直接调用,仅起提供一个与派生类相一致
的接口作用,用作派生类中的具有相同名字的函数存放处。
包含纯虚函数的类与把所有构造函数都声明为 protected成员
的类一样,也称为抽象类。一个抽象类只能作为基类来派生
新类,不能生成抽象类的对象。因此,当 Point被声明为抽象
类后,便不可生成 Point的对象
2)纯虚函数不可以被继承。当基类是抽象类时,在派生类中
必须给出基类中纯虚函数的定义,或在该类中再声明其为纯
虚函数
例 5.2.6
#include "figure.hpp"
const float Pi = 3.141593;
class circle:public figure
{ private,
float radius;
public,
circle(float r)
{ radius = r; }
float area() // 重定义
{ return radius * radius * Pi; }
};
class triangle:public figure
{ protected,
float high,wide;
public,
triangle(float h,float w)
{ high = h;
wide = w; }
float area() // 重定义
{ return high*wide*0.5; }
};
class rectangle:public triangle
{ public,
rectangle(float h,float w):triangle(h,w){} // tectangle类是 triangle类的派生类
float area() // 重定义
{ return high * wide; }
};
测试程序
#include <iostream.h>
#include "shape.h"
float total(figure *pf[],int n) // 求面积和
{
float sum = 0;
for(int i = 0; i < n; i ++)
sum += pf[i] -> area(); // 按实际对象调用 area()
return sum;
}
void main()
{
figure *pf[5]; // 指向抽象类的指针数组
//动态创建计算各种图形面积的对象元素
pf[0] = new triangle(3.0,4.0);
pf[1] = new rectangle(2.0,3.5);
pf[2] = new rectangle(5.0,1.0);
pf[3] = new rectangle(3.0,6.0);
pf[4] = new circle(10.0);
cout<<"total area:"<<total(pf,5)<<endl;
}
虚释放 函 数
?虽然构造函数不可以被声明为虚函数,但是
释放函数可以被声明为虚函数。一般说来,
如果一个类中定义了虚函数,释放函数也应
定义为虚释放函数,尤其是释放函数要完成
一些有意义的工作,如释放内存
例 5.2.7 通用迭代算子类
?对一种数据结构来说,迭代就是对其元素按
某种次序进行搜索的过程。这里,将按照标
准 C++技术,把迭代设计成为迭代算子类,
使其能具有通用性
?所谓通用性是指,
· 操作通用性
· 类型通用性
· 存储通用性
操作通用性
?对不同的数据结构有不同的迭代方法。操作
的通用性就是要抽象出共同方法。一般说来,
这些方法有,
· 构造函数,用以建立要被处理的数据元素;
· 请求所对应数据结构中的下一个元素;
· 判断迭代结束条件是否满足;
· 赋值运算符函数,复制构造函数和释放函数
例
class Int_iterator
{
int * mData; // 数组起始地址
int mLen; // 数组大小
public,
Int_iterator(int *,int); // 构造函数
int valid() const; // 是否可以请求下一个元素
int next(); // 取下一个元素
Int_iterator(const Int_iterator &);
Int_iterator &
Operator = (const Int_iterator &);
~Int_iterator();
};
构造函数中有两个参数:第一个用以指定数据结构的地址,第二个用以指定其中
元素的个数
类型通用性
template <class T>
class Iterator
{
T * mData;
int mLen;
public,
Iterator(T *p,int c)
,mData(p),mLen(c){}
int valid()const
{
return mLen > 0;
}
T next()
{
-- mLen;
return * mData ++;
}
};
sum函数模板
template <class T>
T sum(Iterator<T> ir)
{
T result = 0;
while (ir.valid())
result += ir.next();
return result;
}
存储通用性
?上面给出的 Iterator类只限于对存于数组中的
对象进行访问,因为其它的数据结构中的方
法函数的定义是不同的,数据元素也是不相
同的。但是,由于在( 1)中已经抽象出操作
方法的通用性,所不同的只是它们对不同数
据结构有不同的定义,因此可以把 Iterator类
定义成一个抽象基类,作为对不同数据结构
进行迭代的公共接口
定义如下
template <class T>
class Iterator
{
public,
virtual int valid() const = 0;
virtual T next() = 0;
virtual ~ Iterator() {}
};
多基派生中虚函数的二义性
?前面介绍过,多基派生中的多条路径具有公
共基类时,在这多条路径的汇合处就会因对
公共基类产生多个拷贝而产生同名函数调用
的二义性。消除这个二义性的办法是把公共
基类定义为虚基类,使由它派生的多条路径
的汇聚处只产生一个拷贝
例 5.2.8
#include <iostream.h>
class base
{
public,
virtual void a(){cout << "a() in base\n";}
virtual void b(){cout << "b() in base\n";}
virtual void c(){cout << "c() in base\n";}
virtual void d(){cout << "d() in base\n";}
virtual void e(){cout << "e() in base\n";}
virtual void f(){cout << "f() in base\n";}
};
class A:public base
{
public,
virtual void a(){cout << "a() in A\n";}
virtual void b(){cout << "b() in A\n";}
virtual void f(){cout << "f() in A\n";}
};
class B:public base
{
public,
virtual void a(){cout << "a() in B\n";}
virtual void b(){cout << "b() in B\n";}
virtual void c(){cout << "c() in B\n";}
};
class C:public A,public B
{
public,
virtual void a(){cout << "a() in C\n";}
virtual void d(){cout << "d() in C\n";}
};
例 5.2.9
#include "ex529.hpp"
void main()
{
C c;
base *pa = &c; // 指向公共基类的指针引用派生类对象
pa -> a();
pa -> b();
pa -> c();
pa -> d();
pa -> e();
pa -> f();
}
习题