【C++】面向对象之继承篇

举报
野猪佩奇996 发表于 2022/02/04 23:34:52 2022/02/04
【摘要】 学习总结 (1)实例化Worker对象的时候,到底是先调用谁的构造函数 ——先基类的构造函数,即要想实例化一个派生类,必须先(隐性)实例化一个基类。 ​ 在销毁Worker这个对象的时候,又是先调用谁的...

学习总结

(1)实例化Worker对象的时候,到底是先调用谁的构造函数
——先基类的构造函数,即要想实例化一个派生类,必须先(隐性)实例化一个基类。

在销毁Worker这个对象的时候,又是先调用谁的析构函数——是先子类的析构函数(和构造函数的顺序相反)。

(2)子类继承了父类后,观察是否继承了父类的数据成员和成员函数。Worker对象,子类可以访问父类的成员函数eat(),也可访问自己的成员函数work()

一、继承

工人类属于人类,在定义工人类时为了不重复写部分代码,即两个类具包含关系。

class Worker:public Person{
public:
	void work();
	int m_iSalary;
};

  

PS :继承

(1)内存中的对象

在定义工人类时,就不需要再定义人类共有的属性。
在这里插入图片描述

(2)继承代码实践

/* ************************************************************/
/* 继承
要求:
​ 1.定义Person类
​ 数据成员:姓名(m_strName)和年龄(m_iAge)
​ 成员函数:构造函数、析构函数、eat()函数

​ 2.定义Worker类
​ 公有继承Person类,特有数据成员(工资 m_iSalary)
​ 成员函数:构造函数、析构函数、work()函数

  

​ 目的:
​ 1.实例化Worker对象的时候,到底是先调用谁的构造函数,
​ 在销毁Worker这个对象的时候,又是先调用谁的析构函数

​ 2.子类继承了父类后,观察是否继承了父类的数据成员和成员函数

文件结构为:
在这里插入图片描述

头文件Person.h

#include<string>
using namespace std;
class Person{
public:
	Person();
	~Person();
	void eat();
	string m_strName;
	int m_iAge;
};

  

头文件Worker.h

#include"Person.h"

class Worker:public Person{//Worker共有继承Person类
public:
	Worker();
	~Worker();
	void work();
	int m_iSalary;
};

  

Person.cpp

#include"Person.h"
#include<iostream>
using namespace std;

Person::Person(){
	cout<<"Person()"<<endl;
}
Person::~Person(){
	cout<<"~Person()"<<endl;
}

void Person::eat(){
	cout<<"eat"<<endl;
}

  

Worker.cpp

构造函数、析构函数、work()。

#include"Worker.h"
#include<iostream>
using namespace std;

Worker::Worker(){
	cout<<"Worker()"<<endl;
}
Worker::~Worker(){
	cout<<"~Worker()"<<endl;
}
void Worker::work(){
	cout<<"work()"<<endl;
}

  

主函数demo.cpp

#include<iostream>
#include<stdlib.h>
#include"Worker.h"
using namespace std;

int main(){
	Worker *p=new Worker();//堆
	delete p;
	p=NULL;
	system("pause");
	return 0;
}

  

结果为:
在这里插入图片描述

目的1 :调用顺序

实例化Worker对象的时候,到底是先调用谁的构造函数
——先基类的构造函数,即要想实例化一个派生类,必须先(隐性)实例化一个基类。

在销毁Worker这个对象的时候,又是先调用谁的析构函数——是先子类的析构函数(和构造函数的顺序相反)。

目的2:是否继承

2.子类继承了父类后,观察是否继承了父类的数据成员和成员函数。

#include<iostream>
#include<stdlib.h>
#include"Worker.h"

using namespace std;

int main()
{
    Worker *p = new Worker();
    p->m_strName = "Keiven";
    p->m_iAge = 20;
    p->eat();
    p->m_iSalary = 5000;
    p->work();
    delete p;
    p = NULL;
    system("pause");
    return 0;
}

  

从如下结果,看出Worker对象,子类可以访问父类的成员函数eat(),也可访问自己的成员函数work()
在这里插入图片描述

二、继承方式、隐藏

(1)公有继承

公有继承的方式时,按下表(注意:Protected成员是按照protected继承方式)

基类成员访问属性 继承方式 派生类成员访问属性
private成员 public 无法访问
protected成员 protected
public成员 public

(2)保护继承

保护继承时,按下表(注意:虽然基类的private成员会被继承,但也会无法访问)

基类成员访问属性 继承方式 派生类成员访问属性
private成员 protected 无法访问
protected成员 protected
public成员 protected

(3)私有继承

私有继承时,按下表(注意:虽然基类的private成员会被继承,但也会无法访问)

基类成员访问属性 继承方式 派生类成员访问属性
private成员 private 无法访问
protected成员 private
public成员 private
class Line{
public:
	Line(int x1,int y1,int x2,int y2);
private:
	Coordinate m_coorA;
	Coordinate m_coorB;
};

  

线段类Line只能访问到A点和B点这两个对象的共有数据成员和共有成员函数——Has a关系,即在线段中有一个坐标点的这种包含关系。

私有继承即子类的对象只能访问父类的共有数据和共有的成员函数——私有继承也是一种包含关系(Has a关系)。

(4)继承中的特殊关系

1)隐藏

如果父类A和子类B都定义了一个成员函数ABC(),由于子类B类继承A后即B中也拥有A中的成员函数ABC()
——此时子类B类中的ABC()函数会隐藏掉父类A中的ABC()函数。

隐藏的特性:父子关系、成员同名、隐藏。
(1)在实例化B的对象时,使用该对象只能够直接访问子类B中的ABC()成员函数,而无法访问A类的ABC()。
但是我们能够通过特殊手段访问。

(2)同名的隐藏不仅限于成员函数,还有同名的数据成员——不过父子类的数据成员同名没啥意义,所以少见。

class Person{
public:
	void play();
protected:
	string m_strName;
};
class Soldier:public Person{
public:
	void play();
	void work();
protected:
	int m_iCode;
};

  

上面例子就是父类和子类都有work成员函数,而真正访问的时候:

int main(){
	Soldier soldier;
	soldier.play();
	soldier.Person::play();//注意
	return 0;
}

  
不好的习惯:

让父类的数据成员和子类的数据成员同名,如Person类定义string code,Soldier类定义int code。
——因为2个数据成员都定义在protected下,即实例化的对象无法访问到子类和父类(protected成员是只有子类能访问)——而子类soldier的成员函数去使用code,访问的就是soldier类的code,如code="1234"

如果子类soldier的成员函数要访问父类Person继承下来的code数据成员,就必须使用Person::code="5678"

2)隐藏代码实践

/*******************************/
/* 继承关系中的隐藏
要求:
​ 1. Person类,数据成员:m_strName,成员函数:构造函数、play()
​ 2. Soldier类,数据成员:无,成员函数:构造函数、play()、work()
/*******************************/

  

