C++——多态

虚函数的概念

虚函数是 C++ 中一种机制,用于实现运行时多态。它是基类中的成员函数,通过 virtual 关键字声明。虚函数允许派生类重写(override)基类中的同名函数,从而实现多态性。这个机制使得即使我们只知道基类的类型,程序在运行时仍能调用派生类的相应函数。

虚函数的性质

动态绑定:

虚函数支持动态绑定或运行时绑定。这意味着函数调用的具体实现是在运行时决定的,而不是编译时决定的。

基类指针或引用:

虚函数允许通过基类的指针或引用调用派生类的重写版本。即使指针或引用的类型是基类的,实际调用的函数是派生类中重写的函数。
虚函数表(vtable):

每个包含虚函数的类都有一个虚函数表(vtable),该表维护了该类所有虚函数的地址。每个对象的虚函数表指针(vptr)指向其类的虚函数表,从而在运行时确定调用哪个函数。

虚析构函数:

如果基类指针用于删除派生类对象,基类的析构函数应声明为虚函数,以确保调用派生类的析构函数来正确释放资源。
虚函数的定义
虚函数的定义通常包括以下步骤:

在基类中声明虚函数:

使用 virtual 关键字声明函数为虚函数。例如:

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

在派生类中重写虚函数:

派生类可以重写基类中的虚函数,提供自己的实现。使用 override 关键字可以确保函数确实重写了基类中的虚函数(这是可选的,但推荐使用)。

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

通过基类指针或引用调用虚函数:

当使用基类指针或引用调用虚函数时,实际调用的函数是由对象的实际类型决定的。

int main() {
    Base* basePtr;
    Derived derived;
    basePtr = &derived;
    basePtr->display(); // 输出: Derived display
    return 0;
}

多态的概念

多态的定义

在C++中,多态(Polymorphism)是指不同的对象可以通过相同的接口进行操作的能力。多态性是面向对象编程中的一个重要特性,允许程序在运行时决定调用哪个方法,从而使得代码更加灵活和可扩展。

C++中的多态主要分为两种类型:静态多态动态多态

静态多态(编译时多态)

静态多态通过函数重载和运算符重载实现。编译器在编译时根据函数或运算符的参数类型选择正确的实现。

函数重载:
#include <iostream>

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

int main() {
    Printer p;
    p.print(5);         // 调用 print(int)
    p.print(5.5);       // 调用 print(double)
    p.print("Hello");  // 调用 print(const std::string&)
    return 0;
}
运算符重载:
#include <iostream>

class Complex {
public:
    Complex(double r, double i) : real(r), imag(i) {}

    // 重载 + 运算符
    Complex operator+(const Complex& other) const {
        return Complex(real + other.real, imag + other.imag);
    }

    void print() const {
        std::cout << "Complex number: " << real << " + " << imag << "i" << std::endl;
    }

private:
    double real, imag;
};

int main() {
    Complex c1(1.5, 2.5);
    Complex c2(3.5, 4.5);
    Complex c3 = c1 + c2;  // 使用重载的 + 运算符
    c3.print();
    return 0;
}

动态多态(运行时多态)

动态多态通过虚函数和继承实现。基类中的虚函数可以在派生类中被重写,程序在运行时根据对象的实际类型选择正确的函数实现。

基本示例:
#include <iostream>

class Base {
public:
    virtual void speak() const {
        std::cout << "Base speaking" << std::endl;
    }
    virtual ~Base() = default; // 虚析构函数
};

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

void makeSpeak(const Base& b) {
    b.speak(); // 调用实际对象的 speak 方法
}

int main() {
    Base b;
    Derived d;
    makeSpeak(b); // 输出: Base speaking
    makeSpeak(d); // 输出: Derived speaking
    return 0;
}

在这个示例中,makeSpeak 函数接受一个 Base 类的引用,并调用 speak 方法。由于 speak 是一个虚函数,调用 makeSpeak(d) 时实际调用的是 Derived 类中的 speak 方法,表现出多态性。

实现多态的必要条件包括:

  • 继承:基类和派生类之间必须有继承关系。
  • 虚函数:基类中需要定义虚函数(使用 virtual 关键字),以便派生类可以重写。
  • 指针或引用:必须通过基类指针或引用来访问派生类对象,才能实现动态多态。

说明:要实现多态效果,第⼀必须是基类的指针或引⽤,因为只有基类的指针或引⽤才能既指向派⽣类对象;第⼆派⽣类必须对基类的虚函数重写/覆盖,重写或者覆盖了,派⽣类才能有不同的函数,多态的不同形态效果才能达到。

协变(了解)

协变(Covariance)和逆变(Contravariance)是类型系统中的概念,特别是在面向对象编程和泛型编程中。它们通常用于描述类型之间的关系和函数参数与返回值的兼容性。在C++和其他编程语言中,理解这些概念有助于编写更灵活和安全的代码。

协变的定义

协变指的是在继承层次中,类型系统如何处理函数返回值的类型关系。具体来说:

如果一个类型 B 是类型 A 的派生类(即 B 是 A 的子类),并且你有一个返回 A 类型对象的函数,那么你可以将这个函数重写成返回 B 类型对象的函数。这是协变的例子。

协变的性质

返回值的协变

如果基类中的方法返回类型 A,则在派生类中可以重写该方法,使其返回 B(其中 B 是 A 的派生类)。这意味着返回类型可以变得更加具体。

class A {
public:
    virtual A* getObject() { return new A(); }
};

class B : public A {
public:
    B* getObject() override { return new B(); } // 协变返回类型
};

