C++———— 多态

1.多态的概念

多态的概念:通俗来说,就是多种形态。多态分为编译时多态(静态多态)和运行时多 态(动态多态),这里我们重点讲运行时多态,编译时多态(静态多态)和运行时多态(动态多态)。编译时多态(静态多态)主要就是我们前面讲的函数重载和函数模板,他们传不同类型的参数就可以调用不同的 函数,通过参数不同达到多种形态,之所以叫编译时多态,是因为他们实参传给形参的参数匹配是在 编译时完成的,我们把编译时⼀般归为静态,运行时归为动态。

运行时多态,具体点就是去完成某个行为(函数),可以传不同的对象就会完成不同的行为,就达到多种形态。

以下是一个运⾏时多态的例子,假设你有一个动物类(Animal),其中有一个makeSound方法,表示动物发出声音。不同的动物对象会表现出不同的行为,演示如何通过运⾏时多态实现这一点:

#include <iostream>
using namespace std;

// 基类 Animal
class Animal {
public:
    virtual void makeSound() const { // 虚函数
        cout << "动物的声音" << endl;
    }
};

// 派生类 Dog
class Dog : public Animal {
public:
    void makeSound() const override { // 重写 makeSound
        cout << "汪汪" << endl;
    }
};

// 派生类 Cat
class Cat : public Animal {
public:
    void makeSound() const override { // 重写 makeSound
        cout << "(>^ω^<) 喵" << endl;
    }
};

// 派生类 Student
class Student : public Animal {
public:
    void makeSound() const override { // 重写 makeSound
        cout << "学生的声音: 叽叽喳喳" << endl;
    }
};

// 主函数
int main() {
    Animal* animal1 = new Dog();    // 指向 Dog 对象
    Animal* animal2 = new Cat();    // 指向 Cat 对象
    Animal* animal3 = new Student(); // 指向 Student 对象

    animal1->makeSound(); // 输出: 汪汪
    animal2->makeSound(); // 输出: (>^ω^<) 喵
    animal3->makeSound(); // 输出: 学生的声音: 叽叽喳喳

    delete animal1;
    delete animal2;
    delete animal3;

    return 0;
}

2.多态的定义和实现

2.1多态的构成条件

1.继承关系

多态是面向对象编程的一部分,它依赖于类之间的继承关系。必须有一个基类和一个或多个派生类,派生类必须重写(重载)基类的虚函数,才会发生多态。

2.虚函数(Virtual Functions)

多态的核心是虚函数。基类的函数需要声明为虚函数(virtual),这样派生类才能重写(override)该函数。

在调用该函数时,通过基类指针或引用来调用,并且要使用动态绑定,确保运行时正确调用派生类的函数,而不是基类的函数。

class Animal {
public:
    virtual void sound() {  // 基类中的虚函数
        cout << "动物的声音" << endl;
    }
};

class Dog : public Animal {
public:
    void sound() override {  // 派生类重写虚函数
        cout << "汪汪" << endl;
    }
};

class Cat : public Animal {
public:
    void sound() override {  // 派生类重写虚函数
        cout << "喵喵" << endl;
    }
};

3.基类指针或引用

多态的实现依赖于基类指针或引用指向派生类对象。使用基类指针或引用调用虚函数时,程序会根据实际对象的类型来选择适当的函数(即发生动态绑定)。

Animal* animal = new Dog();  // 基类指针指向派生类对象
animal->sound();  // 输出: 汪汪

animal = new Cat();  // 基类指针指向另一个派生类对象
animal->sound();  // 输出: 喵喵

4.动态绑定

通过虚函数和基类指针/引用,C++ 会在运行时根据对象的实际类型决定调用哪个函数,这称为动态绑定(或运行时多态)。

当基类指针或引用调用虚函数时,编译器不能确定到底调用基类的函数还是派生类的函数,而是在运行时通过对象的实际类型来决定。

2.2虚函数