在这里插入图片描述

头文件
#include<string>
using namespace std;
class Person{
public:
	Person();
	void play();
protected:
	string m_strName;
};


  
#include"Person.h"

class Soldier:public Person{
public:
	Soldier();
	void play();
	void work();
protected:
};

  
源程序

关键在于main函数的访问父类和子类的play()的两种写法

#include"Person.h"
#include<iostream>
using namespace std;

Person::Person(){
	m_strName="Mery";
}
void Person::play(){
	cout<<"Person---play()"<<endl;
	cout<<m_strName<<endl;
}

  
#include"Soldier.h"
#include<iostream>
using namespace std;

Soldier::Soldier(){
}
void Soldier::play(){
	cout<<"Soldier---play()"<<endl;
}
void Soldier::work(){
	cout<<"work()"<<endl;
}

  
#include<iostream>
#include<stdlib.h>
#include"Soldier.h"

using namespace std;
int main(){
	Soldier Soldier;
	Soldier.play();//用子类的play
	Soldier.work();
	Soldier.Person::play();//用父类的play
	system("pause");
}

  
运行结果&分析

在这里插入图片描述
关键在于main函数的访问父类和子类的play()的两种写法

探究是否参数问题

上面是父类和子类的同名函数play()的参数一样(都是无参),这次我们试试参数不同,看是否能有【隐藏】效果。
现对子类Soldier的play函数改为传入一个参数的:

