C++学习笔记7——继承篇

教程:https://www.imooc.com/learn/426

继承

举例:
在这里插入图片描述
在这里插入图片描述
上例优化:

在这里插入图片描述

关系:
在这里插入图片描述
对应使用,不能交叉

编码示例:
Person.h

#include <string>
using namespace std;

class Person
{
public:
	Person();  
	~Person();
	void eat();

public:
	string m_strName;
	int m_iAge;
};

Person.cpp

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

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

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

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

Worker.h

#include "Person.h"

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

Worker.cpp

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

Worker::Worker()
{
	cout << "Worker()" << endl;
}

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

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

demo.cpp

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

int main()
{
	Worker *p = new Worker();  //从堆中实例化对象
	p->m_strName = "zhang";  //检验是否能使用父类的数据成员及成员函数
	p->m_iAge = 10;
	p->eat();
	p->m_iSalary = 100; //检验是否能使用自身的数据成员及成员函数
	p->work();

	delete p;
	p = NULL;

	/*
	Worker worker;  //从栈中实例化对象
	worker.m_strName = "zhang";  //检验是否能使用父类的数据成员及成员函数
	worker.m_iAge = 10;
	worker.eat();
	worker.m_iSalary = 100; //检验是否能使用自身的数据成员及成员函数
	worker.work();

	//结合上例可知,无论是从堆中还是从栈中实例化对象,在公共继承中,父类的public访问限定符下的数据成员和成员函数都被继承到了子类的public访问限定符下

	*/
	return 0;
}

在这里插入图片描述
由运行结果知,要实例化一个子类(派生类),要先实例化父类(基类)。销毁时逆序

继承方式

在这里插入图片描述

公有继承:

public访问限定符下:
在这里插入图片描述
在使用时,Worker实例化的对象可以调用Person的数据成员和成员函数

protected访问限定符下:
在这里插入图片描述
实例化的对象可以调用类中在public下的eat(),但不能访问在protected下的m_iAge和在private下的m_strName
在这里插入图片描述
在实现成员函数时,可以访问在protected下的m_iAge和在private下的m_strName

在公有继承中:
在这里插入图片描述

private访问限定符下:
在这里插入图片描述
父类的private下的被子类继承到不可见位置,不是子类的private下,所以不可访问m_iAge
在这里插入图片描述
private成员会继承到派生类中,但无法通过派生类直接访问

举例证明:
Person.h

#include <string>
using namespace std;

class Person
{
public:
	Person();  
	~Person();
	void eat();

protected:
	string m_strName;
	int born;
private:
	int m_iAge;
	string tel;
};

Person.cpp

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

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

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

void Person::eat()
{
	m_strName = "zhang";
	m_iAge = 20;
	cout << "eat()" << endl;
}

Worker.h

#include "Person.h"

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

Worker.cpp

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

Worker::Worker()
{
	cout << "Worker()" << endl;
}

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

void Worker::work()
{
	m_strName = "wang";
	born = 2000;
	cout << "work()" << endl;
}

void Worker::mas()
{
	m_iAge = 10;
	tel = "12345";
	cout << "mas()" << endl;
}

demo.cpp

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

int main()
{
	Person person;
	person.eat();
	//程序能正常运行,说明在protected和private访问限定符下定义的数据成员,在其成员函数中是可以访问的

	//person.m_strName = "li";  //该句会报错,说明通过外部对象无法访问。m_iAge也一样

	Worker worker;
	worker.work();  //能正常运行,说明Person中protected的数据成员被继承到Worker中的protected下,可访问

	//worker.mas();  //不能运行,//通过公有继承方式,父类的private下的数据成员继承到子类中,所以也就无法访问
	return 0;
}

保护继承

在这里插入图片描述
private成员会继承到派生类中,但无法通过派生类直接访问

私有继承

在这里插入图片描述
private成员会继承到派生类中,但无法通过派生类直接访问

包含关系,父类包含在子类中

编码示例:

公有继承复习:
Person.h

#include <string>
using namespace std;

