我们都知道面向对象编程的三大特征是封装、继承、多态,今天我们就来说一下其中之一的多态。
概念:
多态:
多态字面意思就是多种形态,C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。(取自菜鸟教程)
多态分类:
多态分为编译时多态(早期绑定)和运行时多态(后期绑定)
编译时多态(早期绑定=静态联编)
概念:
? ? ? ? 通过函数重载和运算符重载来实现。
特点:
? ? ? ? 在编译时我们就已经知道调用的是哪些个函数
看下面的代码:
int Max(int a, int b) { return a > b ? a : b }
char Max(char a, char b) { return a > b ? a : b }
double Max(double a, double b) { return a > b ? a : b }
int main()
{
int x = Max(12, 23);
char ch = Max('a', 'b');
double de = Max(12.23, 34.45);
return 0;
}
上述代码我们通过重载函数Max来实现早期绑定,也就是在我们编译的时候通过函数重载的机制就已经知道调用的是哪个函数,比如Max(12,23)参数为int整型类型的参数,所以我们就知道调用的是第一个int Max()这个函数。
运行时多态(动态绑定=动态联编)
概念:
????????运行时的多态性是指在程序执行前,无法根据函数名和参数来确定该调用哪一个函数,必须在程序执行过程中,根据执行的具体情况来动态地确定。它是通过类继承关系public和虚函数来实现的。
特点: ? ? ? ? 同一个方法在不同的对象身上体现出来不同的代码形式,但是我们有统一的接口,例如下面代码中的fun函数这个方法,在基类中和派生类中体现出来的方法不一样!!!
? ? ? ? 就是说运行时多态是通过虚函数来实现的。虚函数在我上一篇说过:【C++】虚函数_神厨小福贵!的博客-CSDN博客虚函数是构成C++多态的重要一步,今天来说一下虚函数!虚函数:在基类(或父类)中,使用virtual关键字对函数进行声明为并在一个或多个派生类(子类)中被重新定义的成员函数,通过指向派生类的基类指针或引用,访问派生类中同名覆盖成员函数。它的用法是这样的:virtual + 函数返回类型 + 函数名 +(参数表) {函数体}首先我们要知道为什么要有虚函数这个东西?我来看下下面这两段代码的结果再来说结果:class A{public:void fun(){couthttps://blog.csdn.net/qq_45829112/article/details/123460960?spm=1001.2014.3001.5502
下面我那代码来实现一下动态绑定:
class A
{
public:
virtual void fun()
{
}
};
class B :public A
{
public:
virtual void fun()
{
cout << "class B::fun()" << endl;
}
};
class C :public A
{
public:
virtual void fun()
{
cout << "class C::fun()" << endl;
}
};
void pun(A& a)
{
a.fun();
}
int main()
{
B b;
C c;
pun(b);
pun(c);
return 0;
}
类B、C都公有继承类A,然后创建bc对象,分别调用函数pun,当传入参数为class B参数时 调用B中的妇女函数,传参为class C时,调用类C中的fun函数!、
上述代码解析如下图所示:
运行结果:
这就是简单的运行时的多态,相同的方法在不同的 不同对象下进行调用时,产生的结果也不尽相同,这也就是简单的运行时的多态!!!
抽象类(纯虚函数)
概念:
? ? ? ? 含有纯虚函数的类称为抽象类。纯虚函数没有实现部分,不能产生对象。
? ? ? ? 简单来说就是抽象类(有纯虚函数的类)不能定义对象!!!
????????virtual? 类型? 函数名(参数列表)= 0? 如下所示:
virtual void fun() { } = 0;
下面来说下纯虚函数的特点:
? ? ? ??抽象类只能作为基类来使用,其纯虚函数的实现由派生类给出。如果派生类没有重新定义纯虚函数,而派生类只是继承基类的纯虚函数,则这个派生类仍然还是一个抽象类。如果派生类中给出了基类纯虚函数的实现,则该派生类就不再是抽象类了,它是一个可以建立对象的具体类了。 ?
? ? ? ? 可能会有些抽象,举个简单例子就是说,你说你要去混社会去了,但是有社会这个实体这个对象嘛,显然是没有的,我们只能在社会上做某些事情,而没有社会这个实体,大致就这么个意思!!!
那么纯虚函数以及这个抽象类有什么作用呢???
class A
{
public:
virtual void fun() {} = 0; //纯虚函数
};
class B :public A
{
public:
virtual void fun() //必须要进行重写,否则class B也是一个抽象类,不可以定义对象
{
cout << "class B::fun()" << endl;
}
};
class C :public A
{
public:
virtual void fun() //必须要进行重写,否则class C也是一个抽象类,不可以定义对象
{
cout << "class C::fun()" << endl;
}
};
? ? ? ? 抽象类一般作为接口来使用,真正的实现都是在其派生类中进行实现,如上述代码中所示,在基类A中的fun啥也不干,仅仅搞个纯虚函数,真正的实现都是在其派生类class B和class C中来进行实现的。
析构函数为什么声明为虚函数???
class A
{
public:
A(int x = 10)
{
cout << "A构造函数运行" << endl;
}
virtual void fun()
{
}
~A()
{
cout << "~A运行" << endl;
}
};
class B :public A
{
public:
B(int x = 10)
{
cout << "B构造函数运行" << endl;
}
~B()
{
cout << "~B运行" << endl;
}
virtual void fun()
{
cout << "class B::fun()" << endl;
}
};
int main()
{
A* op = new B(10);
op->fun();
delete op;
op = nullptr;
return 0;
}
观察上述代码,父类指针指向派生类对象,完成执行fun函数之后,我们对op指针所指向资源进行释放以及置空,运行结果:
?为啥上面我们构造了两次,最后释放却只释放了一次呢?
? ? ? ? 原因是我们op是基类指针,指向派生类的对象,构造class B的无名对象的时候,先构造A的隐藏父对象,再构造B对象,但是我们释放的时候因为是基类指针,所以只能找到基类的析构函数,所以我们派生类的对象就没有进行释放,可能会造成内存泄漏!!!
怎么解决这个问题呢?父类析构函数加虚声明virtual:
class A
{
public:
A(int x = 10)
{
cout << "A构造函数运行" << endl;
}
virtual void fun()
{
}
virtual ~A() //构造函数加virtual声明
{
cout << "~A运行" << endl;
}
};
class B :public A
{
public:
B(int x = 10)
{
cout << "B构造函数运行" << endl;
}
~B()
{
cout << "~B运行" << endl;
}
virtual void fun()
{
cout << "class B::fun()" << endl;
}
};
int main()
{
A* op = new B(10);
op->fun();
delete op;
op = nullptr;
return 0;
}
至于派生类的析构函数,我们可以加也可以不加,因为如果我们派生类还有派生类的话,就得加virtual声明,下面我们来看运行结果:
我们在基类中将析构函数声明为virtual虚函数,派生类相当于继承了虚函数这个特性,所以析构的时候,就可以通过虚表指针来找到派生类中的虚函数来对派生类对象进行析构!!!
|