C++基础(C++Primer学习)

C++基础(十一)
<十三章 类的继承----多态公有继承>

如果希望同一个方法在派生类和基类中的行为不同,方法的行为应取决于调用该方法的对象。这种复杂的行为称为多态------具有多种形态,即同一个方法的行为随着对象不同而改变。有两种重要的机制可用于实现多态公有继承:
在派生类重新定义基类方法
使用虚方法

思考:这里的多态的意思就是对于一个函数来说,我们希望赋予他更多的功能,使得对象不同实现的功能也不同。
使用虚方法的办法时使用关键字virtual来修饰函数,使其成为一个虚函数。
1、构建基类和它的派生类
在这里为了方便我把基类和派生类都放在一个类项目内,其实还是分开比较好:
头文件:

#pragma once
#include"list.h"
class Brass
{
private:
	string name; //名字
	long accountId; //用户id
	double balance; //余额
public:
	Brass(const string &na = "none", long an = 0, double ba = 0);
	void Deposit(double amt); //存钱函数
	virtual void Withdraw(double amt); //取钱函数
	virtual void show_info()const;
	double show_balance()const { return balance; }
};

class BrassPlus :public Brass
{
private:
	double maxLoan;  //最大可借款
	double rate; //利率
	double owesbank; //银行欠款
public:
	BrassPlus(const string& s = "none", long an = 0, double bl = 0, double ml = 0, double ra = 0);
	BrassPlus(const Brass& b, double ml = 0, double ra = 0);//另外一种写法,使用复制构造函数

	virtual void Withdraw(double amt); 
	virtual void show_info()const; //显示信息

	void ResetMax(double m) { maxLoan = m; } //重置最大额度
	void ResetRate(double r) { rate = r; } //重置利率
	void ResetOwes() { owesbank = 0; } //清零欠款额度
};

上述代码可以发现,使用virtual关键字声明了两个虚函数,该虚函数名在基类和派生类当中都是存在的。
下面是在.cpp文件当中对函数进行定义:

	name = na;
	accountId = an;
	balance = ba;
}

void Brass::Deposit(double amt)
{
	if (amt < 0)
		cout << "not allowed" << endl;
	else
		balance += amt;
}

void Brass::Withdraw(double amt)
{
	if (amt < 0)
		cout << "not allowed!" << endl;
	else if (amt <= balance)
		balance -= amt;
	else
		cout << "unable!" << endl;
}

void Brass::show_info()const
{
	cout << "name is: " << name
		<< "  " << "Account id:" << accountId
		<< "  " << "Balance is:" << balance << endl;
}

BrassPlus::BrassPlus(const string& s, long an, double bl, double ml, double ra) : Brass(s, an, bl)
{
	maxLoan = ml;
	rate = ra;
	owesbank = 0;
}

BrassPlus::BrassPlus(const Brass& b, double ml, double ra):Brass(b)
{
	maxLoan = ml;
	rate = ra;
	owesbank = 0;
}

void BrassPlus::show_info()const
{
	Brass::show_info();
	cout << "max load:" << maxLoan
		<< " "
		<< "rate is: " << rate
		<< " "
		<< "owesbank is:" << owesbank << endl;
}

void BrassPlus::Withdraw(double amt)
{
	double bal = show_balance();
	if (amt <= bal)
		Brass::Withdraw(amt);
	else if (amt <= (bal + maxLoan - owesbank))
	{
		double advanced = amt - bal;
		owesbank = advanced * (1 + rate); //更新后的欠款
		cout << "BANK ADVANCED: " << advanced << endl;
		cout << "rate charge:" << advanced * rate
			<< endl;/*当前欠款的手续费*/
		Deposit(advanced);
		Brass::Withdraw(amt);
	}
	else
		cout << "unable!" << endl;
}

2、类功能解释以及使用
再继续之前,首先来解释一下该基类与其派生类的功能,按照书上的介绍来讲,这是模拟了一个银行系统来完成对客户的信息存储以及金融服务。
基类的数据就是保存用户的个人信息以及余额,其成员函数的作用时存钱、取钱、显示余额、显示信息;
派生类的额作用时为用户提供金融服务,在基类的基础上,增加了用户的借钱功能(相当于信用卡,有透支额度以及利率)
但是这些都不重要,重要的是虚函数的使用以及多态的概念理解
在主函数中构建两个对象分别是基类对象和派生类对象:

