C++ 多态性(附示例)

C++ 中的多态是什么?

在 C++ 中,多态是指成员函数根据调用/调用它的对象而表现出不同的行为。多态是一个希腊词,意思是具有多种形式。当你有一个通过继承相关的类层次结构时,就会发生多态。

例如,假设我们有一个函数 makeSound()。当一只猫调用此函数时,它会发出“喵”的声音。当一头牛调用同一函数时,它会发出“哞”的声音。

 Polymorphism in C++

尽管我们只有一个函数,但它在不同情况下表现不同。该函数有多种形式;因此,我们实现了多态。

多态的类型

C++ 支持两种多态

  • 编译时多态,和
  • 运行时多态。

Types of Polymorphism

编译时多态

您通过匹配参数的数量和类型来调用重载的函数。信息在编译时就已存在。这意味着 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;
}

输出

Function Overloading

这是代码的屏幕截图:

Function Overloading

代码解释

  1. 将 iostream 头文件包含到我们的代码中。我们将能够使用它的函数。
  2. 将 std 命名空间包含到我们的代码中。我们将能够使用它的类而不必调用它。
  3. 创建一个名为 test 的函数,该函数接受一个整数参数 i。{ 标记 test 函数体的开始。
  4. 如果调用/调用上述 test 函数,则执行的语句。
  5. 上述 test 函数体的结束。
  6. 创建一个名为 test 的函数,该函数接受一个浮点参数 f。{ 标记 test 函数体的开始。
  7. 如果调用/调用上述 test 函数,则执行的语句。
  8. 上述 test 函数体的结束。
  9. 创建一个名为 test 的函数,该函数接受一个字符参数 ch。{ 标记 test 函数体的开始。
  10. 如果调用/调用上述 test 函数,则执行的语句。
  11. 上述 test 函数体的结束。
  12. 调用 main() 函数。{ 标记函数的开始。
  13. 调用 test 函数并将 5 作为参数值传递给它。这将调用接受整数参数的 test 函数,即第一个 test 函数。
  14. 调用 test 函数并将 5.5 作为参数值传递给它。这将调用接受浮点参数的 test 函数,即第二个 test 函数。
  15. 调用 test 函数并将 five 作为参数值传递给它。这将调用接受字符参数的 test 函数,即第三个 test 函数。
  16. 程序成功运行时必须返回一个值。
  17. 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();
}

输出

Operator Overloading

这是代码的屏幕截图:

Operator Overloading

Operator Overloading

代码解释

  1. 将 iostream 头文件包含到我们的程序中,以便使用其函数。
  2. 将 std 命名空间包含到我们的程序中,以便在不调用它的情况下使用其类。
  3. 创建一个名为 ComplexNum 的类。{ 标记类体的开始。
  4. 使用 private 访问修饰符将变量标记为私有,这意味着它们只能在类内部访问。
  5. 定义两个整数变量 real 和 over。
  6. 使用 public 访问修饰符将构造函数标记为 public,这意味着即使在类外部也可以访问它。
  7. 创建类构造函数并初始化变量。
  8. 初始化变量 real 的值。
  9. 初始化变量 over 的值。
  10. 构造函数体的结束。
  11. 我们需要重写 + 运算符的含义。
  12. 创建 ComplexNum 类型的 result 数据类型。
  13. 使用 + 运算符处理复数。此行将一个数的实部加到另一个数的实部。
  14. 使用 + 运算符处理复数。此行将一个数的虚部加到另一个数的虚部。
  15. 程序将在成功执行后返回 result 变量的值。
  16. 重载(+ 运算符的新含义)定义的结束。
  17. 调用 print() 方法。
  18. 在控制台上打印加法后的新复数。
  19. print() 函数体的结束。
  20. ComplexNum 类的结束。
  21. 调用 main() 函数。
  22. 传递要相加的实部和复数部分的值。c1 的第一部分将加到 c2 的第一部分,即 10+3。c1 的第二部分将加到 c 的第二部分,即 2+7。
  23. 使用重载的 + 运算符执行运算并将结果存储在变量 c3 中。
  24. 在控制台上打印变量 c3 的值。
  25. 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;

}

输出

Function Overriding

这是代码的屏幕截图:

Function Overriding

代码解释

  1. 将 iostream 头文件导入到我们的程序中以使用其函数。
  2. 将 std 命名空间包含到我们的程序中,以便在不调用它的情况下使用其类。
  3. 创建一个名为 Mammal 的类。{ 标记类体的开始。
  4. 使用 public 访问修饰符将我们即将创建的函数标记为公共可访问的。它将可以从此类外部访问。
  5. 创建一个公共函数 eat。{ 标记函数体的开始。
  6. 调用 eat() 函数时,打印添加到 cout 函数的语句。
  7. eat() 函数体的结束。
  8. Mammal 类的结束。
  9. 创建一个继承 Mammal 类的 Cow 类。Cow 是派生类,Mammal 是基类。{ 标记此类的开始。
  10. 使用 public 访问修饰符将我们即将创建的函数标记为公共可访问的。它将可以从此类外部访问。
  11. 重写基类中定义的 eat() 函数。{ 标记函数体的开始。
  12. 调用此函数时在控制台上打印的语句。
  13. eat() 函数体的结束。
  14. Cow 类的结束。
  15. 调用 main() 函数。{ 标记此函数体的开始。
  16. 创建一个 Cow 类的实例并命名为 c。
  17. 调用 Cow 类中定义的 eat() 函数。
  18. 程序在成功完成后必须返回值。
  19. 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();      
	}

输出

C++ Virtual Function

这是代码的屏幕截图:

C++ Virtual Function

代码解释

  1. 将 iostream 头文件包含到代码中以使用其函数。
  2. 包含 std 命名空间到我们的代码中以使用其类而无需调用其名称。
  3. 创建一个名为 ClassA 的类。
  4. 使用 public 访问修饰符将类成员标记为公共可访问的。
  5. 创建一个名为 show() 的虚函数。它将是一个公共函数。
  6. 调用 show() 时打印到控制台的文本。endl 是 C++ 关键字,表示换行。它将鼠标光标移到下一行。
  7. 虚函数 show() 的结束。
  8. ClassA 类的结束。
  9. 创建一个继承 ClassA 类的名为 ClassB 的新类。ClassA 成为基类,ClassB 成为派生类。
  10. 使用 public 访问修饰符将类成员标记为公共可访问的。
  11. 重新定义基类中定义的虚函数 show()。
  12. 调用派生类中定义的 show() 函数时在控制台上打印的文本。
  13. show() 函数体的结束。
  14. 派生类 ClassB 的结束。
  15. 调用 main() 函数。程序逻辑应添加到其主体中。
  16. 创建一个名为 a 的指针变量。它指向名为 ClassA 的类。
  17. 创建一个名为 ClassB 的类实例。实例命名为 b。
  18. 将 b 地址中存储的值赋给变量 a。
  19. 调用派生类中定义的 show() 函数。已实现后期绑定。
  20. main() 函数体结束。

编译时多态与运行时多态

以下是两者的主要区别

编译时多态 运行时多态
也称为早期绑定或静态多态 也称为后期/动态绑定或动态多态
方法在编译时被调用/调用 方法在运行时被调用/调用
通过函数重载和运算符重载实现 通过方法重写和虚函数实现
例如,方法重载。许多方法可能具有相似的名称,但参数的数量或类型不同 例如,方法重写。许多方法可能具有相似的名称和相同的原型。
执行速度更快,因为方法发现是在编译时完成的 执行速度较慢,因为方法发现是在运行时完成的。
由于所有内容在编译时都已知,因此提供的用于解决问题的灵活性较低。 由于方法在运行时发现,因此为解决复杂问题提供了更大的灵活性。

摘要

  • 多态意味着具有多种形式。
  • 当存在通过继承相关的类层次结构时,就会发生这种情况。
  • 通过多态,函数可以根据调用/调用它的对象表现出不同的行为。
  • 在编译时多态中,要调用的函数在编译时建立。
  • 在运行时多态中,要调用的函数在运行时建立。
  • 编译时多态通过函数重载和运算符重载确定。
  • 在函数重载中,有许多具有相似名称但参数不同的函数。
  • 参数可以在数量或类型上有所不同。
  • 在运算符重载中,为 C++ 运算符定义了新的含义
  • 运行时多态通过函数重写实现。
  • 在函数重写中,派生类为基类中定义的函数提供了新的定义。