类继承

本文主要内容:
多态公有继承(虚函数)、抽象基类(纯虚函数)、继承与动态内存分配、protect访问控制。

引言:OOP一个主要目的实现代码的可重用性,对于C语言来说由函数库,也可以实现代码的重用,但需要获取源代码,根据需要该内部代码。但是C++的继承机制使得无需源代码,我们就可以在其基础上,增加新的功能,添加新的数据成员,改变函数行为。
继承包含:公有继承、保护继承、私有继承。
公有继承:基类中的公有成员将成为派生类中的公有成员,基类中的私有成员也将成为派生类的一部分(但是不能直接访问基类私有成员,只能通过基类公有、保护方法访问)。继承时不会舍弃任何成员的,无论这些成员能否使用。
可以说,派生类对象包含基类对象。派生类对象存储了基类对象的私有成员;可以使用基类的公有方法。

在这里插入图片描述
在派生类中,一定要添加构造函数(初始化基类的数据成员和新增的数据成员),可以根据实际情况添加数据成员和函数。
派生类的构造函数写法:
由于初始化基类的私有数据成员,却无法直接访问这些私有成员,因而通过基类的公有方法,即基类的构造函数。派生类对象包含基类对象,因而在进入派生类的构造函数体内需要先创建基类对象,这可以通过成员初始化列表实现语法实现。先创建并初始化基类对象,在进入构造函数体内创建初始化派生类对象。可以看出派生类对象的创建嵌套着基类对象的创建。
在这里插入图片描述

派生类对象生命周期结束时,先调用派生类对象的析构函数、再调用基类的析构函数。
派生类要使用基类,因而基类、派生类的类声明和基类、派生类的类定义时最好写在一个文件中。

基类和派生类间的转化关系(兼容性):
由于is-a的继承关系,使得向上转化可以自然发生。(安全的事情,我们使得编译器认可)
无需强制类型转换,基类对象的引用可以指向派生类对象的引用;基类对象的指针可以指向派生类对象(派生类包含基类,有基类对象的数据和方法);很明显,基类对象的引用或是指针不能访问派生类中新增的成员。
由于兼容性:可以使用派生类对象初始化基类对象,由于基类复制构造函数是基类对象的引用,因而实参可以是派生类对象。同理,可以把派生类对象赋值给基类对象(或是参数传递为值传递时,只使用派生类的部分)。
向上转换:隐式且安全
向下转换:强制且不安全,实际引用或者指向的对象不包含该方法。

公有继承是一种is -a(is-a-kind-of)关系,派生类对象是基类对象(可对派生类对象执行任何操作,可对基类对象执行任何操作)。何时建立is -a 的公有继承?基类所有属性都是派生类所需要的(没有多余)。

has-a :作为数据成员使用
use-a:友元函数
is-like-a:重定义共有的属性,在此基础上继承
is-implemented-as-a:作为数据成员使用

多态公有继承:
为了实现从基类中继承同一个方法,但是希望调用对象不同,同一个方法执行的行为不同。行为由调用对象决定。达到这一效果,有两种机制可以帮助我们:
在派生类中重新定义该方法;
使用虚函数

虚函数:
在基类进行类声明的时候,用virtual修饰,则派生类中定义的该方法也是虚的,但是一个好的编程习惯是在声明派生类中的该方法时也应用virtual修饰。在定义时不要加virtual。

用途:
当调用对象是指向对象的引用或是指针时,使用virtual有明显的意义:不是根据引用类型来调用相应的类函数,而是根据所指对象的类型调用;而不用virtual声明的函数,当面临如此情况,只是根据对象的类型来决定调用的类函数。而基类引用对象通常可指向派生类对象,使用virtual使得多态性发挥得更淋漓尽致。因而要在派生类重定义方法,就在基类声明中将该方法声明为虚函数,以免出现意料之外的状况。此外,我们还需要在基类中声明虚析构函数,为了保障释放对象的正确顺序。
在派生类中调用基类的方法,如果派生类和基类都有该方法的定义,则需要加作用域解析符,否则,可直接调用。
定义派生类的初衷就是为了利用基类已经实现接口,提高代码的重用性。实在没有的部分再自己添加。

#include<string>
#include<iostream>
using std::string;
class BankAccount
{
	string name;
	double balance;
public:
	BankAccount()
	{
		name = nullptr;
		balance = 0;
	}
	BankAccount(string n, double b)
	{
		name = n;
		balance = b;
	}
	double getbalance() const//出现了一个致命的错误:成员名称为balance 函数名也用了balance!!!
	{
		return balance;
	}
	void  deposit(double m)
	{
		balance += m;
	}
	virtual bool  withdraw(double m)
	{
		if (balance<m)
		{ 
			std::cout << "fail" << std::endl;
			return false;
		}
		else
		{
			balance -= m;
			return true;
		}
		
		



	}
		

