【C++】面向对象之继承篇
学习总结
(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
可以传入p1
和s1
,因为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
- 点赞
- 收藏
- 关注作者
评论(0)