C++基础学习(七)-多态

1 静态联编和动态联编

1.1 多态分类

1.1.1 静态多态

  • 函数重载

1.1.2 动态多态

  • 虚函数 继承关系

1.2 静态联编

1.2.1 地址早绑定 编译阶段绑定好地址

1.3 动态联编

1.3.1 地址晚绑定

1.4 多态

父类的引用或指针指向子类对象

1.5 综合实例

#include<iostream>
using namespace std;

class Animal
{
public:
	virtual void speak()
	{
		cout << "动物说话" << endl;
	}
};

class Cat :public Animal
{
public:
	void speak()
	{
		cout << "小猫说话" << endl;
	}
};
// 调用doSpeak,speak函数的地址早就绑定好了,早绑定,静态联编,编译阶段就确定好了地址
// 如果想调用猫的speak,不能提前绑定好函数的地址了,所以需要在运行时候再去确定函数地址
// 动态联编,写法doSpeak方法改为虚函数,在父类上声明虚函数,发生了多态
// 父类的应用或者指针 指向 子类对象
void doSpeak(Animal &animal)
{
	animal.speak();
}

void test()
{
	// 如果发生了继承的关系,编译器允许进行类型转换
	Cat cat;
	doSpeak(cat);
}

int main()
{
	test();
	return EXIT_SUCCESS;
}

输出:

小猫说话

2 多态原理解析

2.1 当父类中有了虚函数后,内部结构就发生了变化

2.2 内部多了一个 vfprt

  • virtual function pointer 虚函数表指针
  • 指向vftable 虚函数表

2.3 父类中结构 vfptr &Animal::speak

还是上一个程序中类Animal,这里是通过vs 2017的开发人员命令提示符,如下:
在这里插入图片描述

2.4 子类中 进行继承 会继承 vfptr vftalbe

这里也是上一个程序中类Cat:
在这里插入图片描述

2.5 构造函数中 会将虚函数表指针 指向自己的虚函数表

2.6 如果发生了重写,会替换掉虚函数表中的原有的speak,改为&Cat::speak

下面对比两个程序,第一种类Cat中,没有与Animal同名的函数,第二个中有一个同名的函数。分别通过vs 2017的开发人员命令提示符来演示这两个效果比对:

#include<iostream>
using namespace std;

class Animal
{
public:
	virtual void speak()
	{
		cout << "动物说话" << endl;
	}
};

class Cat :public Animal
{
public:
};
// 调用doSpeak,speak函数的地址早就绑定好了,早绑定,静态联编,编译阶段就确定好了地址
// 如果想调用猫的speak,不能提前绑定好函数的地址了,所以需要在运行时候再去确定函数地址
// 动态联编,写法doSpeak方法改为虚函数,在父类上声明虚函数,发生了多态
// 父类的应用或者指针 指向 子类对象
void doSpeak(Animal &animal)
{
	animal.speak();
}

void test()
{
	// 如果发生了继承的关系,编译器允许进行类型转换
	Cat cat;
	doSpeak(cat);
}

int main()
{
	test();
	return EXIT_SUCCESS;
}

在这里插入图片描述


    #include<iostream>
using namespace std;

class Animal
{
public:
	virtual void speak()
	{
		cout << "动物说话" << endl;
	}
};

class Cat :public Animal
{
public:
	void speak()
	{
		cout << "小猫说话" << endl;
	}
};
// 调用doSpeak,speak函数的地址早就绑定好了,早绑定,静态联编,编译阶段就确定好了地址
// 如果想调用猫的speak,不能提前绑定好函数的地址了,所以需要在运行时候再去确定函数地址
// 动态联编,写法doSpeak方法改为虚函数,在父类上声明虚函数,发生了多态
// 父类的应用或者指针 指向 子类对象
void doSpeak(Animal &animal)
{
	animal.speak();
}

void test()
{
	// 如果发生了继承的关系,编译器允许进行类型转换
	Cat cat;
	doSpeak(cat);
}

int main()
{
	test();
	return EXIT_SUCCESS;
}

输出:
在这里插入图片描述

2.7 深入剖析,内部到底如何调用

((void(*)()) (*(int*)*(int*)animal))();

在这里插入图片描述

3 抽象类 和 纯虚函数

3.1 纯虚函数写法

virtual void func() = 0;

3.2 抽象类

如有一个类是抽象类,其他类继承这个抽象类,并且没有重写抽象类中的纯虚函数,那么,继承过来的类也是抽象类