void Soldier::play(int x){
	cout<<"Soldier---play()"<<endl;
}

  

而在主函数这次我们为了看能否用Soldier.play()的无参形式访问到父类的play(),用下面代码发现报错。

#include<iostream>
#include<stdlib.h>
#include"Soldier.h"

using namespace std;
int main(){
	Soldier Soldier;
	Soldier.play();//用子类的play
	Soldier.work();
	Soldier.Person::play();//用父类的play
	system("pause");
}

  
1>------ 已启动生成: 项目: person_soldier, 配置: Debug Win32 ------
1>  demo.cpp
1>c:\users\86493\desktop\王道ds\20210420\person_soldier\demo.cpp(8): error C2660: “Soldier::play”: 函数不接受 0 个参数
========== 生成: 成功 0 个,失败 1 个,最新 0 个,跳过 0==========

  

由上面报错即知不用Soldier.play()的无参形式访问到父类的play(),要访问父类的play()只能用Soldier.Person::play()

数据成员同名

(5)is-a

1)什么是is-a

is-a字面上理解是一个从属派生关系,如“学生是人”,但反过则不一定成立。

在C++中,public继承必须满足is-a关系,如果子类B和父类A满足is-a关系,则A对象能够派上用场的地方,B对象一定可以用,父类表现的是一般化的形式,而子类表示的是特殊化的概念。

public继承意味着is-a。适用于base classes身上的每一件事情一定适用于derived classes身上,因为每一个derived对象也都是base class对象。

【栗子1】
在这里插入图片描述
栗子分析:
(1)第一二行:首先实例化Soldier类的一个对象s1,然后将实例化Person对象p1接收s1(注:语法上是正确的,即一个soldier也是人,可以用soldier初始化人)。

(2)第三行:定义一个的Person指针p2指向Soldier对象s1

(3)第四五行:将人的对象赋值给soldier(s1 = p1),同时用一个soldier的指针指向一个人对象(Soldier *s2 = &p1),这两种写法都有问题。

总结:
(1)派生类的对象可以赋值给基类,基类指针可以指向派生类的对象。
(2)基类的指针 or 基类的对象 or 基类的引用 可以作为函数的参数,来接收传入的子类的对象。

【栗子2】
在这里插入图片描述
栗子分析:
(1)fun1的参数是一个Person指针,即可以指向Person对象,也可以指向子类对象Soldier
(2)fun2可以传入p1s1,因为fun2参数是Person的引用,所以不需要加&符号,直接传对象本身就行。

2)存储结构

在这里插入图片描述
在这里插入图片描述

3)is-a代码实践

继承关系中的隐藏,要求:
​ 1. Person类,数据成员:m_strName,成员函数:构造函数、play()
​ 2. Soldier类,数据成员:m_iAge,成员函数:构造函数、析构函数、work()
​ 3. 定义函数test1(Person p) test2(Person &p) test3(Person *p)
在这里插入图片描述



  

三、多继承和多重继承

(1)多重继承

在这里插入图片描述
如果这三个类在继承的时候,都使用的是public方式,也存在如下关系:
在这里插入图片描述

class Person{
...
};
class Soldier: public Person{
...
};
class Infrantryman: public Soldier{
...
};

  

(2)多继承

(3)多重继承代码实践

(4)多继承代码实践

四、虚继承

(1)多继承+多重继承的烦扰

(2)虚继承

(3)虚继承编码实践

文章来源: andyguo.blog.csdn.net,作者:山顶夕景,版权归原作者所有,如需转载,请联系作者。

原文链接:andyguo.blog.csdn.net/article/details/122775646

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。