class Person
{
public:
	Person();  
	void play();
protected:
	string m_strName;
};

Person.cpp

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

Person::Person()
{
	m_strName = "zhang";
}


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

Soldier.h

#include "Person.h"

class Soldier :public Person
{
public:
	Soldier();
	void work();
protected:
	int m_iAge;

};

Soldier.cpp

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

Soldier::Soldier()
{

}

void Soldier::work()
{
	m_strName = "wang";  //Person类的protected成员
	m_iAge = 20;  //Soldier类的protected成员
	cout << m_strName << endl;  
	cout << m_iAge << endl;  
	cout << "Soldier::work()" << endl;
}

Infantry.h

//步兵类
#include "Soldier.h"

class Infantry :public Soldier
{
public :
	void attack();
};

Infantry.cpp

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

void Infantry::attack()
{
	cout << "Infantry::attack()" << endl;
}

demo.cpp

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

int main()
{
	Soldier soldier;
	soldier.work();  //调用Soldier类(相对Person类来说是子类)的成员函数,间接调用了父类的protected成员m_strName
	soldier.play();  //调用父类的public成员函数
	return 0;
}

在这里插入图片描述
能正常运行

保护继承:
修改:
Soldier.h

#include "Person.h"

class Soldier :protected Person  //保护继承,父类的public和protected成员都会继承到子类的protected下
{  //通过子类的对象,只能访问到子类的public下的数据和函数,不能访问到父类public下的数据和函数
public:
	Soldier();
	void work();
protected:
	int m_iAge;

};

Infantry.cpp

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

void Infantry::attack()
{
	m_strName = "li";  //访问Person类的protected数据成员(被继承到Soldier类的protected下)
	cout << m_strName << endl;
	cout << "Infantry::attack()" << endl;
}

demo.cpp

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

int main()
{
	Soldier soldier;
	soldier.work();  //调用Soldier类(相对Person类来说是子类)的成员函数,间接调用了父类的protected成员m_strName
	//soldier.play();  //调用父类的public成员函数,此时此句报错,因为现在是protected继承,不能访问父类public的成员函数

	Infantry infantry;
	infantry.attack();  

	return 0;
}

infantry.attack();能正常运行,说明Soldier类保护继承Person类,Person类的public和protected成员被继承到Soldier类的protected下,Infantry类公有继承Soldier类(可以访问Soldier类的public和protected成员),所以Infantry类的attack()函数中可以访问Person类的protected成员m_strName

私有继承:
修改:
Soldier.h

#include "Person.h"

class Soldier :private Person  //私有继承,父类的public和protected成员都会继承到子类的private下
{  //理论上,可以在Soldier中直接访问Person的相关数据,但Infantry类公有继承Soldier类后,Infantry类无法直接访问Person类的数据成员和成员函数
public:
	Soldier();
	void work();
protected:
	int m_iAge;
private:

};

Infantry.cpp

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

void Infantry::attack()
{
	m_strName = "li";  //运行demo,出错
	cout << m_strName << endl;
	cout << "Infantry::attack()" << endl;
}

demo.cpp

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

int main()
{
	Infantry infantry;
	infantry.attack();  

	return 0;
}

B类从A类公共派生,那么A类的私有成员函数不能被B类继承并使用。(√)

B类从A类公共派生,那么A类的私有成员函数成为B类的私有成员函数。(×)

隐藏

在这里插入图片描述
B公有继承A,此时B中自己有函数ABC(),又从A中继承到了函数ABC(),A、B中都有函数ABC(),当B的对象直接调用ABC()时,调用到的是B的ABC()无法访问到A的ABC()。这样子叫做同名隐藏。同名隐藏不仅局限于函数,数据成员也有,但少见。

在这里插入图片描述
在这里插入图片描述
第一个调用的是Soldier的play()
第二个调用的是Person的play()

在这里插入图片描述
在这里插入图片描述
编码示例:

隐藏的使用:

Person.h

#include <string>
using namespace std;

class Person
{
public:
	Person();  
	void play();
protected:
	string m_strName;

};

