(一三八)多态公有继承

假如一个类继承另一个类,但有一个类方法,在不同类中,其行为是不同的。

换句话说,方法的行为,应取决于调用该方法的对象。这种较复杂的行为称为 多态——具有多种形态,即同一种方法的行为,随上下文而异。

 

有两种重要的机制可用于实现多态公有继承:

①在派生类中重新定义基类的方法;

②使用虚方法。

注:这两种机制共同使用

 

 

 

虚方法的关键字是: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 表示 有虚函数,以防混淆。

 

 

 

时间: 2024-10-25 13:46:21

(一三八)多态公有继承的相关文章

类模拟及多态、继承

在面向对象的语言里面,出现了类的概念.这是编程思想的一种进化.所谓类:是对特定数据的特定操作的集合体.所以说类包含了两个范畴:数据和操作.而C语言中的suct仅仅是数据的集合.(liyuming1978@163.com) 1.实例:下面先从一个小例子看起 #ifndef C_Class #define C_Class suct #endif C_Class A { C_Class A *A_this; void (*Foo)(C_Class A *A_this); int a; int b; }

关于C++中公有继承、私有继承、保护继承的讨论

一.文章来由 简单明了的理由,老生常谈但是没真正解决的问题,想搞清楚这个问题. 二.冗长的定义 我们先来看看这些冗长的定义: 公有继承: 当类的继承方式为公有继承时,基类的公有成员和保护成员的访问属性在派生类中不变,而基类的私有成员不可直接访问.也就是说基类的公有成员和保护成员被继承到派生类中访问属性不变,仍作为派生类的公有成员和保护成员,派生类的其他成员可以直接访问它们.在类族之外只能通过派生类的对象访问从基类继承的公有成员. 私有继承: 当类的继承方式为私有继承时,基类中的公有成员和保护成员

c++-C++程序,公有继承之后成员的值变成了随机值,怎么回事儿

问题描述 C++程序,公有继承之后成员的值变成了随机值,怎么回事儿 #include #include using namespace std; const float Pi = 3.1415; class C { public: C(float r, float h) :Radius(r), high(h) {} protected: float Radius; float high; }; class Round :public C { public: void supArea(); voi

java用接口、多态、继承、类计算三角形和矩形周长及面积的方法_java

本文实例讲述了java用接口.多态.继承.类计算三角形和矩形周长及面积的方法.分享给大家供大家参考.具体如下: 定义接口规范: /** * @author vvv * @date 2013-8-10 上午08:56:48 */ package com.duotai; /** * * */ public interface Shape { public double area(); public double longer(); } /** * @author vvv * @date 2013-8

java继承多态-java继承问题,菜鸟求助

问题描述 java继承问题,菜鸟求助 1.定义一个类,包含两个以上的私有成员,两个以上的方法以及一个构造函数 从该超类继承得到至少三个子类,这三个子类分别要在超类的基础上增加至少一个成员,至少一个方法以及同时重写超类中的同一个方法. 3.编写程序,用子类的对象去访问超类的方法和自己的方法 编写一个方法,用超类作为形式参数. 分别用三个子类对象作为实际参数调用该方法,并在方法中实现多态. 要求满足上面的条件,求大神附上代码,谢谢 解决方案 菜鸟不可怕,可怕的是不学的菜鸟.少年,java语法都学会了

深入浅出OOP(一): 多态和继承(早期绑定/编译时多态)

在本系列中,我们以CodeProject上比较火的OOP系列博客为主,进行OOP深入浅出展现. 无论作为软件设计的高手.或者菜鸟,对于架构设计而言,均需要多次重构.取舍,以有利于整个软件项目的健康构建,有些经验是前辈总结的,我们拿来使用即可,有些是团队知识沉淀的,总之复用前人好的思想有利于减少返工.当然,在面试的时候,如果能围绕OOP大谈特谈,自然会加分多多的. 开始阅读本系列博客的预备知识,多态.封装.面向对象编程等,请通过MSDN学习.如下图的术语,您应该耳熟能详的.本系列文章使用C#作为唯

C++继承一之公有继承

一般来说一个类可以继承于另外一个类,分别叫做派生类和基类, 派生类继承了基类的公有成员和保护成员以及实现,而私有成员只能通过 基类的公有方法进行访问 派生类应该包含如下信息: 1.继承类构造函数 2.需要额外增加的成员以及实现 我们引用C++ primer plus中的例子,当然这些例子我都是手动打过一遍的 <lastname<<", "<<firstname;="" }="" rateplayer::ratepla

深入浅出OOP(四): 多态和继承(抽象类)

在本文中,我们讨论OOP中的热点之一:抽象类.抽象类在各个编程语言中概念是一致的,但是C#稍微有些不一样.本文中我们会通过代码来实现抽象类,并一一进行解析. Abstract Classes 在微软的MSDN中,对抽象类有如下的定义: 用abstract 关键字可定义抽象类,要求其子类必须实现抽象类的函数.属性等.抽象类不可被实例化.抽象类提供了统一的定义,用于其不同子类直接共享数据.函数. 抽象类也可定义抽象函数.   Abstract Classes实战 在Visual Studio中添加C

C语言模式实现C++继承和多态

C语言模式实现C++继承和多态 描述: C实现一个struct A和struct B各包含一个int成员a和b,要求达到B继承了A的效果,也就是B里面包含一个A.并且能达 到多态的效果,也就是一个A*p指向A调的是A的函数,指向B调用的是B的函数. C++中的继承.多态 继承是面向对象复用的重要手段.通过继承定义一个类,继承是类型之间的关系建模,共享公有的东西,实现各自本质 不同的东西.  继承是一种复用手段,在继承关系里基类的成员类的成员派生类的成员,由此达到复用的目的.  如果你想学习C/C