理解多态,让不同的“人”做同一件事情会产生不同的结果

以下代码在vs2019下的x86程序中,涉及指针为4bytes。


多态的概念

多态:多种形态。具体一些,就是不同的对象去做同样的行为,会产生出不同的状态。

例如:买车票的时候,当你是学生的时候,可能就会半价;当你是其他人的时候可能就是全价了。

//举例,这个代码可以暂时不用看懂
class Person
{
public:
	virtual void BuyTicket()
	{
		cout << "普通票---全价" << endl;
	}
};
class Student : public Person
{
	virtual void BuyTicket()
	{
		cout << "学生票---半价" << endl;
	}
};
void func(Person& p)
{
	p.BuyTicket();
}
int main(void)
{
	Person p1;
	Student s1;
	//不同的对象做同一件事,得到的状态不同
	func(p1); 
	func(s1);
	return 0;
}

在这里插入图片描述


多态分为:静态的多态、动态的多态。
静态的多态:是在编译时实现的。例如函数重载,看上去像是在调用同一个函数有着不同的行为。
动态的多态:是在运行时实现的。基类的指针或者引用去调用同一个函数,传不同类型的对象会调用不同的函数。

构成多态的条件

1、多态是基于继承的,只有在有继承关系的体系中才能构成多态。
2、必须通过基类的指针或者引用去调用虚函数。
3、被调用的成员必须是虚函数,且派生类必须对基类的虚函数进行重写。

在这里插入图片描述

虚函数:被virtual关键字修饰的非静态类成员。(ps:在返回值前面加virtual)

虚函数和重写

虚函数的重写:派生类中有一个和基类基本相同的函数,这里的基本相同指,同样是虚函数并且返回值、函数名、参数都相同。而函数体内的内容不同,类似于将这个函数重新改写了,这种情况称为派生类的虚函数重写了基类的虚函数

构成多态的一个例外:返回值不同,其他条件满足,并且这两个函数的返回值是继承关系(父子关系)的指针或者引用,这个时候也构成多态。这种情况称为协变


//协变也构成多态
//协变:返回值可以不同,但是必须是继承关系(父子关系)
class Person
{
public:
	virtual Person* BuyTicket()
	{
		cout << "普通票---全价" << endl;
		return nullptr;
	}
};
class Student : public Person
{
	virtual Student* BuyTicket() //派生类中可以不是虚函数
	{
		cout << "学生票---半价" << endl;
		return nullptr;
	}
};
void func(Person& p)
{
	p.BuyTicket();
}
int main(void)
{
	Person p1;
	Student s1;

	func(p1);
	func(s1);
	return 0;
}

构成多态的另一个例外:基类成员是虚函数、而派生类成员可以不是虚函数,也就是不用virtual修饰,但是其他条件还是需要满足的。这样也构成多态,虽然派生类成员没有被virtual修饰,但它是先继承了基类虚函数的属性,再完成的重写,那么它也算是虚函数。

class Person
{
public:
	virtual void BuyTicket()
	{
		cout << "普通票---全价" << endl;
	}
};
class Student : public Person
{
	void BuyTicket() //派生类可以不是虚函数
	{
		cout << "学生票---半价" << endl;
	}
};
void func(Person& p)
{
	p.BuyTicket();
}
int main(void)
{
	Person p1;
	Student s1;

	func(p1);
	func(s1);
	return 0;
}

存在着一个场景,析构函数必须是虚函数:动态申请基类、派生类的对象,如果都交给了基类指针管理,那么析构函数需要是虚函数。

