C++多态(一)

本文介绍了C++中的多态概念,包括静态多态和动态多态,重点讲述了动态多态中的虚函数、纯虚函数和抽象类。还讨论了虚函数表在单继承、多继承和菱形继承中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

多态按字面的意思就是多种形态。当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态。
C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。
一、基本概念
1.定义:不同对象收到相同的消息时,从而产生不同的动作,即一个名字定义的不同的函数
2.对象类型:(1)静态类型:对象声明时的类型,是在编译时确定的
(2)动态类型:目前所指对象的类型,是在运行确定的
3.静态多态(早绑定)
包含:函数重载、泛型函数(模板)
概念:编译器在编译期间完成的,编译器根据函数实参的类型(可能会进行隐式类型转换),可推出要调用哪个函数,如果有对应的函数就调用,没有的话就编译错误。
有关函数重载:即在同一作用域内,函数名相同,参数列表不同,但函数的返回类型不作为判断函数是否重载的准则,函数重载避免了函数名的污染

int Sub(int a, int b)
{
      return a - b;
}
int Sub(char a, int b)
{
      return a - b;
}
int main()
{
      Sub(1, 2);
      Sub('1', 1);//形成函数重载,避免名字污染
      return 0;
}

4.动态多态(晚绑定)
包含:虚函数
概念:在程序执行(非编译期间)判断所引用对象的实际类型,根据其实际类型调用相应的方法
动态绑定的条件:1.必须是虚函数(在派生类中必须对基类的虚函数进行重写)。2.通过基类类型的引用或者指针调用虚函数。

#include<iostream>
using namespace std;
class Base
{
public:
      Base(int x, int y)
      {
            a = x;
            b = y;
      }
      void show()
      {
            cout << "Base:";
            cout << a << "  " << b << endl;
      }
private:
      int a;
      int b;
};
class Derived :public Base
{
public:
      Derived(int x, int y, int z)
            :Base(x, y)
      {
            c = z;
      }
      void show()
      {
            cout << "Derived:";
            cout << "c=" << c << endl;
      }
private:
      int c;
};
int main()
{
      Base mb(60, 60);//定义基类对象
      Base *pc;//定义基类对象的指针
      Derived mc(10, 20, 30);//定义派生类对象
      pc = &mb;//利用基类对象的指针指向基类对象
      pc->show();//调用基类对象的show函数
      pc = &mc;//让基类对象的指针指向派生类的对象
      pc->show();//这个指针在此调用的是基类中的show函数
      system("pause");
      return 0;
}
注:原因是:基类的对象指针可以指向他公有派生类的对象,但是当其指向公有派生类对象时,他只能访问派生类中从基类继承来的成员,而不能访问公有派生类中定义的成员(详细参考继承中的对象模型)
如若想要定义一个对象,也可以访问派生类中的成员,则需要用到虚函数,或者进行强转

二、虚函数
1.定义:虚函数定义是在基类中进行的,他是在基类中需要定义为虚函数的成员函数的声明中冠以关键字virtual,从而形成一种接口。
2.关键字:virtual
3.形式:

virtual 返回类型 函数名(参数列表)
{
    函数体
}

注:基类中的某个成员函数被声明为虚函数之后,此虚函数就可以在一个或多个派生类中被重新定义。虚函数在重新定义时,其函数原型(返回类型或者满足赋值兼容规则的指针、引用的返回,函数名,参数个数,参数类型的顺序),都必须与基类中的原型相同。
必须在基类中定义虚函数

只在基类中声明
class Base
{
public:
      Base(int x, int y)
      {
            a = x;
            b = y;
      }
      virtual void show()
      {
            cout << "Base:";
            cout << a << "  " << b << endl;
      }
private:
      int a;
      int b;
};
原因:虚函数,是实现动态绑定的,在函数运行时确定所要调用的函数,而此时pc指向派生类的对象,因此调用派生类的函数

这里写图片描述

