C++特性之多态

本文详细介绍了C++中的多态概念及其实现原理。探讨了虚函数、虚函数表及虚函数表指针的作用,以及如何通过重写实现多态。同时,通过实例展示了多态在实际编程中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

什么是多态?

同一个方法在派生类和基类中的不同行为称为多中形态,简称多态。换句话来说,就是方法的行为应取决于调用该方法的对象。

如何实现多态?

1. 多态构成的条件:
  • 必须在继承体系中;
  • 调用函数的对象必须是基类的指针或者基类的引用;
  • 被调函数必须是虚函数,并且派生类对完成了虚函数的重写;

下面为大家解释一下什么是虚函数,以及虚函数的重写;

  • 虚函数

简单来说,就是被virtual关键字修饰的类的成员函数;
在虚函数声明的后边加上“==0”,表示该虚函数为纯虚函数,派生类继承之后必须重写;

class Base
{
	public:
		//虚函数
		virtual void Test(){};
};
  • 虚函数的重写

派生类在继承基类虚函数的基础上,对虚函数做了一定修改而形成的虚函数的过程被称为重写。重写也被称为覆盖;
重写的虚函数和被重写的虚函数必须满足:函数名,参数,返回值都相同(有个例外:协变)。

//虚函数的重写
class Base
{
	public:
		virtual void Test()
		{
			cout<<"Base::Test()"<<endl;
		};
};
class Derived:public Base
{
	public:
		virtual void Test()
		{
			cout<<"Derived::Test()"<<endl;
		};
};

在派生类中重写的虚函数前可以不用加virtual关键字,这样也是构成重写的。

  • 虚函数重写的例外:协变
    协变和普通的虚函数的重写的区别就是:
    协变重写的虚函数的返回值可以不同,但是必须分别是基类的指针和派生类指针(或者基类的引用和拍派生类引用)。
class A{};
class B:public A{};

class Base
{
	public:
		virtual A* func()
		{
			cout<<"Base::func()"<<endl;
		};
};
class Derived:public Base
{
	public:
		virtual B* func()
		{
			cout<<"Derived::func()"<<endl;
		}
};

重载,重写,重定义的对比:
在这里插入图片描述

2. 多态的原理:

要了解多态的原理,就需要了解虚函数表虚函数表指针

  • 虚函数表
    虚函数表顾名思义,就是保存虚函数地址的表,本质上就是一个保存虚函数指针的指针数组;这个指针数组最后面放了一个nullptr 。
    基类中虚函数表和派生类中的虚函数表有什么不同呢?
#include<iostream>
using namespace std;
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;
}

在这里插入图片描述

通过上边的例子可以得出:继承自含有虚函数的类的派生类中也有虚函数表;
派生类的虚函数表的生成过程:
a. 先将基类的虚表内容拷贝一份到派生类的虚表中;
b. 如果派生类重写了某个继承下来的虚函数,那么在派生类会将自己重写的虚函数覆盖掉自己虚表中被重写的虚函数;
(虚函数的重写叫也做覆盖,覆盖是原理层的叫法)
c. 派生类自己增加的虚函数按照声明的次序增加到派生类虚表的后边;

总结:

  1. 虚表可以继承,如果子类的虚函数没有重写,那么,子类虚表中保存的该函数的地址仍然是父类的虚函数的地址;
  2. 派生类的虚表中虚函数地址的排列顺序和基类的虚表中虚函数地址排列顺序相同。

注意:虚函数表中保存的是虚函数的指针,不是虚函数;而虚函数表保存到内存中的代码区;对象通过虚表指针访问虚表;

  • 虚函数表指针
#include<iostream>
using namespace std;
class Base
{
public:
	virtual void Func1()
	{
		cout << "Func1()" << endl;
	}
private:
	int _b = 1;
};
int main()
{
	Base b;
	cout << sizeof(b) << endl;
    return 0;
}

在这里插入图片描述
上面的例子证明了一个含有虚函数的类的对象不仅仅只包括成员变量,还会有一个叫做 _vfptr的指针。而这个指针就是虚函数表指针,也叫做虚表指针;所以这个指针指向的东西就叫做虚函数表;
类中存在虚函数时,每个对象中都有一个指向虚函数表的指针(_vfptr)。

下面这个例子完美体现了多态;

#include<iostream>
using namespace std;
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; 
}

在这里插入图片描述
动态绑定是多态的基础!

