可能会遇到这样的问题:希望派生类和基类在同一个方法上的行为是不同的。我们称这种行为是多态(多种形态)。有两种机制可以实现多态公有继承。
- 派生类中重新定义基类的方法
- 虚方法
写一个班级类,记录每个同学的成绩以及记录班级的平均成绩;
写一个班级类的派生类,特有数据是最高分和最低分,平均成绩的算法是剔除最高分和最低分
/类声明
class Class
{
enum{MAX=5};
private:
double score[MAX];
double average;
public:
Class();
Class(const double arr[]);
double max();
double min();
double aver() {return average;}
virtual void show_s() const;
virtual void show_a() ;
virtual ~Class(){}
} ;
class Pclass : public Class
{
double max;
double min;
public:
Pclass();
Pclass(Class& c);
Pclass(const double d[]);
virtual void show_s() const;
virtual void show_a() ;
};
程序解析:
- 首先score数组不能直接写5为参数,必须枚举或静态常量
- 基类和派生类有同名原型(比如show_s)表明将有2个独立方法定义。基类版本限定名:
Class::show_s()
,派生类的限定名:Pclass::show_s()
,程序将根据使用的对象类型来确定使用哪个方法。- 也可以在基类方法声明前写上virtual,派生类自动生成virtual,所以派生类中可写可不写。将根据引用或指针指向的对象类型来确定使用哪种方法。
如果没有virtual,将根据引用或指针类型选择方法。- 上面是三种选择方法:
- 直接写同名方法的(不过有没有virtual),根据对象类型
比如Class c1
是Class对象,Pclass c2
是Pclass对象- 直接写同名方法的(没有virtual),根据指针或引用类型
比如Class c3 , Pclass c4; Class& p1 = c3; Class& p2 = c4; p1.show_s() /将使用Class::show_s() p2.show_s() /将使用Class::show_s()
因为指针和引用都是Class类型
- 方法前带有virtual的,根据指针或引用所指对象(代码接上)
p1.show_s() //将使用Class::show_s() p2.show_s() //将使用Pclass::show_s()
因为p1是Class方法,p2是Pclass方法
- 在基类里还要写上返回最大值、最小值、平均值的函数,因为派生类不能直接访问基类的私有数据,必须使用基类的共有方法才能访问数据,访问方式取决于方法。
- 派生类构造函数在初始化基类数据时,使用成员初始化列表语句,就是在成员初始化列表里调用基类的构造函数.
- 非构造函数不能使用成员初始化列表语句,派生类方法可以调用公有的基类方法,如果是多态方法,要加上作用域解析运算符,如果不加的话程序会误以为调用的方法可能是本类的,会出现递归的错误。如果不是多态方法,可以不使用。
/源代码文件
#include"class.h"
#include<iostream>
Class::Class():average(0)
{
for(int i=0;i<5;i++)
score[i]=0;
}
Class::Class(const double arr[])
{
double total=0;
for(int i=0;i<5;i++)
{
score[i]=arr[i];
total+=score[i];
}
average = total/5;
}
double Class::max()
{
double m=0;
for(int i=0;i<5;i++)
{
if(score[i]>m)
m=score[i];
}
return m;
}
double Class::min()
{
double m=100;
for(int i=0;i<5;i++)
{
if(score[i]<m)
m=score[i];
}
return m;
}
void Class::show_s() const{
std::cout<<"班级的分数:\n";
for(int i=0;i<5;i++)
{
std::cout<<i+1<<": "<<score[i]<<"\n";
}
}
void Class::show_a()
{
std::cout<<"班级平均分数(未剔除最高最低分):\n"<<average<<"\n";
}
Pclass::Pclass(Class& c):Class(c)
{
max = c.max();
min = c.min();
}
Pclass::Pclass(const double d[]):Class(d)
{
max = Class::max();
min = Class::min();
}
Pclass::Pclass():Class()
{
max=min=0;
}
void Pclass::show_s() const
{
using std::cout;
Class::show_s();
cout<<"最高分:\n"<<max;
cout<<"\n最低分:\n"<<min<<"\n";
}
void Pclass::show_a()
{
using std::cout;
double aver_edit,temp ;
temp = Class::aver();
aver_edit = (temp*5 - max - min)/3;
cout<<"平均分数(剔除最高最低分):\n"<<aver_edit<<"\n";
}
程序解析:
- Class类就不说了,都没什么难度
- Pclass参数为Class引用的构造函数:
- 成员初始化Class(c)将调用Class::Class(const Class&)复制构造函数,由于没有动态内存分配,所以使用默认复制构造函数就OK
- max和min赋值为c.max() , c.min()的返回值,这里的c是一个对象,跟下面的基类方法访问,对象访问方法理解开来!
- Pclass参数为const double数组的构造函数:
- 成员初始化Class(d)将调用Class::Class(const double d[])来初始化基类私有数据。
- max和min赋值为Class::max() , Class::min()的返回值,访问基类方法记得作用域解析运算符。
- Pclass默认构造函数可以调用Class默认构造函数
- Pclass的show_s()会调用Class::show_s(),这里也是派生类方法可以调用基类方法的一个例子。然后自己在添加内容,就能达到不一样的行为的目的。
- Pclass的show_a()不在函数名后加const,别问我为啥,现在是深夜凌晨1点半,我的大脑好混乱。
/main.cpp
#include"class.h"
#include<iostream>
int main()
{
using std::cout;
using std::endl;
using std::cin;
double d1[5]{100,90,80,60,80};
Class c1(d1);
Pclass c2(c1);
c1.show_s();
c1.show_a();
c2.show_s();
c2.show_a();
Class* ptr [3];
for(int i=0;i<3;i++)
{
double d2[5]={0};
cout<<"Enter 5 scores: ";
for(int j=0;j<5;j++)
{
cin>>d2[j];
}
int tap;
cout<<"enter 1 to use Class, enter 2 to use Pclass: ";
while(cin>>tap && (tap != 1 && tap != 2))
cout<<"You can enter 1 or 2 only\n";
if(tap==1)
ptr[i] = new Class(d2);
else
ptr[i] = new Pclass(d2);
while(cin.get()!='\n' )
continue;
}
for(int i=0;i<3;i++)
{
ptr[i]->show_s();
ptr[i]->show_a();
}
for(int i=0;i<3;i++)
{
delete ptr[i];
}
cout<<"Over!\n";
return 0;
}
程序解析:
- 首先方法是根据类对象的类型判断使用哪种方法
- 声明一个成员为Class指针的数组,后面的for循环,让用户选择使用哪种方法,1就是Class方法,将Class指针指向Class内,2就是Pclass方法,将Class指针指向Pclass类。然后体会一下virtual根据指针或引用所指对象类型来选择方法的这个逻辑。
- 最后要delete占用的内存,不是数组,所以上面那种方法。
结果1:(根据对象类型判断)
image.png
结果2:(根据指针或引用所指的对象类型判断,其实没啥区别..)
image.png
为何需要虚析构函数
如果析构函数不是虚的,则将只调用对应指针类型的析构函数。但如果是虚析构函数,则将调用相对应类型的析构函数。虚析构函数可以确保正确的析构函数序列被调用。