[C++]纯虚函数与虚函数

1. 什么是虚函数?

1.1 定义

虚函数是用 virtual 关键字声明的成员函数,允许子类覆盖它,并支持 运行时多态

1.2 特点

1.动态绑定(运行时决定调用函数):

  • 虚函数在运行时,根据对象的实际类型,而不是指针或引用的类型,决定调用哪个函数。

2.基类实现:

  • 虚函数在基类中必须有默认实现(即函数体 {},函数体内必须要有内容)。

3.子类选择覆盖:

  • 虚函数在基类中有实现,子类可以选择重写虚函数,也可以直接在基类中使用该现成的虚函数。

4.虚函数表:

  • 包含虚函数的类会有一个虚函数表,用来存储虚函数的地址。

5.多态支持:

  • 使用基类指针或引用操作对象时,可以调用子类重写的虚函数。

1.3 语法

cpp 复制代码
class Base {
public:
    virtual void functionName() {
        // 基类的默认实现(即出厂设置,虚函数原来的函数体内的代码实现语句)
    }
};

1.4 使用场景

  • 当需要在子类中覆盖基类的默认行为,并通过基类指针或引用调用时。
  • 比如在面向对象的多态设计中,可以通过基类的接口调用子类的实现。

1.5 示例代码

cpp 复制代码
#include <iostream>
using namespace std;

class Animal {
public:
    virtual void makeSound() {  // 虚函数
        cout << "Some animal sound" << endl;
    }
};

class Dog : public Animal {
public:
    void makeSound() override {  // 子类覆盖虚函数
        cout << "Dog barks!" << endl;
    }
};

int main() {
    Animal* animal = new Dog();  // 基类指针指向子类对象
    animal->makeSound();         // 动态绑定,调用 Dog 的 makeSound()
    delete animal;
    return 0;
}
//输出:Dog barks!

2. 什么是纯虚函数?

2.1 定义

纯虚函数是一个没有实现的虚函数,基类只提供函数声明(接口),由子类负责具体实现。

纯虚函数以 = 0 的形式声明。

2.2 特点

没有默认实现:

  • 纯虚函数在基类中没有函数体 {},只是一个接口。用 = 0 表示,如

    cpp 复制代码
    virtual void makeSound() = 0;  // 纯虚函数,没有实现

强制子类实现:

  • 如果子类使用该纯虚函数,则子类必须实现纯虚函数,否则子类本身也会成为抽象类,无法实例化。也就是说这个纯虚函数必须在子类中要有实现语句,否则这个继承了这个纯虚函数的子类也会变成一个抽象类。

抽象类:

  • 包含至少一个纯虚函数的类称为抽象类,抽象类不能直接实例化。

接口设计:

  • 纯虚函数的主要作用是定义接口,让子类实现特定的功能。

2.3 语法

cpp 复制代码
class Base {
public:
    virtual void functionName() = 0;  // 纯虚函数
};

2.4 使用场景

  • 当基类无法提供合理的默认实现,只能提供一个接口,强制子类实现。
  • 比如在设计图形库时,基类"形状"只能规定"绘制"接口,而具体绘制由"圆"或"矩形"实现。

2.5 示例代码

cpp 复制代码
#include <iostream>
using namespace std;

class Shape {
public:
    virtual void draw() = 0;  // 纯虚函数
};

class Circle : public Shape {
public:
    void draw() override {
        cout << "Drawing a Circle" << endl;
    }
};

class Rectangle : public Shape {
public:
    void draw() override {
        cout << "Drawing a Rectangle" << endl;
    }
};

int main() {
    Shape* shape;

    Circle circle;
    Rectangle rectangle;

    shape = &circle;
    shape->draw();  // 调用 Circle 的 draw()

    shape = &rectangle;
    shape->draw();  // 调用 Rectangle 的 draw()

    return 0;
}
/*
输出:
Drawing a Circle
Drawing a Rectangle
*/

2.6扩:关于上面代码中的override关键字

  • override 是 C++11 引入的关键字,表示子类重写了基类的虚函数
  • 如果函数签名(名称、参数)与基类的虚函数不匹配,编译器会报错,防止你意外没有正确重写。
  • 使用 override 仅适用于完全匹配的虚函数覆盖。
  • 但是如果正确书写的话**override** 在代码中也可以省略,但最好带上。使用 override 可以确保正确重写虚函数,避免因函数签名不匹配导致的错误。

示例1:不使用override

cpp 复制代码
#include <iostream>
using namespace std;

class Animal {
public:
    virtual void makeSound() {  // 虚函数,有默认实现
        cout << "Animal makes a sound." << endl;
    }
};

class Dog : public Animal {
public:
    void makeSound() {  // 子类重写虚函数
        cout << "Dog barks: Woof!" << endl;
    }
};

int main() {
    Animal* animalPtr;  // 基类指针
    Dog dog;

    animalPtr = &dog;
    animalPtr->makeSound();  // 动态绑定,调用 Dog 的 makeSound()

    return 0;
}
//输出:Dog barks: Woof!

示例2:使用override

cpp 复制代码
#include <iostream>
using namespace std;

