2009-7-29 1
C++大学基础教程第 11章 多态性
2009-7-29 -2-
多态性( Polymorphism) 是面向对象程序设计的主要特征之一。
多态性对于软件功能的扩展和软件重用都有重要的作用。是学习面向对象程序设计必须要掌握的主要内容之一。
2009-7-29 -3-
第十一章 多态性
11.1 多态性的概念
11.2 继承中的静态联编
11.3 虚函数和运行时的多态
11.4 纯虚函数和抽象类
11.5 继承和派生的应用
11.6 模板
2009-7-29 4
11.1 多态性的概念
2009-7-29 -5-
11.1.1面向对象程序设计中多态的表现总的来说,不同对象对于相同的消息有不同的响应,就是面向对象程序设计中的多态性。
具体在程序中,多态性有两种表现的方式:
同一个对象调用名字相同、但是参数不同的函数,
表现出不同的行为。在同一个类中定义的重载函数的调用,属于这种情况。
不同的对象调用名字和参数都相同的函数,表现出不同的行为。在派生类的应用中,经常会看到这样的调用。
2009-7-29 -6-
11.1.1面向对象程序设计中多态的表现面向对象程序设计中多态性表现为以下几种形式:
重载多态:通过调用相同名字的函数,表现出不同的行为。运算符重载也是一种重载多态。
运行多态:通过基类的指针,调用不同派生类的同名函数,表现出不同的行为。许多面向对象程序设计的书籍中所说的多态性,就是这种多态。
模板多态,也称为参数多态:通过一个模板,得到不同的函数或不同的类。这些函数或者类具有不同的特性和不同的行为。
2009-7-29 -7-
11.1.2 多态的实现:联编一个具有多态性的程序语句,在执行的时候,必须确定究竟是调用哪一个函数。
也就是说,在执行的时候调用哪个函数是唯一地确定的。确定具有多态性的语句究竟调用哪个函数的过程称为联编
( Binding),有的资料也翻译成,绑定,。
2009-7-29 -8-
11.1.2 多态的实现:联编联编有两种方式:静态联编和动态联编。
在源程序编译的时候就能确定具有多态性的语句调用哪个函数,称为静态联编。
对于重载函数的调用就是在编译的时候确定具体调用哪个函数,所以是属于静态联编。
2009-7-29 -9-
11.1.2 多态的实现:联编动态联编则是必须在程序运行时,才能够确定具有多态性的语句究竟调用哪个函数。用动态联编实现的多态,也称为运行时的多态。以上所述的几种多态形式中,只有运行多态是属于动态联编。以后我们会看到,在一个循环中的同一个语句,第一次循环时调用的是一个函数,
第二次循环时调用的是另一个函数。这种结果,
程序不运行是看不到的。所以称为动态联编。
2009-7-29 10
11.2 继承中的静态联编
2009-7-29 -11-
11.2.1派生类对象调用同名函数在派生类中可以定义和基类中同名的成员函数。
这是对基类进行改造,为派生类增加新的行为的一种常用的方法。
通过不同的派生类的对象,调用这些同名的成员函数,实现不同的操作,也是多态性的一种表现。
在程序编译的时候,就可以确定派生类对象具体调用哪个同名的成员函数。这是通过静态联编实现的多态。
2009-7-29 -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的定义
2009-7-29 -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定义结束
#endif
派生类 Circle的定义派生类 Rectangle的定义
2009-7-29 -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函数定义包含头文件基类成员函数的定义
2009-7-29 -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类成员函数的定义
2009-7-29 -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类成员函数的定义
2009-7-29 -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
2009-7-29 -18-
11.2.1派生类对象调用同名函数对于派生类对象调用成员函数,可以有以下的结论:
派生类对象可以直接调用本类中与基类成员函数同名的函数,不存在二义性;
在编译时就能确定对象将调用哪个函数,属于静态联编,不属于运行时的多态。
2009-7-29 -19-
11.2.2通过基类指针调用同名函数从继承的角度来看,派生类对象是基类对象的一个具体的特例。或者说,派生类对象是某一种特定类型的基类对象。
例如,Circle类是 Shape类的公有继承,,圆,是
,图形,的一种特例。或者说,圆是一种特定的图形,具有图形的基本特征。
但是,这种关系不是可逆的。不可以说基类的对象具有派生类对象的特征,基类对象也不是派生类对象的一个特例。
2009-7-29 -20-
11.2.2通过基类指针调用同名函数在关于基类对象和派生类对象的操作上,可以允许以下的操作:
派生类对象可以赋值给基类对象;
派生类对象的地址可以赋值给基类对象的指针。或者说,可以用派生类对象的地址初始化基类对象的指针;
可以将基类对象的引用,定义为派生类对象的别名,
或者说,用派生类对象初始化基类的引用。
通过派生类对象的地址初始化的基类对象的指针,
可以访问基类的公有成员,也可以访问和基类成员函数同名的函数。
2009-7-29 -21-
11.2.2通过基类指针调用同名函数以下这些操作是不可以进行的:
不可以将基类对象赋值给派生类对象;
不可以用基类对象的地址初始化派生类对象的指针;
不可以将派生类对象的引用定义为基类对象的别名;
不可以通过用派生类对象初始化的基类对象的指针,访问派生类新增加的和基类公有成员不重名的公有成员。
2009-7-29 -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 对象初始化 shape_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 对象初始化 shape_ptr指针访问的 getArea函数是基类的 getArea函数,面积是 0
rectangle 对象初始化 shape_ptr指针访问的 getArea函数是基类的 getArea函数,面积是 0
2009-7-29 -23-
11.2.2通过基类指针调用同名函数程序运行结果表明:
确实可以用派生类对象的地址初始化基类对象的指针;
通过用派生类对象地址初始化的基类对象指针,只能调用基类的公有成员函数。在以上例子中,就是调用基类的 getArea函数,而不是派生类的 getArea
函数。
这种调用关系的确定,也是在编译的过程中完成的,
属于静态联编,而不属于运行时的多态。
2009-7-29 24
11.3 虚函数和运行时的多态
2009-7-29 -25-
赋值兼容原则一个公有派生类的对象在使用上可以被当作基类的对象,反之则禁止。具体表现在:
派生类的对象可以被赋值给基类对象。
派生类的对象可以初始化基类的引用。
指向基类的指针也可以指向派生类。
派生类成员的标识与访问
2009-7-29 -26-
11.3 虚函数和运行时的多态通过指向基类的指针访问基类和派生类的同名函数,是实现运行时的多态的必要条件,但不是全部条件。
除此以外,还必须将基类中的同名函数定义为 虚函数 。
2009-7-29 -27-
11.3.1 虚函数虚函数可以在类的定义中声明函数原型的时候来说明,格式如下:
virtual <返回值类型 > 函数名 (参数表 );
在函数原型中声明函数是虚函数后,具体定义这个函数时就不需要再说明它是虚函数了。
如果在基类中直接定义同名函数,定义虚函数的格式是:
virtual <返回值类型 > 函数名 (参数表 )
{<函数体 >}
2009-7-29 -28-
11.3.1 虚函数基类中的同名函数声明或定义为虚函数后,派生类的同名函数无论是不是用 virtual来说明,
都将自动地成为虚函数。从程序可读性考虑,
一般都会在这些函数的声明或定义时,用
virtual来加以说明。
只要对例 11.2中的头文件稍加修改,也就是将基类和派生类中的 getArea函数都声明为虚函数,在重新编译和运行程序,就可以得到运行时的多态的效果。
2009-7-29 -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的定义
2009-7-29 -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定义结束
#endif
派生类 Circle的定义派生类 Rectangle的定义
2009-7-29 -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函数定义包含头文件基类成员函数的定义
2009-7-29 -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类成员函数的定义
2009-7-29 -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类成员函数的定义
2009-7-29 -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
2009-7-29 -35-
11.3.1 虚函数这个结果和例 11.2的结果大不相同。同样的
shape_ptr->getArea()函数调用,当 shape_ptr指针中是 Circle类对象地址时,访问的是 Circle类的 getArea函数。而 shape_ptr指针中是 Rectangle
类对象的地址时,访问的是 Rectangle类的
getArea函数。
这种方式的函数调用,在编译的时候是不能确定具体调用哪个函数的。只有程序运行后,才能知道指针 shape_ptr中存放的是什么对象的地址,然后再决定调用哪个派生类的函数。是 一种运行时决定的多态性 。
2009-7-29 -36-
11.3.1 虚函数要实现运行时的多态,需要以下条件:
必须通过指向基类对象的指针访问和基类成员函数同名的派生类成员函数;
或者用派生类对象初始化的基类对象的引用访问和基类成员函数同名的派生类成员函数;
派生类的继承方式必须是公有继承;
基类中的同名成员函数必须定义为虚函数。
2009-7-29 -37-
11.3.2 虚函数的使用虚函数必须正确的定义和使用。否则,即使在函数原型前加了 virtual的说明,也可能得不到运行时多态的特性。
必须首先在基类中声明虚函数。在多级继承的情况下,也可以不在最高层的基类中声明虚函数。例如在第二层定义的虚函数,可以和第三层的虚函数形成动态联编。但是,一般都是在最高层的基类中首先声明虚函数。
2009-7-29 -38-
11.3.2 虚函数的使用
基类和派生类的同名函数,必须函数名、返回值、参数表全部相同,才能作为虚函数来使用。
否则,即使函数用 virtual来说明,也不具有虚函数的行为。
静态成员函数不可以声明为虚函数。构造函数也不可以声明为虚函数。
析构函数可以声明为虚函数,即可以定义虚析构函数。
2009-7-29 -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被调用 ";}
};
有虚函数的基类派生类
2009-7-29 -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()也是静态联编。
2009-7-29 -41-
11.3.3 虚析构函数如果用 动态创建 的派生类对象的地址初始化基类的指针,创建的过程不会有问题:仍然是先调用基类构造函数,再执行派生类构造函数。
但是,在用 delete运算符删除这个指针的时候,
由于指针是指向基类的,通过静态联编,只会调用基类的析构函数,释放基类成员所占用的空间。而 派生类成员所占用的空间将不会被释放 。
2009-7-29 -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类析构函数被调用
2009-7-29 -43-
11.3.3 虚析构函数为了解决派生类对象释放不彻底的问题,必须将基类的析构函数定义为虚析构函数。格式是在析构函数的名字前添加 virtual关键字。函数原型如下:
virtual ~Shape();
此时,无论派生类析构函数是不是用 virtual
来说明,也都是虚析构函数。
再用 delete shape_ptr来释放基类指针时,就会通过动态联编调用派生类的析构函数。
2009-7-29 -44-
11.3.3 虚析构函数将例 11.5程序中的 ~Shape析构函数作以上修改后,运行的结果将是:
Shape类构造函数被调用
Circle类构造函数被调用
Circle类析构函数被调用
Shape类析构函数被调用
2009-7-29 -45-
11.4 纯虚函数和抽象类
2009-7-29 -46-
11.4 纯虚函数和抽象类在前面的几个例子中,基类 Shape本身并不是一个具体的,形状,的抽象,而是各种实际的,形状,的抽象。
在 C++中,对于那些在基类中不需要定义具体的行为的函数,可以定义为 纯虚函数。
对于那些只是反映一类事物公共特性的类,在 C++中可以定义为,抽象类,。
2009-7-29 -47-
11.4 纯虚函数和抽象类纯虚函数声明的格式是:
virtual <返回值类型 > 函数名 (参数表 ) = 0;
纯虚函数的声明和使用有以下的特点:
纯虚函数一定是在基类中声明的。
在多级继承的情况下,纯虚函数除了在最高层基类中声明外,也可以在较低层的基类中声明。
纯虚函数是没有函数体的。函数体是用,= 0” 来代替了。
纯虚函数是不可以被调用的。凡是需要被调用的函数都不可以声明为纯虚函数。
2009-7-29 -48-
11.4 纯虚函数和抽象类抽象类的定义是基于纯虚函数的。
凡是带有一个或几个纯虚函数的类,就是抽象类。
抽象类定义的一般形式是:
class 类名
{public:
virtual <返回值类型 > 函数名 (参数表 ) = 0;
其他函数的声明 ;
……,
};
2009-7-29 -49-
11.4 纯虚函数和抽象类抽象类的定义和使用具有以下的特点:
抽象类是不可以实例化的,也就是不可以定义抽象类的对象。
但是,可以定义抽象类的指针和抽象类的引用。目的是通过这些指针或引用访问派生类的虚函数,实现运行时的多态。
如果抽象类的派生类中没有具体实现纯虚函数的功能,这样的派生类仍然是抽象类。
抽象类中除了纯虚函数外,还可以定义其他的非纯虚函数。
2009-7-29 -50-
11.4 纯虚函数和抽象类虚函数、纯虚函数、多态性在面向对象程序设计中有很大的作用。可以增强程序的通用性、
可扩展性和灵活性。
2009-7-29 -51-
例 11.6 编写一个程序,可以动态的创建 Circle
类或者 Rectangle类的对象,并且显示所创建对象的面积 。 在编程中注意使用多态性 。
动态创建 Circlae类或者 Rectangle类对象动态创建 Circlae类或者 Rectangle类对象动态创建 Circlae类或者 Rectangle类对象希望用 3个函数实现题目的功能。如果不使用运行时的多态,很难实现
2009-7-29 -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的定义
2009-7-29 -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定义结束
#endif
派生类 Circle的定义派生类 Rectangle的定义
2009-7-29 -54-
// 例 11.6,shape2.cpp
#include <iostream>
using namespace std;
#include "shape2.h" // 包含头文件
void Shape::print() const
{
cout<<"Base class Object"<<endl;
} //Shape类 print函数定义包含头文件基类成员函数的定义
2009-7-29 -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类成员函数的定义
2009-7-29 -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类成员函数的定义
2009-7-29 -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的主函数
2009-7-29 -58-
void creat_object(Shape **ptr)
{ char type;
*ptr = NULL;
do{
cout<<"创建对象 。 c:Circle类对象; 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函数
2009-7-29 -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
2009-7-29 -60-
11.4 纯虚函数和抽象类这个程序中,使用了本章所介绍的虚函数、纯虚函数、虚析构函数、基类指针访问派生类对象等技术,实现了运行时的多态。
程序具有很好的可扩展性。如果需要增加新的派生类,当然要增加和派生类定义有关的代码,
和创建对象所需要的语句。但是,对于函数
display_area,display_object等函数 都不用修改。
2009-7-29 -61-
11.4 纯虚函数和抽象类这个例子还显示了:抽象类中可以为各派生类定义一些通用的接口。这些通用的接口就是抽象类中的纯虚函数。新增加的派生类的对象,
都可以使用这样的通用接口,表现派生类对象的行为特性。
2009-7-29 -62-
11.4 纯虚函数和抽象类例 11.7 在例 11.6的基础上,现在需要增加一个 Rectangle的派生类,Cube,也就是立方体类。任意创建 Circle,Rectangle,Cube类的对象,并显示对象的面积。
现在需要做的只有两件事:
定义新的 Cube类;
在 create_object函数中增加创建 Cube类对象的内容。
其他函数都不需要修改。
2009-7-29 -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新增加的类定义和头文件
2009-7-29 -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函数只是多包含了一个头文件
2009-7-29 -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;
}
2009-7-29 -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函数多增加了一个分支
2009-7-29 -67-
11.4 纯虚函数和抽象类程序执行过程:
创 建 对 象 。 请 选 择,c:Circle 类 对 象 ;
r:Rectangle类对象; u:Cube类对象
u
请输入立方体的长,宽,高,4 5 6
显示所创建对象的面积,调用的是
Cube类的 getArea函数,还要调用 Rectangle类的 getArea函数,面积是 120
………
2009-7-29 -68-
11.5 继承和派生的应用
2009-7-29 -69-
11.5 继承和派生的应用例 11.8 设计并实现一个学生学分管理系统。
主要功能包括:
管理的对象:大学本科生和研究生;
可以增加学生和学生的基本信息,包括姓名,学号,
研究生还要包括导师姓名;
可以相加所选的课程、学分、课程类别,本科生的课程类别是必修和选修,研究生的课程类别是学位课还是任选课;
显示学生的基本信息和课程 /学分信息;
系统要有良好的可扩展性。
2009-7-29 -70-
11.5 继承和派生的应用面向对象程序设计要考虑:
对象的抽象,即类的定义;
对于本例来说,可以有一个基类 Student,反映学生的基本信息和基本行为;有两个派生类:
UnderGraduate( 本科生)和 Graduate( 研究生)。
数据结构。也就是用什么方式存储对象。也包括用什么方式存储对象的数据成员。
由于学生不止一个,所以要用数组或者链表来存放学生对象。为简单起见,使用两个对象数组,
UnderStu[]存放 UnderGraduate对象,
GraduateStu[]存放 Graduate对象。
2009-7-29 -71-
11.5 继承和派生的应用为了解决创建对象存储到数组中去,类的数据成员中需要定义一个静态数据成员 StuNum,用来跟踪创建对象的数目,同时也是决定存放到数组的数组元素的序号。
采用类似于例 11.6的方法,设计 3个函数来完成程序的功能:
创建对象,creat_object(Stu_ptr);
输入学分,InputCredit(Stu_ptr);
显示信息,DisplayStu(Stu_ptr);
其中 Stu_ptr是指向对象的指针。
2009-7-29 -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];
2009-7-29 -73-
11.5 继承和派生的应用另外定义一个函数 SelectStuType,函数的返回值是指针数组的行地址:需要访问 UnderStu对象数组时,返回指针数组第 0行的地址,需要访问
GraduateStu对象数组时,返回指针数组第 1行的地址。函数的原型是:
Student **SelectStuType(Student *Array_ptr[][10]);
注意返回值的类型是 Student **,后一个,*” 表示返回的是指针,而,Student *”表示指针所指向的是
Student类对象的地址。
只要通过简单的运算,就可以访问某一行的某个元素。
2009-7-29 -74-
// 例 11.8 简单信息管理系统
// 例 11.8,student.h
#ifndef SHAPE_H
#define SHAPE_H
struct credit //课程 _学分结构体定义
{char course[20]; //课程名
int hour; //学分
char type[5]; //课程类型
};
用结构体表示学生的学分构成
2009-7-29 -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; //显示学生信息
virtual int GetStuNum()=0; //学生人数
};
基类 Student的定义
2009-7-29 -76-
class UnderGraduate,public Student
{ public:
virtual void InputInfo(); //输入学生基本信息
virtual void input_credit(); //输入课程和学分
virtual void displayInfo(); //显示学生信息
int GetStuNum();
private:
static int StuNum; //本科学生人数
}; // 派生类 UnderGraduate定义结束派生类
UnderGraduate
的定义
2009-7-29 -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
的定义
2009-7-29 -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
}
2009-7-29 -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类显示姓名函数
{return name;
}
2009-7-29 -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].hour
>>credit_hour[num].type;
num++;
cout<<"继续输入课程? \n";
cin>>answer;
}while(answer == 'y');
}
2009-7-29 -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;
}
2009-7-29 -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
}
2009-7-29 -83-
void Graduate::input_credit()
//输入研究生选修课程和学分
{char answer;
do{
cout<<"输入课程名称,学分,课程类别:学位 /任选 \n";
cin>>credit_hour[num].course>>credit_hour[num].hour
>>credit_hour[num].type;
num++;
cout<<"还有课程要输入? ";
cin>>answer;
}while(answer == 'y');
}
2009-7-29 -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;
}
2009-7-29 -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]);
2009-7-29 -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函数
2009-7-29 -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; //返回指针数组的行地址
}
2009-7-29 -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');
}
2009-7-29 -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');
}
2009-7-29 -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');
}
2009-7-29 -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');
}
2009-7-29 -92-
11.6 模板
2009-7-29 -93-
11.6 模板模板是 C++中的通用程序模块。在这些程序模块中有一些数据类型是不具体的,或者说是抽象的。当这些抽象的数据类型更换为不同的具体数据类型以后,就会产生一系列具体的程序模块。
C++中的模块包括函数模板和类模板。
函数模板实例化后产生的函数,称为模板函数。类模板实例化后产生的类,称为模板类。
2009-7-29 -94-
11.6.1 函数模板函数模板定义的基本格式,
template <typename 参数化类型名 >
函数返回类型 函数名 (形式参数列表 )
{函数体 }
,template”是定义模板的关键字。
在一对尖括号 <>内,关键字,typename”后面声明所使用的,参数化类型名,。
关键字,typename”可以用,class”取代 。
2009-7-29 -95-
11.6.1 函数模板模板的其余部分和一般的函数定义的格式完全相同。只是在函数定义时可以使用参数化类型来代表各种具体的数据类型。
参数化类型可以用于:
函数返回值类型;
函数参数表内形式参数的类型;
函数体内,自动变量的类型。
2009-7-29 -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
2009-7-29 -97-
11.6.1 函数模板函数模板定义的一般格式
template <typename 参数化类型名 1,
……… typename 参数化类型名 n>
函数返回类型 函数名 (形式参数列表 )
{函数体 }
2009-7-29 -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
2009-7-29 -99-
11.6.1 函数模板带有确定类型的参数的函数模板函数模板的形式参数表中除了使用参数化类型名以外,还可以使用确定类型的参数。也就是说,函数模板的参数表中,一定要包含参数化类型名,但不一定都使用参数化类型名。还可以根据需要,使用确定类型的参数。
如整型的参数、实型的参数,等等。
2009-7-29 -100-
例 11.11 设计和编写一个通用的输入数组元素的函数模板 。 可以用它来输入各种不同数据类型的数组 。
#include<iostream.h> //p11-11.cpp
#include <typeinfo.h>
template <class Q1> //函数模板
void ArrayInput(Q1 array,int num)
{cout << "输入 "<<num<<"个 "<<typeid(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 ); //输入浮点型数组元素
}
2009-7-29 -101-
11.6.1 函数模板程序中使用了 C++所定义的 typeid运算符。它可以在程序运行时,显示指定的数据的类型。
使用的格式是:
typeid(表达式 ).name()
或者 typeid(类型标识符 ).name()
为了符合一般的阅读习惯,在程序中通过输出一个,退格,符,将,*” 覆盖了。最后看到的就是,输入 X个 XX型数据,。
2009-7-29 -102-
11.6.2 函数模板使用中的问题用户定义的类取代参数化类型,
在这种情况下,函数模板实例化后,在函数的表达式中参与运算的就是类的对象。对象可以直接使用的运算符只有赋值运算符,=” 。如果表达式中需要对象进行其他的运算,就必须在相应的类的定义中,对于要使用的运算符进行重载。
例如,在例 11.9中,函数模板是求 3个实参的最大值,需要使用运算符,>” 。如果要用对象来作实参,相应的类中要对,>” 运算符进行重载。
2009-7-29 -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;
}
2009-7-29 -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定义结束
2009-7-29 -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
2009-7-29 -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”
2009-7-29 -107-
11.6.2 函数模板使用中的问题在定义指向一维数组的指针时,还必须指出一维数组的大小。
声明指向一维数组的指针的格式如下:
<类型名 > (*指针变量名 )[一维数组大小 ];
例如,和图 11.3中两个二维数组所对应的指向一维数组的指针定义如下:
char (*ptchb)[4],(*ptchc)[2];
ptchb=B; ptchc=C;
2009-7-29 -108-
11.6.2 函数模板使用中的问题要解决这样的问题,就需要函数模板和一般的函数联合使用。即需要函数模板和一般的函数的重载。
2009-7-29 -109-
11.6.3 重载 函数模板重载函数模板可以是函数模板和另一个参数数目不同的函数模板的重载,也可以是函数模板和非模板函数的重载。一般所说的函数模板重载是指后一种情况。
函数模板和非模板函数重载,可以解决函数模板不支持参数自动转换的问题。
当所使用的参数类型(不是类类型)不支持函数模板中某些操作时,就需要为这样的参数专门编写相应的非模板函数。和已经定义的函数模板形成重载的关系。
2009-7-29 -110-
11.6.3 重载 函数模板例如,需要用例 11.9的函数模板来比较 3个结构体变量,返回其中的最大值。由于结构体变量不支持直接的比较操作,>” 。要实现这样的功能,就要再写一个函数。
假定结构体的定义是:
struct course
{ char *name;
float score;
};
2009-7-29 -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);
2009-7-29 -112-
11.6.4 类模板类模板的定义
template <class 参数化类型名 1,……,
class 参数化类型名 n>
class 类名
{数据成员定义 ;
成员函数原型 ;
……
};
2009-7-29 -113-
11.6.4 类模板在模板类的外部定义类模板的成员函数时的格式:
首先要使用类模板的头部,以表明类模板定义了几个参数化类型名;
作用域运算符,,:” 前面的类名用类模板名代替,而且也要在尖括号中注明所有类模板的参数化类型名;
函数的参数表或者自动变量的定义,则是可以使用参数化类型名,也可以不使用。
2009-7-29 -114-
11.6.4 类模板在类外部定义成员函数的格式如下:
template < class T1,……,,class Tn>
返回值类型 类模板名 < T1,……,Tn >::成员函数名 ( 参数表 )
{函数体 }
2009-7-29 -115-
11.6.4 类模板类模板的实例化函数模板的实例化和函数调用是一起完成的。
类模板的实例化则是对象的实例化一起完成的:
在类模板后面的尖括号中,表明取代参数化类型的实际类型名;
再写对象名和构造对象所需要的实参数。
一般化的格式如下:
类模板名 <类型 1,……,类型 n> 对象名 (实参数 );
2009-7-29 -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;
}
2009-7-29 -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();
}
2009-7-29 -118-
11.6 类模板示例是带有两个参数化类型的类模板 。 在类外部定义成员函数时,先要声明使用了两个参数化类型的名字,然后,在类模板名后面注明这个模板要使用这些名字 。 再具体的定义成员函数 。
类模板进行两次实例化,分别输出不同类型的数据:第一次输出整型和实型,第二次输出字符和字符串 。
2009-7-29 -119-
11.6 类模板带有确定类型参数的类模板类模板声明时在尖括号内除了声明要使用的参数化类型名外,还可以包括确定类型的类型名 。
如:
template <class T,int i> class MyStack
在类模板实例化和声明对象时,这个参数 i要用具体的整型值来代替,如:
MyStack<int,5> ss;
2009-7-29 -120-
11.6 类模板例 11.14 用类模板来定义栈类 。 栈是一种先进后出的数据区 。 各种数据都可以定义相应的栈类 。 使用类模板后,就不需要定义各种不同的栈类了 。
用数组作为栈的存储体 。 在创建栈对象时,可以指定栈的大小 。 安排一个栈指针 top指向栈顶 。 定义两个栈的基本操作:进栈 ( push) 和出栈 ( pop) 。 定义相应的类模板,并测试其功能 。
2009-7-29 -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 );
};
2009-7-29 -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;
}
2009-7-29 -123-
template <class T,int i> //pop成员函数定义
T MyStack< T,i >::pop( void )
{ if( top < i )
return StackBuffer[top++];
else
cout<< "堆栈无数据可以出栈,"<<endl;
exit(0);
}
2009-7-29 -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
堆栈无数据可以出栈,
2009-7-29 -125-
总结多态性是面向对象程序设计最重要的特点之一 。 本章介绍了多态性中最重要的两个表现:运行多态和参数多态 。 参数多态就是模板的使用 。
运行多态的表现就是:
一种形态的语句:通过基类指针访问基类和派生类的同名函数;
多种条件的执行:用不同派生类对象的地址初始化这个基类指针;
2009-7-29 -126-
总结模板分为函数模板和类模板,定义的方式基本是相同的 。 在使用上稍有差别:
函数模板通过函数参数的虚实结合就能得到具体的模板函数 。
而使用类模板时,要在类模板名后面具体说明模板类的实际数据类型 。