2005-4-28 1
C++大学基础教程
第11章 多态性
北京邮电大学电信工程学院
计算机技术中心
多态性(Polymorphism)是面向
对象程序设计的主要特征之一。
多态性对于软件功能的扩展和软
件重用都有重要的作用。是学习
面向对象程序设计必须要掌握的
主要内容之一。
第十一章 多态性
11.1 多态性的概念
11.2 继承中的静态联编
11.3 虚函数和运行时的多态
11.4 纯虚函数和抽象类
11.5 继承和派生的应用
11.6 模板
11.1 多态性的概念
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-5-
11.1.1面向对象程序设计中多态的表现
总的来说,不同对象对于相同的消息有不同的
响应,就是面向对象程序设计中的多态性。
具体在程序中,多态性有两种表现的方式:
? 同一个对象调用名字相同、但是参数不同的函数,
表现出不同的行为。在同一个类中定义的重载函数
的调用,属于这种情况。
? 不同的对象调用名字和参数都相同的函数,表现出
不同的行为。在派生类的应用中,经常会看到这样
的调用。
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-6-
11.1.1面向对象程序设计中多态的表现
面向对象程序设计中多态性表现为以下几种形
式:
? 重载多态:通过调用相同名字的函数,表现出不同
的行为。运算符重载也是一种重载多态。
? 运行多态:通过基类的指针,调用不同派生类的同
名函数,表现出不同的行为。许多面向对象程序设
计的书籍中所说的多态性,就是这种多态。
? 模板多态,也称为参数多态:通过一个模板,得到
不同的函数或不同的类。这些函数或者类具有不同
的特性和不同的行为。
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-7-
11.1.2 多态的实现:联编
一个具有多态性的程序语句,在执行的
时候,必须确定究竟是调用哪一个函
数。也就是说,在执行的时候调用哪个
函数是唯一地确定的。确定具有多态性
的语句究竟调用哪个函数的过程称为联
编(Binding),有的资料也翻译成 “绑
定 ”。
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-8-
11.1.2 多态的实现:联编
联编有两种方式:静态联编和动态联
编。
在源程序编译的时候就能确定具有多态
性的语句调用哪个函数,称为静态联
编。
对于重载函数的调用就是在编译的时候
确定具体调用哪个函数,所以是属于静
态联编。
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-9-
11.1.2 多态的实现:联编
动态联编则是必须在程序运行时,才能够确定
具有多态性的语句究竟调用哪个函数。用动态
联编实现的多态,也称为运行时的多态。以上
所述的几种多态形式中,只有运行多态是属于
动态联编。以后我们会看到,在一个循环中的
同一个语句,第一次循环时调用的是一个函
数,第二次循环时调用的是另一个函数。这种
结果,程序不运行是看不到的。所以称为动态
联编。
11.2 继承中的静态联编
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-11-
11.2.1派生类对象调用同名函数
在派生类中可以定义和基类中同名的成员函
数。这是对基类进行改造,为派生类增加新的
行为的一种常用的方法。
通过不同的派生类的对象,调用这些同名的成
员函数,实现不同的操作,也是多态性的一种
表现。
在程序编译的时候,就可以确定派生类对象具
体调用哪个同名的成员函数。这是通过静态联
编实现的多态。
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-12-
例 11.1 定义 Circle类和 Rectangle类为 Shape类的派生类,通
过 Circle类和 Rectangle类的对象调用重载函数 getArea()显示
对象的面积。
// 例11.1: shape.h
#ifndef SHAPE_H
#define SHAPE_H
class Shape {
public:
double getArea() const;
void print() const;
}; // Shape类定义结束
基类Shape的定义
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-13-
class Circle : public Shape {
public:
Circle( int = 0, int = 0, double = 0.0 );
double getArea() const; // 返回面积
void print() const; // 输出Circle 类对象t
private:
int x,y; // 圆心座标
double radius; // 圆半径
}; // 派生类Circle定义
结束
class Rectangle : public Shape {
public:
Rectangle( int = 0, int = 0); // 构造函数
double getArea() const; // 返回面积
void print() const; // 输 出 Rectangle
类对象
private:
int a,b; // 矩形的长和宽
}; // 派生类Rectangle定义结束
派生类Circle的定义
派生类Rectangle的定义
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-14-
// 例11.1: shape.cpp
#include <iostream>
using namespace std;
#include "shape.h"
double Shape::getArea() const
{
cout<<"基类的getArea函数,面积是 ";
return 0.0;
} // Shape类getArea函
数的定义
void Shape::print() const
{
cout<<"Base class Object"<<endl;
} //Shape类print函数定义
包含头文件
基类成员函数的定义
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-15-
Circle::Circle( int xValue, int yValue, double radiusValue )
{
x=xValue; y=yValue;
radius= radiusValue ;
} // Circle类构造函数
double Circle::getArea() const
{
cout<<"Circle类的getArea函数,面积是 ";
return 3.14159 * radius * radius;
} // Circle类getArea函数定义
void Circle::print() const
{
cout << "center is ";
cout<<"x="<<x<<" y="<<y;
cout << "; radius is " << radius<<endl;
} // Circle类print函数定义
Circle类成员函数的定义
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-16-
Rectangle::Rectangle( int aValue, int bValue )
{
a=aValue; b=bValue;
} // Rectangle类构造函数
double Rectangle::getArea() const
{
cout<<"Rectangle类的getArea函数,面积是 ";
return a * b;
} // Rectangle类getArea函数定
义
void Rectangle::print() const
{
cout << "hight is "<<a;
cout<<"width is"<<b<<endl;
} // Rectangle类print函数定
义
Rectangle类成
员函数的定义
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-17-
// 例11.1: 11_1.cpp
#include <iostream>
using std::cout;
using std::endl;
#include "shape.h" // 包含头文件
void main()
{
Circle circle( 22, 8, 3.5 ); // 创建Circle类对象
Rectangle rectangle( 10, 10 ); // 创建Rectangle类
对象
cout << "调用的是 ";
cout<<circle.getArea() << endl; // 静态联编
cout << "调用的是";
cout<<rectangle.getArea() << endl; // 静态联编
} // 结束main函数
例11.1的主函数
调用的是 Circle类的 getarea函
数,面积是 38.4845
调用的是 Ractangle类的
getarea函数,面积是 100
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-18-
11.2.1派生类对象调用同名函数
对于派生类对象调用成员函数,可以有以下的
结论:
派生类对象可以直接调用本类中与基类成员函
数同名的函数,不存在二义性;
在编译时就能确定对象将调用哪个函数,属于
静态联编,不属于运行时的多态。
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-19-
11.2.2通过基类指针调用同名函数
从继承的角度来看,派生类对象是基类对象的
一个具体的特例。或者说,派生类对象是某一
种特定类型的基类对象。
? 例如,Circle类是Shape类的公有继承, “圆 ”是 “图
形 ”的一种特例。或者说,圆是一种特定的图形,
具有图形的基本特征。
但是,这种关系不是可逆的。不可以说基类的
对象具有派生类对象的特征,基类对象也不是
派生类对象的一个特例。
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-20-
11.2.2通过基类指针调用同名函数
在关于基类对象和派生类对象的操作上,可以
允许以下的操作:
? 派生类对象可以赋值给基类对象;
? 派生类对象的地址可以赋值给基类对象的指针。或
者说,可以用派生类对象的地址初始化基类对象的
指针;
? 可以将基类对象的引用,定义为派生类对象的别
名,或者说,用派生类对象初始化基类的引用。
? 通过派生类对象的地址初始化的基类对象的指针,
可以访问基类的公有成员,也可以访问和基类成员
函数同名的函数。
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-21-
11.2.2通过基类指针调用同名函数
以下这些操作是不可以进行的:
? 不可以将基类对象赋值给派生类对象;
? 不可以用基类对象的地址初始化派生类对象的
指针;
? 不可以将派生类对象的引用定义为基类对象的
别名;
? 不可以通过用派生类对象初始化的基类对象的
指针,访问派生类新增加的和基类公有成员不
重名的公有成员。
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-22-
例 11.2 在例 11.1所定义的类的基础上,观察通过派生类对象地址初
始化的基类对象的指针访问 getArea函数的结果。
#include <iostream>
using namespace std;
#include "shape.h" // 包含头文件
void main()
{ Shape *shape_ptr; // 指向基类对象的指针
Circle circle( 22, 8, 3.5 ); // 创 建
Circle类对象
Rectangle rectangle( 10, 10 ); // 创建Rectangle类对象
shape_ptr = &circle; // Circle类对象地址初
始化基类指针
cout << "circle 对 象初始化s hape_ptr指针访问的getArea函数是
"<<endl;
cout<<shape_ptr->getArea() << endl; // 静态联编
shape_ptr = &rectangle; // Rectangle类对象地址初始化基类
指针
cout << "rectangle 对象初始化shape_ptr指针访问的getArea函数
是 "<<endl;
cout<<shape_ptr->getArea() << endl; // 静态联编
} // 结束 main函数
circle 对 象初始化sha pe_ptr指
针访问的getArea函数是
基类的getArea函数,面积是 0
rectangle 对 象初始化 shape_ptr
指针访问的getArea函数是
基类的getArea函数,面积是 0
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-23-
11.2.2通过基类指针调用同名函数
程序运行结果表明:
? 确实可以用派生类对象的地址初始化基类对象的指
针;
? 通过用派生类对象地址初始化的基类对象指针,只
能调用基类的公有成员函数。在以上例子中,就是
调用基类的getArea函数,而不是派生类的getArea
函数。
? 这种调用关系的确定,也是在编译的过程中完成
的,属于静态联编,而不属于运行时的多态。
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-24-
11.2.2通过基类指针调用同名函数
程序运行结果表明:
? 确实可以用派生类对象的地址初始化基类对象的指
针;
? 通过用派生类对象地址初始化的基类对象指针,只
能调用基类的公有成员函数。在以上例子中,就是
调用基类的getArea函数,而不是派生类的getArea
函数。
? 这种调用关系的确定,也是在编译的过程中完成
的,属于静态联编,而不属于运行时的多态。
11.3 虚函数和运行时的多态
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-26-
11.3 虚函数和运行时的多态
通过指向基类的指针访问基类和派生类
的同名函数,是实现运行时的多态的必
要条件,但不是全部条件。
除此以外,还必须将基类中的同名函数
定义为 虚函数 。
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-27-
11.3.1 虚函数
虚函数可以在类的定义中声明函数原型的时候
来说明,格式如下:
virtual <返回值类型> 函数名(参数表);
? 在函数原型中声明函数是虚函数后,具体定义这个
函数时就不需要再说明它是虚函数了。
如果在基类中直接定义同名函数,定义虚函数
的格式是:
virtual <返回值类型> 函数名(参数表)
{<函数体>}
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-28-
11.3.1 虚函数
基类中的同名函数声明或定义为虚函数后,派
生类的同名函数无论是不是用virtual来说
明,都将自动地成为虚函数。从程序可读性考
虑,一般都会在这些函数的声明或定义时,用
virtual来加以说明。
只要对例11.2中的头文件稍加修改,也就是将
基类和派生类中的getArea函数都声明为虚函
数,在重新编译和运行程序,就可以得到运行
时的多态的效果。
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-29-
例11.3 将例11.2进行修改,使得程序具有运行时的多态的效
果。
// 例11.3: shape1.h
#ifndef SHAPE_H
#define SHAPE_H
class Shape {
public:
virtual double getArea() const;
void print() const;
}; // Shape类定义结束
基类Shape的定义
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-30-
class Circle : public Shape {
public:
Circle( int = 0, int = 0, double = 0.0 );
virtual double getArea() const; // 返回面积
void print() const; // 输出Circle 类对象t
private:
int x,y; // 圆心座标
double radius; // 圆半径
}; // 派生类Circle定义
结束
class Rectangle : public Shape {
public:
Rectangle( int = 0, int = 0); // 构造函数
virtual double getArea() const; // 返回面积
void print() const; // 输 出 Rectangle
类对象
private:
int a,b; // 矩形的长和宽
}; // 派生类Rectangle定义结束
派生类Circle的定义
派生类Rectangle的定义
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-31-
// 例11.3: shape1.cpp
#include <iostream>
using namespace std;
#include "shape1.h"
double Shape::getArea() const
{
cout<<"基类的getArea函数,面积是 ";
return 0.0;
} // Shape类getArea函
数的定义
void Shape::print() const
{
cout<<"Base class Object"<<endl;
} //Shape类print函数定义
包含头文件
基类成员函数的定义
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-32-
Circle::Circle( int xValue, int yValue, double radiusValue )
{
x=xValue; y=yValue;
radius= radiusValue ;
} // Circle类构造函数
double Circle::getArea() const
{
cout<<"Circle类的getArea函数,面积是 ";
return 3.14159 * radius * radius;
} // Circle类getArea函数定义
void Circle::print() const
{
cout << "center is ";
cout<<"x="<<x<<" y="<<y;
cout << "; radius is " << radius<<endl;
} // Circle类print函数定义
Circle类成员函数的定义
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-33-
Rectangle::Rectangle( int aValue, int bValue )
{
a=aValue; b=bValue;
} // Rectangle类构造函数
double Rectangle::getArea() const
{
cout<<"Rectangle类的getArea函数,面积是 ";
return a * b;
} // Rectangle类getArea函数定
义
void Rectangle::print() const
{
cout << "hight is "<<a;
cout<<"width is"<<b<<endl;
} // Rectangle类print函数定
义
Rectangle类成
员函数的定义
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-34-
// 例11.3: 11_3.cpp
#include <iostream>
using std::cout;
using std::endl;
#include "shape1.h" // 包含头文件
void main()
{
Circle circle( 22, 8, 3.5 ); // 创建Circle类对象
Rectangle rectangle( 10, 10 ); // 创建Rectangle类
对象
cout << "调用的是 ";
cout<<circle.getArea() << endl; // 静态联编
cout << "调用的是";
cout<<rectangle.getArea() << endl; // 静态联编
} // 结束main函数
例11.1的主函数
circle 对象 初始化shape_ptr指针访问的
getArea函数是
Circle类的getArea函数,面积是 38.4845
rectangle 对象初始化shape_ptr指针访问
的getArea函数是
Rectangle类的getArea函数,面积是 100
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-35-
11.3.1 虚函数
这个结果和例11.2的结果大不相同。同样的
shape_ptr->getArea()函数调用,当shape_ptr指
针中是Circle类对象地址时,访问的是Circle类
的getArea函数。而shape_ptr指针中是Rectangle
类对象的地址时,访问的是Rectangle类的
getArea函数。
这种方式的函数调用,在编译的时候是不能确定
具体调用哪个函数的。只有程序运行后,才能知
道指针shape_ptr中存放的是什么对象的地址,然
后再决定调用哪个派生类的函数。是 一种运行时
决定的多态性 。
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-36-
11.3.1 虚函数
要实现运行时的多态,需要以下条件:
? 必须通过指向基类对象的指针访问和基类成员
函数同名的派生类成员函数;
? 或者用派生类对象初始化的基类对象的引用访
问和基类成员函数同名的派生类成员函数;
? 派生类的继承方式必须是公有继承;
? 基类中的同名成员函数必须定义为虚函数。
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-37-
11.3.2 虚函数的使用
虚函数必须正确的定义和使用。否则,即使在
函数原型前加了virtual的说明,也可能得不
到运行时多态的特性。
? 必须首先在基类中声明虚函数。在多级继承的情况
下,也可以不在最高层的基类中声明虚函数。例如
在第二层定义的虚函数,可以和第三层的虚函数形
成动态联编。但是,一般都是在最高层的基类中首
先声明虚函数。
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-38-
11.3.2 虚函数的使用
? 基类和派生类的同名函数,必须函数名、返回
值、参数表全部相同,才能作为虚函数来使
用。否则,即使函数用virtual来说明,也不具
有虚函数的行为。
? 静态成员函数不可以声明为虚函数。构造函数
也不可以声明为虚函数。
? 析构函数可以声明为虚函数,即可以定义虚析
构函数。
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-39-
例 11.4 虚函数的正确使用。分析以下程序,编译时哪个语句会出现
错误?为什么?将有错误的语句 屏蔽掉以后,程序运行结果如何?
其中哪些调用是静态联编,哪些是动态联编?
#include <iostream.h>
class BB
{public:
virtual void vf1(){cout<<"BB::vf1被调用\n";}
virtual void vf2(){cout<<"BB::vf2被调用\n";}
void f(){cout<<"BB::f被调用\n";}
};
class DD:public BB
{public:
virtual void vf1(){cout<<"DD::vf1被调用\n";}
void vf2(int i){cout<<i<<endl;}
void f(){cout<<"DD::f\n被调用";}
};
有虚函数的基类
派生类
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-40-
void main()
{ DD d;
BB *bp=&d;
bp->vf1();
bp->vf2();
bp->vf2(10);
bp->f();
}
将这个语句注释掉后,运
行结果将显示:
DD::vf1被调用
BB::vf2被调用
BB::f被调用
函数调用 bp->vf2(10);是
错误的。因为派生类的
vf2函数和基类的 vf2函数
的参数不同,派生类的
vf2就不是虚函数。
其中 bp->vf1()调用是动态联
编。
bp->vf2()是静态联编。
bp->f()也是静态联编。
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-41-
11.3.3 虚析构函数
如果用 动态创建 的派生类对象的地址初始化基
类的指针,创建的过程不会有问题:仍然是先
调用基类构造函数,再执行派生类构造函数。
但是,在用delete运算符删除这个指针的时
候,由于指针是指向基类的,通过静态联编,
只会调用基类的析构函数,释放基类成员所占
用的空间。而 派生类成员所占用的空间将不会
被释放 。
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-42-
#include <iostream>
using namespace std;
class Shape {
public:
Shape(){cout<<"Shape类构造函数被调用\n";}
~Shape(){cout<<"Shape类析构函数被调用\n";};
}; // Shape类定义结束
class Circle : public Shape { //派生类Circle的定义
public:
Circle( int xx= 0, int yy= 0, double rr= 0.0 )
{x = xx; y = yy; radius =rr;
cout<<"Circle类构造函数被调用\n"; }
~Circle() {cout<<"Circle类析构函数被调用\n"; }
private:
int x,y; // 圆心座标
double radius; // 圆半径
}; // 派生类Circle定义
结束
void main()
{Shape *shape_ptr;
shape_ptr = new(Circle)(3,4,5);
delete shape_ptr;
}
例 11.5 定义简
单的 Shape类和
Circle类,观察
基类指针的创建
和释放时如何调
用构造函数和析
构函数。
基类Shape的定义
主函数
程序运行后在屏
幕上显示:
Shape类构造函
数被调用
Circle类构造函
数被调用
Shape类析构函
数被调用
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-43-
11.3.3 虚析构函数
为了解决派生类对象释放不彻底的问题,必须
将基类的析构函数定义为虚析构函数。格式是
在析构函数的名字前添加virtual关键字。函
数原型如下:
virtual ~Shape();
此时,无论派生类析构函数是不是用virtual
来说明,也都是虚析构函数。
再用delete shape_ptr来释放基类指针时,就
会通过动态联编调用派生类的析构函数。
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-44-
11.3.3 虚析构函数
将例11.5程序中的~Shape析构函数作以上修改
后,运行的结果将是:
Shape类构造函数被调用
Circle类构造函数被调用
Circle类析构函数被调用
Shape类析构函数被调用
11.4 纯虚函数和抽象类
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-46-
11.4 纯虚函数和抽象类
在前面的几个例子中,基类Shape本身并
不是一个具体的 “形状 ”的抽象,而是各
种实际的 “形状 ”的抽象。
在C++中,对于那些在基类中不需要定义
具体的行为的函数,可以定义为 纯虚函
数。
对于那些只是反映一类事物公共特性的
类,在C++中可以定义为 “抽象类 ”。
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-47-
11.4 纯虚函数和抽象类
纯虚函数声明的格式是:
virtual <返回值类型> 函数名(参数表) = 0;
纯虚函数的声明和使用有以下的特点:
? 纯虚函数一定是在基类中声明的。
? 在多级继承的情况下,纯虚函数除了在最高层基类
中声明外,也可以在较低层的基类中声明。
? 纯虚函数是没有函数体的。函数体是用 “= 0”来代
替了。
? 纯虚函数是不可以被调用的。凡是需要被调用的函
数都不可以声明为纯虚函数。
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-48-
11.4 纯虚函数和抽象类
抽象类的定义是基于纯虚函数的。
凡是带有一个或几个纯虚函数的类,就是抽象
类。
抽象类定义的一般形式是:
class 类名
{public:
virtual <返回值类型> 函数名(参数表) = 0;
其他函数的声明;
…….
};
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-49-
11.4 纯虚函数和抽象类
抽象类的定义和使用具有以下的特点:
? 抽象类是不可以实例化的,也就是不可以定义抽象
类的对象。
? 但是,可以定义抽象类的指针和抽象类的引用。目
的是通过这些指针或引用访问派生类的虚函数,实
现运行时的多态。
? 如果抽象类的派生类中没有具体实现纯虚函数的功
能,这样的派生类仍然是抽象类。
? 抽象类中除了纯虚函数外,还可以定义其他的非纯
虚函数。
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-50-
11.4 纯虚函数和抽象类
虚函数、纯虚函数、多态性在面向对象程序设
计中有很大的作用。可以增强程序的通用性、
可扩展性和灵活性。
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-51-
例 11.6 编写一个程序,可以动态的创建 Circle
类或者 Rectangle类的对象,并且显示所创建对
象的面积。在编程中注意使用多态性。
动态创建 Circlae类或
者 Rectangle类对象
动态创建 Circlae类或
者 Rectangle类对象
动态创建 Circlae类或
者 Rectangle类对象
希望用 3个函数实现
题目的功能。如果
不使用运行时的多
态,很难实现
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-52-
// 例11.6: shape2.h
#ifndef SHAPE_H
#define SHAPE_H
class Shape { //基类Shape的定义
public:
virtual double getArea() const=0; //纯虚函数
void print() const;
virtual ~Shape(){} //虚析构函
数
}; // Shape类定义结
束
基类Shape的定义
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-53-
class Circle : public Shape { //派生类Circle的定
义
public:
Circle( int = 0, int = 0, double = 0.0 );
double getArea() const; // 返回面积
void print() const; // 输出Circle 类对象t
private:
int x,y; // 圆心座标
double radius; // 圆半径
}; // 派生类Circle定义
结束
class Rectangle : public Shape {// 派生类Rectangle的定义
public:
Rectangle( int = 0, int = 0); // 构造函数
double getArea() const; // 返回面积
void print() const; // 输出Rectangle类对象
private:
int a,b; // 矩形的长和宽
}; // 派生类Rectangle
定义结束
派生类Circle的定义
派生类Rectangle的定义
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-54-
// 例11.6: shape2.cpp
#include <iostream>
using namespace std;
#include "shape2.h" // 包含头
文件
void Shape::print() const
{
cout<<"Base class Object"<<endl;
} //Shape类print函数定义
包含头文件
基类成员函数的定义
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-55-
Circle::Circle( int xValue, int yValue, double radiusValue )
{
x=xValue; y=yValue;
radius= radiusValue ;
} // Circle类构造函数
double Circle::getArea() const
{
cout<<"Circle类的getArea函数,面积是 ";
return 3.14159 * radius * radius;
} // Circle类getArea函数定义
void Circle::print() const
{
cout << "center is ";
cout<<"x="<<x<<" y="<<y;
cout << "; radius is " << radius<<endl;
} // Circle类print函数定义
Circle类成员函数的定义
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-56-
Rectangle::Rectangle( int aValue, int bValue )
{
a=aValue; b=bValue;
} // Rectangle类构造函数
double Rectangle::getArea() const
{
cout<<"Rectangle类的getArea函数,面积是 ";
return a * b;
} // Rectangle类getArea函数定义
void Rectangle::print() const
{
cout << "hight is "<<a;
cout<<"width is"<<b<<endl;
} // Rectangle类print函数定义
Rectangle类成
员函数的定义
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-57-
// 例11.6: 11_6.cpp
#include <iostream>
using namespace std;
#include "shape2.h" // 包含头文件
void creat_object(Shape **ptr);
void display_area(Shape *ptr);
void delete_object(Shape *ptr);
void main()
{
Shape *shape_ptr;
creat_object(&shape_ptr);
display_area(shape_ptr);
delete_object(shape_ptr);
} // 结束main函数
例11.6的主函数
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-58-
void creat_object(Shape **ptr)
{ char type;
*ptr = NULL;
do{
cout<<"创建对象。c:C ircle类 对象;r:Rectangle类对象
"<<endl;
cin>>type;
switch (type)
{case 'c': {int xx,yy;
double rr;
cout<<"请输入圆心的座标和圆的半径:";
cin>>xx>>yy>>rr;
*ptr = new Circle(xx,yy,rr);
break; }
case 'r': {int aa,bb;
cout<<"请输入矩形的长和宽:";
cin>>aa>>bb;
*ptr = new Rectangle(aa,bb);
break;}
default:cout<<"类型错误,请重新选择\n"; }
}while(*ptr==NULL);
Creat_object函数
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-59-
void display_area(Shape *ptr)
{ cout<<"显示所创建对象的面积,调用的是"<<endl;
cout<<ptr->getArea() << endl;
}
void delete_object(Shape *ptr)
{ delete(ptr);
}
创建对象。c:Circle类对象;r:Rectangle类对
象
c
请输入圆心的座标和圆的半径:1 1 5
显示所创建对象的面积,调用的是Circle类的
getArea函数,面积是78.54
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-60-
11.4 纯虚函数和抽象类
这个程序中,使用了本章所介绍的虚函数、纯
虚函数、虚析构函数、基类指针访问派生类对
象等技术,实现了运行时的多态。
程序具有很好的可扩展性。如果需要增加新的
派生类,当然要增加和派生类定义有关的代
码,和创建对象所需要的语句。但是,对于函
数display_area,display_object等函数 都
不用修改。
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-61-
11.4 纯虚函数和抽象类
这个例子还显示了:抽象类中可以为各派生类
定义一些通用的接口。这些通用的接口就是抽
象类中的纯虚函数。新增加的派生类的对象,
都可以使用这样的通用接口,表现派生类对象
的行为特性。
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-62-
11.4 纯虚函数和抽象类
例11.7 在例11.6的基础上,现在需要增加一
个Rectangle的派生类:Cube,也就是立方体
类。任意创建Circle、Rectangle、Cube类的
对象,并显示对象的面积。
现在需要做的只有两件事:
? 定义新的Cube类;
? 在create_object函数中增加创建Cube类对象的内
容。
? 其他函数都不需要修改。
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-63-
// 例11.7: cube.h
#ifndef CUBE_H
#define CUBE_H
class Cube : public Rectangle { //派生类Cube的定义
public:
Cube(int, int, int);
double getArea() const;
void print() const;
private:
int c;
}; // Cube类定
义结束
#endif
//cube.h文件结束
例11.7新增加的
类定义和头文件
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-64-
// 例11.7: 11_7.cpp
#include <iostream>
using std::cout;
using std::cin;
using std::endl;
#include "shape2.h" // 包含头文件
#include "cube.h"
void creat_object(Shape **ptr);
void display_area(Shape *ptr);
void delete_object(Shape *ptr);
void main()
{
Shape *shape_ptr;
creat_object(&shape_ptr);
display_area(shape_ptr);
delete_object(shape_ptr);
} // 结束main函数
Main函数只是多包
含了一个头文件
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-65-
void creat_object(Shape **ptr)
{ char type;
*ptr = NULL; //空指针
do{
cout<<"创建对象。请选择:";
cout<<"c:Circle类对象;r:Rectangle类对象;u:Cube类对
象"<<endl;
cin>>type;
switch (type)
{case 'c': //创建Ciecle类对象
{int xx,yy;
double rr;
cout<<"请输入圆心的座标和圆的半径:";
cin>>xx>>yy>>rr;
*ptr = new Circle(xx,yy,rr);
break;
}
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-66-
case 'r': //创建Rectangle类对象
{int aa,bb;
cout<<"请输入矩形的长和宽:";
cin>>aa>>bb;
*ptr = new Rectangle(aa,bb);
break;
}
case 'u': //创建Cube类对象
{int aa,bb,cc;
cout<<"请输入立方体的长、宽、高:";
cin>>aa>>bb>>cc;
*ptr = new Cube(aa,bb,cc);
break;
}
default:cout<<"类型错误,请重新选择\n";
}
}while(*ptr==NULL);
}
Creat_object函数
多增加了一个分支
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-67-
11.4 纯虚函数和抽象类
程序执行过程:
创建对象。请选择:c:Circle类对象;
r:Rectangle类对象;u:Cube类对象
u
请输入立方体的长、宽、高:4 5 6
显示所创建对象的面积,调用的是
Cube类的getArea函数,还要调用 Rectangle类
的getArea函数,面积是 120
………
11.5 继承和派生的应用
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-69-
11.5 继承和派生的应用
例11.8 设计并实现一个学生学分管理系统。
主要功能包括:
? 管理的对象:大学本科生和研究生;
? 可以增加学生和学生的基本信息,包括姓名,学
号,研究生还要包括导师姓名;
? 可以相加所选的课程、学分、课程类别,本科生的
课程类别是必修和选修,研究生的课程类别是学位
课还是任选课;
? 显示学生的基本信息和课程/学分信息;
? 系统要有良好的可扩展性。
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-70-
11.5 继承和派生的应用
面向对象程序设计要考虑:
? 对象的抽象,即类的定义;
? 对于本例来说,可以有一个基类Student,反映学
生的基本信息和基本行为;有两个派生类:
UnderGraduate(本科生)和Graduate(研究
生)。
? 数据结构。也就是用什么方式存储对象。也包括用
什么方式存储对象的数据成员。
? 由于学生不止一个,所以要用数组或者链表来存放
学生对象。为简单起见,使用两个对象数组,
UnderStu[]存放UnderGraduate对象,
GraduateStu[]存放Graduate对象。
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-71-
11.5 继承和派生的应用
为了解决创建对象存储到数组中去,类的数据
成员中需要定义一个静态数据成员StuNum,用
来跟踪创建对象的数目,同时也是决定存放到
数组的数组元素的序号。
采用类似于例11.6的方法,设计3个函数来完
成程序的功能:
? 创建对象:creat_object(Stu_ptr);
? 输入学分:InputCredit(Stu_ptr);
? 显示信息:DisplayStu(Stu_ptr);
? 其中Stu_ptr是指向对象的指针。
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-72-
11.5 继承和派生的应用
但是,不可以用所创建的对象数组的成员作为函数的
实参数。
可以另外创建一个对象指针数组,每个成员都是一个
指针,指向对象数组的对象成员。
由于有两个对象数组,所以要用一个二维指针数组 通
过两个 for循环语句,将两个对象数组元素的地址写入
这个指针数组:
for(int i=0;i<10;i++)
Stu_ptr[0][i]=&UnderStu[i];
for(i=0;i<10;i++)
Stu_ptr[1][i]=&GraduateStu[i];
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-73-
11.5 继承和派生的应用
另外定义一个函数SelectStuType,函数的返回值
是指针数组的行地址:需要访问UnderStu对象数
组时,返回指针数组第0行的地址,需要访问
GraduateStu对象数组时,返回指针数组第1行的
地址。函数的原型是:
Student **SelectStuType(Student *Array_ptr[][10]);
? 注意返回值的类型是Student **,后一个 “*”表示返回
的是指针,而 “Student *”表示指针所指向的是Student
类对象的地址。
? 只要通过简单的运算,就可以访问某一行的某个元素。
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-74-
// 例11.8 简单信息管理系统
// 例11.8: student.h
#ifndef SHAPE_H
#define SHAPE_H
struct credit //课程_学分结构体定义
{char course[20]; //课程名
int hour; //学分
char type[5]; //课程类型
};
用结构体表示
学生的学分构
成
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-75-
class Student
{ protected:
char *name; //姓名
int student_ID; //个人编号
credit credit_hour[10]; //选修 的课程和学
分
int num; //修课门数
public:
Student(); //构造函数
virtual ~Student(){}; //虚析构函
数
char * getname(); //显示姓名函数
virtual void InputInfo(); //输入 学生基本信
息
virtual void input_credit()=0; //输入课程和
学分
virtual void displayInfo()=0; //显示学生信息
() //学生人数
基类Student的
定义
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-76-
class UnderGraduate : public Student
{ public:
virtual void InputInfo(); //输入 学生基本信
息
virtual void input_credit(); //输入课程
和学分
virtual void displayInfo(); //显示学生
信息
int GetStuNum();
private:
static int StuNum; //本科学生人数
}; // 派生类UnderGraduate定义结束
派生类
UnderGraduate
的定义
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-77-
class Graduate : public Student
{ public:
virtual void InputInfo(); //输入 学生基本信
息
virtual void input_credit(); //输入课程
和学分
virtual void displayInfo(); //显示学生
信息
int GetStuNum();
private:
static int StuNum; //研究生人
数
char *tutor; //导师姓名
}; // 派生类Graduate定义结束
#endif
派生类Graduate
的定义
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-78-
// 例11.8: student.cpp
#include <iostream>
#include <iomanip>
using namespace::std;
#include "student.h" // 包含头文件
int UnderGraduate::StuNum=0; //静态数据成员初始
化
int Graduate::StuNum=0; //静态数据成员初始
化
Student::Student()
//Student类构造函数
{
num = 0; //选课数初始化为0
}
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-79-
void Student::InputInfo() //Student 类
InputInfo函数
{ char namestr[20];
//输入学生姓名临时存放
在namestr中
cout<<"请输入下一个学生的姓名:";
cin>>namestr;
name=new char[strlen(namestr)+1];
//为指针name申请地址
strcpy(name, namestr);
//将临时存放的姓名复制
到 name
cout<<"输入学生的学号:";
cin>>student_ID;
}
char * Student::getname() //Student类显示姓名函
数
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-80-
void UnderGraduate::InputInfo() //输入本科生信息的函
数
{Student::InputInfo();
StuNum++; //本科生总数加1
}
void UnderGraduate::input_credit()
//输入本科生选修的课程和学分
{char answer;
do{
cout<<"输入课程名称、学分、课程类别:必修/选修\n";
cin>>credit_hour[num].course>>credit_hour[num].h
our >>credit_hour[num].type;
num++;
cout<<"继续输入课程?\n";
cin>>answer;
}while(answer == 'y');
}
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-81-
int UnderGraduate::GetStuNum() //取得本科生人数
{return StuNum;
}
void UnderGraduate::displayInfo() //显示本科生信息
{
cout << "本科学生姓名:"<<name;
cout<<" 学号:"<<student_ID<<endl;
cout<<"课程名称 学分 课程类别 \n";
for(int i=0;i<num;i++)
cout<<left<<setw(10)<<credit_hour[i].course
<<setw(10)<<credit_hour[i].hour
<<setw(10)<<credit_hour[i].type<<endl;
}
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-82-
void Graduate::InputInfo()
{ char namestr[20]; //输入导师姓名临时存放在
namestr中
Student::InputInfo();
StuNum++;
cout<<"请输入导师姓名:";
cin>>namestr;
tutor=new char[strlen(namestr)+1]; //为指针tutor申
请地址
strcpy(tutor, namestr); //将临时存放的姓名
复制到 tutor
}
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-83-
void Graduate::input_credit()
//输入研究生选修课程和学分
{char answer;
do{
cout<<"输入课程名称、学分、课程类别:学位/任选\n";
cin>>credit_hour[num].course>>credit_hour[num].h
our >>credit_hour[num].type;
num++;
cout<<"还有课程要输入? ";
cin>>answer;
}while(answer == 'y');
}
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-84-
int Graduate::GetStuNum()
{return StuNum;
}
void Graduate::displayInfo() //显示研究生信息
{
cout << "研究生姓名:"<<name;
cout<<" 学号:"<<student_ID<<endl;
cout<<" 导师姓名:"<<tutor<<endl;
cout<<"课程名称 学分 课程类别 \n";
for(int i=0;i<num;i++)
cout<<left<<setw(10)<<credit_hour[i].course
<<setw(10)<<credit_hour[i].hour
<<setw(10)<<credit_hour[i].type<<endl;
}
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-85-
// 例11.8: student_app.cpp
#include <iostream>
using namespace::std;
#include "student.h" // 包含头文
件
Student **SelectStuType(Student *Array_ptr[][10]);
void creat_object(Student *Array_ptr[][10]);
void DisplayStu(Student *Array_ptr[][10]);
void InputCredit(Student *Array_ptr[][10]);
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-86-
void main()
{
UnderGraduate UnderStu[10];
Graduate GraduateStu[10];
Student *Stu_ptr[2][10]; //指针数组
for(int i=0;i<10;i++)
Stu_ptr[0][i]=&UnderStu[i];
//第0行存放UnderStu各元素地址
for(i=0;i<10;i++)
Stu_ptr[1][i]=&GraduateStu[i];
//第1行存放GraduateStu各元素地址
creat_object(Stu_ptr);
InputCredit(Stu_ptr);
DisplayStu(Stu_ptr);
} // 结束main函数
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-87-
Student **SelectStuType(Student *Array_ptr[][10])
{ char type;
Student **ptr = NULL;
do{
cout<<"请选择学生类型:";
cout<<"U:本科生;G:研究生 : ";
cin>>type;
switch (type)
{case 'U':
ptr=Array_ptr[0]; //指针数组第0行地址
break;
case 'G':
ptr=Array_ptr[1]; //指针数组第1行地址
break;
default:cout<<"类型错误,请重新选择\n";
}
}while(ptr==NULL);
return ptr; //返回指针数组的行地址
}
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-88-
void creat_object(Student *Array_ptr[][10])
{ char answer;
Student **row_ptr; //存放指针数组行地址
Student *ptr = NULL; //存放指针数组的元素值
cout<<"输入学生基本信息\n";
do{
row_ptr=SelectStuType(Array_ptr);
ptr=*(row_ptr + (row_ptr[0])->GetStuNum());
ptr->InputInfo(); //调用虚函数InputInfo
cout<<"继续输入学生基本信息(y/n)? ";
cin>>answer;
}while(answer=='y');
}
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-89-
void DisplayStu(Student *Array_ptr[][10])
{ char answer;
Student **row_ptr;
Student *ptr = NULL;
int StuNum;
cout<<"显示学生基本信息\n";
do{
row_ptr=SelectStuType(Array_ptr);
ptr=*row_ptr;
StuNum = ptr->GetStuNum();
//取得本科生或研究生人数
for(int i=0;i<StuNum;i++)
row_ptr[i]->displayInfo();
//调用虚函数displayInfo
cout<<"继续显示学生信息(y/n)? ";
cin>>answer;
}while(answer=='y');
}
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-90-
void InputCredit(Student *Array_ptr[][10])
{ char answer;
Student **row_ptr;
Student *ptr = NULL;
int StuNum;
cout<<"输入已选课程、学分、课程类型\n";
do{
row_ptr=SelectStuType(Array_ptr);
ptr=*row_ptr;
StuNum = ptr->GetStuNum();
for(int i=0;i<StuNum;i++)
{cout<<"输入"<<row_ptr[ i]->getname()<<"的课程
和学分:\n";
row_ptr[i]->input_credit();
}
cout<<"继续输入其他类别学生的课程和学分(y/n)? ";
cin>>answer;
}while(answer=='y');
}
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-91-
void InputCredit(Student *Array_ptr[][10])
{ char answer;
Student **row_ptr;
Student *ptr = NULL;
int StuNum;
cout<<"输入已选课程、学分、课程类型\n";
do{
row_ptr=SelectStuType(Array_ptr);
ptr=*row_ptr;
StuNum = ptr->GetStuNum();
for(int i=0;i<StuNum;i++)
{cout<<"输入"<<row_ptr[ i]->getname()<<"的课程
和学分:\n";
row_ptr[i]->input_credit();
}
cout<<"继续输入其他类别学生的课程和学分(y/n)? ";
cin>>answer;
}while(answer=='y');
}
11.6 模板
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-93-
11.6 模板
模板是C++中的通用程序模块。在这些程序
模块中有一些数据类型是不具体的,或者说
是抽象的。当这些抽象的数据类型更换为不
同的具体数据类型以后,就会产生一系列具
体的程序模块。
C++中的模块包括函数模板和类模板。
函数模板实例化后产生的函数,称为模板函
数。类模板实例化后产生的类,称为模板
类。
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-94-
11.6.1 函数模板
函数模板定义的基本格式 :
template <typename 参数化类型名>
函数返回类型 函数名(形式参数列表)
{函数体 }
? “template”是定义模板的关键字。
? 在一对尖括号<>内,关键字 “typename”后面声
明所使用的 “参数化类型名 ”。
? 关键字 “typename”可以用 “class”取代 。
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-95-
11.6.1 函数模板
模板的其余部分和一般的函数定义的格式完
全相同。只是在函数定义时可以使用参数化
类型来代表各种具体的数据类型。
参数化类型可以用于:
? 函数返回值类型;
? 函数参数表内形式参数的类型;
? 函数体内,自动变量的类型。
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-96-
例11.9 函数模板的定义和使用:定义和使用确定3个数
据的最大值函数模板。
#include<iostream>
using namespace::std;
template<typename T>
T max_value(T x,T y,T z)
//函数模板的定义:求x、y、z的最大值
{ T temp;
temp = x>y?x:y;
return temp>z?temp:z;
}
void main()
{ cout<<max_value(12,32,21)<<endl;
//用整数作实参调用函数模板
cout<<max_value('a','A','9')<<endl;
//用字符作实参调用函数模板
}
程序执行后,输出:
32
a
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-97-
11.6.1 函数模板
函数模板定义的一般格式
template <typename 参数化类型名1,
………typename 参数化类型名n>
函数返回类型 函数名(形式参数列表)
{函数体 }
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-98-
例11.10 编写一个函数模板,可以按指定的操作数类型
进行乘法。
// 例11.10 带有两个形式参数的函数模板
#include<iostream.h> //p10-.cpp
template<typename P1,typename P2>
P1 cal(P1 x, P2 y)
//函数模板有两个参数化类型名:P1和P2
{ return (x * (P1)y); } //按x类型进行乘
法
void main()
{ unsigned short w=230;
short z=150;
cout<<cal(w,z)<<endl; //按无符号数
相乘
cout<<cal(z,w)<<endl; //按有符号数
相乘
}
程序运行的结果是:
34500
-31036
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-99-
11.6.1 函数模板
带有确定类型的参数的函数模板
函数模板的形式参数表中除了使用参数化类
型名以外,还可以使用确定类型的参数。也
就是说,函数模板的参数表中,一定要包含
参数化类型名,但不一定都使用参数化类型
名。还可以根据需要,使用确定类型的参
数。如整型的参数、实型的参数,等等。
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-100-
例11.11 设计和编写一个通用的输入数组元素的函数模板。可以用
它来输入各种不同数据类型的数组。
#include<iostream.h> //p11-11.cpp
#include <typeinfo.h>
template <class Q1> //函数模板
void ArrayInput(Q1 array, int num)
{cout << "输入 "<<num<<"个"<<ty peid(Q1).name()<<'\b'<<"型数据
"<<endl;
for (unsigned j= 0; j < num; j++)
cin >> array[j]; //输入数组元素
}
void main()
{ int number;
float floatArray[4];
int intArray[3];
number=sizeof(floatArray)/sizeof(float);
ArrayInput(floatArray, number); //输入整型数组元素
number=sizeof(intArray)/sizeof(int);
ArrayInput(intArray, number ); //输入浮点型数组元
素
}
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-101-
11.6.1 函数模板
程序中使用了C++所定义的typeid运算符。它
可以在程序运行时,显示指定的数据的类
型。使用的格式是:
? typeid(表达式).name()
? 或者 typeid(类型标识符).name()
? 为了符合一般的阅读习惯,在程序中通过输出一
个 “退格 ”符,将 “*”覆盖了。最后看到的就是 “输
入X个XX型数据 ”。
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-102-
11.6.2 函数模板使用中的问题
用户定义的类取代参数化类型 :
? 在这种情况下,函数模板实例化后,在函数的表达
式中参与运算的就是类的对象。对象可以直接使用
的运算符只有赋值运算符 “=”。如果表达式中需要
对象进行其他的运算,就必须在相应的类的定义
中,对于要使用的运算符进行重载。
? 例如,在例11.9中,函数模板是求3个实参的最大
值,需要使用运算符 “>”。如果要用对象来作实
参,相应的类中要对 “>”运算符进行重载。
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-103-
例11.12 用例11.9的函数模板,求3个Circle类对象的最大值。
// 例11.12 对象作为函数模板的实参
#include<iostream.h> //p11_12.cpp
//using namespace::std;
template<typename T>
T max_value(T x,T y,T z)
//函数模板的定义:求x、y、z的最大值
{ T temp;
if(x>y) temp = x;
else temp = y;
if(z>temp) temp =z;
return temp;
}
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-104-
class Circle //Circle类的定义
{public:
friend ostream &operator<<( ostream &, Circle & );
//重载 “<<”运算符
Circle( int a = 0, int b = 0, double c = 0.0 )
{x = a; y = b; radius = c;
}
int operator>(Circle m2) //重载 “>”运算符
{if(radius>m2.radius)
return 1;
else return 0;
}
private:
int x,y; // 圆心座标
double radius; // 圆半径
}; // 类Circle定义结束
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-105-
ostream &operator<<( ostream &out, Circle &C1 )
{out<<"x="<<C1.x<<" y="<<C1.y;
out<<" radius="<<C1.radius;
return out;
}
void main()
{ Circle C1(2,3,5),C2(3,5,8),C3(3,2,6); //定义3个
Circle类对象
cout<<max_value(12,32,21)<<endl;
//用整数作实参调用函数模板
cout<<max_value('a','A','9')<<endl;
//用字符作实参调用函数模板
cout<<max_value(C1,C2,C3)<<endl;
//用对象作参数调用函数模板
}
程序运行结果是:
32
a
x=3 y=5 radius=8
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-106-
11.6.2 函数模板使用中的问题
函数模板不支持参数自动转换
对于例11.9的函数模板:
template <typename T>
T max_value(T x,T y,T z);
? 如果用以下的方式来调用:
cout<<max_value(12,3.2,21)<<endl;
在编译时会出现编译错误: “template parameter
'T' is ambiguous”
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-107-
11.6.2 函数模板使用中的问题
在定义指向一维数组的指针时,还必须指出一
维数组的大小。
声明指向一维数组的指针的格式如下:
<类型名> (*指针变量名)[一维数组大小];
例如,和图11.3中两个二维数组所对应的指向
一维数组的指针定义如下:
? char (*ptchb)[4], (*ptchc)[2];
? ptchb=B; ptchc=C;
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-108-
11.6.2 函数模板使用中的问题
要解决这样的问题,就需要函数模板和一般的
函数联合使用。即需要函数模板和一般的函数
的重载。
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-109-
11.6.3 重载函数模板
重载函数模板可以是函数模板和另一个参数数
目不同的函数模板的重载,也可以是函数模板
和非模板函数的重载。一般所说的函数模板重
载是指后一种情况。
? 函数模板和非模板函数重载,可以解决函数模板不
支持参数自动转换的问题。
? 当所使用的参数类型(不是类类型)不支持函数模
板中某些操作时,就需要为这样的参数专门编写相
应的非模板函数。和已经定义的函数模板形成重载
的关系。
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-110-
11.6.3 重载函数模板
例如,需要用例11.9的函数模板来比较3个结
构体变量,返回其中的最大值。由于结构体变
量不支持直接的比较操作 “>”。要实现这样的
功能,就要再写一个函数。
假定结构体的定义是:
struct course
{ char *name;
float score;
};
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-111-
11.6.3 重载函数模板
3个这样的结构体变量的比较函数如下:
course max_value(course s1,course s2,course s3)
{course s4;
if(s1.score>s2.score)
s4=s1;
else s4=s2;
if(s3.score>s4.score)
s4=s3;
return s4;
}
这样就形成了函数模板和非模板函数的重载:
template<typename T>
T max_value(T x,T y,T z);
course max_value(course s1,course s2,course s3);
11.6.4 类模板
类模板的定义
template <class 参数化类型名1, ……, class
参数化类型名n>
class 类名
{数据成员定义;
成员函数原型;
……
};
11.6.4 类模板
在模板类的外部定义类模板的成员函数时的格
式:
? 首先要使用类模板的头部,以表明类模板定义了几个
参数化类型名;
? 作用域运算符 “::”前面的类名用类模板名代替,而且
也要在尖括号中注明所有类模板的参数化类型名;
? 函数的参数表或者自动变量的定义,则是可以使用参
数化类型名,也可以不使用。
11.6.4 类模板
在类外部定义成员函数的格式如下:
template < class T1, ……., class Tn>
返回值类型 类模板名< T1, ……, Tn >::成员
函数名( 参数表 )
{函数体}
11.6.4 类模板
类模板的实例化
函数模板的实例化和函数调用是一起完成的。类
模板的实例化则是对象的实例化一起完成的:
? 在类模板后面的尖括号中,表明取代参数化类型的实
际类型名;
? 再写对象名和构造对象所需要的实参数。
一般化的格式如下:
? 类模板名<类型1, ……, 类型n> 对象名(实参数);
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-116-
例11.13 类模板的定义和实例化的示例。
// 例11.13 类模板
#include <iostream>
using namespace std;
template <class T1, class T2> class MyClass
{ T1 x;
T2 y;
public:
MyClass( T1 a, T2 b );
void display( );
};
template < class T1, class T2>
MyClass< T1,T2 >::MyClass( T1 a, T2 b )
{ x = a;
y = b;
}
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-117-
template <class T1, class T2>
void MyClass< T1, T2 >::display( )
{ cout<<x<<endl;
cout<<y<<endl;
}
void main()
{ MyClass<int,float> ss(6,6.6);
ss.display();
MyClass<char,char *> ww('x',"A string");
ww.display();
}
11.6 类模板
示例是带有两个参 数化类型的类模板。在类外
部定义成员函数时 ,先要声明使用了两个参数
化类型的名字,然 后,在类模板名后面注明这
个模板要使用这些 名字。再具体的定义成员函
数。
类模板进行两次实 例化,分别输出不同类型的
数据:第一次输出 整型和实型,第二次输出字
符和字符串。
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-119-
11.6 类模板
带有确定类型参数的类模板
类模板声明时在尖 括号内除了声明要使用的参
数化类型名外,还可以包括确定类型的类型
名。如:
template <class T, int i> class MyStack
在类模板实例化和声明对象时,这个参数i要
用具体的整型值来代替,如:
MyStack<int,5> ss;
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-120-
11.6 类模板
例11.14 用类模板来定义栈类。栈是一种先进
后出的数据区。各 种数据都可以定义相应的栈
类。使用类模板后 ,就不需要定义各种不同的
栈类了。
用数组作为栈的存 储体。在创建栈对象时,可
以指定栈的大小。安排一个栈指针top指向栈
顶。定义两个栈的基本操作:进栈(p ush)和
出栈(pop)。定义相应的类模板,并测试其
功能。
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-121-
//例11.14 栈类模板
//例11.14:11_14.cpp
#include <iostream>
using namespace std;
template <class T, int i> //类模板定
义
class MyStack
{ T StackBuffer[i];
int size;
int top;
public:
MyStack( void ) : size( i ) {top = i;};
void push( const T item );
T pop( void );
};
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-122-
template <class T, int i> //push成员函数定
义
void MyStack< T, i >::push( const T item )
{ if( top >0 )
StackBuffer[--top] = item;
else
cout<<"堆栈溢出错误."<<endl;
return;
}
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-123-
template <class T, int i> //pop成员函数定
义
T MyStack< T, i >::pop( void )
{ if( top < i )
return StackBuffer[top++];
else
cout<< "堆栈无数据可以出栈."<<endl;
exit(0);
}
2005-4-28
北京邮电大学电信工程学院计算机技术中心
-124-
void main() //类模板测试程序
{MyStack<int,5> ss;
ss.push(4);
cout<<ss.pop()<<endl;
MyStack<char*,5> ww;
ww.push("China");
cout<<ww.pop()<<endl;
cout<<ww.pop()<<endl;
}
程序运行结果是:
4
China
堆栈无数据可以出栈.
总结
多态性是面向对象程序设计最重要的特
点之一。本章介绍了多态性中最重要的
两个表现:运行多态和参数多态。参数
多态就是模板的使用。
运行多态的表现就是:
? 一种形态的语句:通过基类指针访问基类和
派生类的同名函数;
? 多种条件的执行:用不同派生类对象的地址
初始化这个基类指针;
总结
模板分为函数模板和类模板,定义的方
式基本是相同的。在使用上稍有差别:
函数模板通过函数参数的虚实结合就能
得到具体的模板函数。
而使用类模板时,要在类模板名后面具
体说明模板类的实际数据类型。