	virtual void show() const;
	virtual ~BankAccount () {}

};


class AdvancedAccount : public BankAccount
{
	double rate;
	double maxLoan;
	double owsBank;
public:
	AdvancedAccount();
	AdvancedAccount(string & ,double , double, double);//用基类对象作为函数参数
	AdvancedAccount(BankAccount &b, double, double);//用基类对象作为函数参数
	virtual bool withdraw(double m);
	virtual void show() const ;
	void  set_rate(double r) {
		rate = r;
	}
	void set_maxLoan(double l) {
		maxLoan = l;
	}
	void set_ows(double)
	{
		owsBank = 0;

	}
	
};

#include"account.h"


void BankAccount::show() const
{
	std::cout << name << " balance is" << balance << std::endl;
}

AdvancedAccount::AdvancedAccount(): BankAccount()
{
	rate = 0.001;
	maxLoan = 100;
	owsBank = 0;
}
AdvancedAccount:: AdvancedAccount(string &s, double b, double r, double m):BankAccount(s,b)
{
	rate = r;
	maxLoan = m;
	owsBank = 0;
}
AdvancedAccount::AdvancedAccount(BankAccount &b, double r, double m): BankAccount(b)
{
	rate = r;
	maxLoan = m;
	owsBank = 0;

}
bool AdvancedAccount::withdraw(double m)
{
	if (m > getbalance() + maxLoan)
	{
		std::cout << "fail" << std::endl;
		return false;
	}
	else if(m<getbalance())
	{
		BankAccount::withdraw(m); //不是想着用基类中已经实现的方法,而是想着自己编代码 如果实现很复杂 那不是增加工作量吗
		return true;
	}
	else
	{
		double loan = m - getbalance();
		owsBank = loan*(1 + rate);
		deposit(loan);//存入钱 使得可以取出来 代码可重用性啊
		BankAccount::withdraw(m);
		return true;
		
	}

 }
void AdvancedAccount::show() const
{
	using std::cout;
	using std::endl;
	BankAccount::show();
	cout << rate << " "<<owsBank<<" " <<maxLoan<< endl;
 }


const int SIZE = 3;
#include"account.h"

int main()
{
	using std::cin;
	using std::cout;
	using std::endl;
	BankAccount *p[SIZE];//空间已分配好的
	int choose;
	string sname;
	double b;
	double l;
	double r;
	for (int i = 0; i < SIZE; i++)
	{
		cout << "name :";
		cin >> sname;
		cout << "balance:";
		cin >> b;
		cout << "1 or 2:";
		cin >> choose;
		if (choose == 1)
			p[i] = new BankAccount(sname, b);

		else
		{
			cout << "rate:";
			cin >> r;
			cout << "maxloan:";
			cin >> l;
			p[i] = new AdvancedAccount(sname, b, r, l);



		}
		while (cin.get() != '\n')
			continue;
	
		

	}

	for (int i = 0; i < SIZE; i++)//虚函数的意义所在
	{
		p[i]->show();
	}
	for (int i = 0; i < SIZE; i++)
	{
		delete p[i];
	}
	cin.get();
    return 0;
}

静态联编和动态联编:
在编译阶段,通过函数名和参数个数、类型来确定调用函数的代码块;而虚函数使得只有在执行时根据调用对象的类型确定函数代码块,进一步发展来了动态联编。

虚函数工作原理:
类对象都有一个隐藏的指针成员,指向一个虚函数表,该表存的是每个虚函数的地址。如果重定义了基类中的方法,则替换该地址,没有则保存基类虚函数地址,若新增虚函数,则加入该函数地址。根据所调用的对象找到该指针获取该虚函数表查找虚函数地址以确定该执行哪个函数代码块。

构造函数不能声明为虚函数。因为,基类的构造函数就没有被继承下来。
为了安全起见,将析构函数声明为虚函数。基类指针所指的是派生类对象,释放该片空间时,如果不定义为虚函数则将只释放基类对象空间,没有释放干净,而定义为虚函数后,将调用派生类对象的析构函数再调用基类析构函数。
在这里插入图片描述

友元函数不是成员函数,虚函数只针对成员函数。但希望友元函数也具有多态性,可在函数内部调用虚函数。
在派生类中不定义函数,则使用基类中的函数。若是处于派生链,则使用最新的虚函数版本。(除了函数隐藏)
重新在派生类中与基类定义同名方法(特征标不同)将隐藏基类方法,因而为了达到虚函数目的,基类中有多少重载方法,派生类重新定义时,都要全部定义,否则将出现函数隐藏,派生类对象无法使用其余的基类方法。
返回类型协变:声明函数时,返回类型为对象引用或是指针时,基类使用基类类型,派生类使用派生类类型,是允许的,虽然返回类型不同,但指的是基类中的方法(换了个衣服,人还是没变,返回类型只有是引用才可以,其他都不行了,函数名相同,参数相同,类型不同,编译器直接报错)。