C++的多态性用一句话概括就是:在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数。如果对象类型是派生类,就调用派生类的函数;如果对象类型是基类,就调用基类的函数。

<think>嗯,用户想了解C++中的继承、多态和接口相关的用法,特别是实现和使用教程。首先,我需要回忆一下C++的这些概念。继承是面向对象编程中的一个核心概念,允许子类继承父类的属性和方法。多态则是通过虚函数实现的,使得不同子类对象可以对同一方法有不同的实现。接口在C++中通常通过纯虚函数和抽象类来实现,类似于Java中的接口。 用户提供的三个引用里提到了一些关键点。引用1提到多态性允许根据对象实际类型调用不同函数版本,这涉及到虚函数表和动态绑定。引用2给出了一个具体的代码示例,展示了基类A1和派生类B1、C1中的虚函数重写。引用3指出子类可以拥有父类的非私有属性和方法,并可以扩展或重写它们。 用户可能需要一个结构化的教程,涵盖基本概念、语法、接口实现方法以及实际应用示例。首先应该解释继承的基本用法,比如公有继承、保护继承和私有继承的区别,但通常主要使用公有继承。然后是多态,需要详细说明虚函数的作用,以及如何通过基类指针或引用调用派生类的方法。接口的实现部分需要介绍纯虚函数和抽象类,因为C++没有专门的接口关键字,但可以通过纯虚类模拟接口。 接下来要考虑用户可能的疑问点。例如,虚函数表的工作原理,这在引用1和2中都有涉及,可能需要简要解释。另外,构造函数和析构函数在继承中的注意事项,特别是虚析构函数的重要性,避免资源泄漏。还有override和final关键字的用法,这些C++11引入的特性可以帮助避免错误,提高代码可读性。 实际应用场景方面,可以举例说明多态在框架设计中的用途,比如插件系统或GUI事件处理,这些地方需要统一的接口但不同的实现。性能方面,可能需要提到虚函数调用会有一定的开销,但通常可以忽略,除非在极端性能敏感的场景。 最后,生成相关问题时,需要考虑用户可能进一步想知道的内容,比如虚函数表的具体结构,多态的性能影响,设计模式中的应用,以及如何避免常见的陷阱如对象切片等。这些问题可以帮助用户深入理解并正确应用这些概念。</think>### C++继承与多态详解:接口实现与应用 #### 一、继承基础 1. **核心概念** - 子类继承父类非`private`成员属性和方法[^3] - 继承类型:`public`/`protected`/`private`(常用`public`) - 代码示例: ```cpp class Animal { public: void eat() { cout << "Eating" << endl; } }; class Cat : public Animal { public: void meow() { cout << "Meow!" << endl; } }; ``` 2. **内存结构** 子类对象包含父类子对象,内存布局为:父类成员在前,子类新增成员在后[^1] #### 二、多态实现 1. **虚函数机制** - 虚函数声明:`virtual void func()` - 虚函数表(vtable)存储函数指针 - 代码示例: ```cpp class Shape { public: virtual void draw() = 0; // 纯虚函数 virtual ~Shape() {} // 虚析构函数 }; class Circle : public Shape { public: void draw() override { cout << "Drawing circle" << endl; } }; ``` 2. **多态调用** 通过基类指针/引用实现动态绑定: ```cpp Shape* shape = new Circle(); shape->draw(); // 输出"Drawing circle" delete shape; ``` #### 三、接口实现 C++通过纯虚函数实现接口: ```cpp class IPrintable { public: virtual void print() const = 0; virtual ~IPrintable() = default; }; class Document : public IPrintable { public: void print() const override { cout << "Printing document" << endl; } }; ``` #### 四、关键实践技巧 1. **override关键字** 明确表示覆盖父类虚函数,增强代码可读性: ```cpp class Derived : public Base { public: void func() override { ... } }; ``` 2. **虚析构函数** 必须为多态基类声明虚析构函数,防止内存泄漏: ```cpp class Base { public: virtual ~Base() = default; }; ``` 3. **final关键字** 禁止类被继承或方法被重写: ```cpp class FinalClass final { ... }; class Base { public: virtual void func() final { ... } }; ``` #### 五、典型应用场景 1. GUI框架:不同控件统一事件处理接口 2. 插件系统:通过基类接口扩展功能 3. 算法策略:运行时选择不同算法实现
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值