【C++】多态以及多态原理

本文详细介绍了C++中的多态概念,包括虚函数、重写、协变和析构函数的特殊情况。讨论了构成多态的条件,如必须通过基类指针或引用调用虚函数,以及虚函数在单继承和多继承下的表现。同时,文章通过面试题探讨了多态在实际问题中的应用和理解难点。
摘要由CSDN通过智能技术生成

多态概念

多态的概念:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态

构成多态的条件

那么在继承中要构成多态还有两个条件:

  1. 必须通过基类的指针或者引用调用虚函数
  2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写。

什么是虚函数?

虚函数:即被virtual修饰的类成员函数称为虚函数。

class Person {
public:
virtual void BuyTicket() { cout << "买票-全价" << endl;}
};

什么是重写?

构成重写的条件

  1. 基类和派生类的函数都是虚函数
  2. 函数名、参数、返回值要求全部一样。(区别重载、重定义的重要区别)

虚函数的重写(覆盖):派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了基类的虚函数。

虚函数重写的例外

协变

派生类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变

class A{};
class B : public A {};
class Person {
public:
virtual A* f() {return new A;}
};
class Student : public Person {
public:
virtual B* f() {return new B;}
}

析构函数的重写

如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同。虽然函数名不相同,看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor。

建议将基类的析构函数都加上virtual,如果不加,特殊情况下,可能会内存泄漏。

class Person {
public:
	// 各种开后门特例,本质都是为了析构函数构成重写
	// 因为如果不构成重写,特殊情况下,可能会存在内存泄露
	virtual ~Person() { cout << "~Person()" << endl; }
};

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

int main()
{
	//Person p;
	//Student s;
	Person* p1 = new Person;
	delete p1;

	Person* p2 = new Student;
	delete p2;

	return 0;
}

只有派生类Student的析构函数重写了Person的析构函数,下面的delete对象调用析构函数,才能构成多态,才能保证p1和p2指向的对象正确的调用析构函数。

构成多态的原理

先来看虚函数类中是怎样的表现形式?
在这里插入图片描述
虚函数表何时生成?存放在哪里?
在这里插入图片描述
单继承不重写时,单继承且重写(覆盖)
在这里插入图片描述
多态的原理:
还记得多态的条件吗?
用基类的指针或引用去调用。为什么呢?

在这里插入图片描述

  1. 静态绑定又称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也称为静态多态,比如:函数重载
  2. 动态绑定又称后期绑定(晚绑定),是在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数,也称为动态多态。

多继承下的多态

多继承下,类的虚表指针就作为类成员,会被继承下去。

如果派生类有没重写的虚函数,vs下会把这个未重写虚函数指针放在派生类自己,第一个声明的基类的虚表下。

面试难题

面试题1:以下程序输出什么?

class A
{
public:
	A() :m_iVal(0){ test(); }
	virtual void func() { std::cout << m_iVal << ' '; }
	void test(){ func(); }
public:
	int m_iVal;
};


class B : public A
{
public:
	B(){ test(); }
	virtual void func()
	{
		++m_iVal;
		std::cout << m_iVal << ' ';
	}
};


int main(int argc, char* argv[])
{
	A*p = new B;
	p->test();
	return 0;
}

分析:new B时先调用父类A的构造函数,执行test()函数,在调用func()函数,由于此时还处于对象构造阶段,多态机制还没有生效,所以,此时执行的func函数为父类的func函数,打印0,构造完父类后执行子类构造函数,又调用test函数,然后又执行func(),由于父类已经构造完毕,虚表已经生成,func满足多态的条件,所以调用子类的func函数,对成员m_iVal加1,进行打印,所以打印1, 最终通过父类指针p->test(),也是执行子类的func,所以会增加m_iVal的值,最终打印2, 所以答案为 0 1 2。

面试题2:以下程序输出什么?

class A
{
public:
	virtual void func(int val = 1){ std::cout << "A->" << val << std::endl; }
	virtual void test(){ func(); }
};
class B : public A
{
public:
	void func(int val = 0){ std::cout << "B->" << val << std::endl; }
};
int main(int argc, char* argv[])
{
	B*p = new B;
	p->test();
	return 0;
}

答案:B->1。

是不是特别奇怪?

分析:

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值