在这里插入图片描述
访问类别:protected,在类外无法直接访问(实现了数据隐藏,有利于解决名称冲突问题),但是在派生类中可直接访问保护成员,在基类中最好别用保护类别的数据成员,这使得派生类能够访问修改数据。通常我们把方法限定为保护的,在派生类中可直接调用,而数据别用保护。

抽象基类:
简单继承、多态继承(根据调用对象的不同执行不同的方法)、抽象继承。
为了解决两个类符合is-a关系,但是派生类希望舍弃基类一部分的数据成员或是方法。为了达到这个目的,提出了抽象基类。提取出共同的属性和方法写入到到抽象基类,同时将部分方法声明为纯虚函数(在不同的派生类中执行的操作不同)。
纯虚函数声明:
在这里插入图片描述
至少包含一个纯虚函数的类称为抽象类,不能创建其对象。纯虚函数可以有定义(书写在类定义文件中)。
创建抽象基类的另一个好处:可以用一个基类指针数组管理多个不同派生类型的派生对象
纯虚函数在派生类中必须要实现,这使得派生类遵循基类的接口规则。

//类声明
#ifndef ACCOUNT_H
#define ACCOUNT_H
#include<iostream>
#include<string>

class AccountAbc
{
	long id;
	std::string name;
	double balance;
public:
	AccountAbc(long i = -1, std::string n = nullptr, double b=0)
	{
		id = i;
		name = n;
		balance = b;

	}
	virtual ~AccountAbc(){}
	void deposit(double m) {
		balance += m;
	}
	virtual void withdraw(double)  = 0;
	virtual void show() const = 0;
protected:
	//辅助函数
	double get_balance() const { return balance; }
	struct Formatting
	{
		std::ios_base::fmtflags flags;
		std::streamsize precise;
	};
	Formatting set_format() const;
	void restore(Formatting & ) const ;

};

class Account :public AccountAbc
{
public:
	Account(long i = -1, std::string s = nullptr, double b = 0) : AccountAbc(i, s, b) {}
	Account(AccountAbc & c) : AccountAbc(c) {}
	virtual void withdraw(double);
	virtual void show() const;
	
	
};


class AccountPlus :public AccountAbc
{
private:
	double rate;
	double maxloan;
	double owebank;
public:
	AccountPlus(long i = -1, std::string s = nullptr, double b = 0,double r=0.0012,double m=300):AccountAbc(i,s,b)
	{
		rate = r;
		maxloan = m;
		owebank = 0;
	}
	AccountPlus(AccountAbc & c, double r = 0.0012, double m = 300) :AccountAbc(c)
	{
		rate = r;
		maxloan = m;
		owebank = 0;
	}
	virtual void withdraw(double);
	virtual void show() const;
	void set_r(double r) { rate = r; }
	void set_loan(double l) { maxloan = l; }
	double get_maxloan() const { return maxloan; }
	double get_rate() const { return rate; }
	double get_owebank() const { return owebank; }
};

#endif


#include"account.h"
using std::cout;
using std::endl;
#include<string>
AccountAbc::Formatting AccountAbc::set_format() const  //由于Formatting定义在类中,作用域时整个类 ,不加作用域解析符无法访问到,就像函数一样,
{
	Formatting f;
	f.flags = cout.setf(std::ios_base::fixed, std::ios_base::floatfield);
	f.precise = cout.precision(2);
	return f;

}
void AccountAbc::restore(Formatting & f) const
{
	cout.setf(f.flags, std::ios_base::floatfield);
	cout.precision(f.precise);

	

}

void AccountAbc::withdraw(double m)
{
	if (m < 0)
		cout << "error" << endl;
	else
		balance -= m;
 }
void AccountAbc::show() const 
{
	cout << "name is"<< name << " balance is " << balance << endl;
	
 }

void Account::withdraw(double m)
{
	Formatting f = set_format();
	AccountAbc::withdraw(m);
	restore(f);
 }
 void Account::show() const
 {
	 AccountAbc::show();

 }

 void AccountPlus::withdraw(double m)
 {


	 if (m <=get_balance())
		 AccountAbc::withdraw(m);
	 else if (m <=get_balance() + maxloan)
	 {
		 double cun = m - get_balance();
		 deposit(cun);
		 owebank += cun*(1+rate);
		 AccountAbc::withdraw(m);
	 }
	 else
	 {
		 cout << "fail!" << endl;
	 }
  }
 void AccountPlus::show() const
 {
	 Formatting f=set_format();
	 AccountAbc::show();
	 cout << "rate is " << rate << " maxloan is " << maxloan << owebank << endl;
	 restore(f);
  }
