C++学习初探---C++面向对象-多态&虚函数

前言

记录第四次的学习,多态


多态

概念

派生类对象的指针可以赋值给基类指针,对于通过基类指针调用基类、派生类中都有的同名同参数表的虚函数的语句,编译时并不能确定要执行的是基类的还是派生类的虚函数;
而当程序运行到该语句时,如果基类指针指向的是一个基类对象,则基类的虚函数被调用,如果基类指针指向的是一个派生类对象,则派生类的虚函数被调用。这种机制就叫作“多态(polymorphism)”

多态允许不同类的对象对相同的函数进行调用,但根据对象的实际类型,会执行不同的操作。也就是说,同一条函数调用语句调用后能有不同的调用效果。

C++多态的实现主要依赖于虚函数和继承。

因此形成多态必须具备三个条件:

  • 必须存在继承关系
  • 继承关系之间必须有同名虚函数(虚函数在基类中 使用 virtual声明,这样在派生类中重新定义基类中的虚函数时,编译器就不会静态链接到该函数。)
  • 存在基类类型的指针或引用,通过该指针或引用调用虚函数。

虚函数

虚函数是一种用于实现多态性的特殊类型的函数。

虚函数允许在派生类中重写基类的函数,并在运行时根据对象的实际类型调用正确的函数版本。

使用virtual关键字来声明虚函数。virtual 关键字只在类定义中的成员函数声明处使用,不能在类外部写成员函数体时使用。静态成员函数不能是虚函数。

包含虚函数的类也被称为“多态类”。

俩个实例:

#include <iostream>

class Base {
public:
    virtual void show() {
        std::cout << "Base class" << std::endl;
    }
};

class Derived : public Base {
public:
    void show() override {
        std::cout << "Derived class" << std::endl;
    }
};

int main() {
    Base* basePtr;
    Base baseObj;
    Derived derivedObj;

    basePtr = &baseObj;
    basePtr->show();  // 调用基类的虚函数,输出 "Base class"

    basePtr = &derivedObj;
    basePtr->show();  // 调用派生类的虚函数,输出 "Derived class"

    return 0;
}

在这个示例中,我们定义了一个基类 Base,它包含一个虚函数 show。然后,我们派生了一个类 Derived,并重写了 show 函数。在 main 函数中,我们创建了一个基类指针 basePtr,然后将它指向基类对象和派生类对象,并分别调用 show 函数。由于 show 被声明为虚函数,调用的实际函数版本取决于对象的实际类型。

#include <iostream>

class Animal {
public:
    virtual void makeSound() {
        std::cout << "Animal makes a sound" << std::endl;
    }
};

class Dog : public Animal {
public:
    void makeSound() override {
        std::cout << "Dog barks" << std::endl;
    }
};

class Cat : public Animal {
public:
    void makeSound() override {
        std::cout << "Cat meows" << std::endl;
    }
};

int main() {
    Animal* animal1 = new Dog();
    Animal* animal2 = new Cat();

    animal1->makeSound();  // 输出 "Dog barks"
    animal2->makeSound();  // 输出 "Cat meows"

    delete animal1;
    delete animal2;

    return 0;
}

在上述示例中,Animal 类有一个虚函数 makeSound,而 Dog 和 Cat 类都继承自 Animal 并重写了 makeSound 函数。在 main 函数中,我们创建了 Dog 和 Cat 的对象指针,并调用它们的 makeSound 函数。由于 makeSound 被声明为虚函数,实际执行的函数取决于对象的实际类型。

值得强调的是:
编译器并不能通过分析程序的上下文来判断在什么地方指针指向的对象类型,自然也无法知道接下来该调用哪个成员函数;多态的语句调用哪个类的成员函数是在运行时才可以确定的,编译时不能确定;
因此,多态的函数语句调用被称为是“动态联编”的,而普通函数语句的函数调用语句是“静态联编”的。

  • 静态联编:编译时就可以确定调用的函数版本,在静态联编中,编译器根据函数或方法的名称、参数类型、或者接收者的类型来决定将调用哪个函数版本。
  • 动态联编:动态联编是在运行时确定调用的函数版本的过程。它通常与虚函数一起使用。在动态联编中,编译器会根据对象的实际类型来动态查找并调用适当的函数版本。

这里提一嘴虚函数的作用

通过将函数声明为虚函数,编译器生成了一张虚函数表(vtable),其中包含了类的虚函数地址。在运行时,根据对象的实际类型,程序会查找虚函数表并确定要调用的函数版本。


纯虚函数

您可能想要在基类中定义虚函数,以便在派生类中重新定义该函数更好地适用于对象,但是您在基类中又不能对虚函数给出有意义的实现,这个时候就会用到纯虚函数。

纯虚函数是特殊类型的虚函数,它没有默认的函数体实现,只有函数声明,而且必须在**抽象基类(Abstract Base Class)**中定义。纯虚函数用于定义接口,派生类必须实现这些函数。

下面是一个定义和使用纯虚函数的基本实例:

#include <iostream>

class AbstractShape {
public:
    virtual double area() const = 0;  // 纯虚函数
    virtual double perimeter() const = 0;  // 纯虚函数
};

class Circle : public AbstractShape {
private:
    double radius;

public:
    Circle(double r) : radius(r) {}

    double area() const override {
        return 3.14159265 * radius * radius;
    }

    double perimeter() const override {
        return 2 * 3.14159265 * radius;
    }
};

int main() {
    Circle circle(5.0);
    AbstractShape* shapePtr = &circle;

    std::cout << "Area: " << shapePtr->area() << std::endl;
    std::cout << "Perimeter: " << shapePtr->perimeter() << std::endl;

    return 0;
}

在上例中,

  • AbstractShape 是一个抽象基类,它包含两个纯虚函数 area 和 perimeter,这些函数只有声明而没有实际实现。
  • Circle 类继承了 AbstractShape,并实现了这两个纯虚函数。
  • 在 main 函数中,我们创建了一个 Circle 对象并使用基类指针 shapePtr 来调用纯虚函数。

关于抽象基类:

抽象基类(Abstract Base Class)是一个在面向对象编程中经常用到的概念。它是一个类,通常包含了至少一个或多个纯虚函数,这些函数没有实际的实现,只有函数的声明。抽象基类的主要目的是定义接口和规范,而不是提供具体的实现。


通过基类引用实现多态

通过基类引用(指针)来实现多态是C++中实现多态的一种常见方式。

#include <iostream>

class Animal {
public:
    virtual void makeSound() {
        std::cout << "Animal makes a sound" << std::endl;
    }
};

class Dog : public Animal {
public:
    void makeSound() override {
        std::cout << "Dog barks" << std::endl;
    }
};

class Cat : public Animal {
public:
    void makeSound() override {
        std::cout << "Cat meows" << std::endl;
    }
};

int main() {
    Animal animal;
    Dog dog;
    Cat cat;

    Animal& animalRef1 = animal;
    Animal& animalRef2 = dog;
    Animal& animalRef3 = cat;

    animalRef1.makeSound();  // 输出 "Animal makes a sound"
    animalRef2.makeSound();  // 输出 "Dog barks"
    animalRef3.makeSound();  // 输出 "Cat meows"

    return 0;
}

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

znonono

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值