----------------"没有人在乎你的过程,但我在乎"
(1)抽象类:纯虚函数
在虚函数后面写上 =0,则这个虚函数就成为 “纯虚函数”。这种包含纯虚函数的类,叫做抽象类(接口类)。
值得注意,包含纯虚函数的类不能实例化出对象,当然 继承这样类的子类,也不能实例化出对象,除非派生类重写。
①纯虚函数语法
class Car
{
public:
virtual void Drive() = 0
{}
};
class BMW :public Car
{
public:
};
class Car
{
public:
virtual void Drive() = 0
{}
};
class BMW :public Car
{
public:
virtual void Drive()
{
cout << "BMW" << endl;
}
};
②接口继承和实现继承
可能你会对 接口继承感到陌生。所谓的接口继承,是和实现继承是对应关系。
普通函数的继承,就是实现继承。继承的实现是为了复用。
虚函数的继承是一种接口继承,派生类继承基类虚函数的接口,实现函数的重写,达到多态。
(2)多态原理:
上一篇,仅仅是使用讲解了 多态的语法。并没有深入谈及多态的原理。
比如: 编译器是如何在动态的多态下,找到指定的函数 并进行调用?
为什么虚函数有重写 ,重写又叫做覆盖呢?
要回答上面的问题,我们就要知道,一个包含虚函数的类,其中有多少成员?
①虚函数表(虚表)和虚函数表指针(虚表指针)
一道笔试题:下面Base类 多少字节
class Base
{
public:
virtual void Func1()
{
cout << "Func1()" << endl;
}
private:
int _b = 1;
char _ch = '\0';
};
可能你会觉得,这仅仅是一道考内存对齐的题。也就得8字节。
然而结果并非8字节。
这里可能会有概念混淆。
虚函数表!=虚基表
虽然 他们两者 都使用了virtual 关键字,但他们的使用的情况,解决的问题都不一样。
虚继承产生虚基表,虚基表里面存的是,基类偏移量。
(vfptr)虚函数表存的是,指向虚函数的指针(地址).
class Base
{
public:
virtual void Func1()
{
cout << "Func1()" << endl;
}
virtual void Func2()
{}
void Func3()
{}
private:
int _b = 1;
char _ch = '\0';
};
②深度理解多态(父类调用父类 子类调用子类):
class Person
{
public:
virtual void BuyTicket() { cout << "买票-全价" << endl; }
int _p = 1;
};
class Student : public Person
{
public:
virtual void BuyTicket() { cout << "买票-半价" << endl; }
int _s = 2;
};
void Func(Person& p)
{
p.BuyTicket();
}
int main()
{
Person Mike;
Student Jack;
Func(Mike);
Func(Jack);
return 0;
}
满足动态多态的条件:
父类指针或是引用,调用虚函数。不是在编译的时候就确定的(静态多态是这样),而是在运行时,父类引用、指针 指向的是父类 就调用父类的虚函数,指向的是子类调用的就是子类的虚函数。
所以: 如果不够成多态,那么跟你传过来什么对象,是没有关系的。
而是你接收 的对象指向什么,你就调用什么函数。
class Person
{
public:
virtual void BuyTicket() { cout << "买票-全价" << endl; }
int _p = 1;
};
class Student : public Person
{
public:
virtual void BuyTicket() { cout << "买票-半价" << endl; }
int _s = 2;
};
综上:
构成多态:接收哪个对象 ,就去调用哪个对象的虚函数
不构成多态: 接收对象类型是什么,就去调用该类型的函数。
多态与不构成多态,在汇编角度>_:
为什么,实现多态的条件之一:父类的引用/指针 调用虚函数?
③虚表和虚函数:
问一:对象中,虚表指针是在何时进行初始化的?虚表又是在上面时候生成?
初始化列表 编译时
问二:虚函数放在虚表 这句话对吗?
不太准确,虚表只是保存了虚函数的地址。虚函数还是存在代码段
问三:一个类的虚表,存放在哪里的?! 请验证一下(问得刁钻)。
大概存放在代码区
虚表和 代码段(常量区)的地址相近。
问四: 为什么虚函数的重写 ,也叫作覆盖?!
重写是在语法概念 虚表底层概念
④动态绑定和静态绑定(可以了解)
1.静态绑定(前期绑定),即程序在编译时期确定的程序行为。esg:函数重载
2.动态绑定(晚绑定),程序运行期间,根据拿到的不同类型确定自己的行为。esg:动态多态
⑤打印虚表:
class Base
{
public:
virtual void func1() { cout << "Base::func1" << endl; }
virtual void func2() { cout << "Base::func2" << endl; }
private:
int a;
};
//只让子类重写func1
class Derive :public Base {
public:
virtual void func1() { cout << "Derive::func1" << endl; }
virtual void func3() { cout << "Derive::func3" << endl; }
virtual void func4() { cout << "Derive::func4" << endl; }
private:
int b;
};
这里可以认为,是编译器监视窗口的优化 或者是bug。此时没有显示子类的虚函数。
我们在内存中可以猜测,这两个函数的地址。
注:虚表的内的 虚函数指针都会在末尾放上0;
class Base
{
public:
virtual void func1() { cout << "Base::func1" << endl; }
virtual void func2() { cout << "Base::func2" << endl; }
private:
int a;
};
//只让子类重写func1
class Derive :public Base {
public:
virtual void func1() { cout << "Derive::func1" << endl; }
virtual void func3() { cout << "Derive::func3" << endl; }
virtual void func4() { cout << "Derive::func4" << endl; }
private:
int b;
};
//定义一个 函数指针 指向的是虚表
typedef void(*VTFTPEY)();
void PrintVTF(VTFTPEY* p) //传入函数指针数组
{
printf("虚表地址:\n");
//虚表 最后末尾放0
for (int i = 0;p[i]!=nullptr;i++)
{
printf("VTF[%d]:%p->", i, p[i]);
p[i]();//拿到虚函数地址 并调用
}
}
int main()
{
Base b;
Derive d;
//强转 //拿到虚表
VTFTPEY* ptr1 =(VTFTPEY*) (*(int*)&b);
PrintVTF(ptr1); //接收 虚表指针
//我们可以通过 虚表指针--->找到虚表 从而可以从虚表中---->拿到函数指针去调用虚函数
VTFTPEY* ptr2 = (VTFTPEY*)(*(int*)&d);
PrintVTF(ptr2);
return 0;
}
(3)多继承虚表:
在多继承中,状况也更复杂。
class Base1
{
public:
virtual void func1() { cout << "Base::func1" << endl; }
virtual void func2() { cout << "Base::func2" << endl; }
private:
int a;
};
class Base2
{
public:
virtual void func3() { cout << "Base::func3" << endl; }
virtual void func4() { cout << "Base::func4" << endl; }
private:
int a;
};
//只让子类重写func1
class Derive :public Base1,public Base2
{
public:
virtual void func1() { cout << "Derive::func1" << endl; }
virtual void func3() { cout << "Derive::func3" << endl; }
virtual void func4() { cout << "Derive::func4" << endl; }
virtual void func5() { cout << "Derive::func5" << endl; }
private:
int b;
};
此时,Derive继承Base1 Base2
子类会继承 两个虚表。此时有个问题。当子类自己的虚函数,会放在哪一个虚表中呢?
我们借助上面打印虚表地址的函数;
所以,多继承下,子类的虚函数,会放在第一个虚表里。
(4)虚基表 和 虚函数表:
class A
{
public:
virtual void func()
{
cout << "A::func()" << endl;
}
public:
int _a;
};
class B : public A
//class B : virtual public A
{
public:
virtual void func()
{
cout << "B:func()" << endl;
}
virtual void func1()
{
cout << "B:func1()" << endl;
}
public:
int _b;
};
class C : public A
//class C : virtual public A
{
public:
virtual void func()
{
cout << "C::func()" << endl;
}
virtual void func1()
{
cout << "C::func1()" << endl;
}
public:
int _c;
};
class D : public B, public C
{
public:
virtual void func()
{
cout << "D::func()" << endl;
}
virtual void func1()
{
cout << "D::func1()" << endl;
}
public:
int _d;
};
当我们引入虚继承后:
可以看出一个类可以同时拥有虚基表 和 虚函数表。
内容也就这么多了。感谢你的阅读
祝你好运~