4.在基类中声明虚函数,在派生类中,可以不再需要写虚函数关键字
5.只有通过基类指针访问虚函数时才能获得运行时的多态性
6.虚函数必须是类的成员函数,不能是友元函数与静态成员函数( 虚函数调用要靠特定的对象来决定激活哪个函数)
7.内联函数不能是虚函数()
8.构造函数不能是虚函数,但是析构函数可以是虚函数,而且通常说明为虚函数
在基类中用virtual声明成员函数为虚函数,在类外定义虚函数时,不必在加虚函数关键字

int main()
{
      Base *p;//定义基类对象的指针
      p = new Derived;//用运算符new为派生类的无名对象动态地分配了一个存储空间,并将地址赋给对象指针p
      delete p;
      system("pause");
      return 0;
}
注意:运行此函数,在调用析构函数时,采用的是静态连接的方式,因此只调用了基类的析构函数。因为p是基类的对象,就是为了防止动态开辟时

这里写图片描述

析构函数定义为虚函数的形式:

virtual ~ 类名
{
    函数体
}

三、纯虚函数与抽象类
(一)纯虚函数
1.作用:考虑到派生类的需要,在基类中预留一个函数名,具体功能留给派生类去实现,以便派生类根据需要对它进行重新定义,在基类中不需要实现
2.形式:

virtual 函数类型 函数名(参数表)=0;

3.举个例子

class A
{
public:
      void set(int x)
      {
            r = x;
      }
      virtual void show() = 0;//纯虚函数的定义
protected:
      int r;
};
class B
{
public:
      void show()
      {
            cout << "A is" << r;
      }
};
class C :public A
{
public:
      void show()
      {
            cout << "C is " << 2 * r << endl;
      }
};

(二)抽象类
1.定义:一个类中至少有一个虚函数,那么就称为该类为抽象类
2.几点规定:(1)由于抽象类中至少包含一个没有定义功能的纯虚函数。因此=,抽象类只能作为其他类的基类来使用,不能建立抽象类对象
(2)不允许从具体类派生出抽象类
(3)抽象类不能做函数的参数类型、函数的返回类型或显示转换类型
(4)可以声明指向抽象类的指针或引用,此指针可以指向他的派生类,进而实现多态
(5)如果派生类中没有定义纯虚函数的实现,而派生类只是继承基类的纯虚函数,则这个派生类仍然是一个抽象类。如果派生类中给出了基类纯虚函数的实现,则派生类就不再是抽象类了,他是一个可以建立对象的具体类了。

以下函数可以通过编译吗?编译的结果是什么?

#include<iostream>
using namespace std;
class Base
{
public:
      //在基类中对其声明为虚函数,在派生类中对这个函数进行了重写
      virtual void FunTest1(int i)
      {
            cout << "Base::FunTest1()" << endl;
      }
      //没有对其进行重写,因为没有在基类中将其声明为虚函数,即使在派生类中已是虚函数,也不进行重写
      void FunTest2(int i)
      {
            cout << "Base::FunTest2()" << endl;
      }
      //在基类中对其声明为虚函数,在派生类中对这个函数进行了重写
      virtual void FunTest3(int i)
      {
            cout << "Base::FunTest3()" << endl;
      }
      //即使在基类中对其声明为虚函数,但是在虚类中,两个函数的原型不同(参数列表的个数不一样)因此没有对其形成重写
      virtual void FunTest4(int i)
      {
            cout << "Base::FunTest4()" << endl;
      }
};
class Derived :public Base
{
public:
      virtual void FunTest1(int i)
      {
            cout << "Derived::FunTest1()" << endl;
      }
      virtual void FunTest2(int i)
      {
            cout << "Derived::FunTest2()" << endl;
      }
      void FunTest3(int i)
      {
            cout << "Derived::FunTest3()" << endl;
      }
      virtual void FunTest4(int i,int j)
      {
            cout << "Derived::FunTest4()" << endl;
      }
};
int main()
{
      Base b;//定义基类的对象
      Base*pb;//定义基类对象的指针
      Derived d;//定义派生类对象
      pb = &d;//利用派生类为基类赋值(赋值兼容规则)
      pb->FunTest1(0);//调用派生类函数
      pb->FunTest2(0);//调用基类函数
      pb->FunTest3(0);//调用派生类函数
      pb->FunTest4(0);//调用基类函数
      //pb->FunTest4(0, 0);//编译出错
      system("pause");
      return 0;
}