3.3 抽象类 不可以实例化对象

3.4 如果类 继承了抽象类,必须重写抽象类中的纯虚函数

3.5 综合实例

#include<iostream>
using namespace std;

class abstractCalcutor
{
public:
	// 虚函数
	// virtual int getResult() {return 0;}
	// 纯虚函数
	// 如果父类中有了 纯虚函数 子类继承父类,就必须要实现 纯虚函数
	// 如果父类中有了纯虚函数,这个父类 就无法实例化对象了
	// 这个类有了纯虚函数,通常又称为 抽象类  
	virtual int getResult() = 0;

	void setv1(int v)
	{
		this->val1 = v;
	}
	void setv2(int v)
	{
		this->val2 = v;
	}

	int val1;
	int val2;
};
// 如果父类中有了 纯虚函数 子类继承父类,就必须要实现 纯虚函数
class A :public abstractCalcutor
{
public:
	virtual int getResult()
	{
		return 0;
	}

};

void test()
{
	A a;
	// abstractCalcutor b;
}

int main()
{
	test();
	return EXIT_SUCCESS;
}

4 虚析构和纯虚析构

4.1 虚析构

写法:virtual ~类名() {}
解决问题:通过父类指针指向子类对象释放时候不干净导致的问题
例子:

#include<iostream>
using namespace std;

class Animal
{
public:
	virtual void speak()
	{
		cout << "动物在说话" << endl;
	}
	// 普通析构 是不会调用子类的析构的,所以可能会导致释放不干净
	// 利用虚析构来解决这个问题
	virtual ~Animal()
	{
		cout << "Animal的析构函数" << endl;
	}
};

class Cat:public Animal
{
public:
	Cat(const char* name)
	{
		this->m_Name = new char[strlen(name) + 1];
		strcpy(this->m_Name, name);
	}
	virtual void speak()
	{
		cout << "小猫在说话" << endl;
	}

	char* m_Name;
	~Cat()
	{
		cout << "Cat的析构调用" << endl;
		if (this->m_Name != NULL)
		{
			delete[] this->m_Name;
			this->m_Name = NULL;
		}
	}
};

void test()
{
	Animal *animal = new Cat("TOM");

	animal->speak();
	delete animal;
}

int main()
{
	test();
	return EXIT_SUCCESS;
}

输出:

小猫在说话
Cat的析构调用
Animal的析构函数

4.2 纯虚析构函数

写法:virtual ~类名() = 0;
类内声明 类外实现
如果出现了纯虚析构函数,这个类也是抽象类,不可以实例化对象
例子:

#include<iostream>
using namespace std;

class Animal
{
public:
	virtual void speak()
	{
		cout << "动物在说话" << endl;
	}
	// 普通析构 是不会调用子类的析构的,所以可能会导致释放不干净
	// 利用虚析构来解决这个问题
	/*virtual ~Animal()
	{
		cout << "Animal的析构函数" << endl;
	}*/
	// 纯虚函数 写法
	// 纯虚函数,需要声明 还需要实现 类内声明,类外实现
	// 如果函数中出现了 纯虚析构函数,那么这个类也算抽象类
	// 抽象类 不可实例化对象
	virtual ~Animal() = 0;
};

Animal::~Animal()
{
	// 纯虚函数实现
	cout << "Animal的纯虚析构调用" << endl;
}

class Cat:public Animal
{
public:
	Cat(const char* name)
	{
		this->m_Name = new char[strlen(name) + 1];
		strcpy(this->m_Name, name);
	}
	virtual void speak()
	{
		cout << "小猫在说话" << endl;
	}

	char* m_Name;
	~Cat()
	{
		cout << "Cat的析构调用" << endl;
		if (this->m_Name != NULL)
		{
			delete[] this->m_Name;
			this->m_Name = NULL;
		}
	}
};

void test()
{
	Animal *animal = new Cat("TOM");
	// 抽象类不能实例化
	// Animal a;
	animal->speak();
	delete animal;
}

int main()
{
	test();
	return EXIT_SUCCESS;
}

输出:

小猫在说话
Cat的析构调用
Animal的纯虚析构调用

5 向上类型转换和向下类型转换

5.1 基类转派生类

向下类型转换 不安全

5.2 派生类转基类

向上类型转换 安全

5.3 发生多态总是安全的

示意图:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_42711815/article/details/89106205