Person.cpp

#include <iostream>
#include "Person.h"  //" " 搜索的是同目录下的头文件,< > 搜索的是系统的默认库
using namespace std;

Person::Person()
{
	m_strName = "zhang";
}


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

Soldier.h

#include "Person.h"

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

};

Soldier.cpp

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

Soldier::Soldier()
{

}

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

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

demo.cpp

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

int main()
{
	Soldier soldier;
	soldier.play();
	soldier.work();
	soldier.Person::play();
	return 0;
}

当同名函数的参数不同时:
修改:
soldier.h

#include "Person.h"

class Soldier :public Person  
{ 
public:
	Soldier();
	void play(int x);
	void work();
protected:
	
private:

};

Soldier.cpp

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

Soldier::Soldier()
{

}

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

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

demo.cpp

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

int main()
{
	Soldier soldier;
	soldier.play(7);
	soldier.work();
	//soldier.play(); //此句错误,不能通过这个调用Person类的play(),系统只会提示不符合Soldier类的play(int x)的格式,缺少参数
	return 0;
}

所以就算同名函数的参数不同,也不能直接调用,父类的还是会被隐藏

修改:同名数据成员
Soldier.cpp

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

Soldier::Soldier()
{

}

void Soldier::play(int x)
{
	cout << "Soldier::play()" << endl;
	cout << m_strName << endl;  //打印出来的是在Soldier类中定义的m_strName
	cout << Person::m_strName << endl; //Person类定义的m_strName
}

void Soldier::work()
{
	m_strName = "Jack";  //给赋值的是Soldier类中定义的m_strName,不是Person类中定义的m_strName
	Person::m_strName = "Jackson";  //Person类定义的m_strName
	cout << "Soldier::work()" << endl;
}

demo.cpp

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

int main()
{
	Soldier soldier;
	soldier.work();
	soldier.play(7);
	return 0;
}

isA

在这里插入图片描述
Person p1 = s1; //用s1实例化p1

派生类的对象可以赋值给基类,可以用基类的指针指向派生类的对象

在这里插入图片描述
可以将基类的指针或基类的对象或基类的引用作为函数的参数,使函数可以接收传入的派生类的对象,并且也可以传入基类的对象

存储结构:

1、将子类的对象赋值给父类的对象(用子类的对象初始化父类的变量)

在这里插入图片描述
赋值时,父类没有的数据成员会丢失

2、父类的指针指向一个子类对象
在这里插入图片描述
父类的指针只能访问到父类原有的数据成员,无法访问到其他子类独有的数据成员

编码示例:
Person.h

#include <string>
using namespace std;

class Person
{
public:
	Person(string name = "Jim");  
	~Person();
	void play();
protected:
	string m_strName;

};

Person.cpp

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

Person::Person(string name)
{
	m_strName = name;
	cout << "Person::Person(string name)" << endl;
}

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


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

Soldier.h

#include "Person.h"

class Soldier :public Person  
{ 
public:
	Soldier(string name = "Jack", int age = 20);
	~Soldier();
	void work();
protected:
	int m_iAge;
private:

};

Soldier.cpp

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

Soldier::Soldier(string name, int age)
{
	m_strName = name;
	m_iAge = age;
	cout << "Soldier::Soldier(string name, int age)" << endl;
}

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

void Soldier::work()
{
	cout << m_strName << endl;
	cout << m_iAge << endl;
	cout << "Soldier::work()" << endl;
}

demo.cpp

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

int main()
{
	Soldier soldier;
	Person p = soldier;  //或: Person p;  p = soldier;  第一个是用soldier初始化Person的对象,第二个是将soldier赋值给Person的对象。两个取得的结果一样
	p.play();

	return 0;
}

在这里插入图片描述
用Soldier实例化Person的对象时,Soldier本身是要执行构造函数的,因为Soldier是子类,所以执行Soldier的构造函数前,会先执行父类Person的构造函数。接下来调用的函数是Person 类的,原来在Person初始化的m_strName = “Jim”,在Soldier类初始化的m_strName = “Jack”,由结果可知,用Soldier初始化Person类后,Soldier中的m_strName的值赋给了Person中的m_strName

