欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 新闻 > 焦点 > [C++]纯虚函数与虚函数

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

2024/12/21 23:49:50 来源:https://blog.csdn.net/2302_80281315/article/details/144459865  浏览:    关键词:[C++]纯虚函数与虚函数

1. 什么是虚函数?

1.1 定义

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

1.2 特点

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

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

2.基类实现:

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

3.子类选择覆盖:

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

4.虚函数表:

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

5.多态支持:

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

1.3 语法

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

1.4 使用场景

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

1.5 示例代码

#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 特点

  1. 没有默认实现:

    • 纯虚函数在基类中没有函数体 {},只是一个接口。用 = 0 表示,如
      virtual void makeSound() = 0;  // 纯虚函数,没有实现
  2. 强制子类实现:

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

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

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

2.3 语法

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

2.4 使用场景

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

2.5 示例代码

#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

#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

#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:未正确重写虚函数(纯虚函数同理)时:子类中继承的虚函数名与基类中的虚函数不匹配。如果子类要正确重写基类的虚函数,函数的 名称、参数列表和返回类型 必须完全匹配。

    #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);  // 尝试调用错误的 makeSoundreturn 0;
    }//错误提示:
    //error: ‘void Dog::makeSound(int)’ marked ‘override’, but does not override
    

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

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

4. 子类的行为

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

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

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 如果子类没有实现某些纯虚函数

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

class Base {
public:virtual void functionName() = 0;  // 纯虚函数
};class Derived : public Base {// 没有实现 functionName
};int main() {Derived d;  // 这句代码会报错,Derived 是抽象类,不能实例化return 0;
}

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

  1. 动态绑定:

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

    • 都可以通过基类指针或引用调用子类的实现。
  3. 用于继承体系:

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

6. 应用场景总结

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

7. 总结

  1. 虚函数:

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

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

    • 虚函数提供灵活的默认行为。
    • 纯虚函数强制子类实现特定功能,适用于接口设计。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com