类的继承学习总结(1)

一、概念

	继承(is-a)是类之间关系的一种形式,其他形式还有组合(has-a)等;
	基类:也称为父类,一个类继承父类的全部成员
	派生类:也称为子类,他是由基类派生而来
	语法:
	class base{};
	class derived  : public base{};

二、继承的用途

  1. 可以在已有类的基础上添加功能
  2. 可以给类添加数据
  3. 可以修改类的行为

三、如果继承一个类,需要在派生类添加哪些特性

派生类需要自己的构造函数
派生类可以根据自己的需要添加额外的数据成员和成员函数

class RatedPlayer :
	public TableTennisPlayer
{
private:
	unsigned int rating;// add a data member 

public:
	RatedPlayer(unsigned int r, const TableTennisPlayer &tp);
	RatedPlayer(unsigned int r = 0, const string &fn = "none",const string &ln = "none",bool ht = false);
	~RatedPlayer();

	unsigned int Rating()const; //add a method
	void ResetRating(unsigned int r);//add a method
};

四、派生类构造函数

派生类不能直接访问基类的私有成员,但可以通过基类的public方法进行访问。
派生类创建对象过程:

  • 首先调用基类的构造函数创建基类对象
  • 调用派生类构造函数创建派生类对象
    因此,引出成员初始化列表语法的概念
RatedPlayer::RatedPlayer(unsigned int r, const string & fn, const string & ln, bool ht)
						:rating(r),TableTennisPlayer(fn,ln,ht)
{
}

由上段代码可知,派生类成员变量也可以使用初始化列表进行初始化。

总结:派生类构造函数的要点

  • 首先创建基类对象; 派生类构造函数通过成
  • 员初始化列表将基类信息传递给基类构造函数;
  • 派生类构造函数应该初始化派生类新增的数据成员。
    备注:初始化列表只能用于构造函数,如果没有成员初始化列表,程序将使用默认的基类构造函数。

五、 派生类和基类之间的特殊关系

派生类对象可以使用基类的公有方法
基类指针可以不进行显示类型转化的情况下指向派生类对象
基类引用可以不进行显示类型转化的情况下引用派生类对象

基类指针或者引用只能调用基类的方法,不能调用派生类的方法。

	TableTennisPlayer 	player1("Tara", "Boomdea", false);
	RatedPlayer 		rplayer1(1140, "Mallory", "Duck", true);
	//基类引用/指针可以不进行显示类型转化的情况下引用派生类对象/指向派生类对象
	TableTennisPlayer & rt = rplayer1;
	TableTennisPlayer * pt = &rplayer1;
	rt.Name();
	printf("\n");
	pt->Name();

不能将基类对象赋给派生类指针或者引用的原因

派生类继承了基类全部成员变量和方法,派生类是一个特殊的基类,派生类可以用自己特有的成员变量和成员函数,因此当派生类指针和引用访问了基类中不存在的成员变量将出错。

多态公有继承

实现多态公有继承的机制:

  • 在派生类中重新定义基类的方法;
  • 使用虚方法;
    基类:
class Brass
{
private:
	string	fullName;//客户姓名
	long	acctNum;//账号
	double	balance;//余额

public:
	Brass(const string &s = "Nullbody",long an = -1,double bal = 0.0);
	virtual ~Brass();

	void Deposit(double amt);//存款
	virtual void Withdraw(double amt);//取款
	double Balance()const;//当前余额
	virtual void ViewAcct()const;//显示账户信息
};

派生类:

class BrassPlus :
	public Brass
{
private:
	double maxLoan;//最大额度
	double rate;//税率
	double owesBank;//负债

public:
	BrassPlus(const std::string &s = "Nullbody",long an = -1, double bal = 0.0, 
				double ml = 500,double r = 0.11125);
	BrassPlus(const Brass &ba, double ml = 500, double r = 0.11125);
	~BrassPlus();

	virtual void ViewAcct()const;//显示账户信息
	virtual void Withdraw(double amt);//取款
	void ResetMax(double m);
	void ResetRate(double r);
	void ResetOwes();
};

实现多态:

	Brass		Piggy("Porcelot Pigg", 381299, 4000.00);
	BrassPlus	Hoggy("Horatio Hogg", 382288, 3000.00);

	Piggy.ViewAcct();//调用基类方法
	cout << endl;
	Hoggy.ViewAcct();//调用派生类方法
	cout << endl;

虚方法语法:

virtual void ViewAcct()const;//显示账户信息

备注:
1、可以使用类型限定符指出具体使用哪个类的方法
2、如果使用了虚方法,程序将根据引用或者指针的对象的类型来选择方法
3、如果没有使用虚方法,程序将根据引用类型或者指针类型选择方法

虚析构函数

虚析构函数的作用:让析构函数呈现多态,能够释放子类的对象

如果析构函数不是虚的,则将只调用对于指针类型的析构函数;
如果析构函数是虚的,则将调用相应对象类型的析构函数;
使用析构函数可以保证正确的析构函数序列被调用。

静态联编和动态联编

静态联编:编译器在编译过程完成的联编
动态联编:编译器在程序运行时生成的正确的代码
指针和引用兼容性原则原理就是动态联编。

为什么有两种联编以及默认时静态联编的原因

不摒弃静态联编的原因:
效率:如果派生类不重新定义基类的任何方法,不需要使用动态联编;如果类不用做基类,也不需要动态联编。在这些情况下,使用静态联编更合理。效率更高。
概念模型:在设计类时,可能包含一些不在派生类重新定义的成员函数。不定义为虚函数,其效率会很高。因此,如果在派生类中重新定义基类的方法,则将其设置为虚方法;否则,设置为非虚方法。

虚函数原理

编译器处理虚函数的方法:给每个对象添加一个隐形成员。

隐形成员保存了一个指向函数地址数组的指针,这个数组称为虚函数表。
虚函数表里面存放了类对象声明的虚函数的地址。
例如:
基类对象包含一个指针,指向基类的虚函数表;
派生类对象包含一个指针,指向派生类的虚函数表;
如果派生类提供了虚函数的新定义,则将对应的新地址加入虚函数表;如果没有重新定义虚函数,则拷贝基类中虚函数表中对应的函数地址。

虚函数成本分析

每个对象都将增大,增大量为存储地址的空间;
每个类,都要创建虚函数表;
每个函数调用,需要到虚函数表中查找地址

哪些函数可以设置为虚函数,哪些不可以

构造函数不能是虚函数:派生类创建对象的机制决定
虚函数可以为虚函数;
友元不能是虚函数:友元不是类的成员(友元函数调用虚函数)

抽象基类

基本思想:以Ellipse和Circle类为例,从Ellipse和Circle类中抽象出他们的共性,将这些共性放到一个抽象基类里面。然后从该抽象基类派生出Ellipse和Circle类。这样就可以使用基类指针数组管理Ellipse和Circle类对象,即可以使用多态方法。

定义

含有纯虚函数的类,称为抽象类。抽象类不可以创建该类对象。通常被用来设计接口。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值