首页 > 编程语言 >[C++]纯虚函数与虚函数

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

时间:2024-12-13 23:56:29浏览次数:10  
标签:函数 实现 子类 C++ 纯虚 基类 public

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);  // 尝试调用错误的 makeSound
        return 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. 区别:

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

标签:函数,实现,子类,C++,纯虚,基类,public
From: https://blog.csdn.net/2302_80281315/article/details/144459865

相关文章

  • [C++]类的继承
    一、什么是继承1.定义:        在C++中,继承是一种机制,允许一个类(派生类)继承另一个类(基类)的成员(数据和函数)。继承使得派生类能够直接访问基类的公有和保护成员,同时也可以对这些成员进行扩展或修改。继承是一种“是一个”的关系,它允许一个类从另一个类继承其属性和方......
  • 2024年12月GESPC++三级真题解析
    一、单选题(每题2分,共30分)题目123456789101112131415答案BDAADBCAADDCDCA1.下列二进制表示的十进制数值分别是()[10000011]原=()[10000011]补=()A.-125,-3B.-3,-125C.-3,-3D.-125,-125【答案】B【考纲知识点】原码和补码的计算及转换【......
  • 在CodeBolcks+Windows API下的C++编程教程——给你的项目中添加头文件和菜单
    0.前言我想通过编写一个完整的游戏程序方式引导读者体验程序设计的全过程。我将采用多种方式编写具有相同效果的应用程序,并通过不同方式形成的代码和实现方法的对比来理解程序开发更深层的知识。了解我编写教程的思路,请参阅体现我最初想法的那篇文章中的“1.编程计划”:学习编程......
  • C++入门
    目录1.C++的第一个程序2.命名空间2.1namespace2.2命名空间的嵌套2.3多文件定义同名namespace默认合并2.3.1Stack.h2.3.2Stack.cpp2.3.3test.cpp2.4使用3.C++输入&输出4.缺省参数4.1全缺省、半缺省4.2多文件缺省参数使用4.2.1Stack.h4.2.2Stack.cpp4.2.3test.c......
  • 【MySQL中多表查询和函数】
    目录1.多表查询1.1外键1.2链接查询2.MySQL函数内置函数简介数值函数字符串函数时间日期函数条件判断操作开窗函数1.多表查询本质:把多个表通过主外键关联关系链接(join)合并成一个大表,在去单表查询操作1.1外键外键概念:在从表(多方)创建一个字段,引用主表(一方)......
  • 43. JavaScript流程控制、函数、对象、BOM、DOM
    1.流程控制1.1if判断[1]单if分支if(条件){条件成立运行的代码}[2]if...else分支if(条件){条件成立运行的代码}else{条件不成立运行的代码}vara=10;if(a>=20){console.log("ok")}else{console.log("g......
  • C/C++实例汇集(1)
    1、用代码判断一个系统是16位系统还是32位系统?以下是几种常见编程语言中判断系统是16位还是32位的代码示例C语言:#include<stdio.h>intmain(){//方法一:利用sizeofif(sizeof(int)==2){printf("16位系统\n");}elseif(sizeof(int)==4){......
  • 函数(C语言)
    前后两个void最好都写上库函数举例:doublesqrt(doublex);//sqrt是函数名//x是函数的参数,表示调用sqrt函数需要传递一个double类型的值。//最前面的double是返回值类型,表示函数的计算结果是double类型的值。a,b未交换原因:实参传递给形参,这时候形参是实参的一份临......
  • C++哈希表
    哈希表教程目录哈希表是什么怎么用哈希表插入键值对查找元素删除元素遍历哈希表count检查是否存在某个键怎么用哈希表1.包含头文件首先,你需要包含unordered_map的头文件:#include<unordered_map>2.创建哈希表std::unordered_map<KeyType,ValueType>hashTableKey......
  • 在CodeBolcks+Windows API下的C++编程教程——给你的项目中添加资源文件和图标
    0.前言我想通过编写一个完整的游戏程序方式引导读者体验程序设计的全过程。我将采用多种方式编写具有相同效果的应用程序,并通过不同方式形成的代码和实现方法的对比来理解程序开发更深层的知识。了解我编写教程的思路,请参阅体现我最初想法的那篇文章中的“1.编程计划”:学习编程......