Cpp-虚函数

虚函数,虚析构函数,纯虚函数,抽象类
虚函数
定义:在函数前加virtual,例子void virtual demo();
作用:多态的重要部分
一般来说父类中的函数被子类函数重写必须采用虚函数。
class Shape
{
public:
    virtual double calcArea(){...}//虚函数
    ....                                      //其他部分
private:
    ....
};
....
class Circle:public Shape
{
public:
    Circle(double r);
    virtual double calcArea();//此处的virtual不是必须的,如果不加,系统会自动加
                                        //上,如果加上则会在后续的时候看的比较明显(推荐加上)
    ....
private:
    ....
};
....
class Rect:public Shape
{
    Rect(double width,double height);
    virtual double calcArea();
private
    ....
};
....
虚函数在使用中的限制(重要):
  • 普通函数不能是虚函数,也就是说这个函数必须是某一个类的成员函数,不可以是一个全局函数,否则会导致编译错误。
  • 静态成员函数不能是虚函数 static成员函数是和类同生共处的,他不属于任何对象,使用virtual也将导致错误。
  • 内联函数不能是虚函数 如果修饰内联函数 如果内联函数被virtual修饰,计算机会忽略inline使它变成存粹的虚函数。
  • 构造函数不能是虚函数,否则会出现编译错误
虚函数的实现:

       虚函数表指针:类中除了定义的函数成员,还有一个成员是虚函数表指针(占四个基本内存单元),这个指针指向一个虚函数表的起始位置,这个表会与类的定义同时出现,这个表存放着该类的虚函数指针,调用的时候可以找到该类的虚函数表指针,通过虚函数表指针找到虚函数表,通过虚函数表的偏移找到函数的入口地址,从而找到要使用的虚函数。

        当实例化一个该类的子类对象的时候,(如果)该类的子类并没有定义虚函数,但是却从父类中继承了虚函数,所以在实例化该类子类对象的时候也会产生一个虚函数表,这个虚函数表是子类的虚函数表,但是记录的子类的虚函数地址却是与父类的是一样的。所以通过子类对象的虚函数表指针找到自己的虚函数表,在自己的虚函数表找到的要执行的函数指针也是父类的相应函数入口的地址。

        如果我们在子类中定义了从父类继承来的虚函数,对于父类来说情况是不变的,对于子类来说它的虚函数表与之前的虚函数表是一样的,但是此时子类定义了自己的(从父类那继承来的)相应函数,所以它的虚函数表当中管于这个函数的指针就会覆盖掉原有的指向父类函数的指针的值,换句话说就是指向了自己定义的相应函数,这样如果用父类的指针,指向子类的对象,就会通过子类对象当中的虚函数表指针找到子类的虚函数表,从而通过子类的虚函数表找到子类的相应虚函数地址,而此时的地址已经是该函数自己定义的虚函数入口地址,而不是父类的相应虚函数入口地址,所以执行的将会是子类当中的虚函数。这就是多态的原理。

函数的覆盖和隐藏

父类和子类出现同名函数称为隐藏。

  • 父类对象.函数函数名(...);     //调用父类的函数
  • 子类对象.函数名(...);           //调用子类的函数  
  • 子类对象.父类名::函数名(...);//子类调用从父类继承来的函数。

父类和子类出现同名虚函数称为覆盖

  • 父类指针=new 子类名(...);父类指针->函数名(...);//调用子类的虚函数。
虚析构函数