兼容性

协变返回类型增强了子类的灵活性,使得在使用继承体系时能够返回更具体的类型。这有助于保持代码的类型安全性,同时允许在派生类中提供更具体的实现。
协变的用处

增强子类的灵活性

协变允许在派生类中定义更加具体的返回类型,这使得子类可以返回更有意义或更有用的对象,而不是基类类型的通用对象。
提升代码重用性:

协变使得在基类中定义的方法可以被子类重写以返回派生类类型,从而增强了代码的重用性和灵活性。

保持类型安全

通过协变,子类的方法可以返回更具体的类型,这保证了类型安全性,并减少了类型转换的需要。
C++中的协变
在C++中,协变通常与虚函数相关联。以下是一个示例,展示了如何在派生类中使用协变返回类型:

#include <iostream>
using namespace std;

class A {
public:
    virtual A* create() { return new A(); }
    virtual void print() { cout << "A"; }
};

class B : public A {
public:
    B* create() override { return new B(); } // 协变返回类型
    void print() override { cout << "B"; }
};

int main() {
    A* a = new B();
    A* b = a->create(); // b 的类型是 B*,因为 create() 返回 B* 的协变类型
    b->print();         // 输出 "B"
    delete a;
    delete b;
    return 0;
}

在这个示例中:

类 A 中的 create 函数返回 A*。
类 B 中重写的 create 函数返回 B*,这是一种协变。
在 main 函数中,我们通过 A* 指针调用 create 方法,实际得到的是 B* 类型的对象。

override和final关键字

在C++中,override 和 final 是两个与虚函数相关的重要关键字,它们用于增强类的类型安全性和代码的可维护性。下面详细介绍这两个关键字的用法。

override 关键字

  1. 定义和用途
    override 关键字用于指示一个虚函数是对基类中虚函数的重写。这确保了派生类中的函数确实是对基类中虚函数的重写,而不是误用。

  2. 语法和示例
    在派生类中,使用 override 修饰符声明函数时,编译器会检查基类中是否存在一个虚函数与之匹配。如果没有,编译器会产生错误。

#include <iostream>
using namespace std;

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

class Derived : public Base {
public:
    void show() override { cout << "Derived class show" << endl; } // 正确重写
    // void display() override { cout << "Derived class display" << endl; } // 错误:基类中没有display()
};

int main() {
    Base* b = new Derived();
    b->show(); // 输出: Derived class show
    delete b;
    return 0;
}
  1. 好处
    防止错误:使用 override 可以避免常见的错误,比如拼写错误或函数签名不匹配等。
    代码自文档化:它明确指出该函数是重写的,使代码更具可读性和自文档化。

final 关键字

  1. 定义和用途
    final 关键字用于防止类被继承或防止虚函数被进一步重写。这对于设计不可继承的类或方法来说是非常有用的。

  2. 语法和示例
    防止继承:将 final 关键字应用于类声明中,表示该类不能被进一步继承。

#include <iostream>
using namespace std;

class Base final { // 不允许被继承
public:
    virtual void show() { cout << "Base class show" << endl; }
};

// class Derived : public Base { }; // 错误:Base 是 final,不允许继承

int main() {
    Base b;
    b.show();
    return 0;
}

防止重写:将 final 关键字应用于虚函数声明中,表示该函数不能在派生类中被重写。

#include <iostream>
using namespace std;

class Base {
public:
    virtual void show() final { cout << "Base class show" << endl; } // 不允许在派生类中重写
};

class Derived : public Base {
public:
    // void show() override { cout << "Derived class show" << endl; } // 错误:show() 是 final
};

int main() {
    Base b;
    b.show();
    return 0;
}
  1. 好处
  • 增强安全性:通过将类标记为 final,可以防止意外的继承,这有助于保持设计的意图。
  • 优化性能:编译器可以对 final 类或函数进行更多的优化,因为它知道这些类和函数不会被继承或重写。
    总结
  • override:确保派生类中的函数是对基类中虚函数的正确重写,从而增加代码的安全性和可维护性。
  • final:用于禁止继承或禁止进一步重写,提供更严格的控制,增强代码的设计意图和优化潜力。

重载,重写和隐藏

在这里插入图片描述

纯虚函数和抽象类

纯虚函数

  1. 定义
    纯虚函数是在基类中声明但没有实现的虚函数,用于定义接口并要求派生类实现它。它使得类变成抽象类。

  2. 语法

virtual ReturnType FunctionName() = 0;
  1. 性质
    接口定义:指定子类必须实现该函数。
    没有实现:只提供声明,没有函数体。
    使类变为抽象类:类中含有至少一个纯虚函数即为抽象类。
  2. 用法
    用于定义接口,让派生类提供具体实现。用于多态的基类设计。

抽象类

  1. 定义
    包含至少一个纯虚函数的类称为抽象类。抽象类不能被实例化,只能作为基类。

  2. 语法

class AbstractClass {
public:
    virtual void pureVirtualFunction() = 0; // 纯虚函数
};
  1. 性质
    不能实例化:抽象类不能创建对象。
    派生类实现:派生类必须实现所有的纯虚函数,才能实例化派生类。
    提供接口:定义一组函数供派生类实现,形成接口。
  2. 用法
    用于定义多态接口和抽象基类,使得不同的派生类可以实现相同的接口。

总结

纯虚函数:用于声明一个函数,并强制派生类实现该函数。

抽象类:包含至少一个纯虚函数的类,不能实例化,提供接口和多态的基类设计。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值