【C++虚函数】

虚函数是 C++ 中实现多态的一种方式,它的作用主要有以下几个方面:

1.实现运行时多态:虚函数可以实现运行时多态,也称为动态多态。在运行时多态中,通过基类指针或引用调用虚函数时,会根据对象的动态类型来决定调用哪个函数实现。这样,不同类型的对象可以对同一个消息作出不同的响应,实现了多态。

2.支持继承和多态:C++ 是一种面向对象的编程语言,继承和多态是其核心特性之一。虚函数可以支持继承和多态,使得派生类可以重写基类中的虚函数,并提供自己的行为实现。

3.提高代码可维护性:虚函数可以提高代码的可维护性,使得代码更加灵活、易读、易扩展。通过基类指针或引用调用虚函数时,可以不关心具体对象的类型,只需要调用虚函数即可,这样可以减少代码的重复性,提高代码的可维护性。

4.支持多种设计模式:虚函数可以支持多种设计模式,例如策略模式、工厂模式、观察者模式等,使得代码更加清晰、易读、易扩展。通过虚函数实现多态,可以将不同的算法、工厂、观察者等实现封装成不同的类,从而实现代码的高内聚、低耦合。

因此,虚函数是 C++ 中一种重要的特性,它的作用不仅仅是实现多态,还涉及到继承、多态、可维护性、设计模式等多个方面,是 C++ 面向对象编程的重要基础。

如果在基类 Animal 中有一个非虚函数 run,并且派生类 Cat 重写了这个函数,那么在使用指针或引用调用 run 函数时,会根据指针或引用的静态类型来决定调用哪个函数实现。

例如,假设代码如下:

#include <iostream>
 
class Animal {
public:
    virtual void speak() const {
        std::cout << "This is an animal." << std::endl;
    }
    void run() const {
        std::cout << "Animal is running." << std::endl;
    }
};
 
class Cat : public Animal {
public:
    void speak() const override {
        std::cout << "This is a cat." << std::endl;
    }
    void run() const {
        std::cout << "Cat is running." << std::endl;
    }
};
 
int main() {
    Animal* p1 = new Cat();
    p1->run(); // 输出 Animal is running.
    delete p1;
    return 0;
}

在上面的代码中,我们在基类 Animal 中定义了一个非虚函数 run,并且派生类 Cat 重写了这个函数。在 main 函数中,我们创建了一个 Cat 对象,并使用基类指针 Animal* 指向这个对象,然后调用了 run 函数。由于 run 函数不是虚函数,因此会根据指针 p1 的静态类型 Animal* 来调用 Animal 类中的 run 函数实现,输出 Animal is running.。

因此,如果基类中有一个非虚函数,并且派生类重写了这个函数,那么在使用指针或引用调用这个函数时,会根据指针或引用的静态类型来决定调用哪个函数实现。如果要调用派生类中的函数实现,可以将函数声明为虚函数。

虚函数原理

虚函数的原理是通过虚函数表(VTable)实现的。虚函数表是一个存储了类的虚函数地址的表格,每个含有虚函数的类都有一个对应的虚函数表。

在类的对象中,会有一个指向虚函数表的指针(即虚函数指针),该指针存储了类的虚函数表的地址。当通过基类指针或引用调用虚函数时,程序会先从对象的虚函数指针中获取虚函数表的地址,然后根据函数的偏移量(即函数在虚函数表中的位置)获取对应的虚函数地址,最后调用该虚函数。

#include <iostream>
 
 
class Animal {
public:
    virtual void speak() const {
        std::cout << "This is an animal. Address: " << reinterpret_cast<void*>(&Animal::speak) << std::endl;
    }
    void run() const {
        std::cout << "Animal is running. Address: " << reinterpret_cast<void*>(&Animal::run) << std::endl;
    }
};
 
class Cat : public Animal {
public:
    void speak() const override {
        std::cout << "This is a cat. Address: " << reinterpret_cast<void*>(&Cat::speak) << std::endl;
    }
    void run() const {
        std::cout << "Cat is running. Address: " << reinterpret_cast<void*>(&Cat::run) << std::endl;
    }
};
 
int main() {
    Animal* p1 = new Cat();
    p1->speak(); // 输出 This is a cat. Address: 0x...
    p1->run();   // 输出 Animal is running. Address: 0x...
    Cat c;
    c.speak();   // 输出 This is a cat. Address: 0x...
    c.run();     // 输出 Cat is running. Address: 0x...
    delete p1;
    return 0;
}
//結果:
//This is a cat. Address: 0x55f97cfe3d0a
//Animal is running. Address: 0x55f97cfe3cc4
//This is a cat. Address: 0x55f97cfe3d0a
//Cat is running. Address: 0x55f97cfe3d50

在上面的代码中,我们首先使用 Animal* p1 = new Cat(); 方式创建了一个 Cat 对象,并调用了它的虚函数 speak 和非虚函数 run,并输出了它们的内存地址。然后,我们直接创建了一个名为 c 的 Cat 对象,并调用了它的虚函数 speak 和非虚函数 run,并输出了它们的内存地址。在输出时,我们使用 (void*)(&函数名) 的方式获取函数的内存地址,并将其转换为 void* 类型输出。

需要注意的是,函数的内存地址可能因编译器和系统而异,因此输出的地址可能会与实际情况略有不同。另外,在使用 Animal* p1 = new Cat(); 方式创建对象时,虽然 p1 的静态类型是 Animal*,但它指向的是一个 Cat 对象,因此在调用虚函数时,会根据对象的动态类型来调用相应的函数实现。因此,使用 Animal* p1 = new Cat(); 方式创建对象时,调用虚函数的地址和直接创建 Cat 对象时调用虚函数的地址是一致的,都是 Cat 类中的 speak 函数的地址。而调用非虚函数时,由于 run 函数不是虚函数,因此不会发生多态,调用的是指针或对象的静态类型 Animal 类中的函数实现。因此,使用 Animal* p1 = new Cat(); 方式创建对象时,调用非虚函数的地址和直接创建 Cat 对象时调用非虚函数的地址是不一致的。

  • 6
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值