假如一个类继承另一个类,但有一个类方法,在不同类中,其行为是不同的。
换句话说,方法的行为,应取决于调用该方法的对象。这种较复杂的行为称为 多态——具有多种形态,即同一种方法的行为,随上下文而异。
有两种重要的机制可用于实现多态公有继承:
①在派生类中重新定义基类的方法;
②使用虚方法。
注:这两种机制共同使用
虚方法的关键字是:virtual
例如:virtual void show();
虚方法的关键字,不在类外部使用,例如.cpp文件中的方法定义
虚方法的作用在于:
在基类和派生类都有同名函数时,决定使用哪个类的方法,不是取决于指针/引用类型,而是取决于他们指向的对象的类型。
解释①:如果不是指针、引用,而是对象,那么根据对象类型决定,虚方法无影响;
解释②:因为基类的指针、引用,可以指向派生类对象;
解释③:假如不使用虚方法,决定使用哪一个方法,取决于指针/引用的类型,而不是取决于它们指向的类型(例如基类指针指向派生类,如果是非虚方法,则使用基类的方法;如果是虚方法,则使用派生类的方法);
代码:
//1.h 基类和派生类声明 #pragma once #include<iostream> #include<string> using std::string; class Brass { string name; int ID; double money; public: Brass(string na = "None", int id = -1, double mo = 0); //创建账户 bool Save(double mo); //存款 virtual bool Load(double mo); //取款 virtual void Show(); //显示账户信息 double Money() { return money; } //返回当前存款 }; class Brass_plus :public Brass { double overdraft_Max; //透支上限 double overdraft_Rate; //透支贷款利率 double overdraft ; //当前透支总额 public: Brass_plus(const Brass& br,double ov_M = 500, double ov_R = 0.11125, double ov = 0); bool ch_ov_M(double ov_M); //设置透支上限 bool ch_ov_R(double ov_R); //设置透支利率 virtual bool Load(double mo); //取款,透支保护 virtual void Show(); //显示账号信息,更多 }; //2.cpp 基类和派生类的定义 #include"1.h" using std::cout; using std::endl; using std::string; typedef std::ios_base::fmtflags format; typedef std::streamsize precis; //这个不明白是什么意思 format setFormat(); void restore(format f, precis p); Brass::Brass(string na, int id, double mo) { name = na; ID = id; money = mo; } bool Brass::Save(double mo) { if (mo < 0) { cout << "你不能存入小于0的金钱。" << endl; return false; } else { money += mo; cout << "存款成功。" << endl; return true; } } bool Brass::Load(double mo) { if (mo < 0) { cout << "你不能取出小于0的金钱。" << endl; return false; } else if (mo>money) { cout << "余额不足。" << endl; return false; } else { money -= mo; cout << "取款成功。" << endl; return true; } } void Brass::Show() { cout << "姓名:" << name << ",存款账号:" << ID << ",账户余额:" << money << "元" << endl; } Brass_plus::Brass_plus(const Brass& br, double ov_M, double ov_R, double ov):Brass(br) { overdraft_Max = ov_M; overdraft_Rate = ov_R; overdraft = ov; } bool Brass_plus::ch_ov_M(double ov_M) //设置透支上限 { if (ov_M < 0) { cout << "设置失败,不能设置为负数。" << endl; return false; } else { overdraft_Max = ov_M; cout << "设置成功,新的透支上限为:" << overdraft_Max << "元" << endl; return true; } } bool Brass_plus::ch_ov_R(double ov_R) //设置透支利率 { if (ov_R < 0) { cout << "设置失败,不能设置为负数。" << endl; return false; } else { overdraft_Rate = ov_R; cout << "设置成功,新的利率为:" << overdraft_Rate * 100 << "%" << endl; return true; } } void Brass_plus::Show() //显示账号信息,more { Brass::Show(); cout << "账户透支上限:" << overdraft_Max << " 元" << endl; cout << "透支偿还利率:" << overdraft_Rate * 100 << " %" << endl; cout << "当前透支额度为:" << overdraft << " 元" << endl; } bool Brass_plus::Load(double mo) //取款,带有透支保护 { format initialState = setFormat(); //这行貌似是存储输入状态(这个输入状态是函数的返回值) precis prec = cout.precision(2); //这行感觉是设置为两行输出 double MO = Brass::Money(); if (mo < 0||mo<MO) //不涉及透支的取款 { return Brass::Load(mo); } else if (mo>overdraft_Max - overdraft + MO) //透支程度大于限额 { cout << "超出限额,取款失败。" << endl; return false; } else { Brass::Load(MO); //先取光余额 overdraft += mo - MO; cout << "取款成功,余额为:" << Brass::Money() << ",透支额为:" << overdraft << " 元,最大透支额为: " << overdraft_Max << "元" << endl; return true; } restore(initialState, prec); //这行好像是恢复 } format setFormat() { return cout.setf(std::ios_base::fixed, std::ios_base::floatfield); } void restore(format f, precis p) { cout.setf(f, std::ios_base::floatfield); cout.precision(p); } //1.cpp main函数测试用 #include<iostream> #include"1.h" int main() { using namespace std; string name; cout << "输入姓名:"; cin >> name; //不能读取空格 cout << "输入ID编号(数字形式):"; int ID; cin >> ID; cout << "输入存款金额:"; double money; cin >> money; Brass one(name, ID, money); cout << "银行账户创建完毕。" << endl; Brass_plus two(one); cout << "已建立信用账号:" << endl; double a; cout << "s.存\tl.取.\tc.查询\tq.退出\n选择->"; char ch; while (cin>>ch&&ch!='q') { cin.sync(); switch (ch) { case's':cout << "输入存款金额:"; cin >> a; two.Save(a); break; case'l':cout << "输入取款金额:"; cin >> a; two.Load(a); break; case'c':two.Show(); break; default:cout << "输入错误。" << endl; cin.clear(); cin.sync(); break; } cout << "s.存\tl.取.\tc.查询\tq.退出\n选择->"; } cout << "设置利率(%):"; double LiLv; cin >> LiLv; LiLv /= 100; two.ch_ov_R(LiLv); cout << "设置最大透支额度:"; double Max; cin >> Max; two.ch_ov_M(Max); cout << "再次查看账户信息:"; two.Show(); cout << "Done." << endl; system("pause"); return 0; }
显示:
输入姓名:王冬 输入ID编号(数字形式):12321 输入存款金额:1000 银行账户创建完毕。 已建立信用账号: s.存 l.取. c.查询 q.退出 选择->c 姓名:王冬,存款账号:12321,账户余额:1000元 账户透支上限:500 元 透支偿还利率:11.125 % 当前透支额度为:0 元 s.存 l.取. c.查询 q.退出 选择->s 输入存款金额:400 存款成功。 s.存 l.取. c.查询 q.退出 选择->c 姓名:王冬,存款账号:12321,账户余额:1400元 账户透支上限:500 元 透支偿还利率:11.125 % 当前透支额度为:0 元 s.存 l.取. c.查询 q.退出 选择->l 输入取款金额:1500 取款成功。 取款成功,余额为:0.00,透支额为:100.00 元,最大透支额为: 500.00元 s.存 l.取. c.查询 q.退出 选择->c 姓名:王冬,存款账号:12321,账户余额:0.00元 账户透支上限:500.00 元 透支偿还利率:11.13 % 当前透支额度为:100.00 元 s.存 l.取. c.查询 q.退出 选择->l 输入取款金额:500 超出限额,取款失败。 s.存 l.取. c.查询 q.退出 选择->q 设置利率(%):15 设置成功,新的利率为:15.00% 设置最大透支额度:5000 设置成功,新的透支上限为:5000.00元 再次查看账户信息:姓名:王冬,存款账号:12321,账户余额:0.00元 账户透支上限:5000.00 元 透支偿还利率:15.00 % 当前透支额度为:100.00 元 Done. 请按任意键继续. . .
总结:
①派生类调用基类的公有方法,采用:基类名::基类方法 的形式。例如:
Brass::Show();
就是Brass_plus类的方法内,调用Brass类方法show()。
由于派生类和基类都有show()函数,假如不加类名。那么在派生类函数show()中使用show(),并不会调用基类的show()函数,反而会进入到无限递归之中。
②我在程序中,没有在透支时直接加上利息。原因在于,假如透支500元,加上利息后,实际需要偿还金额可能已经超过默认上限500元了,也就是超出透支额度。
③可以用指针数组。具体用法可以如下:
声明一个基类的指针数组:Brass people[4];
然后指针指向基类或者派生类,可以使用new来分配内存:
people[0]=new Brass(xxxxxxx);
people[1]=new Brass_plus(xxxx); //这个需要输入的内容更多,包括Brass类的数据成员
然后调用people[i],使用虚方法,就可以展现出不同类型的结果(原因在于虚方法是根据指针指向的对象的类型决定调用哪一个,而不是根据指针的类型)。
关于虚函数的更多说明:
①当基类使用虚函数的时候,那么使用基类的指针/引用,将根据其指向的对象的类型决定使用哪个类的方法。
例如,基类的函数是自动继承到派生类的。如果派生类需要自定义使用某个基类的函数的实现,那么是可以直接在派生类中添加代码。
调用时,根据对象的类型决定调用哪个;
如果是指针,则根据指针的类型。——但若使用虚函数,这里则是根据指针指向的类型,即Brass类虚指针也可能使用Brass_plus类的方法。
②对于析构函数而言,派生类的对象调用析构函数时,则先调用派生类的析构函数,然后随之调用基类的虚构函数。
若使用基类的指针,那么基类的指针是可以指向派生类的对象的(前面说过)。
假如因为某种需要,基类的指针是new分配内存的派生类的对象(这是可以的),那么在delete的时候,也应该调用调用派生类的析构函数,再调用基类的析构函数。
然而,对于非虚函数而言,由于是基类的指针,因此直接调用了基类的析构函数,而没有调用派生类的析构函数。
但若基类的析构函数是虚函数。那么在调用时,则会根据指针或引用指向的对象,决定调用是基类还是派生类的析构函数。
基类指针被delete释放内存——》查看析构函数——》发现关键字virtual——》决定根据指针指向内容而决定使用哪个类方法。
也就是说,假如某个类是基类,那么最好给他的析构函数加上关键字virtual,让它成为一个虚析构函数。
③由此反推,假如某个类不会成为基类,那么它的函数就不需要加上关键字成为虚函数(即使他是某个基类的派生类),因为派生类的指针只能指向它自己,而不是指向基类(但基类指针可以指向派生类)。
但由于为了一目了然,因此,一般情况下,假如基类是虚函数,那么派生类也应该加上关键字 virtual 表示 有虚函数,以防混淆。