多态理解以及重载、重写和重定义的区别

目录

  • 多态条件
  • 多态原理
    • 虚函数表
  • 重载、覆盖(重写)、隐藏(重定义)的对比
    • 重载
    • 重写(覆盖)
    • 重定义(隐藏)

首先多态可以理解为:同一事件的不同形态
例如:买票这一事件,普通人买票就是全价票,学生买票是半价票,军人买票是优先买票。

多态条件

  1. 前提:继承
  2. 有虚函数
  3. 子类重写父类的虚函数
  4. 调用此函数的类型是基类的指针、引用
class Person
{
public:
	//虚函数:virtual + 正常的函数定义
	virtual void BuyTicket()
	{
		cout << "买票-全价" << endl;
	}
	//一般基类的析构函数都定义为虚函数,避免内存泄漏的问题
	virtual ~Person()
	{
		cout << "~Person()" << endl;
	}
};

class Student : public Person
{
public:
	//虚函数重写:子类定义了一个和父类接口完全相同的函数
	//重写的虚函数:函数名,参数列表,返回值类型和父类对应的函数完全一致
	virtual void BuyTicket()
	{
		cout << "买票-半价" << endl;
	}
	//即使前面不加virtual 也会有虚函数属性
	//都是虚函数,在底层和父类析构函数名相同,构成虚函数重写,构成多态
	~Student()
	{
		delete[100] ptr;
		cout << "~Student()" << endl;
	}
protected:
	char* ptr = new char[100];
};
//多态:看对象
void fun(Person& p)
{
	p.BuyTicket();
}
//非多态:看类型
void fun2(Person p)
{
	p.BuyTicket();
}

void test()
{
	Person p;
	Student s;
	fun(p);
	fun(s);
	//构成多态,指向子类对象,先调用子类的析构,再调用父类析构
	Person* p1 = new Student;
	delete p1;
	Student* p2 = new Student;
	delete p2;
}

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

协变:返回值类型可以不同,但是必须是有继承关系的指针/引用

class A
{};
class B: public A
{};
class Person
{
public:
	//虚函数:virtual + 正常的函数定义
	//协变:返回值类型可以不同,但是必须是有继承关系的指针/引用
	virtual A* BuyTicket()
	{
		cout << "买票-全价" << endl;
		return new A;
	}
};

class Student : public Person
{
public:
	//虚函数重写:子类定义了一个和父类接口完全相同的函数
	//重写的虚函数:函数名,参数列表,返回值类型和父类对应的函数完全一致
	virtual B* BuyTicket()
	{
		cout << "买票-半价" << endl;
		return new B;
	}
};

final 在父类中使用:表示父类的虚函数不能再被子类重写
虚函数+final:此函数不能被子类重写

class A
{
	//子类不能再对该函数进行重写
	virtual void fun() final
	{
		cout << "A::fun()"<<endl;
	}
}

override 通常在子类中使用:表示会自动检查必须重写父类的一个函数
虚函数+override:强制重写父类的一个虚函数

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

class B:public A
{
public:
	virtual void fun() override
	{
		cout<<"B::fun()"<<endl;
	}
}

接口继承和实现继承
普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数。

多态原理

虚函数表

  1. 只要类中有虚幻书,类对象中都会有一个虚表指针成员:_vfptr :二级指针–>函数指针指针
  2. 虚表是虚函数指针数组,需表中不存放普通函数的指针
  3. 子类会继承父类的虚表
  4. 子类的需表中,如果有重写的虚函数,则对应的函数指针也会被子类的虚函数指针覆盖(地址的覆盖)
  5. 虚表没有存放在对象中,虚表一般存放在代码段

获取虚表地址

//定义vfptr为函数指针类型
typedef void(*vfptr)();

//vfTable代表虚表指针,指向虚表的首元素地址,为二级指针
//虚表内存放的都是虚函数指针
void printVTable(vfptr vfTable[])
{
	cout << "虚表地址:"<< vfTable << endl;
	//定义一个二级指针fptr也指向虚表首元素地址
	vfptr* fptr = vfTable;
	//做解引用,拿到一个虚函数指针
	while(*fptr != nullptr)
	{
		//虚函数指针(),虚函数指针指向的函数开始执行
		(*fptr)();
		//往下移动,指向下一个虚函数指针
		++fptr;
	}
}

void test()
{
	Person p;
	cout << "Person vfptr: ";
	vfptr* vftable = (vfptr*)(*(int*)&p);
	printVtable(vftable)}

多继承:

  • 子类中虚表的个数和直接父类的个数一致
  • 子类新定义的虚函数,其虚函数指针存放在第一个直接父类的虚表中

重载、覆盖(重写)、隐藏(重定义)的对比

重载

  • 两个函数在同一作用域
  • 函数名/参数相同

重写(覆盖)

  • 两个函数分别在基类和派生类的作用域
  • 函数名/参数/返回值都必须相同(协变例外)
  • 两个函数必须是虚函数

重定义(隐藏)

  • 两个函数分别在基类和派生类的作用域
  • 函数名相同
  • 两个基类和派生类的同名函数不构成重写就是重定义

你可能感兴趣的:(c++,数据结构)