类成员函数前面加virtual修饰,那么这个成员函数被称为虚函数。注意非成员函数不能加virtual修 饰。

class Person
{
public:
	virtual void BuyTicket() {
		cout << "买票全价" << endl;}
};

函数重写/覆盖的条件

在派生类中重写基类的虚函数时,必须满足以下几个条件:

函数签名一致:派生类中的函数签名(包括函数名、参数类型、返回值类型等)必须与基类中的虚函数一致。

访问权限:派生类中重写的函数的访问权限必须与基类中的虚函数相同或更宽松。例如,如果基类中的虚函数是 public,则派生类中的重写函数也必须是 public,不能是 private 或 protected

#include <iostream>
using namespace std;

// 基类
class Animal {
public:
    virtual void sound() const {  // 基类中的虚函数
        cout << "动物的声音" << endl;
    }
};

// 派生类 Dog
class Dog : public Animal {
public:
    void sound() const override {  // 重写基类的虚函数
        cout << "汪汪" << endl;
    }
};

// 派生类 Cat
class Cat : public Animal {
public:
    void sound() const override {  // 重写基类的虚函数
        cout << "喵喵" << endl;
    }
};

// 主函数
int main() {
    Animal* animal1 = new Dog();
    Animal* animal2 = new Cat();

    animal1->sound();  // 输出: 汪汪
    animal2->sound();  // 输出: 喵喵

    delete animal1;
    delete animal2;

    return 0;
}

2.3析构函数的重写

基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,都与基类的析 构函数构成重写,虽然基类与派生类析构函数名字不同看起来不符合重写的规则,实际上编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统⼀处理成destructor,所以基类的析构函数加了 vialtual修饰,派生类的析构函数就构成重写。

下面的代码我们可以看到,如果~A(),不加virtual,那么delete p2时只调用的A的析构函数,没有调用B的析构函数,就会导致内存泄漏问题,因为~B()中在释放资源。

class A
{
public:
	virtual ~A()
	{
		cout << "~A()" << endl;
	}
};
class B : public A {
public:
	~B()
	{
		cout << "~B()->delete:" << _p << endl;
		delete _p;
	}
protected:
	int* _p = new int[10];
};
int main()
{
	A* p1 = new A;
	A* p2 = new B;
	// 	只有派⽣类Student的析构函数重写了Person的析构函数,下⾯的delete对象调⽤析构函数,
	// 才能构成多态,才能保证p1和p2指向的对象正确的调⽤析构函数。

	delete p1;
	delete p2;
	return 0;
}

输出:

2.4 override 和final关键字

2.4.1 override

override 关键字用于标记派生类中的成员函数,以明确表示该函数是重写(覆盖)基类中的虚函数。这可以帮助编译器检查函数签名是否正确,并确保你真的重写了基类中的虚函数。

#include <iostream>
using namespace std;

class Base {
public:
    virtual void display() {
        cout << "Base display" << endl;
    }
    virtual ~Base() {}  // 虚析构函数
};

class Derived : public Base {
public:
    // 使用 override 确保正确重写了基类的 display 函数
    void display() override {
        cout << "Derived display" << endl;
    }
};

int main() {
    Base* basePtr = new Derived();
    basePtr->display();  // 调用 Derived 类的 display
    delete basePtr;
    return 0;
}

输出:

Derived display

错误示例

如果函数签名不一致,编译器会报告错误。

class Derived : public Base {
public:
    // 错误:参数类型不匹配,编译错误
    void display(int x) override {  // 错误,基类的 display 没有参数
        cout << "Derived display" << endl;
    }
};

编译器会提示错误,因为派生类的 display 函数签名与基类的虚函数不匹配。

2.4.2 final

final 关键字有两个主要用途:

禁止继承:当一个类被声明为 final 时,表示该类不能再被继承。

禁止重写:当 final 用于虚函数时,表示该函数不能被进一步重写。如果在派生类中尝试重写这个函数,编译器会报错。