int main()
{
	Brass client1("jack", 0001, 2000);
	BrassPlus client2("rose", 0002, 2000);
	client1.show_info();
	client2.show_info();

然后执行显示信息操作(是一个虚函数),显示结果表明,其中的基类对象调用的是基类方法,而派生类对象调用的是派生类的方法。
为了更好的说明函数的多态,在基类和派生类虚函数的定义中,我让其输出一下属于基类还是派生类:
基类函数:

void Brass::show_info()const
{
	cout << "name is: " << name
		<< "  " << "Account id:" << accountId
		<< "  " << "Balance is:" << balance;
	cout << "打印为基类方法!" << endl;
}

派生类:

void BrassPlus::show_info()const
{
	Brass::show_info();
	cout << "max load:" << maxLoan
		<< " "
		<< "rate is: " << rate
		<< " "
		<< "owesbank is:" << owesbank;
	cout << "打印为派生类方法!" << endl;
}

最后执行结果为:
在这里插入图片描述
因为派生类函数中调用了基类的打印函数,所以这里也显示了“打印为基类方法”。
若想要使用虚方法,那么就必须配合指针或者引用来使用,因为对象时明确的条件下其调用的方法归宿也应当是确定的。
创建一个指针数组来存储不同的对象:

int main()
{

	Brass clientarr0("jeck", 2001, 2000);
	BrassPlus clientarr1("keil", 2002, 2000, 2000, 0.001);
	Brass* parr[2] = { &clientarr0,&clientarr1 };
	clientarr0.show_info();
	clientarr1.show_info();
	parr[0]->show_info();
	parr[1]->show_info();

思考:指针数组的类型应该是基类还是派生类?
应该是基类的,前面讲过基类的指针既可以指向基类对象,也可以指向派生类对象。
允许程序,发现同样为Brass类型的指针,但是其调用的虚方法则是根据对象来确定的。
在这里插入图片描述
3、虚析构函数
若程序中涉及了new和delete,那么就应该在基类中声明虚析构函数,因为若使用普通的析构函数,那么编译器就会调用默认指针类型的析构函数,由于我们使用了基类类型的指针指向了派生类对象,那么假设有两个对象,则基类对象会被释放,但是派生类对象得不到释放。

virtual ~Brass{}

总结:虚方法地存在使得同名方法可以根据对象类型来进行转换,对于基类与派生类对象,如果方法不是虚方法,那么则都会调用基类方法。(仅在引用或者指针操作的条件下)
验证结论,将类声明中地virtual关键字全部去掉,包括基类与派生类,然后执行程序:

int main()
{
	Brass clientarr0("jeck", 2001, 2000);
	BrassPlus clientarr1("keil", 2002, 2000, 2000, 0.001);
	Brass* parr[2] = { &clientarr0,&clientarr1 };
	parr[0]->show_info();
	parr[1]->show_info();
	......

发现无论指针指向的对象是基类还是派生类对象,都只能调用基类方法。
在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
相当经典的教程,这次的上传一共5集,5分,相当于每1集要1分下载. 本课程是C++ Primer初级教程,课程内容是学习C++语言基础知识,对应着教材的第1章到第8章。 第1章 快速入门 1.1 编写简单的C++程序 1.2 初窥输入/输出 1.2.1 标准输入与输出对象 1.2.2 一个使用IO库的程序 1.3 关于注释 1.4 控制结构 1.4.1 while语句 1.4.2 for语句 1.4.3 if语句 1.4.4 读入未知数目的输入 1.5 类的简介 1.5.1 Sales_item类 1.5.2 初窥成员函数 1.6 C++程序 第2章 变量和基本类型 2.1 基本内置类型 2.1.1 整型 2.1.2 浮点型 2.2 字面值常量 2.3 变量 2.3.1 什么是变量 2.3.2 变量名 2.3.3 定义对象 2.3.4 变量初始化规则 2.3.5 声明和定义 2.3.6 名字的作用域 2.3.7 在变量使用处定义变量 2.4 const限定符 2.5 引用 2.6 typedef名字 2.7 枚举 2.8 类类型 2.9 编写自己的头文件 2.9.1 设计自己的头文件 2.9.2 预处理器的简单介绍 第3章 标准库类型 3.1 命名空间的using声明 3.2 标准库string类型 3.2.1 string对象的定义和初始化 3.2.2 String对象的读写 3.2.3 string对象的操作 3.2.4 string对象中字符的处理 3.3 标准库vector类型 3.3.1 vector对象的定义和初始化 3.3.2 vector对象的操作 3.4 迭代器简介 3.5 标准库bitset类型 3.5.1 bitset对象的定义和初始化 3.5.2 bitset对象上的操作 第4章 数组和指针 4.1 数组 4.1.1 数组的定义和初始化 4.1.2 数组操作 4.2 指针的引入 4.2.1 什么是指针 4.2.2 指针的定义和初始化 4.2.3 指针操作 4.2.4 使用指针访问数组元素 4.2.5 指针和const限定符 4.3 C风格字符串 4.3.1 创建动态数组 4.3.2 新旧代码的兼容 4.4 多维数组 第5章 表达式 5.1 算术操作符 5.2 关系操作符和逻辑操作符 5.3 位操作符 5.3.1 bitset对象或整型值的使用 5.3.2 将移位操作符用于IO 5.4 赋值操作符 5.4.1 赋值操作的右结合性 5.4.2 赋值操作具有低优先级 5.4.3 复合赋值操作符 5.5 自增和自减操作符 5.6 箭头操作符 5.7 条件操作符 5.8 sizeof操作符 5.9 逗号操作符 5.10 复合表达式的求值 5.10.1 优先级 5.10.2 结合性 5.10.3 求值顺序 5.11 new和delete表达式 5.12 类型转换 5.12.1 何时发生隐式类型转换 5.12.2 算术转换 5.12.3 其他隐式转换 5.12.4 显式转换 5.12.5 何时需要强制类型转换 5.12.6 命名的强制类型转换 5.12.7 旧式强制类型转换 第6章 语句 6.1 简单语句 6.2 声明语句 6.3 复合语句(块) 6.4 语句作用域 6.5 if语句 6.6 switch语句 6.6.1 使用switch 6.6.2 switch中的控制流 6.6.3 default标号 6.6.4 switch表达式与case标号 6.6.5 switch内部的变量定义 6.7 while语句 6.8 for循环语句 6.8.1 省略for语句头的某些部分 6.8.2 for语句头中的多个定义 6.9 do while语句 6.10 break语句 6.11 continue语句 6.12 goto语句 6.13 try块和异常处理 6.13.1 throw表达式 6.13.2 try块 6.13.3 标准异常 6.14 使用预处理器进行调试 第7章 函数 7.1 函数的定义 7.1.1 函数返回类型 7.1.2 函数形参表 7.2 参数传递 7.2.1 非引用形参 7.2.2 引用形参 7.2.3 vector和其他容器类型的形参 7.2.4 数组形参 7.2.5 传递给函数的数组的处理 7.2.6 main:处理命令行选项 7.2.7 含有可变形参的函数 7.3 return语句 7.3.1 没有返回值的函数 7.3.2 具有返回值的函数 7.3.3 递归 7.4 函数声明 7.5 局部对象 7.5.1 自动对象 7.5.2 静态局部对象 7.6 内联函数 7.7 类的成员函数 7.7.1 定义成员函数的函数体 7.7.2 在类外定义成员函数 7.7.3 编写Sales_item类的构造函数 7.7.4 类代码文件的组织 7.8 重载函数 7.8.1 重载与作用域 7.8.2 函数匹配与实参转换 7.8.3 重载确定的三个步骤 7.8.4 实参类型转换 7.9 指向函数的指针 第8章 标准IO库 8.1 面向对象的标准库 8.2 条件状态 8.3 输出缓冲区的管理 8.4 文件的输入和输出 8.4.1 文件流对象的使用 8.4.2 文件模式 8.4.3 一个打开并检查输入文件的程序 8.5 字符串流

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值