继承/虚函数/多态/虚析构———(学习笔记)


正片开始

继承

掌握要点:
  1. 继承的写法

    1. 单继承:class 类名 :权限 父类名{};
    2. 多继承:class 类名 :权限 父类1名,权限 父类2名,父类3名…{};
  2. 子类通过什么样的权限继承

    1. 以public权限继承:则父类中public下所有内容,会在子类中public下有一份,同理父类中protected下的所有内容会在子类中protected下有一份。
      public权限继承的特点:(在父类中是什么权限,在子类中就以相同的权限继承)
    2. 以protected权限继承:则父类中public下所有内容,会在子类中protected下有一份,父类中protected下的所有内容会在子类中protected下有一份。
      protected权限继承的特点:(将父类中的public下的所有内容继承到子类的protected下)
    3. 以private权限继承:则父类中public和protected下所有内容,会在子类中private下有一份。
      private权限继承的特点:(将父类中的public和protected下所有内容继承到子类的private下)
  3. 构造函数:

    1. 子类在创建对象时会优先调用父类的构造函数,再调用子类构造函数。
    2. 要先子类创建无参对象,父类中的构造函数必须存在无参的调用形态。

注意点:无论子类是用什么权限继承的,父类中的私有属性在子类中是无法访问的,并且子类创建对象也无法访问父类中的私有熟悉,父类中的私有属性只有在父类中才能访问。

单继承的实现
代码展示
#include<iostream>
#include<string>
using namespace std;

class A
{
public:
	int age;
protected:
	int money;
	string name;
};

class B:public A
{
public:
	B(int age, int money, string name,int num)
	{
		//因为B继承了A,A的数据会在B中有一份可以这种方式初始化A的数据
		this->age = age;
		this->money = money;
		this->name = name;
		//初始化B的数据
		this->num = num;
	}
	void print()
	{
		cout << "age = " << age << endl;
		cout << "money = " << money << endl;
		cout << "name = " << name << endl;
		cout << "num = " << num << endl;
	}
public:
	int num;
};

int main()
{

	B b(18, 1000, "蟑螂", 1014);
	b.print();

	return 0;
}
输出结果
age = 18
money = 1000
name = 刘亦辰
num = 1014
多继承的实现
#include<iostream>
#include<string>
using namespace std;

class A
{
public:
	int age;
protected:
	int money;
	string name;
};

class A1
{
public:
	int A1_age;
protected:
	int A1_money;
	string A1_name;
};

class B:public A,public A1
{
public:
	B(int age, int money, string name, int A1_age, int A1_money, string A1_name, int num)
	{
		//因为B继承了A,A的数据会在B中有一份可以这种方式初始化A的数据
		this->age = age;
		this->money = money;
		this->name = name;
		//因为B继承了A1,A1的数据会在B中有一份可以这种方式初始化A1的数据
		this->A1_age = A1_age;
		this->A1_money = A1_money;
		this->A1_name = A1_name;
		//初始化B的数据
		this->num = num;
	}
	void print()
	{
		cout << "这是A的数据" << endl;
		cout << "age = " << age << endl;
		cout << "money = " << money << endl;
		cout << "name = " << name << endl<<endl;
		cout << "这是B的数据" << endl;
		cout << "num = " << num << endl<<endl;
		cout << "这是A1的数据" << endl;
		cout << "A1_age = " << age << endl;
		cout << "money = " << money << endl;
		cout << "name = " << name << endl;
	}
public:
	int num;
};

int main()
{
	B b(18,1000,"小丑",20,20000,"小帅",1014);
	b.print();
	return 0;
}
多继承中构造和析构的顺序问题
#include<iostream>
#include<string>
using namespace std;

class A
{
public:
	A()
	{
		cout << "A" << endl;
	}
	~A()
	{
		cout << "A" << endl;
	}
};

class B
{
public:
	B()
	{
		cout << "B" << endl;
	}
	~B()
	{
		cout << "B" << endl;
	}
};

class C
{
public:
	C()
	{
		cout << "C" << endl;
	}
	~C()
	{
		cout << "C" << endl;
	}

};

class D:public A,public B, public C
{
public:
	D()
	{
		cout << "D" << endl;
	}
	~D()
	{
		cout << "D" << endl;
	}
};

int main()
{
	D d;  //输出结果 A B C D D C B A
	return 0;
}

在多太继承中,常考点,那就是调用构造函数和析构函数的顺序问题,在我们创建子类对象的时候,会优先调用父类的构造函数,再调用自身的构造函数,那么当父类比较多的时候,是按照什么顺序来调用各个父类的构造函数的呢?我们来看看子类继承的写法 class D:public A,public B, public C{};其实调用顺序与我么们创建子类时写的父类顺序有关,从左自右依次调用父类的构造函数,当构造函数调用完了后就是析构函数了,析构函数调用顺序与构造函数相反,所以我们看见上面输出的结果是:构造函数->A B C D,析构函数->D C B A

虚继承
在多继承中,难免会碰到如下情况,A类中有数据a,B类和C类是A的继承类,此时a自然也会在B类和C类中有一份,然后D类又是C类和B类的继承类, D类自然有一份a,那么这份a是来自C类中的还是B类中的呢,就不得而知了,为了解决此类事件,产生了我们现在所说的虚继承

在这里插入图片描述

虚继承听着很抽象,到底什么是虚继承呢?在C类和B类中在父类前面加上修饰词virtual 此时C类和B类就成了虚继承了,是不是听起来很简单,我也这么觉得,在D类的初始化参数列表中需要初始化A类,此时数据a就来自A类,而不是来自B,C类了,然后此问题就完美解决了。

我们来看看代码

#include<iostream>
#include<string>
using namespace std;

