本篇文章进行C++中类多态(polymorphism)的学习!!!
本文章代码在x64程序中测试,涉及指针是8bytes的,如果为x86,涉及指针则为4bytes
在继承中构成多态的条件有两种:
class Person
{
public:
virtual void BuyTicket() { cout << "买票全价" << endl; }
};
class Person
{
public:
virtual void BuyTicket() { cout << "买票全价" << endl; }
};
class Student : public Person
{
/*注意:在重写基类虚函数时,派生类的虚函数在不加virtual关键字时,虽然也可以构成重写(因为继承后
基类的虚函数被继承下来了在派生类依旧保持虚函数属性),但是该种写法不是很规范,不建议这样使用*/
/*void BuyTicket() { cout << "买票-半价" << endl; }*/
virtual void BuyTicket() { cout << "买票半价" << endl; }
};
void Func(Person& p)
{
p.BuyTicket();
}
int main()
{
Person p;
Func(p);
Student s;
Func(s);
return 0;
}
虚函数重写的二个例外:(1)
class A {};
class B : public A {};
// 协变 --- 派生类虚函数返回类型与基类虚函数不同
class Person
{
public:
virtual A* f() { return new A; }
};
class Student : public Person
{
public:
public:
virtual B* f() override { return new B; }
};
void Test(Person& p)
{
A* ps = p.f();
}
int main()
{
Person p;
Student s;
Test(p);
Test(s);
return 0;
}
2. 析构函数的重写(基类与派生类析构函数的名称不同)
class Person
{
public:
virtual ~Person() { cout << "~Person()" << endl; }
};
class Student :public Person
{
public:
virtual ~Student() { cout << "~Student()" << endl; }
};
void Test()
{
// 首先开辟sizeof(Person)的堆空间 随后调用Person构造函数
Person* p = new Person;
Person* ps = new Student;
// 首先调用p的析构函数 随后free堆空间
delete p;
delete ps;
}
🌈前言:
1. final:修饰虚函数,表示该虚函数不能再被重写
class A
{
public:
virtual void Test() final {}
};
class B : public A
{
public:
virtual void Test() {}
};
2. override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错
class A
{
public:
virtual void Test(){}
};
class B : public A
{
public:
virtual void Test() override {}
};
纯虚函数:在虚函数的后面写上=0,即virtual = 0,则这个函数为纯虚函数
class Base
{
public:
// 纯虚函数
virtual void Show() = 0;
};
class Derive1 : public Base
{
public:
virtual void Show() { cout << "Derive1::Show()" << endl; }
};
class Derive2 : public Base
{
public:
virtual void Show() { cout << "Derive2::Show()" << endl; }
};
void Test()
{
Base* pb1 = new Derive1;
pb1->Show();
Base* pb2 = new Derive2;
pb2->Show();
delete pb1;
delete pb2;
}
接口继承和继承的区别:
// 这里常考一道笔试题:sizeof(Test)为多少bytes?
class Test
{
public:
virtual void f1() {}
private:
int _a = 0;
};
int main()
{
Test t;
cout << sizeof(t) << endl;
return 0;
}
那么派生类中这个表放了些什么呢?
// 1.我们增加一个派生类Derive去继承Base
// 2.Derive中重写Func1
// 3.Base再增加一个虚函数Func2和普通成员函数Func3
class Base
{
public:
virtual void Func1()
{
cout << "Base::Func1()" << endl;
}
virtual void Func2()
{
cout << "Base::Func2()" << endl;
}
void Func3()
{
cout << "Base::Func3()" << endl;
}
private:
int _a = 1;
};
class Derive : public Base
{
public:
virtual void Func1() override
{
cout << "Derive::Func1()" << endl;
}
private:
int _b = 2;
};
void Test(Base* b)
{
b->Func1();
b->Func2();
b->Func3();
}
int main()
{
Base b;
Derive d;
Test(&b);
Test(&d);
return 0;
}
通过调试,我们发现了一下几点问题:
多态的原理:
void Func(Person* p)
{
p->BuyTicket();
}
int main()
{
Person mike;
Func(&mike);
mike.BuyTicket();
return 0;
}
// 以下汇编代码中跟你这个问题不相关的都被去掉了
void Func(Person* p)
{
...
p->BuyTicket();
// p中存的是mike对象的指针,将p移动到eax中
001940DE mov eax, dword ptr[p]
// [eax]就是取eax值指向的内容,这里相当于把mike对象头4个字节(虚表指针)移动到了edx
001940E1 mov edx, dword ptr[eax]
// [edx]就是取edx值指向的内容,这里相当于把虚表中的头4字节存的虚函数指针移动到了eax
00B823EE mov eax, dword ptr[edx]
// call eax中存虚函数的指针。这里可以看出满足多态的调用,不是在编译时确定的,是运行起来以后到对象的中取找的。
001940EA call eax
00头1940EC cmp esi, esp
}
int main()
{
...
// 首先BuyTicket虽然是虚函数,但是mike是对象,不满足多态的条件,所以这里是普通函数的调用转换成地址时,是在编译时已经从符号表确认了函数的地址,直接call地址
mike.BuyTicket();
00195182 lea ecx, [mike]
00195185 call Person::BuyTicket(01914F6h)
...
}
class Base
{
public:
virtual void func1() { cout << "Base::func1" << endl; }
virtual void func2() { cout << "Base::func2" << endl; }
private:
int a = 0;
};
class Derive : public Base
{
public:
virtual void func1() { cout << "Derive::func1" << endl;}
virtual void func2() { cout << "Derive::func2" << endl; }
virtual void func3() { cout << "Derive::func3" << endl; }
virtual void func4() { cout << "Derive::func4" << endl; }
private:
int b = 0;
};
// 重命名函数指针类型,返回值为void,参数为空
typedef void (*VFPTR) ();
void PrintVTable(VFPTR* vTable)
{
// 依次取虚表中的虚函数指针打印并调用,查看调用哪个函数
cout << "虚表地址>" << vTable << endl;
for (int i = 0; vTable[i] != nullptr; ++i)
{
std::printf("第%d个虚函数地址: 0x%x,->", i, vTable[i]);
VFPTR f = vTable[i];
f();
}
cout << endl;
}
int main()
{
Base b;
Derive d;
// 32位下指向虚表的指针偏移量是4,用int
// 64位下指向虚表的指针偏移量是8,需要使用long long
VFPTR* vTableb = (VFPTR*)(*(long long*)&b);
PrintVTable(vTableb);
VFPTR* vTabled = (VFPTR*)(*(long long*)&d);
PrintVTable(vTabled);
return 0;
}
这里在x86下测试
class Base1
{
public:
virtual void Fun1() { cout << "Base1::Fun1()" << endl; }
virtual void Fun2() { cout << "Base1::Fun2()" << endl; }
private:
int b1 = 0;
};
class Base2
{
public:
virtual void Fun1() { cout << "Base2::Fun1()" << endl; }
virtual void Fun2() { cout << "Base2::Fun2()" << endl; }
private:
int b2 = 1;
};
class Derive : public Base1, public Base2
{
public:
virtual void Fun1() { cout << "Derive::Fun1()" << endl; }
virtual void Fun3() { cout << "Derive::Fun3()" << endl; }
private:
int d = 2;
};
using VFPTR = void(*) (); // 类型重命名
void PrintVTable(VFPTR vTable[]) // 参数类型:指向函数指针的指针(虚表首地址)
{
cout << " 虚表地址>" << vTable << endl;
for (int i = 0; vTable[i] != nullptr; ++i)
{
std::printf(" 第%d个虚函数地址 :0X%x,->", i, vTable[i]);
VFPTR f = vTable[i];
f();
}
cout << endl;
}
int main()
{
Derive d;
// 第一个虚表
VFPTR* vTableb1 = (VFPTR*)(*(int*)&d);
PrintVTable(vTableb1);
// 第二个虚表
VFPTR* vTableb2 = (VFPTR*)(*(int*)((char*)&d + sizeof(Base1)));
PrintVTable(vTableb2);
return 0;
}
感谢支持!!!