修改demo.cpp

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

int main()
{
	Soldier soldier;
	Person p;
	p.play();

	return 0;
}

在这里插入图片描述
可对比结果

修改(改用指针):
demo.cpp

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

int main()
{
	Soldier soldier;
	Person *p = &soldier;
	p->play();
	//p->work();  //报错,说明使用Person的对象或指针,只能调用Person自有的数据成员和成员函数,无法调用子类的数据成员和成员函数
	return 0;
}

在这里插入图片描述
所以,无论是用对象赋值的方式,还是用指针指向的方式,如果用父类接收子类对象的值或指向子类对象,打印出来的都是子类对象的值

修改:
demp.cpp

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

int main()
{
	Person *p = new Soldier;
	p->play();

	delete p;
	p = NULL;

	return 0;
}

在这里插入图片描述
用父类的指针指向子类的对象时,子类的对象会进行实例化,实例化的过程会先调用父类的构造函数,再调用子类的构造函数。当delete销毁时,只执行父类了的析构函数,这样子类没有析构,可能造成内存泄露,要修改,用虚析构函数。

虚析构函数:当存在继承关系时,使用父类的指针指向堆中的子类的对象(用new实例化,用->调用的),还想使用父类的指针去释放内存,这时就需要虚析构函数

virtual ~Person();  //虚析构函数,virtual是可以被继承的,所以即便在Soldier类的析构函数不写virtual,该析构函数也是虚析构函数

修改:
Person.h

#include <string>
using namespace std;

class Person
{
public:
	Person(string name = "Jim");  
	virtual ~Person();
	void play();
protected:
	string m_strName;

};

Soldier.h

#include "Person.h"

class Soldier :public Person  
{ 
public:
	Soldier(string name = "Jack", int age = 20);
	virtual ~Soldier();
	void work();
protected:
	int m_iAge;
private:

};

在这里插入图片描述
可见虽然demo.cpp中并没有delete Soldier类的对象,但是Soldier类的析构函数还是被调用了

编码举例2(体现函数参数传递关系)
demo.cpp

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

void test1(Person p)
{
	p.play();
}

void test2(Person &p)
{
	p.play();
}

void test3(Person *p)
{
	p->play();
}


int main()
{
	Person p;
	Soldier s;

	test1(p);  //传入的是Person的对象,输出结果是Jim
	test1(s);  //传入的是Soldier的对象,输出结果是Jack
	//可见,如果函数参数是基类的对象,基类的对象和派生类的对象都可以作为实参传递进来,并能够正常使用

	return 0;
}

在这里插入图片描述
结果可见,先调用了Person的构造函数两次,再调用Soldier的构造函数,这是因为先实例化一个Person的对象,调用一次Person的构造函数,然后实例化一个Soldier的对象(Soldier继承Person),所以先后调用一次Person的构造函数和Soldier的构造函数。

test1定义的参数是一个对象,所以传值时会先实例化一个临时对象p,通过临时对象p调用play(),在test1这个函数执行完毕时,临时对象p会自动销毁,这就是为什么在两次调用play()时,都会分别执行一次Person的析构函数。

修改(把两个test1改为test2)
在这里插入图片描述
实例化Person对象(第一行),实例化Soldier对象(2、3行),调用test2,是引用,所以在传入参数时,会给参数起别名p,通过别名p调用play(),在test2执行过程中,没有实例化临时对象,所以没有销毁临时对象,其他输出结果与test1输出结果一致。说明使用基类的引用也可以接收基类的对象和派生类的对象

修改为:

test3(&p);
test3(&s);

在这里插入图片描述
输出结果与test2一致,因为test3的参数是基类的指针,当使用基类或派生类的对象地址传入后,会使用指针p分别调用基类或派生类的play()

对比test1、test2、test3,使用2、3不会产生新的临时对象,效率更高。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值