c++中的多态性指的是什么
面向对象编程
C++中的多态性是一个重要的面向对象编程(OOP)概念,它允许使用同一个接口(函数名或消息)来调用不同类中实现的不同功能。12
多态性可以分为两种主要类型:静态多态性和动态多态性。静态多态性,也称为编译时多态性,主要通过函数重载(function overloading)实现,在编译时就能确定具体调用的函数版本。动态多态性,也称为运行时多态性,主要通过虚函数(virtual functions)实现,在运行时才能确定具体调用的函数版本。
要实现动态多态性,需要在基类中声明虚函数,并在派生类中重写这些虚函数。这样,即使使用基类的指针或引用调用函数,实际执行的是与该指针或引用关联的派生类的实现。
C++中的多态性(Polymorphism)是面向对象编程中的一个重要概念,它允许通过基类的指针或引用来调用派生类的成员函数,以实现动态绑定和运行时的多态行为。
C++中的多态性有两种形式:静态多态性(静态多态性)和动态多态性(运行时多态性)。
- 静态多态性:
-
- 函数重载(Function Overloading):在同一个作用域内使用相同的函数名,但参数列表不同,可以实现函数重载。编译器根据函数调用时的参数类型或个数来选择合适的函数进行调用。
- 操作符重载(Operator Overloading):通过重新定义操作符的含义和行为,使其适用于自定义类型的对象。
- 动态多态性:
-
- 虚函数(Virtual Functions):使用虚函数实现动态多态性。通过在基类中声明虚函数,并在派生类中进行重写,可以在运行时根据对象的实际类型来调用对应的函数。
- 纯虚函数(Pure Virtual Functions)和抽象类(Abstract Classes):纯虚函数没有具体的实现,在基类中只进行声明。包含纯虚函数的类被称为抽象类,不能创建对象,只能被继承并在派生类中实现纯虚函数。
下面是一个使用虚函数实现多态性的示例:
class Animal {
public:
virtual void makeSound() {
std::cout << "Animal makes a sound\n";
}
};
class Dog : public Animal {
public:
void makeSound() {
std::cout << "Dog barks\n";
}
};
class Cat : public Animal {
public:
void makeSound() {
std::cout << "Cat meows\n";
}
};
int main() {
Animal* animal1 = new Dog();
Animal* animal2 = new Cat();
animal1->makeSound(); // 调用Dog类的makeSound()函数
animal2->makeSound(); // 调用Cat类的makeSound()函数
delete animal1;
delete animal2;
return 0;
}
在上述示例中,Animal
类是一个基类,Dog
和Cat
类是派生类。它们都有一个名为makeSound()
的虚函数。在main()
函数中,通过基类指针调用makeSound()
函数,根据对象的实际类型在运行时来确定要执行的函数。这就是动态多态性的体现。
使用多态性可以提高代码的灵活性和扩展性,使得程序更易于维护和修改
多态概念
多态(英语:polymorphism)指为不同数据类型的实体提供统一的接口。多态类型(英语:polymorphic type)可以将自身所支持的操作套用到其它类型的值上。(百度百科)
通俗的来讲,就是去完成某一个行为,不同的对象执行出来的状态不一样。C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。
函数重载,也是一种多态现象,通过命名倾轧在编译阶段决定,故称为静多态。
动多态,不是在编译器阶段决定,而是在运行阶段决定,故称为动多态。本篇文章是讲的是动多态。
多态的基础:
- 基类声明虚函数
- 派生类声明一个函数覆盖该虚函数
多态带来的好处这里不多说,其多态带来的麻烦主要是继承控制的问题。类之间是通过继承关联时,就会用到多态。
多态形成的条件
根据父类指针指向的实际对象类型决定调用的函数,即多态。多态中,父类指针(引用)指向父类对象则调用父类中定义的函数,父类指针(引用)指向子类对象时调用子类对象的函数。
C++通过virtual关键字对多态进行支持,被virtual声明的函数被重写后具有多态属性。
多态形成的条件:
- 父类中有虚函数。
class 类名
{
virtual 函数声明;
}
2.子类override(覆写)父类中的虚函数。
3.通过己被子类对象赋值的父类指针,调用共用接口。
多态的意义
多态的意义如下:
A、在程序运行过程中展现出的动态特性
B、函数重写必须多态实现,否则没有意义
C、多态是面向对象组件化程序设计的基础特性
D、多态是一种跨层操作
静态联编是在程序的编译期间就能确定具体的函数调用,如函数重载。
动态联编是在程序实际运行时才能确定具体的函数调用,如函数重写。
举一个例子
#include <iostream>
using namespace std;
class Animal
{
protected: //如果不是public 常用默认的private 是会报出错误的
//error: ‘int Animal::age’ is private within this context
int age, weight;
public:
Animal( int a=0, int b=0)
{
age = a;
weight = b;
}
int show()
{
cout << "Parent class show :" <<endl;
return 0;
}
};
class Dog: public Animal{
public:
Dog( int a=0, int b=0):Animal(a, b) { }
int show ()
{
cout << "Dog class area :" <<endl;
return (age /weight);
}
};
class Cat: public Animal{
public:
Cat( int a=0, int b=0):Animal(a, b) { }
int show ()
{
cout << "Cat class show:" <<endl;
return (age /weight);
}
};
// 程序的主函数狗 猫
int main( )
{
Animal *animal;
Dog dog(10,7);
Cat cat(10,5);
// 狗的地址
animal = &dog; //或者 Animal &animal = dog;
// 调用狗函数show
animal->show();
// 猫的地址 //或者 Animal &animal = cat;
animal = &cat;
// 调用猫函数show
animal->show();
return 0;
}
编译运行:
Parent class show :
Parent class show :
导致错误输出的原因是,调用函数 show() 被编译器设置为基类中的版本,这就是所谓的静态多态,或静态链接 - 函数调用在程序执行前就准备好了。有时候这也被称为早绑定,因为 show() 函数在程序编译期间就已经设置好了。但现在,让我们对程序稍作修改,在Animal 类中,show() 的声明前放置关键字 virtual,如下所示:
class Animal
{
protected: //如果不是public 常用默认的private 是会报出错误的
//error: ‘int Animal::age’ is private within this context
int age, weight;
public:
Animal( int a=0, int b=0)
{
age = a;
weight = b;
}
virtual int show()
{
cout << "Parent class show :" <<endl;
return 0;
}
};
修改后,当编译和执行前面的实例代码时,它会产生以下结果:
Dog class area :
Cat class show:
赋值兼容规则
赋值兼容规则是指在需要基类对象的任何地方都可以使用公有派生类的对象来替代。
赋值兼容是一种默认行为,不需要任何的显式的转化步骤,只能发生在public继承方式中,是多态实现的前提条件。
赋值兼容规则中所指的替代包括以下的情况:
A、子类对象可以直接赋值给父类对象
B、子类对象可以直接初始化父类对象
C、父类引用可以直接引用子类对象
D、父类指针可以直接指向子类对象
当使用父类指针(引用)指向子对象时,子类对象退化为父类对象,只能访问父类中定义的成员,可以直接访问被子类覆盖的同名成员。
Animal *animal;
Dog dog(10,7);
Cat cat(10,5);
animal = &dog; //指向子类对象的父类指针退化为父类对象 使用->
animal->show();
animal = &cat; // 调用猫函数show
animal->show();
如果是:
Dog dog(10,7);
Cat cat(10,5);
Animal animals;
Animal &animal = dog;
animal.show();
Animal &animal = cat; //指向子类对象的父类引用退化为父类对象 使用 .
animal.show();
Dog *dog= static_cast<Dog*>(&animals);
dog->show();
在替代后,派生类对象就可以作为基类的对象使用,但只能使用从基类继承的成员。
父类也可以通过强转的方式转化为子类,但存在访问越界的风险。
子类中可以重定义父类中已经存在的成员函数,即函数重写。
当函数重写遇到赋值兼容时,编译器只能根据指针的类型判断所指向的对象,根据赋值兼容原则,编译器认为父类指针指向的是父类对象,只能调用父类中定义的同名函数。
面向对象编程中,通常需要根据实际的对象类型判断如何调用重写函数。当父类指针指向父类对象,则调用父类定义的函数;当父类指针指向的是子类对象时,需要调用子类定义的函数;当父类引用对父类对象进行引用时,调用父类对象定义的函数;当父类引用对子类对象进行引用时,调用子类定义的函数。
作者:黑马程序员
链接:https://www.zhihu.com/question/333280913/answer/2747703775
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
多态分为两类:
静态多态和动态多态区别:
- 静态多态的函数地址早绑定 — 编译阶段确定函数地址
- 动态多态的函数地址晚绑定 — 运行阶段确定函数地址
class Animal
{
public:
//Speak函数就是虚函数
//函数前面加上virtual关键字,变成虚函数,那么编译器在编译的时候就不能确定函数调用了。
virtual void speak()
{
cout << "动物在说话" << endl;
}
};
class Cat :public Animal
{
public:
void speak()
{
cout << "小猫在说话" << endl;
}
};
class Dog :public Animal
{
public:
void speak()
{
cout << "小狗在说话" << endl;
}
};
//我们希望传入什么对象,那么就调用什么对象的函数
//如果函数地址在编译阶段就能确定,那么静态联编
//如果函数地址在运行阶段才能确定,就是动态联编
void DoSpeak(Animal & animal)
{
animal.speak();
}
//
//多态满足条件:
//1、有继承关系
//2、子类重写父类中的虚函数
//多态使用:
//父类指针或引用指向子类对象
void test01()
{
Cat cat;
DoSpeak(cat);
Dog dog;
DoSpeak(dog);
}
int main() {
test01();
system("pause");
return 0;
}
总结:
多态满足条件:
- 有集成关系
- 子类重写父类的虚函数
多态使用条件
- 父类指针或引用指向子类对象
重写:函数返回值类型 函数名 参数列表 完全一致称为重写
多态案例—计算器类
案例描述:
分别利用普通写法和多态技术,设计实现两个操作数进行运算的计算器类
多态的优点:
- 代码组织结构清晰
- 可读性强
- 利于前期和后期的扩展以及维护
//普通实现
class Calculator {
public:
int getResult(string oper)
{
if (oper == "+") {
return m_Num1 + m_Num2;
}
else if (oper == "-") {
return m_Num1 - m_Num2;
}
else if (oper == "*") {
return m_Num1 * m_Num2;
}
//如果要提供新的运算,需要修改源码
}
public:
int m_Num1;
int m_Num2;
};
void test01()
{
//普通实现测试
Calculator c;
c.m_Num1 = 10;
c.m_Num2 = 10;
cout << c.m_Num1 << " + " << c.m_Num2 << " = " << c.getResult("+") << endl;
cout << c.m_Num1 << " - " << c.m_Num2 << " = " << c.getResult("-") << endl;
cout << c.m_Num1 << " * " << c.m_Num2 << " = " << c.getResult("*") << endl;
}
//多态实现
//抽象计算器类
//多态优点:代码组织结构清晰,可读性强,利于前期和后期的扩展以及维护
class AbstractCalculator
{
public :
virtual int getResult()
{
return 0;
}
int m_Num1;
int m_Num2;
};
//加法计算器
class AddCalculator :public AbstractCalculator
{
public:
int getResult()
{
return m_Num1 + m_Num2;
}
};
//减法计算器
class SubCalculator :public AbstractCalculator
{
public:
int getResult()
{
return m_Num1 - m_Num2;
}
};
//乘法计算器
class MulCalculator :public AbstractCalculator
{
public:
int getResult()
{
return m_Num1 * m_Num2;
}
};
void test02()
{
//创建加法计算器
AbstractCalculator *abc = new AddCalculator;
abc->m_Num1 = 10;
abc->m_Num2 = 10;
cout << abc->m_Num1 << " + " << abc->m_Num2 << " = " << abc->getResult() << endl;
delete abc; //用完了记得销毁
//创建减法计算器
abc = new SubCalculator;
abc->m_Num1 = 10;
abc->m_Num2 = 10;
cout << abc->m_Num1 << " - " << abc->m_Num2 << " = " << abc->getResult() << endl;
delete abc;
//创建乘法计算器
abc = new MulCalculator;
abc->m_Num1 = 10;
abc->m_Num2 = 10;
cout << abc->m_Num1 << " * " << abc->m_Num2 << " = " << abc->getResult() << endl;
delete abc;
}
int main() {
//test01();
test02();
system("pause");
return 0;
}
总结:C++开发提倡利用多态设计程序架构,因为多态优点很多
总结
编译器看的是指针的内容,而不是它的类型。因此,由于 dog 和cat 类的对象的地址存储在 *animal 中,所以会调用各自的 show() 函数。正如您所看到的,每个子类都有一个函数 show() 的独立实现。这就是多态的一般使用方式。有了多态,我们可以有多个不同的类,都带有同一个名称但具有不同实现的函数,函数的参数甚至可以是相同的。