首页 > 编程语言 > 详细

C++基础:多态

时间:2020-06-22 09:06:35      阅读:70      评论:0      收藏:0      [点我收藏+]

多态

(1)面向对象需求:多态

  1. 问题引出:如果子类定义了与父类中原型相同的函数会发生什么?
class Parent
{
public:
	Parent(int a)	{	this->a = a;	}
	//void print()	{	cout << "Parent print a: " << a << endl;	}
	virtual void print() { cout << "Parent print a: " << a << endl; }
	//virtual 父类写了virtual,子类可写可不写
private:
	int a;
};

class Child : public Parent
{
public:
	Child(int b):Parent(20)	{	this->b = b;	}
	// 子类的和父类的函数名字一样
	void print()	{	cout << "Child print b: " << b << endl;	}
private:
	int b;
};

void printFunc1(Parent* p) {	p->print();	}
void printFunc2(Parent &p) {	p.print();	}

void main()
{
	Parent* p = NULL;
	Parent  p1(10);
	Child   c1(1);

	{
		// 指针
		p = &p1;
		p->print();
		p = &c1;
		p->print();
		// 引用
		Parent &p2 = p1;
		p2.print();
		Parent &p3 = c1;
		p3.print();

		// 指针做函数参数
		printFunc1(&p1);
		printFunc1(&c1);
		// 引用做函数参数
		printFunc2(p1);
		printFunc2(c1);
	}
	// 以上均执行父类的打印函数
	// 如何根据实际传入的对象类型,判断函数的调用,即
	// -->  让一种调用语句 有多种表现形态
	// -->  使用关键字 virtual 

	system("pause");
}
  1. 面向对象新需求

需求:(同样的调用语句有多种不同的表现形态)

? 根据实际的对象类型来判断重写函数的调用;

? 如果父类指针指向的是父类对象则调用父类中定义的函数;

? 如果父类指针指向的是子类对象则调用子类中定义的重写函数 ;

解决:通过 Virtual 关键字对多态进行支持

(2)多态使用

多态是设计模式的基础,多态是框架的基础

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

class C
{
public:
	virtual string code()
	{
		return "OPP:Procedure Oriented Programming";
	}
};

class Cplusplus : public C
{
public:
	virtual string code()
	{
		return "OOP:Object Oriented Programming";
	}
};

string getcode(C* obj)
{
	return obj->code();
}

void main()
{
	C			obj1;
	Cplusplus	obj2;

	std::cout << getcode(&obj1) << std::endl;
	std::cout << getcode(&obj2) << std::endl;
	system("pause");
}
  1. 多态成立的条件

①继承;②函数重写(虚函数);③父类指针(引用)指向子类对象

  1. 多态的理论基础

联编:是指一个程序模块、代码之间互相关联的过程。

静态联编:是程序的匹配、连接在编译阶段实现,也称为早期匹配。例子:函数重载

动态联编:指程序联编推迟到运行时进行,所以又称为晚期联编(迟绑定)。 例子:switch、if、多态

(3) 虚析构函数

虚析构函数用于指引 delete 运算符正确析构动态对象

class A
{
public:
	A()
	{
		p = new char[20];
		strcpy(p, "obja");
		printf("A()\n");
	}
	virtual  ~A()
	{
		delete[] p;
		printf("~A()\n");
	}
private:
	char *p;
};

class B : public A
{
public:
	B()
	{
		p = new char[20];
		strcpy(p, "objb");
		printf("B()\n");
	}
	virtual  ~B()
	{
		delete[] p;
		printf("~B()\n");
	}
private:
	char *p;
};

// 通过父类指针 释放所有的子类资源 --> 执行所以的析构函数
void todelete(A* obj)
{
	delete obj;
}

void main()
{
	// new
	B* obj = new B;
	// delete 直接通过子类对象释放资源
	//delete obj;

	todelete(obj);
	//正常情况下,只执行了父类的析构函数

	system("pause");
}

(4)重载、重写、重定义

  1. 函数重载:

重载是在编译期间根据参数类型和个数决定函数调用;

必须在同一个类中进行;

子类无法重载父类的函数,父类同名函数将被名称覆盖

  1. 函数重写

必须发生于父类与子类之间

并且父类与子类中的函数必须有完全相同的原型(参数类型和个数);

虚函数重写:将发生多态;非虚函数重写:重定义

(5)多态原理

  1. 理论知识

当类中声明虚函数时,编译器会在类中生成一个虚函数表

