网站中的冒号,郴州网站制作公司,wordpress主题无法创建目录,怎么自己做游戏软件的app目录 1. 多态相关概念1.1 简述#xff1a;多态1.2 概念汇总与补充 2. 多态重写的底层原理2.1 虚函数存储的结构与位置2.2 重写覆盖 1. 多态相关概念
1.1 简述#xff1a;多态
1. 什么是多态 不同的对象去做同一个行为时#xff0c;得到的结果不同。反应到编程语言中#… 目录 1. 多态相关概念1.1 简述多态1.2 概念汇总与补充 2. 多态重写的底层原理2.1 虚函数存储的结构与位置2.2 重写覆盖 1. 多态相关概念
1.1 简述多态
1. 什么是多态 不同的对象去做同一个行为时得到的结果不同。反应到编程语言中即为不同类型的对象调用同一个函数得到的返回值不同。 class Person
{
public:virtual void BuyTicket(){cout 全票 endl;}
};class Children : public Person
{
public:virtual void BuyTicket(){cout 半票 endl;}
};void BuyTicket(Person p)
{p.BuyTicket();
}int main()
{Person p1;Children p2;BuyTicket(p1);BuyTicket(p2);return 0;
}2. 虚函数 虚函数被关键字virtual修饰的函数 virtual void func1()
{//...
}3. 构成多态的条件 父类与子类的虚函数必须构成重写关系三同函数名函数参数函数返回值相同必须用父类的指针或者引用去调用虚函数。 4. 多态的特殊情况 协变父类与子类的虚函数返回值不同也可以构成重写只是返回值类型必须为父类/子类不是同一父类/子类也可以的指针或者引用。析构函数即使函数名不同也构成重写这时因为编译在编译时会将析构函数的名字统一处理为destructor。特殊的父类的虚函数加virtual子类的虚函数不加virtual也构成重写子类的虚函数被视作实现重写建议不要省略。 class A
{
public:virtual void func(int a 1){cout A- a endl;}virtual void test(){func();}
};class B : public A
{
public:virtual void func(int a 0){cout B- a endl;}
};int main()
{B().test();return 0;
}1 B类因为本身没有进行test函数的重写所以调用test时会调用从A类继承而来的test。 2 调用继承而来的test的时需要使用父类指针进行调用会发生赋值兼容转换从B类中截断出A类的部分。 3 调用的func函数为B类重写A类后的func函数又因为虚函数的重写是实现重现会直接继承父类的函数框架只重写内部的实现。 5. 多态调用与普通调用 普通调用根据指针/引用的类型调用指针/引用类型的函数多态调用使用父类指针指向子类对象根据指针/引用指向对象的类型调用指向对象的函数。即使父类与子类构成多态但不采用多态调用的方式也不会达到多态的效果。普通函数会在编译时就将函数的地址写入符号表中而重写的虚函数其地址存储在对象的虚表中当我们使用多态调用对其进行调用时则是在运行时从对象的虚表中获得对应虚函数的地址。 6. 补充语法 如何定义实现一个不能被继承的父类 1 使用private私有化构造函数 2 使用关键字final修饰父类 class A final
{//...
};关键字override检查子类是否重写了父类的虚函数如果没有会发生报错 class Person()
{
public:virtual void func1(){//...}
};class Student : public Person
{
public:virtual void func1() override{//...}
}7. 含有虚函数的类的大小
class Base
{
public:virtual void func1(){cout hello world endl;}
private:int _b 1;char _c a;
};内存对齐VS最大对齐数为8 1 int对齐数4大小 4地址相对位置起始处0小于8取自己大于8取8 2 char对齐数1大小 1地址相对位置起始处4 3 最后空间大小取最大对齐数的整数倍处取88字节虚函数表指针虚表指针指向虚函数表的指针虚函数表内存储序函数的地址虚函数表可以视作一个函数指针数组含有虚函数的类内部除开本身包含的成员变量外还有额外包含一个虚表指针虚表内存储着这个类所有虚函数的地址。 int main()
{Base a;return 0;
}8. 抽象类 包含纯虚函数的类这种类无法实例化出对象。继承抽象类的子类必须要对纯虚函数进行重写重写后子类才能够实例化出对象。 class Car
{
public:virtual void Drive() 0{}
};class Benz : public Car
{
public:virtual void Drive(){cout 舒适 endl;}
};class BMW : public Car
{
public:virtual void Drive(){cout 操控 endl;}
};1.2 概念汇总与补充
1. 重载隐藏与重写 重载会函数的重载构成重载的函数必须在一个作用域中且函数名相同参数类型不同。隐藏覆盖在父类与子类中的同名函数在不同的作用域中参数类型可以不同。重写重定义在父类与子类中函数名相同参数类型相同返回值相同的虚函数协变例外。注分别在父类作用域与子类作用域的同名函数不是重写就是隐藏。 2. 内联函数可以作为虚函数 想要能够作为一个虚函数的前提为必须是一个函数拥有函数地址可是内联函数在一般情况下不会创建栈帧会直接在原地展开没有函数地址。但特殊的内联函数在多态调用是不会展开其也就不再具备内联属性这里体现了内联函数的双向属性。 3. 静态成员函数与构造函数 静态成员函数没有this指针而虚函数的调用需要通过父类指针来实现多态调用运行时也就无法通过虚表指针进行调用所以静态成员函数不能时虚函数。构造函数只有在创建对象时的初始化列表中初始化对象而虚表指针需要在编译阶段就完成初始化所以构造函数不能是虚函数。 4. 虚函数的调用速度与抽象类 虚函数在进行普通调用时与普通函数的调用方式相同只有在进行多态调用时因为在运行时要通过虚表指针去搜索虚函数地址所以会比普通函数的调用慢。抽象类强制其子类进行虚函数重写是一种接口继承的体现。 2. 多态重写的底层原理
2.1 虚函数存储的结构与位置
1. 虚函数的存储结构
class Base
{
public:virtual void func1(){cout func1 endl;}virtual void func2(){cout func2 endl;}virtual void func3(){cout func3 endl;}private:int _b 1;char _c a;
};typedef void(*PTR)();void Print(PTR* p)
{for (int i 0; p[i]; i){printf(p[%d] %p\n, i, p[i]);}p[0]();p[1]();p[2]();
}int main()
{Base* p new Base;//相近类型才可以发生类型转换Print((PTR*)(*((int*)p)));return 0;
}2. 虚函数与虚表指针在内存中的位置
class A
{
public:virtual func(){}
}int main()
{//栈int a 10;//堆int* b new int;//静态区static int c 0;//常量区const char* str hello world;printf(栈%p\n, a);printf(堆%p\n, b);printf(静态区%p\n, c);printf(常量区%p\n, str);//函数指针typedef void (*PTR)();A d;PTR* pd (PTR*)(*((int*)(d)));printf(虚表指针%p\n, pd);printf(虚函数地址%p\n, pd[0]);return 0;
}3. 补充 当子类拥有独属于自己的虚函数时也会将此虚函数的函数指针记录至虚表中但监视窗口无法查看。 2.2 重写覆盖
class A
{
public:virtual void func1(){}
};class B : public A
{
public://重写/未重写//virtual void func1()//{}
};int main()
{A* p1 new A;B* p2 new B;return 0;
}语法上的重写反映到底层实现上就是函数指针的覆盖。