使用示例:禁止继承

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

class Derived final : public Base {  // Derived 类不能再被继承
public:
    void display() override {
        cout << "Derived display" << endl;
    }
};

class MoreDerived : public Derived {  // 错误:无法继承 final 类
    // 编译错误: cannot derive from 'final' base 'Derived'
};

在上面的代码中,Derived 类被声明为 final,意味着不能从 Derived 类再派生出新类,尝试这样做会导致编译错误。

使用示例:禁止重写

class Base {
public:
    virtual void display() final {  // 不能再被重写
        cout << "Base display" << endl;
    }
};

class Derived : public Base {
public:
    // 错误:无法重写基类的 final 函数
    void display() override {
        cout << "Derived display" << endl;
    }
};

在上面的代码中,Base 类的 display 函数被标记为 final,这意味着任何派生类都不能重写这个函数。如果派生类尝试重写该函数,编译器会报错。

3. 重载/重写/隐藏的对比

3.1 函数重载

定义:重载是指在同一个作用域内,可以有多个函数名相同但参数列表不同的函数。编译器根据函数调用时提供的参数类型和数量来决定调用哪个版本的函数。

#include <iostream>
using namespace std;

class Printer {
public:
    void print(int i) {
        cout << "Printing integer: " << i << endl;
    }
    void print(double d) {
        cout << "Printing double: " << d << endl;
    }
    void print(const string& s) {
        cout << "Printing string: " << s << endl;
    }
};

int main() {
    Printer printer;
    printer.print(5);           // 调用 print(int)
    printer.print(3.14);        // 调用 print(double)
    printer.print("Hello");     // 调用 print(const string&)
    return 0;
}

3.2 函数重写

定义:重写是指在派生类中重新定义基类中已有的虚函数。派生类的函数签名(包括函数名、参数列表和返回类型)与基类的虚函数完全相同,但其功能可以有所不同。

#include <iostream>
using namespace std;

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

class Derived : public Base {
public:
    void display() override {  // 重写基类的 display 函数
        cout << "Derived display" << endl;
    }
};

int main() {
    Base* ptr = new Derived();
    ptr->display();  // 调用 Derived 类的 display 函数
    delete ptr;
    return 0;
}

3.3 函数隐藏

定义:隐藏是指在派生类中定义了与基类中已有函数相同名称的函数(但可能有不同的参数)。派生类中的函数将隐藏基类中的函数,即派生类的函数遮盖了基类中的函数。

#include <iostream>
using namespace std;

class Base {
public:
    void display() {
        cout << "Base display" << endl;
    }
};

class Derived : public Base {
public:
    void display(int i) {  // 隐藏基类的 display 函数
        cout << "Derived display with int: " << i << endl;
    }
};

int main() {
    Derived d;
    d.display(5);  // 调用 Derived 类的 display(int)
    // d.display();  // 错误:基类的 display 被隐藏了,无法调用
    return 0;
}

4.纯虚函数和抽象类

在虚函数的后面写上=0,则这个函数为纯虚函数,纯虚函数不需要定义实现(实现没啥意义因为要被 派生类重写,但是语法上可以实现),只要声明即可。包含纯虚函数的类叫做抽象类,抽象类不能实例 化出对象,如果派生类继承后不重写纯虚函数,那么派生类也是抽象类。纯虚函数某种程度上强制了派生类重写虚函数,因为不重写实例化不出对象。

class Car
{
public:
	virtual void Drive() = 0;
};
class Benz :public Car
{
public:
	virtual void Drive()
	{
		cout << "Benz舒适" << endl;
	}
};
class BMW :public Car
{
public:
	virtual void Drive()
	{
		cout << "BMW操控" << endl;
	}
};
int main()
{
	// 编译报错:error C2259 : “Car”:⽆法实例化抽象类
	Car car;
	Car* pBenz = new Benz;
	pBenz->Drive();
	Car* pBMW = new BMW;
	pBMW->Drive();
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值