当前位置: 首页 > news >正文

C++三大特性之多态

一、多态的定义及实现

1.1、多态的概念

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

1.2、多态的构成条件

多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。比如Student继承了Person。Person对象买票全价,Student对象买票半价。
  在继承中要构成多态还有两个条件:

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

1.2.1、虚函数的重写

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

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

上面的派生类Student 的 BuyTicket() 与Person 的 BuyTicket() 构成了重写。注意:在重写基类虚函数时,派生类的虚函数在不加virtual关键字时,虽然也可以构成重写(因为继承后基类的虚函数被继承下来了在派生类依旧保持虚函数属性),但是该种写法不是很规范,不建议这样使用。

1.2.2、析构函数的重写(基类与派生类析构函数名字不同)

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

1.3、C++11 override和final

1、final:修饰虚函数,表示该虚函数不能再被重写
class Car
{
public:
	virtual void Drive() final {}
};
class Benz :public Car
{
public:
	virtual void Drive() { cout << "Benz-舒适" << endl; }
};
2、override:检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。
class Car {
public:
	virtual void Drive() {}
};
class Benz :public Car {
public:
	virtual void Drive() override { cout << "Benz-舒适" << endl; }
};

二、多态的原理

2.1、虚函数表

class Base
{
public:
	virtual void Func1()
	{
		cout << "Func1()" << endl;
	}
private:
	int _b = 1;
};
 
int main()
{
	Base a;
	cout << sizeof(a) << endl;
	return 0;
}

输出:8

 我们看到,除了_b成员,还多一个__vfptr放在对象的前面(注意有些平台可能会放到对象的最后面,这个跟平台有关),对象中的这个指针我们叫做虚函数表指针(v代表virtual,f代表function)

具体代码如下:

class Base
{
public:
	virtual void Func1()
	{
		cout << "Base::Func1()" << endl;
	}
	virtual void Func2()
	{
		cout << "Base::Func2()" << endl;
	}
	void Func3()
	{
		cout << "Base::Func3()" << endl;
	}
private:
	int _b = 1;
};
class Derive : public Base
{
public:
	virtual void Func1()
	{
		cout << "Derive::Func1()" << endl;
	}
private:
	int _d = 2;
};
 
int main()
{
	Base b;
	Derive d;
	return 0;
}

2.2、多态的原理

class Person {
public:
	virtual void BuyTicket() { cout << "买票-全价" << endl; }
};
class Student : public Person {
public:
	virtual void BuyTicket() { cout << "买票-半价" << endl; }
};
void Func(Person* p)
{
	p.BuyTicket();
}
int main()
{
	Person Mike;
	Func(&Mike);
	Student Johnson;
	Func(&Johnson);
	return 0;
}

通过上面对虚表的学习,我们也大概清楚了每个对象都有属于自己的虚表。而自己的虚表中存储的是自己的虚函数。在调用时,会到指针所指向的对象的虚表中找到对应的虚函数进行调用。具体我们可看下图:

 2.3、静态绑定与动态绑定

1、静态绑定: 静态绑定是在编译时确定调用的函数或方法,它是通过函数或方法的名称、参数数量、类型和顺序来匹配确定的。对于非虚拟函数和静态成员函数,默认情况下都是静态绑定。例如,在以下代码中:

class Base {
public:
    void display() {
        std::cout << "Base class" << std::endl;
    }
};
 
class Derived : public Base {
public:
    void display() {
        std::cout << "Derived class" << std::endl;
    }
};
 
int main() {
    Base baseObj;
    Derived derivedObj;
    
    baseObj.display();      // 静态绑定,输出 "Base class"
    derivedObj.display();   // 静态绑定,输出 "Derived class"
}

2、动态绑定: 动态绑定是指在运行时确定调用的函数或方法,它是通过虚拟函数和指针/引用来实现的。虚拟函数是在基类中声明为虚拟的成员函数,在派生类中进行重写。通过使用基类的指针或引用调用虚拟函数时,实际调用的是派生类中重写的函数。例如,在以下代码中:

class Base {
public:
    virtual void display() {
        std::cout << "Base class" << std::endl;
    }
};
 
class Derived : public Base {
public:
    void display() {
        std::cout << "Derived class" << std::endl;
    }
};
 
int main() {
    Base* basePtr;
    Derived derivedObj;
 
    basePtr = &derivedObj;
    basePtr->display();   // 动态绑定,输出 "Derived class"
}

三、抽象类

所谓的抽象类,在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。我们可结合如下例子理解:

class Car
{
public:
	virtual void Drive() = 0;
};
class Benz :public Car
{
public:
	virtual void Drive()
	{
		cout << "Benz-舒适" << endl;
	}
};
class BMW :public Car
{
public:
	virtual void Drive()
	{
		cout << "BMW-操控" << endl;
	}
};
void Test()
{
	Car* pBenz = new Benz;
	pBenz->Drive();
	Car* pBMW = new BMW;
	pBMW->Drive();
}

相关文章:

  • Mission Planner MP地面站添加Cesium三维地图
  • 我国公共数据授权运营的实践调查与展望——目标定位、行动要素、政策保障及平台支持
  • JavaScript 简单类型与复杂类型-简单类型的内存分配
  • 【DeepSeek开发】Python实现纽约房价热力图
  • 基于LangChain4j调用火山引擎DeepSeek R1搭建RAG知识库实战指南
  • FreeRTOS-计数型信号量
  • 【OpenCV C++】图像增强:三种锐化方式,图像清晰度增强
  • TCP基本入门-简单认识一下什么是TCP
  • 关于“你对 Spring Cloud 的理解”
  • SpringBoot 中的 Redis 序列化
  • 【全栈开发】从0开始搭建一个图书管理系统【一】框架搭建
  • DeepSeek开源周 Day04:从DualPipe聊聊大模型分布式训练的并行策略
  • 微信小程序细小知识累计记录
  • 1. HTTP 数据请求
  • 期权帮|国内期权交易投资人做卖出期权价差交易收取的保证金是单边的还是双向的?
  • SpringBoot3—快速入门
  • 大白话css第三章实践与提升
  • seacmsv9报错注入管理员账号密码,order by 注入,如何解决 information_schema关键字被过滤掉了
  • AF3 _make_msa_df函数解读
  • Java语言Leetcode中常用的一些基础语法
  • 全国林业院校校长论坛举行,聚焦林业教育的创新与突破
  • “GoFun出行”订单时隔7年扣费后续:平台将退费,双方已和解
  • 大外交丨3天拿下数万亿美元投资,特朗普在中东做经济“加法”和政治“减法”
  • 北方将现今年首场大范围高温天气,山西河南山东陕西局地可超40℃
  • 美国务卿鲁比奥抵达会场,将参加俄乌会谈
  • 涉案资金超2亿元 “健康投资”骗局,专挑老年人下手