//存在着一种场景,析构函数必须是函数
//基类、派生类对象,都交给了基类指针管理。
class Person
{
public:
	virtual void BuyTicket()
	{
		cout << "普通票---全价" << endl;
	}
	~Person()
	{
		cout << "~Person" << endl;
	}
};
class Student : public Person
{
	virtual void BuyTicket()
	{
		cout << "学生票---半价" << endl;
	}
	~Student()
	{
		cout << "~Student" << endl;
	}
};
void func(Person& p)
{
	p.BuyTicket();
}
int main(void)
{
	//派生类对象交给了基类指针管理
	Person* p2 = new Person;
	Person* s2 = new Student;

	delete p2;
	delete s2;

	return 0;
}

运行结果:p2和s2都是调用基类的析构函数去清理资源。
在这里插入图片描述
派生类对象地址赋值给基类指针,虽然被切片了,但还是需要调用派生类自己的析构函数去清理资源。所以这并没有正确地调用析构函数,我们需要的是,S2调用Student自己的析构函数。析构函数的函数名,编译时会被统一处理成destructor,并且它不需要参数和返回值,所以认为析构函数函数名、参数、返回值相同。那要使得析构函数构成多态,那么可以在用virtual修饰这两个析构函数,或者修饰基类的析构函数就可以了。

class Person
{
public:
	virtual void BuyTicket()
	{
		cout << "普通票---全价" << endl;
	}
	virtual ~Person() //析构函数修饰成虚函数
	{
		cout << "~Person" << endl;
	}
};
class Student : public Person
{
	virtual void BuyTicket()
	{
		cout << "学生票---半价" << endl;
	}
	~Student()
	{
		cout << "~Student" << endl;
	}
};
void func(Person& p)
{
	p.BuyTicket();
}
int main(void)
{
	//派生类对象交给了基类指针管理
	Person* p2 = new Person;
	Person* s2 = new Student;

	delete p2;
	delete s2;

	return 0;
}

在这里插入图片描述
这里调用了两次基类的析构函数,是因为,派生类的析构函数调用之后,会自动调用一次基类的析构函数去析构被继承下来的基类部分。

总结:如果存在切片行为,那么析构函数必须是虚函数,使之构成多态。

关键字override 、final

final:修饰虚函数,表示该虚函数不能被重写。
修饰方法:在基类虚函数参数括号后,函数体之前使用。

//关键字final
class Car
{
public:
	virtual void Drive() final 
	{}
};
class Benz :public Car
{
public:
	virtual void Drive() 
	{ cout << "Benz-舒适" << endl; }
};

在这里插入图片描述
【补充】
使用final修饰类,那么会使得该类不能被继承。修饰方法:在基类类名之后,类体之前使用。

//关键字final
class Car final
{
public:
	virtual void Drive() 
	{}
};
class Benz :public Car
{
public:
	virtual void Drive() 
	{ cout << "Benz-舒适" << endl; }
};

在这里插入图片描述


override:修饰虚函数,检查派生类的虚函数是否重写了基类的某个虚函数,如果没有重写编译报错。
修饰方式:在派生类虚函数参数括号后面,函数体之前使用。

//关键字override
//使用override修饰派生类的虚函数,判断该虚函数是否重写,如果没有重写则会编译报错
class Car
{
public:
	virtual void Drive()
	{}
};
class Benz :public Car
{
public:
	virtual void Drive(int i) override
	{
		//cout << "Benz-舒适" << endl;
	}
};

在这里插入图片描述

重载、重写(覆盖)、重定义(隐藏)区分

  • 重载:
    1、两个函数在同一作用域中
    2、要求函数名相同。参数个数、类型或者顺序不同

  • 重写(覆盖):
    1、两个函数在不同的作用域。一个在基类的作用域、一个在父类的作用域。
    2、函数名、参数、返回值都必须相同(协变除外,返回值可以不同,但是一定是父子关系)
    3、两个函数都是虚函数,或者基类中的那个函数是虚函数。

  • 重定义(隐藏)
    1、两个函数在不同的作用域。一个在基类的作用域、一个在父类的作用域
    2、两个函数的函数名、参数相同
    3、两个基类和派生类的同名函数不构成重写就是重定义

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小小酥诶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值