class Animal {
public:
    virtual void makeSound() {  // 虚函数,有默认实现
        cout << "Animal makes a sound." << endl;
    }
};

class Dog : public Animal {
public:
    void makeSound() override {  // 使用 override 明确表示重写
        cout << "Dog barks: Woof!" << endl;
    }
};

int main() {
    Animal* animalPtr;  // 基类指针
    Dog dog;

    animalPtr = &dog;
    animalPtr->makeSound();  // 动态绑定,调用 Dog 的 makeSound()

    return 0;
}
//输出:Dog barks: Woof!

示例3:未正确重写虚函数(纯虚函数同理)时:子类中继承的虚函数名与基类中的虚函数不匹配。如果子类要正确重写基类的虚函数,函数的 名称、参数列表和返回类型 必须完全匹配。

cpp 复制代码
#include <iostream>
using namespace std;

class Animal {
public:
    virtual void makeSound() {  // 虚函数,没有参数
        cout << "Animal makes a sound." << endl;
    }
};

class Dog : public Animal {
public:
    void makeSound(int volume) override {  // 错误:不匹配基类的虚函数签名
        cout << "Dog barks with volume: " << volume << endl;
    }
};

int main() {
    Dog dog;
    dog.makeSound(5);  // 尝试调用错误的 makeSound
    return 0;
}

//错误提示:
//error: 'void Dog::makeSound(int)' marked 'override', but does not override

3. 虚函数和纯虚函数的区别

特性 虚函数 纯虚函数
实现 基类有实现(函数体 {})。 基类没有实现,用 = 0 声明。
子类是否必须实现 子类可以选择不实现虚函数,直接使用基类实现。 子类必须实现纯虚函数,否则也是抽象类。
基类实例化 基类不是抽象类,可以实例化。 基类是抽象类,不能实例化。
主要用途 提供默认行为,支持子类覆盖实现多态。 提供接口,强制子类实现特定功能。

4. 子类的行为

4.1 如果子类实现所有纯虚函数

子类会成为一个"完整的类",可以直接实例化。

cpp 复制代码
class Base {
public:
    virtual void functionName() = 0;  // 纯虚函数
};

class Derived : public Base {
public:
    void functionName() override {  // 子类实现纯虚函数
        cout << "Function implemented" << endl;
    }
};

int main() {
    Derived d;  // 子类可以实例化
    d.functionName();
    return 0;
}

4.2 如果子类没有实现某些纯虚函数

子类会变成抽象类,不能实例化。

cpp 复制代码
class Base {
public:
    virtual void functionName() = 0;  // 纯虚函数
};

class Derived : public Base {
    // 没有实现 functionName
};

int main() {
    Derived d;  // 这句代码会报错,Derived 是抽象类,不能实例化
    return 0;
}

5. 虚函数和纯虚函数的共同点

动态绑定:

  • 都支持动态绑定(通过虚函数表实现)。
  • 在运行时,根据对象的实际类型调用函数。

多态支持:

  • 都可以通过基类指针或引用调用子类的实现。

用于继承体系:

  • 都需要在继承中使用,子类可以覆盖虚函数或实现纯虚函数。

6. 应用场景总结

场景 选择虚函数 选择纯虚函数
基类可以提供默认实现 如果基类能给出合理的默认行为。 如果基类没有合理的默认行为。
子类有灵活实现需求 子类可以选择覆盖,也可以直接使用基类功能。 子类必须实现功能,不允许不实现。
接口设计 不强制子类实现虚函数,可以灵活使用基类功能。 强制子类实现特定功能,用于接口设计。

7. 总结

虚函数:

  • 有默认实现,子类可以选择覆盖或使用默认实现。
  • 运行时多态: 调用函数时根据对象的实际类型决定。

纯虚函数:

  • 没有实现,基类只规定接口,必须由子类实现。
  • 抽象类: 包含纯虚函数的类不能实例化。

区别:

  • 虚函数提供灵活的默认行为。
  • 纯虚函数强制子类实现特定功能,适用于接口设计。
相关推荐
Trustport4 分钟前
C# EventLog获取Windows日志进行查询设置多个EventLogQuery查询条件
开发语言·c#
前往深圳7 分钟前
数据结构:LinkedList与链表
java·开发语言·学习
敲键盘的小夜猫11 分钟前
Java服务端性能优化:从理论到实践的全面指南
java·开发语言·性能优化
勘察加熊人21 分钟前
c#的form实现飞机大战
开发语言·c#
Tadecanlan23 分钟前
[C++面试] explicit面试8问 —— 较难,可简单了解即可
开发语言·c++
the_nov31 分钟前
9.进程信号
linux·c++
独好紫罗兰40 分钟前
洛谷题单3-P5725 【深基4.习8】求三角形-python-流程图重构
开发语言·python·算法
哒宰的自我修养1 小时前
0.DJI-PSDK开发准备及资料说明(基于DJI经纬M300RTK和M350RTK无人机上使用)
c++·学习·无人机
byte轻骑兵1 小时前
【C++进阶】顺序容器
开发语言·c++
三雷科技1 小时前
Qt的window注册表读写以及删除
开发语言·qt·注册表