我们知道类有三大特性:封装、继承和多态。封装在前面已经介绍完。本篇开始将会介绍继承。继承是在封装基础上的扩展,也是多态的一个承接。
总结:
- 类的组合关系:将一个类的对象作为另外一个类的成员
Class CStudent{ ... public:/private: CPerson m_Per ... }
- 类的组合关系的访问方法
- 类对象放到
public
进行访问,使用stu.m_per.SetGender(123);
的方式访问 - 类对象放到
private
,使用公共访问权限的函数进行访问:stu.SetGender(123);
- 继承:一个类可以继承另外一个类中的东西,即为继承。
- 继承的概念:
A类是B的儿子
A被称为子类,B被称为父类或者A被称为派生类,B被称为基类
- 关系:子类 is-a 父类 (子类是一个父类的…,学生是一个人)
- 代码形式为:
class CStudent :public CPerson
1. 学生管理系统的传统写法
以一个学生管理系统为例,按照之前的思路,需要创建不同的类,然后将类组合在一起
。
创建学生类,老师类,这两个类都是有姓名和性别的成员变量,此时就造成了冗余,易造成错误,可能让人嗅到坏坏的味道。
其代码如下:
#include <iostream>
//面向对象:继承
//学生与老师具有人的共有特征
class CStudent {
public:
CStudent() {
}
~CStudent() {
}
int GetGender() {
return m_nGender;
}
void SetGender(int nGender) {
m_nGender= nGender;
}
private:
char m_nszName[255];
int m_nGender;
};
//增加老师管理系统,老师的数据与行为和学生是类似的,但也有学号,老师号的不同
//如果重建一个老师类就会造成冗余,易造成错误
class CTeacher {
public:
CTeacher() {
}
~CTeacher() {
}
int GetGender() {
return m_nGender;
}
void SetGender(int nGender) {
m_nGender = nGender;
}
private:
char m_nszName[255];
int m_nGender;
};
int main(int argc, char* argv[])
{
CStudent stu;
return 0;
}
除了学生和老师类,如果需要再增加宿管阿姨的信息,那就得再创建一个类,同样的代码会反复出现,可能就带来问题,如果同类型的一个地方出错,就需要在代码不同位置修改,十分不利于程序编写。
2. 类的组合关系
将一个类的对象作为另外一个类的成员这种关系称之为 类的组合关系
针对上面的问题,如何解决?
解决思路,出现上面的问题的原因是:代码出现了重复,这是因为学生和老师存在共同的部分。
但是除了共性部分,学生可能有学生ID m_nStuID
,老师有老师ID m_nTeaID
,对于相同的代码我们使用代码去描述,不同的地方加以区别
。
学生和老师共有人的特征(名字和性别),因此将其 重复共有的部分单独提取
出来写出一个人类。将人作为学生和老师类的属性和特点-将一个类的对象作为另外一个类的成员这种关系称之为 类的组合关系
。
类的组合关系的访问方法
- 类对象放到
public
进行访问,使用stu.m_per.SetGender(123);
的方式访问
#include <iostream>
class CPerson
{
public:
CPerson() {
}
~CPerson() {
}
int GetGender() {
return m_nGender;
}
void SetGender(int nGender) {
m_nGender = nGender;
}
private:
char m_nszName[255];
int m_nGender;
};
class CStudent {
public:
CStudent() {
}
~CStudent() {
}
CPerson m_per;//组合关系:将一个类的成员作为另一个类的成员(共用人的共性)
private:
int m_nStuID;
};
//增加老师管理系统,老师的数据与行为和学生是类似的,但也有学号,老师号的不同
//如果重建一个老师类就会造成冗余,易造成错误
class CTeacher {
public:
CTeacher() {
}
~CTeacher() {
}
CPerson m_per;//组合关系:将一个类的成员作为另一个类的成员(共用人的共性)
private:
int m_nTeaID;
};
int main(int argc, char* argv[])
{
CStudent stu;
stu.m_per.SetGender(123);
return 0;
}
- 类对象放到
private
,使用公共访问权限的函数进行访问:stu.SetGender(123);
#include <iostream>
class CPerson
{
public:
CPerson() {
}
~CPerson() {
}
int GetGender() {
return m_nGender;
}
void SetGender(int nGender) {
m_nGender = nGender;
}
private:
char m_nszName[255];
int m_nGender;
};
class CStudent {
public:
CStudent() {
}
~CStudent() {
}
void SetGender(int nGender) {
m_per.SetGender(nGender);
}
private:
CPerson m_per;//组合关系:将一个类的成员作为另一个类的成员(共用人的共性)
int m_nStuID;
};
//增加老师管理系统,老师的数据与行为和学生是类似的,但也有学号,老师号的不同
//如果重建一个老师类就会造成冗余,易造成错误
class CTeacher {
public:
CTeacher() {
}
~CTeacher() {
}
private:
CPerson m_per;//组合关系:将一个类的成员作为另一个类的成员(共用人的共性)
int m_nTeaID;
};
int main(int argc, char* argv[])
{
CStudent stu;
stu.SetGender(123);
return 0;
}
上面的过程虽然一定程度上实现了公共属性的复用,只是将关系进行了简单的组合,还是有些繁琐。
2.1 类的组合关系中内存释放问题
首先我们需要了解,如果是栈上的对象,出作用域编译器会自动调用析构函数,并进行内存释放,但是这里的内存释放,不是说立马就将内存中的数据进行销毁,而是将改内存空间标记为不可用,后期会重新分配使用,以下是我用基本数据类型进行的实验。
int a
原先所占的栈空间,在出{}
作用域之后,数据并没有立刻销毁。
说这个是为了演示上面CStudent stu;
和组合对象CPerson m_per;
都是创建在栈上的,那么出作用域之后也会自动调用析构函数,并进行内存释放,但是像上面所说 “不是说立马就将内存中的数据进行销毁,而是将改内存空间标记为不可用,后期会重新分配使用”,演示结果如下:
如果CStudent类的对象是创建在堆上,而组合对象CPerson m_per;
是创建在栈上的,再进行析构的时候会是什么样的结果呢?
可以看到CPerson m_per;
中的空间也是会自动销毁,并且看起来是立即生效的,理解这个对以后创建组合关系是很有用处的。
上面在做delete stu
之后,应该要改为以下代码,否则会创造出野指针,也就是内存释放了,但是stu指向的确实一个无效内存。
if (nullptr!==stu)
{
delete stu;
stu = nullptr;
}
3. 类的继承
为了使得重复属性的使用更为简单,提出了继承的语法
继承的性质:
-
继承的概念:
A类是B的儿子
-
A被称为子类,B被称为父类或者A被称为派生类,B被称为基类
-
关系:子类 is-a 父类 (子类是一个父类的…,学生是一个人)
-
代码形式为:
class CStudent :public CPerson
#include <iostream>
//面向对象:继承
//学生与老师具有人的共有特征
class CPerson
{
public:
CPerson() {
}
~CPerson() {
}
int GetGender() {
return m_nGender;
}
void SetGender(int nGender) {
m_nGender = nGender;
}
private:
char m_nszName[255];
int m_nGender;
};
class CStudent : public CPerson
{
public:
CStudent() {
}
~CStudent() {
}
private:
int m_nStuID;
};
class CTeacher : public CPerson
{
public:
CTeacher() {
}
~CTeacher() {
}
private:
int m_nTeaID;
};
int main(int argc, char* argv[])
{
CStudent stu;
stu.SetGender(123);
return 0;
}
运行结果:stu
对象调用了父类CPerson
中的成员函数
4.学习视频地址:C++57个入门知识点_27 继承的概念