西安曲江文化园区建设开发有限公司网站,wordpress 3.8漏洞,紫搜做网站,鹿泉建设网站目录 一. 多态的概念
二. 多态的定义及实现
多态的构成条件
虚函数重写的例外
协变(基类与派生类虚函数返回值类型不同)
析构函数的重写(基类与派生类析构函数的名字不同)
练习例题
final
override
重载、覆盖(重写)、隐藏(重定义)的对比
三. 抽象类
四. 多态的原理…
目录 一. 多态的概念
二. 多态的定义及实现
多态的构成条件
虚函数重写的例外
协变(基类与派生类虚函数返回值类型不同)
析构函数的重写(基类与派生类析构函数的名字不同)
练习例题
final
override
重载、覆盖(重写)、隐藏(重定义)的对比
三. 抽象类
四. 多态的原理
虚函数表
五. 单继承和多继承关系中的虚函数表 一. 多态的概念 多态的概念通俗来说就是多种形态具体点就是去完成某个行为当不同的对象去完成时会产生出不同的状态。 就比如拿我们的支付宝红包举例子老用户往往会扫出很小的红包而新用户却往往能扫到大额红包~ 二. 多态的定义及实现
多态的构成条件 多态是在不同继承关系的类对象去调用同一函数产生了不同的行为。比如Student继承了 Person。Person对象买票全价Student对象买票半价。 那么在继承中要构成多态还有两个条件 必须通过父类的指针或者引用调用虚函数 被调用的函数必须是虚函数加上virtual)且派生类必须对基类的虚函数进行重写(函数名相同参数相同返回值相同 class Person {
public:virtual void BuyTicket() { cout 买票-全价 endl; }
};class Student : public Person {
public:virtual void BuyTicket() { cout 买票-半价 endl; }
};void Func(Person p)
{p.BuyTicket();
}int main()
{Person ps;Student st;Func(ps);Func(st);return 0;
} 正常情况下调用函数是通过其指针或引用类型然后去调该类型的类成员函数~而在多态中是看父类指针或引用的对象通过对象去调用该对象的类成员函数~ 虚函数重写的例外 协变(基类与派生类虚函数返回值类型不同) class A {};
class B : public A {};
class Person {
public:virtual A* f() { return new A; }
};
class Student : public Person {
public:virtual B* f() { return new B; }
}; 重写是必须要求要相同返回值的但也可以有例外就是返回值可以不同但是两个返回值必须得是指针或引用同时得保证这两个返回值构成继承关系。即父类虚函数返回父类对象的指针或者引用子类虚函数返回子类对象的指针或者引用时称为协变~ ps:不常出现了解即可~ 析构函数的重写(基类与派生类析构函数的名字不同) class Person {
public:~Person() { cout ~Person() endl;}
};// 重写实现
class Student : public Person {
public:~Student(){ // delete _ptr;cout ~Student() endl;}
};int main()
{Person* p1 new Person;delete p1;Person* p2 new Student;delete p2;return 0;
} 如果我们没有用虚函数那么在代码执行中p1去调用父类的析构函数p2也会去调用父类的析构函数~ 可是这样忽略了一个问题如果我们new出来的子类当中有额外的资源呢本来子类的析构函数可以去清理这个额外资源但由于正常调用而去调用父类的析构导致内存泄漏~ 所以为了能让析构函数也形成多态的效果我们选择使用虚函数进行重写~ 而且在同名函数这块也进行了特殊处理一律把析构的函数名当作destructor来看~使重写的条件名字最后在父类指针的调用下根据指向对象来调用对象的成员函数~ 练习例题 ps小知识点 如果父类的virtual去掉那就是与子类构成隐藏关系了就不再是虚函数无法构成重写也就没有多态只能是普通的调用看类型。如果子类的virtual去掉那仍是虚函数仍符合多态并且为多态调用看对象 // 以下程序输出结果是什么A: A-0 B: B-1 C: A-1 D: B-0 E: 编译出错 F: 以上都不正确
class A
{
public:virtual void func(int val 1) {std::cout A- val std::endl;}virtual void test() { func(); }
};class B : public A
{
public:void func(int val 0) { std::cout B- val std::endl; }
};int main(int argc, char* argv[])
{B* p new B;p-test();return 0;
} 本题答案选B~ 接下来我来为大家分析解答~ 首先我们发现子类func没有virtual而父类有那么这也是能构成虚函数重写的~ 然后我们可以得出在父类中的隐藏this指针类型是父类的而父类指针去调用func函数是满足多态条件的那么就要去找到所调用的对象而非类型而test函数是被指向B对象的指针所调用的~那么说明多态作用下func调用的是B类的成员函数func~ 又因为B类中的func函数中virtual是没有的那么它只能去用父类的函数声明然后再结合自己的定义作组合所以在这个过程中子类的func函数是用了父类func中的val再加上实现的B-最终形成答案B~ 如果是普通对象调用或者非父类指针去调用那也就没那么多事了~ final final修饰虚函数表示该虚函数不能再被重写 如果我们想要让子类无法继承父类有两种方法 法一让父类构造私有 class A
{
public:
protected:int _a;
private:A(){}
};class B : public A
{};int main()
{B bb;return 0;
} 这样子类就无法实例化出对象了因为子类构造必须调用父类构造~ 法二使用final class A final
{
public:
protected:int _a;
private:A(){}
};class B : public A
{};int main()
{B bb;return 0;
} override override: 检查派生类虚函数是否重写了基类某个虚函数如果没有重写编译报错。 重载、覆盖(重写)、隐藏(重定义)的对比 三. 抽象类 在虚函数的后面写上 0 则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类也叫接口 类抽象类不能实例化出对象。 class Car
{
public:virtual void Drive() 0;
};
class Benz :public Car
{
public:virtual void Drive(){cout Benz-舒适 endl;}
};
class BMW :public Car
{
public:virtual void Drive(){cout BMW-操控 endl;}
};
int main()
{Car c;//无法实例化出对象Car* pBenz new Benz;pBenz-Drive();Car* pBMW new BMW;pBMW-Drive();return 0;
} 子类只有进行虚函数重写避开去调用父类的纯虚函数就可以实例化了~从另一种角度上看抽象类也有强迫子类进行多态调用的提醒作用~ 四. 多态的原理
虚函数表 //sizeof(Base)是多少
class Base
{
public:virtual void Func1(){cout Func1() endl;}
private:int _b 1;char _ch;
};int main()
{cout sizeof(Base) endl;Base bb;return 0;
} 很多人会说是8但实际上却为12。因为在有了虚函数后对象里面还会多出一个指针~ 该指针内放置虚函数Func1的地址~ 我们再来修改一下代码~ class Base
{
public:void Func1(){cout Base::Func1() endl;}virtual void Func2(){cout Base::Func2() endl;}void Func3(){cout Base::Func3() endl;}
private:int _b 1;char _ch a;
};class Derive : public Base
{
public:virtual void Func1(){cout Derive::Func1() endl;}
private:int _d 2;
};int main()
{cout sizeof(Base) endl;Base bb;cout sizeof(Derive) endl;Derive dd;return 0;
} 我们在父类多添加了一个虚函数和普通函数在子类添加了一个虚函数 不出我们意外都多出了一个指针~ 其实虚函数建立的时候在内存中会有虚函数表这种对象~而虚函数表也就是函数指针的数组~ 当有多个虚函数的时候就会出现一个指针(_vfptr)去指向虚函数数组虚函数表然后访问数组里面各个虚函数的地址~ 子类中是没有func2函数的那么就会去拿父类中func2的地址而子类是有自己的func1函数的那么在虚函数表中就会让子类的func1地址去覆盖父类的func1地址~ ps:虚函数表中先声明的放在前面~ 五. 单继承和多继承关系中的虚函数表 我们再来对虚函数表做一些补充~ 首先如果是普通调用那么函数地址是在编译时就确定的。在编译链接的时候从符号表中找到函数地址然后去调用它。而在多态调用中是通过指针进入对应对象虚函数表在里面找到虚函数地址并成功调用它~ 下面我们来了解一些多继承关系中的虚函数表~ class Base1 {
public:virtual void func1() { cout Base1::func1 endl; }virtual void func2() { cout Base1::func2 endl; }
private:int b1;
};class Base2 {
public:virtual void func1() { cout Base2::func1 endl; }virtual void func2() { cout Base2::func2 endl; }
private:int b2;
};class Derive : public Base1, public Base2 {
public:virtual void func1() { cout Derive::func1 endl; }virtual void func3() { cout Derive::func3 endl; }
private:int d1;
};
typedef void(*VF_PTR)();
void PrintVFT(VF_PTR* vft)
{for (size_t i 0; vft[i] ! nullptr; i){printf([%d]:%p-, i, vft[i]);VF_PTR f vft[i];f();//(*f)();}cout endl endl;
}int main()
{Derive d;cout sizeof(d) endl;Base1* ptr1 d;Base2* ptr2 d;PrintVFT((VF_PTR*)(*(int*)ptr1));PrintVFT((VF_PTR*)(*(int*)ptr2));return 0;
} 由于vs的监视窗口无法查看2个以上的虚函数所以我们这里选择人工打印出所属对象的虚函数表里面的内容~ 首先我们需要取得类对象中的前4个字节因为那里代表指向虚函数表的指针~所以我们对切片过的指针ptr1进行强转为int*类型再解引用就可以拿到其指针地址了~然后我们再通过对指针强转为(VF_PTR*类型因为我们的打印函数中打印的是函数指针所以需要实参与形参类型相同以便接收~最后我们再把得到的函数指针去回调func函数形成多态的效果~ 在多继承中有两张虚函数表一张代表Base1,一张代表Base2~ 而在Base1表中原有的虚函数会被子类独有的虚函数覆盖例如子类只复写了func1与func3虚函数那么在Base1中就会被子类的func1与func3虚函数覆盖~ 而在Base2表中同样如此不过需要注意的是func3最后只放在了先声明的那个父类中Base2是不放func3的~ 总结对于func3这种在父类没有的虚函数一般会放在最先声明的类中~至于其他的该拷贝拷贝该替换替换~ 最后是其他知识点的总结 inline函数可以是虚函数吗答可以不过编译器就忽略inline属性这个函数就不再是 inline因为虚函数要放到虚表中去。 静态成员可以是虚函数吗答不能因为静态成员函数没有this指针使用类型::成员函数的调用方式无法访问虚函数表所以静态成员函数无法放进虚函数表。 构造函数可以是虚函数吗答不能因为对象中的虚函数表指针是在构造函数初始化列表阶段才初始化的。 对象访问普通函数快还是虚函数更快答首先如果是普通对象是一样快的。如果是指针 对象或者是引用对象则调用的普通函数快因为构成多态运行时调用虚函数需要到虚函 数表中去查找。 虚函数表是在什么阶段生成的存在哪的答虚函数表是在编译阶段就生成的一般情况 下存在代码段(常量区)的。