class A
{
public:
	A(int a):a(a){}
	int a;
};
//虚继承
class B:virtual public A
{
public:
	B(int a,int b):A(a),b(b){}
	int b;
};
//虚继承
class C :virtual public A
{
public:
	C(int a, int c) :A(a), c(c) {}
	int c;
};

class D :public B, public C
{
public:
	D():B(1,1),C(2,2),A(3){} //这位置必须要调用A类
	void print()
	{
		//只有a来自A类,b和c来自B类和C类
		cout << "a = " <<a  << endl;
		cout << "b = " << b << endl;
		cout << "c = " << c << endl;
	}
};

int main()
{
	D d;
	d.print();
	return 0;
}

虚函数和纯虚函数和抽象类的介绍

1.虚函数

小知识点:

  1. 一个空类所占内存大小为1个字节
  2. 当一个类中只有虚函数时,无论存在多少个虚函数,此类所站内存大小都只与你在多少位平台下有关(列如:你在64位平台下,此时这个类所占内存大小就是8个字节;在32位平台下,这个内就占4个字节)
  3. 当一个类中有虚函数时,用这个类无论创建多少个对象,这些对象的内存中都用的是一份二级指针

问:你应该很好奇在一个类中只有虚函数存在的情况下为啥这个类所占大小会与多少位平台挂钩吧?
解:因为这个类的内存中存放着二级指针,这个二级指针又存放着一级指针的地址,一级指针存放着函数的地址

内存

在这里插入图片描述

虚函数的写法:在函数前面加上virtual关键字,此函数就是虚函数。

验证虚类是否真的存在二级指针,一级指针是否存在函数的地址

//x64平台下,地址8个字节
#include<iostream>
#include<string>
using namespace std;

class A
{
public:
	
	virtual void print1()
	{
		cout << "我是虚函数1" << endl;
	}
	virtual void print2()
	{
		cout << "我是虚函数2" << endl;
	}
};


int main()
{
	A a;
	//需要将a对象的地址转换成long long**是因为此指针向后偏移取8个字节
	long long** p =(long long **) & a;
	/* p[0]取前8个字节的内容,则拿到一级指针的地址,此时p[0]的类型为long
	 long*,p[0][0]则拿到虚函数的地址,此时的地址为long long 类型,
	 需要转换成函数指针类型 */
	auto q = (void(*)())p[0][0];  //q拿到虚函数的地址
	q(); //调用虚函数1
	q = (void(*)())p[0][1];
	q(); //调用虚函数2

	return 0;
}
输出结果
我是虚函数1
我是虚函数2

经过调式发现用虚类创建的对象中确实存在着二级指针

在这里插入图片描述

  1. 纯虚函数

注意要点:

  1. 纯虚函数的写法:virtual 返回类型 函数名()= 0;
  2. 具有纯虚函数的类是无法创建对象的,但是可以创建指针如:类型* p = NULL;
  3. 如果父类有纯虚函数,继承此父类的子类也无法创建对象,除非在子类将纯虚函数实现例如:父类的纯虚函数是:virtual void print() = 0;,则子类的实现写法是:virtual void print(){}; virtual 可写可不写。

抽象类:具有纯虚函数的类为抽象类。

多态

注意要点:

1.父类没虚函数:

  1. 当父类指针拿到父类对象的地址时,父类指针会指向父类。
  2. 当父类指针拿到子类对象地址时,父类指针还是会指向父类。

2.父类有虚函数:

  1. 当父类指针拿到父类对象的地址时,父类指针会指向父类。
  2. 当父类指针拿到子类对象地址时,父类指针会指向子类。

总结:
3. 父类没虚函数时,我们看指针类型,就知道它指向谁。
4. 父类有虚函数时,我们看对象类型,就知道它指向谁。

代码实现
此时是父类没虚函数的情况

#include<iostream>
#include<string>
using namespace std;

class A
{
public:
	void print()
	{
		cout << "A" << endl;
	}
};

class B :public A
{
public:
	void print()
	{
		cout << "B" << endl;
	}
};

int main()
{
	A* pa = new A;
	pa->print();
	pa = new B;
	pa->print();
	
	return 0;
}
输出结果
A
A

此时是父类有虚函数的情况

#include<iostream>
#include<string>
using namespace std;

class A
{
public:
	//虚函数
	virtual void print()
	{
		cout << "A" << endl;
	}
};

class B :public A
{
public:
	void print()
	{
		cout << "B" << endl;
	}
};

int main()
{
	A* pa = new A;
	pa->print();
	pa = new B;
	pa->print();
	
	return 0;
}
输出结果
A
B

虚析构

#include<iostream>
#include<string>
using namespace std;

class A
{
public:
	virtual void print()
	{
		cout << "A" << endl;
	}
	~A()
	{
		cout << "A被析构啦~" << endl;
	}
};

class B :public A
{
public:
	void print()
	{
		cout << "B" << endl;
	}
	~B()
	{
		cout << "B被析构啦~" << endl;
	}
};

int main()
{
	A* pa = new B;
	delete pa;
	return 0;
}
输出结果
A被析构啦~

你会放发现我开辟了B类,结果却没释放刚刚开辟的内存,只释放了父类中的内存,这样会导致内存泄露。针对这一情况,C++引入了虚析构函数很好的解决了此问题

看代码

#include<iostream>
#include<string>
using namespace std;

class A
{
public:
	virtual void print()
	{
		cout << "A" << endl;
	}
	virtual ~A()
	{
		cout << "A被析构啦~" << endl;
	}
};

class B :public A
{
public:
	void print()
	{
		cout << "B" << endl;
	}
	~B()
	{
		cout << "B被析构啦~" << endl;
	}
};

int main()
{
	A* pa = new B;
	delete pa;
	return 0;
}

输出结果
B被析构啦~
A被析构啦~

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值