C++ 多态性(附示例)
C++ 中的多态是什么?
在 C++ 中,多态是指成员函数根据调用/调用它的对象而表现出不同的行为。多态是一个希腊词,意思是具有多种形式。当你有一个通过继承相关的类层次结构时,就会发生多态。
例如,假设我们有一个函数 makeSound()。当一只猫调用此函数时,它会发出“喵”的声音。当一头牛调用同一函数时,它会发出“哞”的声音。
尽管我们只有一个函数,但它在不同情况下表现不同。该函数有多种形式;因此,我们实现了多态。
多态的类型
C++ 支持两种多态
- 编译时多态,和
- 运行时多态。
编译时多态
您通过匹配参数的数量和类型来调用重载的函数。信息在编译时就已存在。这意味着 C++ 编译器将在编译时选择正确的函数。
编译时多态通过函数重载和运算符重载来实现。
函数重载
当我们在具有相似名称但参数不同的多个函数时,就会发生函数重载。参数可能在数量或类型上有所不同。
示例 1
#include <iostream> using namespace std; void test(int i) { cout << " The int is " << i << endl; } void test(double f) { cout << " The float is " << f << endl; } void test(char const *ch) { cout << " The char* is " << ch << endl; } int main() { test(5); test(5.5); test("five"); return 0; }
输出
这是代码的屏幕截图:
代码解释
- 将 iostream 头文件包含到我们的代码中。我们将能够使用它的函数。
- 将 std 命名空间包含到我们的代码中。我们将能够使用它的类而不必调用它。
- 创建一个名为 test 的函数,该函数接受一个整数参数 i。{ 标记 test 函数体的开始。
- 如果调用/调用上述 test 函数,则执行的语句。
- 上述 test 函数体的结束。
- 创建一个名为 test 的函数,该函数接受一个浮点参数 f。{ 标记 test 函数体的开始。
- 如果调用/调用上述 test 函数,则执行的语句。
- 上述 test 函数体的结束。
- 创建一个名为 test 的函数,该函数接受一个字符参数 ch。{ 标记 test 函数体的开始。
- 如果调用/调用上述 test 函数,则执行的语句。
- 上述 test 函数体的结束。
- 调用 main() 函数。{ 标记函数的开始。
- 调用 test 函数并将 5 作为参数值传递给它。这将调用接受整数参数的 test 函数,即第一个 test 函数。
- 调用 test 函数并将 5.5 作为参数值传递给它。这将调用接受浮点参数的 test 函数,即第二个 test 函数。
- 调用 test 函数并将 five 作为参数值传递给它。这将调用接受字符参数的 test 函数,即第三个 test 函数。
- 程序成功运行时必须返回一个值。
- main() 函数体的结束。
我们有三个同名但参数类型不同的函数。我们已经实现了多态。
运算符重载
在运算符重载中,我们为 C++ 运算符定义了新的含义。它还会改变运算符的工作方式。例如,我们可以定义 + 运算符来连接两个字符串。我们知道它是在数值相加时使用的加法运算符。在我们的定义之后,当放在整数之间时,它会相加。当放在字符串之间时,它会连接它们。
示例 2
#include<iostream> using namespace std; class ComplexNum { private: int real, over; public: ComplexNum(int rl = 0, int ov = 0) { real = rl; over = ov; } ComplexNum operator + (ComplexNum const &obj) { ComplexNum result; result.real = real + obj.real; result.over = over + obj.over; return result; } void print() { cout << real << " + i" << over << endl; } }; int main() { ComplexNum c1(10, 2), c2(3, 7); ComplexNum c3 = c1+c2; c3.print(); }
输出
这是代码的屏幕截图:
代码解释
- 将 iostream 头文件包含到我们的程序中,以便使用其函数。
- 将 std 命名空间包含到我们的程序中,以便在不调用它的情况下使用其类。
- 创建一个名为 ComplexNum 的类。{ 标记类体的开始。
- 使用 private 访问修饰符将变量标记为私有,这意味着它们只能在类内部访问。
- 定义两个整数变量 real 和 over。
- 使用 public 访问修饰符将构造函数标记为 public,这意味着即使在类外部也可以访问它。
- 创建类构造函数并初始化变量。
- 初始化变量 real 的值。
- 初始化变量 over 的值。
- 构造函数体的结束。
- 我们需要重写 + 运算符的含义。
- 创建 ComplexNum 类型的 result 数据类型。
- 使用 + 运算符处理复数。此行将一个数的实部加到另一个数的实部。
- 使用 + 运算符处理复数。此行将一个数的虚部加到另一个数的虚部。
- 程序将在成功执行后返回 result 变量的值。
- 重载(+ 运算符的新含义)定义的结束。
- 调用 print() 方法。
- 在控制台上打印加法后的新复数。
- print() 函数体的结束。
- ComplexNum 类的结束。
- 调用 main() 函数。
- 传递要相加的实部和复数部分的值。c1 的第一部分将加到 c2 的第一部分,即 10+3。c1 的第二部分将加到 c 的第二部分,即 2+7。
- 使用重载的 + 运算符执行运算并将结果存储在变量 c3 中。
- 在控制台上打印变量 c3 的值。
- main() 函数主体的结束。
运行时多态
当对象的成员函数在运行时而不是在编译时被调用/调用时,就会发生这种情况。运行时多态是通过函数重写实现的。要被调用/调用的函数在运行时建立。
函数重写
当派生类定义了基类中定义的函数时,就会发生函数重写。此时,我们可以说基类函数已被重写。
例如
#include <iostream> using namespace std; class Mammal { public: void eat() { cout << "Mammals eat..."; } }; class Cow: public Mammal { public: void eat() { cout << "Cows eat grass..."; } }; int main(void) { Cow c = Cow(); c.eat(); return 0; }
输出
这是代码的屏幕截图:
代码解释
- 将 iostream 头文件导入到我们的程序中以使用其函数。
- 将 std 命名空间包含到我们的程序中,以便在不调用它的情况下使用其类。
- 创建一个名为 Mammal 的类。{ 标记类体的开始。
- 使用 public 访问修饰符将我们即将创建的函数标记为公共可访问的。它将可以从此类外部访问。
- 创建一个公共函数 eat。{ 标记函数体的开始。
- 调用 eat() 函数时,打印添加到 cout 函数的语句。
- eat() 函数体的结束。
- Mammal 类的结束。
- 创建一个继承 Mammal 类的 Cow 类。Cow 是派生类,Mammal 是基类。{ 标记此类的开始。
- 使用 public 访问修饰符将我们即将创建的函数标记为公共可访问的。它将可以从此类外部访问。
- 重写基类中定义的 eat() 函数。{ 标记函数体的开始。
- 调用此函数时在控制台上打印的语句。
- eat() 函数体的结束。
- Cow 类的结束。
- 调用 main() 函数。{ 标记此函数体的开始。
- 创建一个 Cow 类的实例并命名为 c。
- 调用 Cow 类中定义的 eat() 函数。
- 程序在成功完成后必须返回值。
- main() 函数结束。
C++ 虚函数
虚函数是 C++ 中实现运行时多态的另一种方法。它是在基类中定义并在派生类中重新定义的特殊函数。要声明虚函数,应使用 virtual 关键字。该关键字应放在基类函数声明的前面。
如果继承了虚函数类,虚函数类会重新定义虚函数以适应其需求。例如
#include <iostream> using namespace std; class ClassA { public: virtual void show() { cout << "The show() function in base class invoked..." << endl; } }; class ClassB :public ClassA { public: void show() { cout << "The show() function in derived class invoked..."; } }; int main() { ClassA* a; ClassB b; a = &b; a->show(); }
输出
这是代码的屏幕截图:
代码解释
- 将 iostream 头文件包含到代码中以使用其函数。
- 包含 std 命名空间到我们的代码中以使用其类而无需调用其名称。
- 创建一个名为 ClassA 的类。
- 使用 public 访问修饰符将类成员标记为公共可访问的。
- 创建一个名为 show() 的虚函数。它将是一个公共函数。
- 调用 show() 时打印到控制台的文本。endl 是 C++ 关键字,表示换行。它将鼠标光标移到下一行。
- 虚函数 show() 的结束。
- ClassA 类的结束。
- 创建一个继承 ClassA 类的名为 ClassB 的新类。ClassA 成为基类,ClassB 成为派生类。
- 使用 public 访问修饰符将类成员标记为公共可访问的。
- 重新定义基类中定义的虚函数 show()。
- 调用派生类中定义的 show() 函数时在控制台上打印的文本。
- show() 函数体的结束。
- 派生类 ClassB 的结束。
- 调用 main() 函数。程序逻辑应添加到其主体中。
- 创建一个名为 a 的指针变量。它指向名为 ClassA 的类。
- 创建一个名为 ClassB 的类实例。实例命名为 b。
- 将 b 地址中存储的值赋给变量 a。
- 调用派生类中定义的 show() 函数。已实现后期绑定。
- main() 函数体结束。
编译时多态与运行时多态
以下是两者的主要区别
编译时多态 | 运行时多态 |
---|---|
也称为早期绑定或静态多态 | 也称为后期/动态绑定或动态多态 |
方法在编译时被调用/调用 | 方法在运行时被调用/调用 |
通过函数重载和运算符重载实现 | 通过方法重写和虚函数实现 |
例如,方法重载。许多方法可能具有相似的名称,但参数的数量或类型不同 | 例如,方法重写。许多方法可能具有相似的名称和相同的原型。 |
执行速度更快,因为方法发现是在编译时完成的 | 执行速度较慢,因为方法发现是在运行时完成的。 |
由于所有内容在编译时都已知,因此提供的用于解决问题的灵活性较低。 | 由于方法在运行时发现,因此为解决复杂问题提供了更大的灵活性。 |
摘要
- 多态意味着具有多种形式。
- 当存在通过继承相关的类层次结构时,就会发生这种情况。
- 通过多态,函数可以根据调用/调用它的对象表现出不同的行为。
- 在编译时多态中,要调用的函数在编译时建立。
- 在运行时多态中,要调用的函数在运行时建立。
- 编译时多态通过函数重载和运算符重载确定。
- 在函数重载中,有许多具有相似名称但参数不同的函数。
- 参数可以在数量或类型上有所不同。
- 在运算符重载中,为 C++ 运算符定义了新的含义。
- 运行时多态通过函数重写实现。
- 在函数重写中,派生类为基类中定义的函数提供了新的定义。