这里写图片描述

3.有关函数重载,同名隐藏,函数重写(覆盖)的区别
函数重载:同一作用域,函数名字相同,参数列表不同,与返回值是否相同没有关系
同名隐藏:在继承体系中,如果基类和派生类有相同名称的成员。如果使用派生类对象调用基类和派生类中的同名函数或成员,优先调用派生类说的同名函数。
同名成员变量,和类型无关。同名函数,和原型是否相同没有关系
函数重写:继承体系中,函数原型相同(返回值,参数列表,函数名)协变除外以及析构函数除外,基类函数必须有virtual关键字,但是访问限定符可以不相同
协变:在基类与派生类中,函数的返回值不同(基类返回基类类型的引用或者指针,派生类返回派生类类型的引用或者指针)

4.总结:
1、派生类重写基类的虚函数实现多态,要求函数名、参数列表、返回值完全相同。(协变除外)——重写概念
2、基类中定义了虚函数,在派生类中该函数始终保持虚函数的特性。
3、只有类的非静态成员函数才能定义为虚函数,静态成员函数不能定义为虚函数。(需要利用this指针调用)
4、如果在类外定义虚函数,只能在声明函数时加virtual关键字,定义时不用加。
5、构造函数不能定义为虚函数,虽然可以将operator=定义为虚函数,但最好不要这么做,使用时容易混淆
6、不要在构造函数和析构函数中调用虚函数,在构造函数和析构函数中,对象是不完整的,可能会出现未定义的行为。
7、最好将基类的析构函数声明为虚函数。(析构函数比较特殊,因为派生类的析构函数跟基类的析构函数名称不一样,但是构成覆盖,这里编译器做了特殊处理)
8、虚表是所有类对象实例共用的

四、有关虚表
(一)单继承
(1)派生类中有对基类中的虚函数重写

#include<iostream>
using namespace std;
typedef void(*FuncPer)();
class Base
{
public:

      virtual void FunTest1()
      {
            cout << "Base::FunTest1()" << endl;
      }

      virtual void FunTest2()
      {
            cout << "Base::FunTest2()" << endl;
      }

      virtual void FunTest3()
      {
            cout << "Base::FunTest3()" << endl;
      }
      int _b;
};
class Derived :public Base
{
public:
      virtual void FunTest1()
      {
            cout << "Derived::FunTest1()" << endl;
      }
      virtual void FunTest2()
      {
            cout << "Derived::FunTest2()" << endl;
      }
      //新增的虚函数
      virtual void FunTest4()
      {
            cout << "Derived::FunTest4()" << endl;
      }
      int _d;
};
void PrintVF(Base& b)
{
      FuncPer*pFun = (FuncPer*)(*(int*)&b);//(*(int*)&b)相当于取出虚表的前四个字节中的内容,
                                                            //(FuncPer*)(*(int*)&b)取出这个虚表中保存的内容,即由虚表查询虚函数
      while (*pFun)
      {
            (*pFun)();
            pFun++;
      }
}
void Funtest(Base&b)
{
      PrintVF(b);
}
int main()
{
      Base b;//定义基类的对象 
      b._b = 0;
      cout << sizeof(b) << endl;
      Derived d;//定义派生类对象
      Funtest(b);
      Funtest(d);
      system("pause");
      return 0;
}

分析过程:
这里写图片描述

下面改变基类中虚函数的次序:
这里写图片描述