#include"account.h"
const int SIZE = 3;

int main()
{
	using std::cout;
	using std::cin;
	using std::endl;
	AccountAbc *p[SIZE];
	std::string sname;
	double b;
	int choose;
	double r;
	double max;
	for (int i = 0; i < SIZE; i++)
	{
		cout << "your name is :";
		cin >> sname;
		cout << "your balance:";
		cin >> b;
		cout << "account type:";
		cin >> choose;
		if (choose == 1)
		{
			p[i] = new Account( i + 1,sname, b);
		}
		else
		{
			cout << "rate is ";
			cin >> r;
			cout << "max loan ";
			cin >> max;
			p[i] = new AccountPlus(i + 1, sname, b,r,max);
		}
	}
	for (int i = 0; i < SIZE; i++)
	{
		p[i]->show();
	}
	cin.get();
	cin.get();
    return 0;
}

继承与动态分配
1.基类成员用了动态分配,派生类成员没有使用动态分配,则定义派生类时无需加入复制构造函数、赋值运算符重载(会自动调用基类中的复制构造函数和赋值函数,因而基类需要显示定义特殊的几个成员函数)、析构函数(派生类中析构函数会自动调用基类析构函数,由于基类的析构函数释放了在构造函数中申请的空间,因此空间释放完毕)。(类似于成员类对象,在赋值将会自动调用该类的赋值运算符函数,这里变为在基类需要动态分配空间)
2.基类成员用了动态分配,派生类成员也使用动态分配,这个时候为了给派生类对象数据成员分配空间,一定得重写复制构造函数、赋值运算符、析构函数,显示调用基类已经实现的复制构造函数、赋值运算符、析构函数,来完成整个数据成员的赋值和释放。

#ifndef DMA_H
#define DMA_H
#include<cstring>
#include<iostream>
class Dma
{
	char * p;
	int n;
public:
	Dma(char *s) {

		n = std::strlen(s);
		p = new char[n + 1];
		std::strcpy(p, s);
	}
	Dma(Dma & c)
	{
		n = c.n;
		p = new char[n + 1];
		std::strcpy(p, c.p);
	}
	virtual ~Dma() {
		delete[] p;
	}
	Dma & operator=(Dma &);//不能声明为虚函数
	friend std::ostream & operator<<(std::ostream &os, const  Dma&);
};

class Havedma:public Dma
{
private:
	char * temp;
public:
	Havedma(char *s1, char *s2):Dma(s1)
	{
		temp = new char[std::strlen(s2) + 1];
		std::strcpy(temp, s2);

	}
	Havedma(Havedma &c) : Dma(c)
	{
		temp=new char[std::strlen(c.temp) + 1];
		std::strcpy(temp, c.temp);
	}
	virtual ~Havedma()
	{
		delete[] temp;
	}

	Havedma & operator=(Havedma &);//不能声明为虚函数
	friend std::ostream & operator<<(std::ostream &os, const  Havedma&);
};




#endif
#include"dma.h"


Dma & Dma::operator=(Dma & c)//不能声明为虚函数
{
	if (this == &c)
		return *this;
	delete [] p;
	n = c.n;
	p = new char[std::strlen(c.p) + 1];
	std::strcpy(p, c.p);
	return *this;
}
std::ostream & operator<<(std::ostream &os, const  Dma&c)
{
	os << c.p << std::endl;
	return os;
 }


Havedma & Havedma::operator=(Havedma & c)//由于参数不同 隐藏基类函数 
{
	if (this == &c)
		return *this;
	Dma::operator=(c);
	delete[] temp;
	temp = new char[std::strlen(c.temp) + 1];
	std::strcpy(temp, c.temp);
	return *this;

}
std::ostream & operator<<(std::ostream &os, const  Havedma& c)//派生类的友元函数只能直接访问派生类中新增成员,而希望获取基类私有成员,只能关于调用基类的友元函数,但是注意友元函数不是成员函数,无法使用使用对象调用(类解析符也无效,就是一普通函数,所以只能靠参数区分是派生类还是基类的友元函数)
{
	os << (const Dma &)c;
	os << c.temp << std::endl;
	return os;
 }
#include"dma.h"
const int SIZE = 5;
int main()
{
	
	using std::cout;
	using std::cin;
	char  tp[SIZE];
	cout << "a string";
	//cin >> tp;
	cin.get(tp, SIZE);
	while (cin.get() != '\n')
		continue;
	Dma b=Dma(tp);
	cout << b;
	Havedma d(tp, "kdkfke");
	cout << d;
	Havedma e(d);
	cout << e;
	d = e;
	cout << d;

	cin.get();
	cin.get();
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值