石头科技 网站开发,自助建站平台便宜,php网站iis设置,北京齐力众信网站建设今天我们来谈谈C的继承与多态#x1f60a;#x1f60a;#x1f60a;#xff0c;本篇的关键内容如下#xff1a;
继承的本质及其原理派生类的构造和析构过程重载、隐藏和覆盖类的向下或向上转型静态绑定与动态绑定虚函数对类的影响虚析构函数
下面#xff0c;我们将对这…今天我们来谈谈C的继承与多态本篇的关键内容如下
继承的本质及其原理派生类的构造和析构过程重载、隐藏和覆盖类的向下或向上转型静态绑定与动态绑定虚函数对类的影响虚析构函数
下面我们将对这几个有关于C继承与多态的关键内容进行详述的论述 浅谈C的继承与多态 一、继承的本质及其原理二、派生类的构造和析构过程三、重载、隐藏和覆盖四、类的向下或向上转型五、静态绑定与动态绑定**一个问题:thinking::thinking::thinking:一个类声明了虚函数后当调用该虚函数时一定是动态绑定吗** **不一定 !**六、虚析构函数**什么时候把基类的析构函数声明为虚函数呢**七、虚函数对类的影响 一、继承的本质及其原理
在C中继承是一种面向对象编程思想的概念允许一个类继承另一个类的属性和方法同时也可以在此基础上添加新的属性和方法。究其本质即
代码的复用在基类中给所有派生类提供一个统一的接口让派生类重写满足开闭原则
二、派生类的构造和析构过程
在定义的派生类中由于派生类是从基类的基础上继承过来的对应继承而来的成员的初始化和清理是由基类的构造函数和析构函数负责而派生类的构造和析构函数则负责初始化和清理派生类中特定的部分所以在派生类调用构造函数需要初始化基类里的成员变量时不能直接指定而是需要通过基类的构造函数来进行初始化如下
class Base
{
public:Base(int d) :ma(d) { cout Base() endl; }~Base() { cout ~Base() endl; }
protected:int ma;
};class Derive :public Base
{
public://Derive(int d 20) :ma(d), mb(d) {cout Derive() endl;}//此句会出现报错ma 不是类 Derive 的非静态数据成员或基类Derive(int d 20) :Base(d), mb(d) {cout Derive() endl;}~Derive() { cout ~Derive() endl; }
private:int mb;
};这里我们给出一段关于派生类构造和析构过程的主要描述
派生类调用基类的构造函数初始化从基类继承而来的成员调用派生类自己的构造函数初始化派生类自己特有的成员调用派生类的析构函数释放派生类成员可能占用的外部资源堆空间、文件等调用基类的构造函数释放派生类内存中从基类继承而来的成员可以占用的外部资源堆空间、文件等
int main()
{Derive m(20); //可以自己允许一遍查看程序允许结果return 0;
}三、重载、隐藏和覆盖
重载 一组函数重载必须处在同一个作用域中且函数名相同参数列表不同 隐藏 隐藏其实就是隐藏作用域即在继承结构中派生类的同名成员把基类的同名成员隐藏掉了 覆盖 如果派生类中定义的方法与在基类继承而来的方法在返回值、函数名、参数列表都相同的前提下且基类的实现方法为virtual虚函数那么派生类的这个方法就会被自动的处理成虚函数本质上是虚函数表上的虚函数地址的覆盖详见后文
若我们在上面的Base类和Derive中添加方法 void show() 在派生类调用show函数时会优先在自己类中寻找对应的函数方法即会打印Derive::show()基类的show方法被隐藏了
class Base
{...void show() { cout Base::show() endl; }...
};
class Derive :public Base
{void show() { cout Derive::show() endl; }...
};int main()
{Derive m(20);m.show(); //优先找派生类自己show成员return 0;
}四、类的向下或向上转型
在C中派生类对象转化为基类对象、基类对象转化为派生类对象的情形可以描述为
向上转型 派生类对象指针 - 基类对象指针 YES向下转型 基类对象指针 - 派生类对象指针 NO
由于派生类是由基类继承而来的其给它分配地址空间大于基类的地址空间故可以将派生类对象指针转化为基类对象指针即Base* pb m而不可以可以将基类类对象指针转化为派生类对象指针即Derive* pd m;如下图所示 对于 派生类 -》 基类 相当于切片红色部分内存在基类指针访问不了也不会被访问 对于 基类 -》 派生类 派生类申请的地址空间大于基类一旦派生类对象/指针访问红色部分时由于基类对象是没有这块空间的直接会发生非法访问问题
五、静态绑定与动态绑定
静态绑定和动态绑定是C中两种不同的绑定方式
静态绑定 在编译时进行的绑定。它根据函数调用时的静态类型来确定要调用的函数。静态绑定适用于非虚函数它会默认绑定到函数定义中的相应代码。动态绑定 在运行时进行的绑定。它根据函数调用时的动态类型来确定要调用的函数。动态绑定适用于虚函数它允许在运行时根据实际对象类型来调用相应的函数。这样可以实现多态性即不同对象调用同名函数时可以执行不同的操作。
#include iostreamclass Base {
public:void print() {std::cout Base class std::endl;}virtual void display() {std::cout Base class std::endl;}
};class Derived : public Base {
public:void print() {std::cout Derived class std::endl;}void display() {std::cout Derived class std::endl;}
};int main() {Base* d new Derived();d-print(); // 静态绑定调用Base类的print函数/*mov eax, dword ptr[pb]mov ecx, dword ptr[eax]call ecx;(虚函数地址) 动态运行时期的绑定函数的调用*/d-display(); // 动态绑定调用Derived类的display函数return 0;
}d-print() 调用的是静态绑定因为 print() 函数在基类中不是虚函数。即使 d 指向的对象实际类型是 Derived 类编译器也会根据指针类型Base*来静态绑定调用基类的 print() 函数。所以输出为 Base class
d-display() 这里调用的是动态绑定因为 display() 函数是虚函数。在运行时实际调用的是指向的对象的类型的版本即RTTI run-time type information 即 Derived 类中的 display() 函数。所以输出为 Derived class 一个问题一个类声明了虚函数后当调用该虚函数时一定是动态绑定吗 不一定 ! 动态绑定的实现通常涉及虚函数表vtable的使用。每个包含虚函数的类都会有一个虚函数表其中包含了指向虚函数的指针。子类会继承父类的虚函数表并在其自己的虚函数表中重写父类的虚函数指针。这样当通过指针或引用调用虚函数时会根据对象的实际类型找到对应的虚函数指针并调用正确的函数。 然而如果直接通过对象调用虚函数编译器会根据对象的静态类型来进行绑定即根据对象的声明类型来调用虚函数,而这就是静态绑定因为编译器在编译时已经确定了应该调用哪个函数不需要在运行时根据对象的实际类型进行判断。因此在通过指针或引用调用虚函数时会进行动态绑定根据对象的实际类型确定调用哪个函数而在直接通过对象调用虚函数时会进行静态绑定根据对象的声明类型确定调用哪个函数 六、虚析构函数
虚析构函数是一种特殊的析构函数用于实现多态性。它允许在基类指针指向派生类对象时使用基类指针来调用派生类对象的析构函数从而正确地释放派生类对象的资源我们先来看看下面几个问题
我们来看看虚函数所依赖的条件
虚函数能产生地址存储在vftable虚函数表中对象必须存在vfptr - vftable - 虚函数地址
哪些函数不能声明为虚函数
构造函数构造函数在对象创建和销毁的过程中是特殊的在构造函数中的所有函数都是静态绑定的不能声明为virtual静态成员函数static虚函数是通过对象访问的而静态成员函数没有 this 指针是直接通过类名访问的不能被继承和覆盖内联函数内联函数在编译时会直接将函数体嵌入到函数调用点没有函数调用的开销。而虚函数是通过虚函数表来确定的无法进行内联 什么时候把基类的析构函数声明为虚函数呢 当基类指针或引用指向在堆上通过new创建的派生类对象时派生类对象也会分配一份外部空间。如果基类的析构函数没有声明为虚函数在准备使用delete删除基类指针时会发生静态绑定。这样基类对象会调用基类的析构函数而派生类对象则无法调用其自己的析构函数导致内存泄漏。 class Base
{
public:Base(int d) :ma(d) { cout Base() endl; }~Base() { cout ~Base() endl; }virtual void show() { cout Base::show() endl; }
protected:int ma;
};class Derive :public Base
{
public:Derive(int d) :Base(d), mb(d), ptr(new int(d)){cout Derive() endl;}// 基类的析构函数是virtual那么派生类的析构函数自动变成virtual~Derive(){delete ptr;cout ~Derive() endl;}void show() { cout Derive::show() endl; }private:int mb;int* ptr;
};int main()
{Base* pb new Derive(10);pb-show();delete pb;return 0;
} 在将基类的析构函数声明为虚函数后当使用delete删除基类指针时由于基类的析构函数是虚函数会发生动态绑定。这样派生类的析构函数会自动成为虚析构函数。在执行delete时通过虚函数表指针vfptr找到虚函数表vftable将基类在虚函数表上的虚析构函数覆盖为派生类的虚析构函数。这样就会先调用派生类的析构函数再调用基类的虚构函数进行释放。 七、虚函数对类的影响 一个类里面定义了虚函数那么编译阶段编译器会给这个类生成唯一的虚函数表 vftable 主要存放的是RTTI指针和虚函数地址当程序运行时每一张虚函数表都会加载到内存的.rodate区只读不能写 一个类中定义了虚函数那么这个类定义的对象在程序运行时内存中开始的部分多存储一个vfptr虚函数指针指向相同类型的虚函数表vftable一个类型定义的n个对象它们的 vfptr 指向的都是同一张虚函数表 一个类里的虚函数个数不影响对象内存的大小(vfptr)影响的是虚函数表的大小 如果派生类中的方法和基类继承而来的某个方法的返回值、函数名、参数列表相同而基类的方法是virtual虚函数那么派生类的这个方法会自动被处理成虚函数将虚函数表中的原来的虚函数地址覆盖成派生类的虚函数地址 以上就是有关浅浅谈C的继承与多态静态绑定、动态绑定和虚函数等的内容如果聪明的你浏览到这篇文章并觉得文章内容对你有帮助请不吝动动手指给博主一个小小的赞和收藏