则发现虚函数的调用,与基类中的虚函数的次序有关
具体分析过程:
在基类中,定义虚函数,并在派生类中进行重写,可以发现在最后的结果中多了四个字节,通过分析可得,这个字节中保存的是一个地址,通过进一步的调用,发现这四个字节保存的是三个地址,从而的知,这三个地址保存的是基类虚函数中的地址,而在派生类中,对基类中的部分虚函数进行重写,从而用派生类中重写的虚函数,顶替相同偏移量中的基类虚函数,如若没有重写,则调用基类中的虚函数表,最后将派生类中新增的虚函数添加到虚表之后。
(2)派生类中没有对基类中的虚函数进行重写

#include<iostream>
using namespace std;
typedef void(*FuncPer)();
class Base
{
public:

      virtual void FunTest1()
      {
            cout << "Base::FunTest1()" << endl;
      }

      virtual void FunTest2()
      {
            cout << "Base::FunTest2()" << endl;
      }


      int _b;
};
class Derived :public Base
{
public:
      virtual void FunTest3()
      {
            cout << "Derived::FunTest3()" << endl;
      }
      virtual void FunTest5()
      {
            cout << "Derived::FunTest5()" << endl;
      }
      //新增的虚函数
      virtual void FunTest4()
      {
            cout << "Derived::FunTest4()" << endl;
      }
      int _d;
};
void PrintVF(Base& b)
{
      FuncPer*pFun = (FuncPer*)(*(int*)&b);//(*(int*)&b)相当于取出虚表的前四个字节中的内容,
                                                            //(FuncPer*)(*(int*)&b)取出这个虚表中保存的内容,即由虚表查询虚函数
      while (*pFun)
      {
            (*pFun)();
            pFun++;
      }
}
void Funtest(Base&b)
{
      PrintVF(b);
}
int main()
{
      Base b;//定义基类的对象 
      b._b = 0;
      cout << sizeof(b) << endl;
      Derived d;//定义派生类对象
      Funtest(b);
      cout << sizeof(d) << endl;
      Funtest(d);
      system("pause");
      return 0;
}

这里写图片描述
总结:(派生类中虚表的构造)
1.基类中的虚表拷贝一份
2.检测派生类中是否对基类中的虚函数进行重写——>用派生类中重写的虚函数来替换相同偏移量位置的基类函数
3.在虚表之后添加派生类自己的虚函数

(二)多继承
由于在派生类中是否对基类中的虚函数的重写,只是替换相同偏移量的函数,则在多继承中不对在不进行重写的虚函数进行讨论

#include<iostream>
using namespace std;
typedef void(*FuncPer)();
class A
{
public:

      virtual void FunTest1()
      {
            cout << "A::FunTest1()" << endl;
      }

      virtual void FunTest2()
      {
            cout << "A::FunTest2()" << endl;
      }
      virtual void FunTest3()
      {
            cout << "A::FunTest3()" << endl;
      }



      int _a;
};
class B
{
public:
      virtual void FunTest1()
      {
            cout << "B::FunTest1()" << endl;
      }
      //新增的虚函数
      virtual void FunTest4()
      {
            cout << "B::FunTest4()" << endl;
      }
      virtual void FunTest5()
      {
            cout << "B::FunTest5()" << endl;
      }
      int _b;
};
class D :public A, public B
{
public:
      virtual void FunTest1()
      {
            cout << "D::FunTest1()" << endl;
      }
      virtual void FunTest2()
      {
            cout << "D::FunTest2()" << endl;
      }
      virtual void FunTest4()
      {
            cout << "D::FunTest4()" << endl;
      }
      virtual void FunTest6()
      {
            cout << "D::FunTest6()" << endl;
      }
      int _d;
};
void PrintVF(A& a)
{
      FuncPer*pFun = (FuncPer*)(*(int*)&a);//(*(int*)&b)相当于取出虚表的前四个字节中的内容,
                                                            //(FuncPer*)(*(int*)&b)取出这个虚表中保存的内容,即由虚表查询虚函数
      while (*pFun)
      {
            (*pFun)();
            pFun++;
      }
}
void PrintVF(B& b)
{
      FuncPer*pFun = (FuncPer*)(*(int*)&b);//(*(int*)&b)相当于取出虚表的前四个字节中的内容,
      //(FuncPer*)(*(int*)&b)取出这个虚表中保存的内容,即由虚表查询虚函数
      while (*pFun)
      {
            (*pFun)();
            pFun++;
      }
}
int main()
{
      //cout << sizeof(D) << endl;
      A a;//定义基类的对象

      B b;
      D d;//定义派生类对象
      d._a = 0;
      d._b = 1;
      d._d = 2;
      A&pa = d;
      B&pb = d;


      PrintVF(pa);
      PrintVF(pb);
      system("pause");
      return 0;
}