[:->虚析构函数的特点:

  • 当我们在父类中通过virtual修饰析构函数之后,通过父类指针指向子类对象,通过delete接父类指针就可以释放掉子类对象

[:->理论前提:

  • 执行完子类的析构函数就会执行父类的析构函数

原理:

        如果父类当中定义了虚析构函数,那么父类的虚函数表当中就会有一个父类的虚析构函数的入口指针,指向的是父类的虚析构函数,子类虚函数表当中也会产生一个子类的虚析构函数的入口指针,指向的是子类的虚析构函数,这个时候使用父类的指针指向子类的对象,delete接父类指针,就会通过指向的子类的对象找到子类的虚函数表指针,从而找到虚函数表,再虚函数表中找到子类的虚析构函数,从而使得子类的析构函数得以执行,子类的析构函数执行之后系统会自动执行父类的虚析构函数。这个是虚析构函数的实现原理。

纯虚函数

class Shape
{
public:
    virtual  double calcArea()//虚函数
    {....}
    virtual  double calcPerimeter()=0;//纯虚函数
    ....
};
纯虚函数只提供一个接口,基类不实现纯虚函数

抽象类

含有纯虚函数的类

接口类
1.没有任何数据成员
2.仅有成员函数
3.成员函数都是纯虚函数

注意:若父类有虚函数,则必须实现否则无法实例化,抽象类子类只有把抽象类当中的所有的纯虚函数都做了实现才可以实例化对象。







<think>嗯,用户问的是C++中虚函数的作用。我需要先回忆一下虚函数的基本概念,然后想想它的主要用途。虚函数应该和面向对象的多态有关吧。对了,多态允许子类重写父类的方法,这样在通过基类指针或引用调用时,实际执行的是子类的方法。 那虚函数具体是怎么实现的呢?可能涉及到虚函数表和虚指针。每个含有虚函数的类都有一个虚表,里面存着函数指针,对象则有一个指向这个表的指针。这样在运行时,根据对象的实际类型来调用正确的函数,也就是动态绑定。这跟非虚函数的静态绑定不同,静态绑定在编译时就确定了。 用户可能需要知道为什么需要虚函数,比如在实现接口或者设计模式的时候。比如,基类定义一个虚函数,子类可以重写它,这样通过基类指针操作子类对象时,能调用到正确的实现。举个例子,比如形状类有计算面积的虚函数,子类圆和矩形各自实现,这样用基类指针调用area()时会根据实际对象类型执行对应的计算。 可能还需要提到纯虚函数,抽象类,这样基类不能被实例化,必须由子类实现。这是定义接口的一种方式。 另外,要注意虚函数的开销,每个对象会有虚指针,虚表会占用内存,调用虚函数时有一次间接寻址,可能影响性能。不过现代编译器优化下,这部分开销通常可以接受。 用户可能还关心什么时候使用虚函数。比如,当设计需要扩展的类层次结构,或者需要运行时多态的时候。如果不需要多态,可能用普通函数或者模板会更高效。 有没有其他注意事项?比如析构函数如果是基类的,应该声明为虚函数,这样才能正确调用子类的析构函数,避免内存泄漏。这点挺重要的,用户可能容易忽略。 总结的话,虚函数的作用主要是实现运行时多态,允许子类重写方法,通过基类指针或引用调用实际子类的方法。实现机制是虚函数表和虚指针,使用时需要注意虚析构函数的问题,以及性能上的考虑。</think>在C++中,虚函数(Virtual Function)是面向对象编程中实现多态(Polymorphism)的核心机制。其作用可总结为以下关键点: --- ### 一、核心作用:实现运行时多态 1. **动态绑定(Dynamic Binding)** 通过基类指针/引用调用虚函数时,实际执行的是**对象所属派生类的重写版本**。 ```cpp class Animal { public: virtual void speak() { std::cout << "Animal sound" << std::endl; } }; class Dog : public Animal { public: void speak() override { std::cout << "Woof!" << std::endl; } // 重写虚函数 }; Animal* animal = new Dog(); animal->speak(); // 输出 "Woof!"(调用Dog的版本) ``` 2. **与非虚函数的对比** 若未使用`virtual`,函数调用在编译时静态绑定(Static Binding),结果取决于指针类型而非对象类型: ```cpp Animal* animal = new Dog(); animal->speak(); // 若未声明virtual,输出 "Animal sound" ``` --- ### 二、底层实现机制 1. **虚函数表(vtable)** - 每个包含虚函数的类有一个虚函数表,存储该类所有虚函数的地址。 - 对象内部包含一个指向虚函数表的指针(vptr)。 2. **内存结构示例** ```cpp class Base { public: virtual void func1() {} virtual void func2() {} }; class Derived : public Base { public: void func1() override {} // 覆盖Base::func1 }; ``` - `Base`的虚表:`[Base::func1, Base::func2]` - `Derived`的虚表:`[Derived::func1, Base::func2]` --- ### 三、关键应用场景 1. **接口定义与实现分离** 通过基类定义接口,派生类提供具体实现: ```cpp class Shape { public: virtual double area() const = 0; // 纯虚函数(抽象类) }; class Circle : public Shape { public: double area() const override { return 3.14 * r * r; } }; ``` 2. **设计模式基础** 工厂模式、策略模式等依赖虚函数实现多态行为。 --- ### 四、重要注意事项 1. **虚析构函数** 若类可能被继承,基类析构函数必须声明为`virtual`,否则通过基类指针删除派生类对象时会导致资源泄漏: ```cpp class Base { public: virtual ~Base() {} // 虚析构函数 }; ``` 2. **性能开销** - 每个对象需存储vptr(通常4/8字节)。 - 虚函数调用需通过vptr间接寻址,比普通函数稍慢(现代CPU优化后影响较小)。 --- ### 五、总结对比表 | 特性 | 虚函数 | 非虚函数 | |---------------------|---------------------------|-------------------------| | **绑定时机** | 运行时(动态绑定) | 编译时(静态绑定) | | **多态支持** | 是 | 否 | | **内存开销** | 每个对象包含vptr | 无额外开销 | | **适用场景** | 需要扩展的类层次结构 | 固定实现、无需覆盖的方法 | --- 通过虚函数,C++实现了面向对象的核心特性——多态,使得代码更灵活、可扩展性更强。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值