虚函数表是一个存储类成员函数指针的数据结构;

当存在虚函数时,每个对象中都有一个指向虚函数表的指针(VPTR,一般作为类对象的第一个成员 )

  1. 实现原理

存在虚函数时,每个对象中都有一个指向虚函数表的指针(VPTR 指针)

Base base; ---> 找到VPTR指针 ---> 虚函数表 ---> 执行函数

技术分享图片

通过虚函数表指针 VPTR 调用重写函数是在程序运行时进行的,因此需要通过寻址操作才能确定真正应该调用的函数。

而普通成员函数是在编译时就确定了调用的函数。

// 基类
class Base
{
public:
	Base() {};
	virtual void fun1()
	{
		cout << "Base::fun1()" << endl;
	}
	virtual void fun2() 
	{
		cout << "Base::fun2()" << endl;
	}
	~Base() {};
private:
	int a;
};

// 派生类
class Derived : public Base
{
public:
	Derived() {};
	void fun1()
	{
		cout << "Derived::fun1()" << endl;
	}
	~Derived() {};
private:
	int b;
};

void main()
{
	cout << "Base sizeof: " << sizeof(Base) << endl;
	Base	base;
    
	//对象的前四个字节就是虚函数表		
	int vptr_addr = *(int*)&base;
	printf("base 的虚函数表地址为:%08X\n", vptr_addr);

	//通过函数指针调用函数,验证正确性				
	typedef void(*pFunction)();
	pFunction pFn;

	// 通过vptr指针访问虚表,加上偏移量 取出单个函数地址
	// 执行 Base::fun1()
	int temp1 = *((int*)vptr_addr + 0);
	pFn = (pFunction)temp1;
	pFn();
	// 执行 Base::fun2()
	int temp2 = *((int*)vptr_addr + 1);
	pFn = (pFunction)temp2;
	pFn();

	return;
}
  1. VPTR指针

VPTR,一般作为类对象的第一个成员;

所以 sizeof(Base) 其中有四个字节是 VPTR指针(32位)

  1. 对象中的 VPTR 指针初始化(分步初始化)

对象在创建的时,由编译器对 VPTR 指针进行初始化;

只有当对象的构造完全结束后 VPTR 的指向才最终确定;

父类对象的 VPTR 指向父类虚函数表;子类对象的 VPTR 指向子类虚函数表;

Child c1;
// 初始化是分步的
// 先执行父类的构造函数时, c1的VPTR指向父类的虚函数表
// 当父类的构造函数指向完后,才会把c1的VPTR指向子类的虚函数表
// --> 子类对象构造时,在父类的构造函数调用虚函数,无法产生多态。
  1. 父类指针和子类指针的步长

指针运算是按照指针所指的类型进行的。

如果子类中有新的成员,那么子类的类型大小和父类的类型大小不相同,也就是步长不同。

不要使用 父类指针++的方式操作数组,父类p++与子类p++的步长不同。

p++ --> p=p+1 --> p = (unsigned int)basep + sizeof(*p)

(6)相关题目

  1. 请谈谈你对多态的理解

效果 ---> 实现 ---> 条件 ---> 理论 ---> 意义 ---> 原理

效果:多态使同样的调用语句有多种不同的表现形态;

实现:virtual 关键字,告诉编译器这个函数要支持多态;

条件:继承;virtual 重写、父类指针(引用)指向子类对象;

理论:动态联编

意义:设计模式的基础

原理:虚函数表,函数指针做函数参数

  1. 谈谈 C++编译器是如何实现多态

存在虚函数时,每个对象中都有一个指向虚函数表的指针(VPTR 指针)

Base base; ---> 找到VPTR指针 ---> 虚函数表 ---> 执行函数

  1. 谈谈你对重写,重载理解

  2. 是否可类的每个成员函数都声明为虚函数,为什么?

多态的实现原理,虚函数指向效率低

  1. 构造函数中调用虚函数能实现多态吗?为什么?

对象中的 VPTR 指针初始化(分步初始化);

子类对象构造时,在父类的构造函数调用虚函数,无法产生多态。

  1. 虚函数表指针(VPTR)被编译器初始化的过程,你是如何理解的?

当存在虚函数时,每个对象中都有一个指向虚函数表的指针(VPTR,一般作为类对象的第一个成员 )

  1. 为什么要定义虚析构函数?

虚析构函数用于指引 delete 运算符正确析构动态对象

C++基础:多态

原文:https://www.cnblogs.com/2dx3906/p/13174998.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!