C++多态的实现


前言

本篇文章主要介绍了C++虚函数、动态析构、纯虚函数与抽象类的实现


1.多态的概念

多态的本质: 形式上,使用统一的父类指针做一般性处理, 但是实际执行时,这个指针可能指向子类对象, 形式上,原本调用父类的方法,但是实际上会调用子类的同名方法。

2.虚函数

C++通过虚函数(在成员函数前加virtual关键字,在子类中重载该函数)来实现多态。
多态的本质:
形式上,使用统一的父类指针做一般性处理,但是实际执行时,这个指针可能指向子类对象,
形式上,原本调用父类的方法,但是实际上会调用子类的同名方法。

2.1 虚函数的使用

虚函数的定义:
在函数的返回类型之前使用virtual
只需要在成员函数的声明中添加virtual
虚函数的继承:
1. 如果某个成员函数被声明为虚函数,那么它的子类【派生类】,以及子类的子类中,所继承的这个成员函数,也自动是虚函数。
2. 如果在子类中重写这个虚函数,可以不用再写virtual, 但是仍建议写virtual, 更可读。(或者在声明时加 override 或 final)
3. final 关键字禁止子类重写该函数(声明时使用即可)。
4. override 表示该函数是从父类继承的,增加可读性(声明时使用即可)。

#include <iostream>
using namespace std;
class Person{
public:
    virtual void print(const char *word){
        cout << "person:" << word << endl;
    }
};

class Father:public Person{
public:
    void print(const char *word) override{
        cout << "father:" << word << endl;
    }
};

class Son:public Father{
public:
    void print(const char *word) final{
        cout << "son:" << word << endl;
    }
};


void say(Person *peo, const char *word){
    peo->print(word);
}
//void say(Person &peo, const char *word){
//    peo.print(word);
//}
int main() {
    Father f1;
    Son s1;

    say(&f1,"playing games?");
    say(&s1,"no,I'm learning");

    // say(f1,"playing games?");
    // say(s1,"no,I'm learning");

    return  0;
}

2.2 虚函数表的概念

1. 存在虚函数的类都有一个一维的虚函数表叫做虚表。当类中声明虚函数时,编译器会自动在类中生成一个虚函数表。
2.类的对象有一个指向虚表开始的虚指针。虚表是和类对应的,虚表指针是和对象对应的。
3.虚函数表是一个存储类成员函数指针的数据结构。
4.virtual成员函数会被编译器放入虚函数表中。
5.当存在虚函数时,每个对象中都有一个指向虚函数表的指针(C++编译器给父类对象,子类对象提前布局vptr指针),当进行test(father *base)函数的时候,C++编译器不需要区分子类或者父类对象,只需要再base指针中,找到vptr指针即可)。
6. vptr一般作为类对象的第一个成员。

使用继承的虚函数表构建过程:
1.直接复制父类的虚函数表;
2.如果子类重写了某个虚函数,那么就在这个虚函数表中进行相应的替换;
3.如果子类增加了新的虚函数,就把这个虚函数添加到虚函数表中
如果继承了多个父类(会继承多个虚函数表),如果第一个父类有其他数据成员(存放在栈中),那么第二个虚函数表会排在父类所有成员的后面,子类新加的虚函数放在第一个虚函数表中。

#include <iostream>

using namespace std;

class Person {

public:
	virtual void fun1() {
		cout << __FUNCTION__ << endl;
	}
	virtual void fun2() {
		cout << __FUNCTION__ << endl;
	}
};

class Father : public Person {
public:
	void fun1() override {
		cout << __FUNCTION__ << endl;
	};
	virtual void fun3(){
		cout << __FUNCTION__ << endl;
	}
	int a = 10;
};

class Mather {
public:
	virtual void funMather() {
		cout << __FUNCTION__ << endl;
	}
};

class Son : public Father ,public Mather{
public:
	virtual void fun4() {
		cout << __FUNCTION__ << endl;
	}
	void fun1() final {
		cout << __FUNCTION__ << endl;
	}
	
};


