《C++ primer plus》精炼(OOP部分)——对象和类(8)

学习是一项持续的投资,永远不白费——本杰明·富兰克林

第13章:类继承

一个基类和派生类

从一个类派生出另一个类时,原始类称为基类,继承类称为派生类。以下是一个简单的基类:

class Person
{
private:
	string name;
public:
	Person(){name="none";};
	Person(string s)
	{
		name=s;
	}
	~Person(){};
	void show()
	{
		cout<<name;
	}
}

这是一个表示人的简约的类,接下来我们用这个人来派生一个学生类:

class Student:public Person
{
private:
	int grade;
public:
	Student():Person()
	{
		grade=0;
	}
	Student(string s,int g):Person(s)
	{
		grade=g;
	}
	void s_show()
	{
		show();
		cout<<grade;
	}
}
  1. 要使用类继承,在类声明中需要使用如下格式:

class 派生类:public/protected/private 基类

其中public表示公有继承,protected表示保护继承,private表示私有继承,在本章我们只考虑公有继承。

  1. 在继承中,派生类自动包含基类的所有属性和方法,但派生类中的方法不能直接访问基类的私有成员,而要通过基类提供的方法来访问,例如:
Student():Person()
{
	grade=0;
}

这是Student类的默认构造函数。对于派生类和基类的逻辑关系,可以用下面这张图来表示:
在这里插入图片描述
所以当要构造一个Student类的时候,从概念上要先构造出其中的Person类,所以要在构造函数执行到函数体之前将Person类初始化,即必须选用列表初始化语法。而且因为派生类不能直接访问基类的私有属性,因此下面的代码是错误的:

Student():name("none")//不能直接访问name属性
{
	grade=0;
}
  1. 使用时,可以用基类指针来指向派生类:
int main(void)
{
	Person* p[2];
	p[0]=Person();//指向一个Person类对象
	p[1]=Student();//也可以指向一个Student类对象
}

注意,这一规则是单向的,也就是说不能把派生类指针指向基类,这是因为编译器看到Person类指针后,会认为这个指针指向的对象拥有Person类声明的方法和属性,下面的代码显然是错误的:

int main(void)
{
	Student* s[2];
	s[0]=Student();//可以通过编译
	s[1]=Person();//不能通过编译
	s[0]->s_show();//因为s[0]指向的是一个Student类对象,自然可以调用这个方法
	s[1]->s_show();//但s[1]指向一个Person对象,因此显然是不行的

另外,如果用Person类指针指向了Student类对象,也只能用对象中从Person类继承下来的方法:

int main(void)
{
	Person* p[2];
	p[0]=Person();//指向一个Person类对象
	p[1]=Student();//也可以指向一个Student类对象
	p[1]->s_show();//错误,不能通过编译,即使指向一个Studnet类对象
}

对于形参为指向基类的指针的函数,也可以用派生类的地址作为实参,因此也可以用派生类对象来初始化基类对象(实际上是使用了复制构造函数),实际效果为将派生类对象中的基类部分拷贝到新的基类对象上。

公有继承的逻辑关系:is-a

当派生类is a基类时,适合使用公有继承,这就是公有继承的逻辑关系,例如在上面的例子中,Student is a Person.
在这里插入图片描述

多态公有继承

在弄清Person类和Student类的关系后,我们可以把这种关系扩展到其他职业:
在这里插入图片描述
同样的,因为teacher is a person,headmaster is a person,也可以使用公有继承。可以试着自己写一写代码,笔者的代码如下:

class Person
{
private:
	string name;
public:
	Person(){name="none";};
	Person(string s)
	{
		name=s;
	}
	virtual ~Person(){};
	virtual void show()
	{
		cout<<name;
	}
}

class Student:public Person
{
private:
	int grade;
public:
	Student():Person()
	{
		grade=0;
	}
	Student(string s,int g):Person(s)
	{
		grade=g;
	}
	virtual void show()
	{
		Person::show();
		cout<<grade;
	}
}

class Teacher:public Person
{
private:
	int number;//负责哪个班级的班号,简单即可
public:
	Teacher():Person()
	{
		number=0;
	}
	Teacher(string s,int n):Person(s)
	{
		number=n;
	}
	virtual void show()
	{
		Person::show();
		cout<<number;
	}
}

class HeadTeacher:public Person
{
private:
	int s_number;//负责哪个学校的代号
public:
	HeadTeacher():Person()
	{
		number=0;
	}
	HeadTeacher(string s,int n):Person(s)
	{
		s_number=n;
	}
	virtual void show()
	{
		Person::show();
		cout<<s_number;
	}
}

这里需要注意的就是virtual关键字。被virtual关键字当要将基类的方法在派生类中重写时,可以使用这个关键字。当使用这个关键字时,如果一个对象由指针引用,那么这个对象调用函数时,所调用的函数由这个对象的实际类决定。另外,因为进行了函数重写,因此在调用基类的方法时需要使用::运算符。下面是这四个类的测试代码:

Person* p[4];
p[0]=Person();
p[1]=Student();
p[2]=Teacher();
p[3]=HeadTeacher();

p[0]->show();//调用Person类中的show函数
p[1]->show();//调用Student类中的show函数
p[2]->show();//调用Teacher类中的show函数
p[3]->show();//调用HeadTeacher类中的show函数

另外,如果将函数的声明和定义分开,那么virtual关键字只在类声明的时候加上即可。
用一个基类指针的数组指向不同派生类的对象,当执行同样的代码时,实际行为会随被执行的对象类型而变化,这就是多态。多态保证了代码的重用性,使得程序员不需要频繁修改测试代码,所以多态也是继封装和继承后OOP中第三个重要的思想。
最后,在Person类中的构造函数也被声明为虚函数,这是因为如果不这样声明,那么编译器会按照指针类型而非实际对象类型来析构数组p所指向的对象,这会导致内存泄漏,所以即使Person类析构函数不进行任何操作,也要声明为虚函数。
请添加图片描述
我是霜_哀,在算法之路上努力前行的一位萌新,感谢你的阅读!如果觉得好的话,可以关注一下,我会在将来带来更多更全面的知识讲解!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

霜_哀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值