这里写图片描述

具体调用过程:先继承的基类的虚表在前,将派生类自己新增的虚表放在第一张的虚表之后(共有两张虚表,则两个基类各自一张)

(三)菱形继承
有关带虚函数的虚拟继承

//菱形继承+虚继承+虚函数
#include<iostream>
using namespace std;
typedef void(*FuncPer)();
class A
{
public:
      virtual void FunTest1()
      {
            cout << "A::FunTest1()" << endl;
      }
      virtual void FunTest2()
      {
            cout << "A::FunTest2()" << endl;
      }
      int _a;
};
class C1 :virtual public A
{
public:
      virtual void FunTest1()
      {
            cout << "C1::FunTest1()" << endl;
      }
      virtual void FunTest3()
      {
            cout << "C1::FunTest3()" << endl;
      }
      int _c1;
};
class C2 :virtual public A
{
public:
      virtual void FunTest1()
      {
            cout << "C2::FunTest1()" << endl;
      }
      virtual void FunTest4()
      {
            cout << "C2::FunTest4()" << endl;
      }
      int _c2;
};
class D :public C1, public C2
{
public:
      virtual void FunTest1()
      {
            cout << "D::FunTest1()" << endl;
      }
      virtual void FunTest3()
      {
            cout << "D::FunTest3()" << endl;
      }
      virtual void FunTest4()
      {
            cout << "D::FunTest4()" << endl;
      }
      virtual void FunTest5()
      {
            cout << "D::FunTest5()" << endl;
      }
      int _d;
};
void PrintVF(A& a)
{
      FuncPer*pFun = (FuncPer*)(*(int*)&a);//(*(int*)&b)相当于取出虚表的前四个字节中的内容,
      //(FuncPer*)(*(int*)&b)取出这个虚表中保存的内容,即由虚表查询
      while (*pFun)
      {
            (*pFun)();
            pFun++;
      }
}
void PrintVF(C1& c1)
{
      FuncPer*pFun = (FuncPer*)(*(int*)&c1);//(*(int*)&b)相当于取出虚表的前四个字节中的内容,
      //(FuncPer*)(*(int*)&b)取出这个虚表中保存的内容,即由虚表查询
      while (*pFun)
      {
            (*pFun)();
            pFun++;
      }
}
void PrintVF(C2& c2)
{
      FuncPer*pFun = (FuncPer*)(*(int*)&c2);//(*(int*)&b)相当于取出虚表的前四个字节中的内容,
      //(FuncPer*)(*(int*)&b)取出这个虚表中保存的内容,即由虚表查询虚函数
      while (*pFun)
      {
            (*pFun)();
            pFun++;
      }
}
int main()
{
      cout << sizeof(A) << endl;
      cout << sizeof(C1) << endl;
      cout << sizeof(C2) << endl;
      cout << sizeof(D) << endl;
      A a;//定义基类的对象
      C1 c1;
      C2 c2;
      D d;
      d._a = 0;
      d._c1 = 1;
      d._c2 = 2;
      d._d = 3;
      A&pa = d;
      C1&pc1 = d;
      C2&pc2 = d;
      PrintVF(pa);
      PrintVF(pc1);
      PrintVF(pc2);
      system("pause");
      return 0;
}

这里写图片描述

总结:在菱形继承+虚函数继承中,在派生类中,最底层只有一个对最底层基类中虚函数的重写(A),在C1与C2中,里面分别有自己的偏移量表格与虚函数,在派生中,如果有对其里面的重写了,则在打印虚函数表示,将其替换即可。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值