typedef void (*fun)();//定义一个函数指针方便调用虚函数表里的函数


int main(void) {

	//含有虚函数的对象中,最先存储的就是"虚函数表"的地址(即对象名,就是虚函数表的地址)
	Person peo;

	/*
		先拿到虚函数表的地址 将其转换为int类型指针(int *)&peo,
		然后寻址找拿到虚函数表的首个函数地址 *(int *)&peo,
		最后用int *vptr接收拿到的地址 int *vptr = (int *)*(int *)&peo(此时vptr指向的是虚函数表里的首个虚函数)。
	*/
	int *vptr = (int *)*(int *)&peo;

	(fun (*(vptr + 0)))();
	(fun (*(vptr + 1)))();

	/*
		使用继承的虚函数表构建过程:
			1.直接复制父类的虚函数表;
			2.如果子类重写了某个虚函数,那么就在这个虚函数表中进行相应的替换;
			3.如果子类增加了新的虚函数,就把这个虚函数添加到虚函数表中
		如果继承了多个父类(继承多张虚函数表),那么虚函数表按照继承的先后顺序排序(class Son : public Father, public Mather
		先Father 后Mather)在对象的内存中排列。并且子类增加的虚函数会添加到第一个表中。
	
	*/
	cout << "---------一条优雅的分割线-----------" << endl;

	Son son;

	vptr = (int *)*(int *)&son;

	(fun (*(vptr + 0)))();
	(fun (*(vptr + 1)))();
	(fun (*(vptr + 2)))();
	(fun (*(vptr + 3)))();

	int *vptr2 = (int *)*((int *)&son + 2);// int 类型指针加2,拿到vptr2(第二张虚函数表的地址)
	(fun(*vptr2 + 0))();


	system("pause");
	return 0;
}

vs分析类内内存分布(在项目属性的命令行选项中加/d1 reportSingleClassLayout类名):
在这里插入图片描述
虚函数表内存分布:


2.3 虚析构函数(多态中的动态析构)

为了防止内存泄露,最好是在基类析构函数上添加virtual关键字,使基类析构函数为虚函数
目的在于,当使用delete释放基类指针时,会实现动态的析构:
如果基类指针指向的是基类对象,那么只调用基类的析构函数
如果基类指针指向的是子类对象,那么先调用子类的析构函数,再调用父类的析构函数

#include <iostream>

using namespace std;

class Person {
public:
	Person(const char *name = "null") {
		int len = strlen(name) + 1;
		this->name = new char[len];
		strcpy_s(this->name,len, name);
	}

	virtual ~Person() {
		cout << "执行了Person析构函数" << endl;
		if (this->name) {
			delete this->name;
			name = NULL;
		}
	}
protected:
	char *name;
};

class Student : public Person {
public:
	Student(const char *name="null", const char *addr="null") : Person(name) {
		int len = strlen(addr) + 1;
		this->addr = new char[len];
		strcpy_s(this->addr,len, addr);
	}
	~Student() {
		cout << "执行了Student析构函数" << endl;
		if (this->addr) {
			delete this->addr;
			addr = NULL;
		}
	}
private:
	char *addr;
};
int main(void) {
	Person *peo = new Person();
	delete peo;
	Student *stu = new Student();
	delete stu;
	peo = new Student();
	//这里如果Person的析构函数不加virtual,\
	在析构时只会执行Person的析构函数,从而造成内存泄漏
	delete peo;
	system("pause");
	return 0;
}

3. 纯虚函数与抽象类

1.C++ 接口是使用抽象类来实现的。
2.如果类中至少有一个函数被声明为纯虚函数,则这个类就是抽象类。纯虚函数是通过在声明中使用 “= 0” 来指定的。
3.抽象类是不允许被实例化的。

#include <iostream>
#include <Windows.h>

using namespace std;

class Person {
public:
	virtual void fun1() = 0;
};
class Student{
public:
	virtual void fun1() {
		cout << __FUNCTION__ << endl;
	}
};
int main(void) {
	Student stu;
	stu.fun1();
	system("pause");
	return 0;
}


  • 9
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值