c++ 继承关系中的虚函数表
-
子类继承父类的虚函数表的方式是复制一份。存在虚函数的类,都有自己的虚函数表,不与其他类共用。
-
只要祖先类的某个函数被声明位virtual, 则在后代中无论是否显式地添加virtual,该函数一直都是虚的。
-
如果子类重写了某个虚函数,则该类的虚函数表对应位置上的虚函数地址会被覆盖。如果完全不重写,则子类的虚函数表里面的内容和父类虚函数表内容保持一致。
-
虚函数表类似于类的静态成员,被该类的所有实例共享。只不过该表是只读的,不存在线程安全问题。
-
实例对象在起始4字节中,会存放虚函数表的地址(跟系统寻址范围有关,有的系统8字节才能表示一个地址),可以通过手动寻址的方式完成虚函数调用。
-
多重继承(有多个父类并列)时,该类有多张虚函数表,03字节,47字节分别表示第一张虚函数表地址,第二张虚函数表地址,以此类推。
-
即便父类的虚函数是private的,子类的虚函数表中也依然存在它的地址,我们可以通过子类实例的首地址,寻找到虚函数表进而找到该虚函数,完成调用。这样会违背c++语言的设计初衷,破坏数据的安全性,一般不要轻易使用。
#include <iostream> #include <string> using namespace std; typedef void(__stdcall *Fun)(); typedef void(__stdcall *Fun1)(double); //声明virtual,让该成员函数地址在虚函数表中留下记录 class Brass{ private: std::string fullname; long acctNum; double balance; public: Brass(const std::string & s ="NullBody",long an=-1,double bal=0.0); void Deposit(double amt); virtual void ViewAcct() const; virtual void withdraw(double amt); double Balance() const; virtual void test(){cout<<"Brass::test"<<endl;} virtual ~Brass(){} }; //即便子类不再将父类的虚函数函数声明为virtual,只要祖先类中该函数是virtual,则它依然是虚函数 class BrassPlus:public Brass{ private: double maxLoan; double rate; double owesBank; public: BrassPlus(const std::string & s ="NullBody",long an=-1, double bal=0.0,double ml=500,double r=0.11125); BrassPlus(const Brass &ba,double ml=500,double r=0.11125); void ViewAcct()const; void withdraw(double amt); void ResetMax(double m){maxLoan=m;} void ResetRate(double r){rate = r;} void ResetOwes(){owesBank=0;} }; class BrassPlusPlus:public BrassPlus{ public: void ViewAcct() const override{cout<<"BrassPlusPlus viewacct"<<endl;} }; Brass::Brass(const std::string & s,long an,double bal):fullname(s),acctNum(an),balance(bal){} void Brass::Deposit(double amt){return;} void Brass::withdraw(double amt){cout<<"Brass withdraw: "<<amt<<endl;return;} double Brass::Balance() const{return balance;} void Brass::ViewAcct()const{cout<<"Brass viewacct"<<endl;} BrassPlus::BrassPlus(const std::string & s,long an,double bal,double ml,double r):Brass(s,an,bal),maxLoan(ml),rate(r){} BrassPlus::BrassPlus(const Brass &ba,double ml,double r):Brass(ba),maxLoan(ml),rate(r){} void BrassPlus::ViewAcct()const{cout<<"BrassPlus viewacct"<<endl;} void BrassPlus::withdraw(double amt){cout<<"BrassPlus withdraw: "<<amt<<endl;return;} void test(const Brass& br){ br.ViewAcct(); //br.withdraw(6.0);//编译不通,const 实例只能调用const成员函数; Brass& bbp=const_cast<Brass&>(br); bbp.withdraw(6.0); } int main() { #ifdef _WIN64 typedef __int64 ADTY;//64位操作系统,8个字节表示一个地址 #else typedef __int32 ADTY; #endif cout << "Hello World!" << endl; Brass bra("Jim",2,0.06); BrassPlus brap("Jhon",5,0.58,500,0.014); const Brass bra_2("Jack",3,0.02); Brass& b1 = bra; Brass& b2 = brap; Brass *bas[2]; bas[0] = &bra; bas[1] = &brap; cout << "对象bas[0]的虚函数表位于:"<<hex<<*(ADTY*)bas[0]<<",对象bas[0]的第一个虚函数位于:"<<hex<<*( (ADTY *)*(ADTY*)bas[0] + 0 )<<endl; cout << "对象bas[1]的虚函数表位于:" <<hex<<*(ADTY*)bas[1]<<",对象bas[1]的第一个虚函数位于:"<<hex<<*( (ADTY *)*(ADTY*)bas[1] + 0 )<<endl; cout << "子类虚函数表会从父类继承(复制一份,不是共用一份),如果子类重写了某个虚函数,则子类的虚函数表中对应的函数地址会被覆盖"<<endl; Fun cc = (Fun)( *( (ADTY *)*(ADTY*)bas[1] + 0 ) ); Fun1 dd = (Fun1)( *( (ADTY *)*(ADTY*)bas[1] + 1 ) ); cout<<"对象bas[1]虚函数调用结果:"<<endl; cc(); dd(8.9); ADTY *ptr = (ADTY*)(&bra); //可以看出虚函数表类似于类的静态成员,对象的首地址位置保存了虚函数表的地址 cout << "对象bra的虚函数表位于:"<<hex<<*ptr<<",对象bra的第一个虚函数位于:"<<hex<<*( (ADTY*)*ptr + 0 )<<endl; cout << "对象bra_2的虚函数表位于:" <<hex<<*(ADTY*)(&bra_2)<<",对象bra_2的第一个虚函数位于:"<<hex<<*( (ADTY*)*(ADTY*)(&bra_2) + 0 )<<endl; cout << "说明bra的虚函数表 与 bra_2的虚函数表 是同一张表"<<endl; ADTY* ptr1 =(ADTY*)*ptr; Fun pFun = (Fun)*(ptr1+0); //等效于 (Fun)(*((ADTY*)*(ADTY*)(&bra)+0)); pFun(); Fun1 pFun1 = (Fun1)*(ptr1+1); //等效于 (Fun)(*((ADTY*)*(ADTY*)(&bra)+1)); pFun1(9.0); cout << "Hello World end" << endl; return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)