阿里云虚拟主机做网站,外包公司软件开发怎么样,重庆推广一个网站,wordpress卸载多说注意 virtual关键字#xff1a; 1、可以修饰原函数#xff0c;为了完成虚函数的重写#xff0c;满足多态的条件之一 2、可以菱形继承中#xff0c;去完成虚继承#xff0c;解决数据冗余和二义性 两个地方使用了同一个关键字#xff0c;但是它们互相一点关系都没有 虚函… 注意 virtual关键字 1、可以修饰原函数为了完成虚函数的重写满足多态的条件之一 2、可以菱形继承中去完成虚继承解决数据冗余和二义性 两个地方使用了同一个关键字但是它们互相一点关系都没有 虚函数重写 多态的条件
1、虚函数的重写
2、父类对象的指针或者引用去调用虚函数 必须是父类指针或者引用 不可以是子类因为父类不可以传给子类 class Person
{
public:virtual void BuyTicket() { cout Person全票 endl; }
};
class Student : public Person
{
public:virtual void BuyTicket() { cout Student半票 endl; }
};
void func(Person p1)
{p1.BuyTicket();
}
int main()
{Person p1;Student s1;func(p1);func(s1);return 0;
}协变是多态的一种特殊情况
多态
1、虚函数的重写必须要函数名、返回值、参数要相同
2、父类对象的指针或者引用去调用虚函数
但是协变可以返回值可以不同
但是返回值必须是基类的指针或引用和子类的指针或引用 //class A
//{
//};
//class B :public A
//{
//}
//其他类的基类和派生类也可以
//class Person
//{
//public:
// virtual A* BuyTicket() { cout Person全票 endl; return nullptr; }
//};
//class Student : public Person
//{
//public:
// virtual B* BuyTicket() { cout Student半票 endl; return nullptr; }
//};
//void func(Person p1)
//{
// p1.BuyTicket();
//}
//class Person
{
public:virtual Person* BuyTicket() { cout Person全票 endl; return nullptr; }
};
class Student : public Person
{
public:virtual Student* BuyTicket() { cout Student半票 endl; return nullptr;}
};
void func(Person p1)
{p1.BuyTicket();
}
int main()
{Person p1;Student s1;func(p1);func(s1);return 0;
}析构函数
面试题析构函数需不需要加vitrual
class Person
{
public:~Person() { cout ~Person() endl; }};
class Student : public Person
{
public:~Student() { cout ~Student() endl;}
};int main()
{Person* p1 new Student;delete p1;return 0;
}这种情况下父类的指针指向了new Student 但是使用完会造成内存泄漏父类的指针只会调用父类的析构函数去清理该指向部分的空间但是我们需要清理子类的空间就要调用子类的析构函数所以需要加virtual 构成虚函数的重写让父类的指针调用构成多态就可以调用子类的析构函数。 看下一道面试题
在做面试题之前先看下面代码 在继承关系中 如何理解上述话呢
看下面代码 在满足多态的条件下虚函数的继承是继承了接口所以缺省值继承了但是子类要自己重写实现 所以当父类中的有虚函数子类的就可以不用加virtual但是不规范 答案是B 为什么多态就要继承父类的接口突然感悟 比喻子类中的函数 driveBanz* const this父类也有Car* const this //子类这个this是接收不了父类的指针只有父类的指针或引用才可以指向子类 //所以这个继承接口才需要继承父类的接口----突然感悟 下面代码
//作者蚂蚁捉虫虫
//链接https ://www.zhihu.com/question/517444641/answer/2390138862
//来源知乎
//著作权归作者所有。商业转载请联系作者获得授权非商业转载请注明出处。
#include iostream // std::cout
class Base {public:Base() {};virtual void func_a(int a 0) {}; //这个是虚函数子类只继承接口具体的实现由子类去实现void func_b(int b) { std::cout b 10 \n; }; //这个是实函数其接口和实现都会被子类继承
};class Base_A : public Base {
public:void func_a(int a15) { std::cout a \n; };
};class Base_B : public Base {
public:void func_a(int a) { std::cout a 15 \n; };
};int main()
{Base_A a;Base_B b;a.func_a(); //仅仅继承了基类的接口但没有继承实现a.func_b(10); //继承了基类的接口及实现std::cout std::endl;b.func_a(10); //仅仅继承了基类的接口但没有继承实现b.func_b(10); //继承了基类的接口及实现return 0;
}只有在满足多态的情况下虚函数的继承才是父类的虚函数继承对于子类来说继承的是父类的接口包括缺省值子类函数的实现需要子类来写 上述代码只是完成了重写并没有满足多态所以并没有继承接口 关键字final和override
1、final修饰虚函数表示该虚函数不能再被继承 也可以修饰class叫最终类不能被继承 override关键字检查子类的虚函数是否完成重写 构成虚函数重写吗 没有认真看但是不会报错所以加上override就可以自动检测检查子类的虚函数是否完成重写 重载、重写、重定义 抽象类 可以看下列代码
//作者蚂蚁捉虫虫
//链接https://www.zhihu.com/question/517444641/answer/2390138862
//来源知乎
//著作权归作者所有。商业转载请联系作者获得授权非商业转载请注明出处。class Base {public:Base(){};virtual void func_a(int a) 0; //这个是纯虚函数子类只继承接口具体的实现由子类去实现void func_b(int b) {std::cout b10 \n;}; //这个是实函数其接口和实现都会被子类继承
};class Base_A: public Base{
public:void func_a(int a){std::cout a \n;};
};class Base_B: public Base{
public:void func_a(int a){std::cout a 15 \n;};
};int main ()
{Base_A a;Base_B b;a.func_a(10); //仅仅继承了基类的接口但没有继承实现a.func_b(10); //继承了基类的接口及实现std::cout std::endl;b.func_a(10); //仅仅继承了基类的接口但没有继承实现b.func_b(10); //继承了基类的接口及实现return 0;
} 上述代码里定一个基类里面有两个成员函数一个是虚函数一个是实际函数然后又定义了两个子类Base_A和Base_B两个子类对基类中的func_b函数有不一样的实现 纯虚函数的作用强制子类完成重写 表示抽象的类型。抽象就是在现实中没有对应的实体的 接口继承和实现继承 多态的原理 测试我们发现b对象是8个字节除了_b成员还多了一个指针_vfptr放在对象对面我们叫做虚函数指针我们叫做虚函数表指针。一个含有虚函数表的类中至少都有一个虚函数表指针因为虚函数的地址要被放到虚函数表中虚函数表称虚表 注意虚函数存放在哪里 虚表存在哪里
虚表存的是虚函数指针不是虚函数虚函数也是函数所以也是存在代码区只是它的地址被存进虚函数指针中这个指针被虚表记录着 重写接口继承实现重写在原理上是覆盖将父类继承下来的vfptr的父类虚函数的地址覆盖成子类的虚函数地址 从反汇编看原理
普通类函数 在编译的过程中就已经确定了调用函数的地址 现在我们加上virtual虚函数 进入汇编当形成多态时是如何调用的 00B021E1 8B 45 08 mov eax,dword ptr [A] //将A指向空间地址给eax
00B021E4 8B 10 mov edx,dword ptr [eax] //将eax空间中的前四个字节地址给edx就是虚函数表指针
00B021E6 8B F4 mov esi,esp//这个是维护函数栈帧的寄存器不用管
00B021E8 8B 4D 08 mov ecx,dword ptr [A] //将A指向空间地址给ecx
00B021EB 8B 42 04 mov eax,dword ptr [edx4] //因为edx保存的是前四个字节空间的地址就是虚函数表指针4就是run的地址将run地址给eax前4个是speak的地址
00B021EE FF D0 call eax //调用run
00B021F0 3B F4 cmp esi,esp
00B021F2 E8 1A F1 FF FF call __RTC_CheckEsp (0B01311h) 多态就是有virtual函数是用虚函数表指针去存放虚函数的地址在由虚函数表指针调用对应的函数 面试题
虚函数存在哪里代码段虚函数和普通函数一样都是函数所以都是编译成指令存进代码段中
虚函数表存在哪里
存在代码段中不是存在栈区因为栈区是由一个个栈帧堆建的所以每调用创建一个对象就要建立一个虚表是很消耗内存的
证明一下 虚表存放在代码区中的代码段最合适堆区是动态开辟的数据区分为bss区存放未初始化的static和未初始化的全局变量和数据区存放存放初始化的static和初始化的全局变量所以代码段是最合适的 反向验证 发现很接近代码区 总结
多态的本质原理符合多态的两个条件。那么在父类的指针或引用调用时会到指向对象的虚表找到对应的虚函数地址进行调用
多态程序运行时去指向对象的虚表中找到函数地址进行调用所以p指向谁就调用谁的虚函数
普通函数的调用编译链接时确定函数的地址运行时直接调用。类型时谁就是谁调用 动态绑定和静态绑定 编译就是代码和语法检查其实就是预处理、编译、汇编、链接 运行就是将可执行文件加载到内存中进行对数据区的数据替换 静态绑定更具调的类型就确定了调用的函数 动态绑定运行时具体拿到类型确定程序的具体行为就是在编译时无法确定函数的行为
运行时根据寄存器去拿到函数的地址 单继承和多继承的虚表不是虚基表
单继承 void(*p)(); //函数指针 补充 函数名就是函数的地址 那我们手动打印虚函数表 class base
{
public:virtual void func1() { cout base::func1() endl; }virtual void func2() { cout base::func2() endl; }};
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; }};
//void(*)()
typedef void(*VF_PTR)();//重命名函数指针void PrintVFTable(VF_PTR* pTable)//VF_PTR pTable[] 函数指针数组虚函数表指针
{for (size_t i 0; pTable[i] ! 0; i){printf(pTable[%d]%p-, i, pTable[i]);VF_PTR f pTable[i];//得到函数的地址函数名f();}cout endl;
}int main()
{base b1;derive d2;PrintVFTable((VF_PTR*)(*(int*)b1));//取b1的地址因为要取到虚函数表指针它在对象的前四个字节//所以转换成int*在解引用就是取空间b1的前四个字节因为此时是int*//所以要转成VF_PTR*PrintVFTable((VF_PTR*)(*(int*)d2));return 0;
}
多继承的虚表
计算一下test 对象等于多少
class base
{
public:virtual void func1() { cout base::func1() endl; }virtual void func2() { cout base::func2() endl; }int i 0;
};
class derive
{
public:virtual void func1() { cout derive::func1() endl; }virtual void func3() { cout derive::func3() endl; }virtual void func4() { cout derive::func4() endl; }int i 0;
};
class test:public base,public derive
{
public:virtual void func3() { cout test::func1() endl; }virtual void func2() { cout test::func3() endl; }virtual void func7() { cout test::func4() endl; }
public:int i 0;
};//void(*)()
typedef void(*VF_PTR)();//重命名函数指针void PrintVFTable(VF_PTR* pTable)//VF_PTR pTable[] 函数指针数组虚函数表指针
{for (size_t i 0; pTable[i] ! 0; i){printf(pTable[%d]%p-, i, pTable[i]);VF_PTR f pTable[i];//得到函数的地址函数名f();}cout endl;
}int main()
{test i;cout sizeof(i) endl;return 0;
} 等于20 编译器又没显示那我们手动去看看 继承的子类和其父类的表不是同一张表只有同一类才是用一张表哦