调用派生类自己的构造函数
发布时间:2025-06-24 20:04:52 作者:北方职教升学中心 阅读量:780
static静态成员方法 (错误)
虚析构函数 (可以)
析构函数调用时,对象是存在的
基类的虚函数是虚函数,派生类的析构函数自动变成虚函数
当基类指针(引用)指向堆上new出来的派生类对象的时候,delete 基类指针,它调用析构函数时,必须发生动态绑定,否则会导致派生类的析构函数无法调用。
- protected和private的区别?
- 在基类中·定义的成员,想被派生类访问,但是不想被外界访问,那么在基类中,把相关成员定义成protected保护的,如果派生类和外部都不打算访问,那么在基类中,就把相关成员定义成private私有的
- 默认继承方式:
class定义派生类,默认继承方式就是private私有的
struct定义派生类,默认方式就是public
- 派生类从继承可以继承所有的成员(变量和方法),除过构造函数和析构函数
- 派生类怎么初始化从基类继承来的成员变量呢?
通过调用·基类相应的构造函数来初始化
- 派生类的构造函数和析构函数,负责初始化和清理派生类部分
- 派生类从基类继承来的成员的初始化和清理谁来负责?
- 派生类对象构造和析构的过程是:
派生类调用基类的构造函数,初始化从基类继承来的成员。
Base *pb2 =newDerive();pd2 ->show();deletepb2;
首先vfptr同样被置0地址,因为Base先进行构造函数,vfptr 《- &Base::vftable,再调用clear函数,vfptr被置成0地址。
#include<iostream>classBase{public:intdata;};classDerived1:virtualpublicBase{public:voidsetData(intvalue){data =value;}};classDerived2:virtualpublicBase{public:voiddisplayData(){std::cout <<"Data: "<<data <<std::endl;}};classFinalDerived:publicDerived1,publicDerived2{public:voidaccessData(){setData(42);displayData();}};intmain(){FinalDerived obj;obj.accessData();return0;}
虚基类是用于解决多重继承中的菱形继承问题的一种机制。通过使用 dynamic_cast 将 basePtr 转换为 Derived* 类型的指针 derivedPtr,我们可以安全地调用 Derived 类的方法。这样做可以确保每个派生类只包含一份虚基类的实例,从而避免了数据重复和二义性。派生类对象构造过程:先调用的是基类的构造函数 再调用派生类的构造函数。
在声明虚基类时,需要在派生类的继承列表中使用关键字 virtual。为了解决由此可能产生的二义性和数据重复的问题,可以将这些共同的基类声明为虚基类。
一般情况会把基类定义成抽象类。如果尝试转换失败,dynamic_cast 将返回一个空指针(对指针进行转换)或引发 std::bad_cast 异常(对引用进行转换)。如果转换失败(例如 basePtr 指向的对象不是 Derived 类型的),dynamic_cast 将返回 nullptr。
调用派生类自己的构造函数。
函数模板 不会参与编译,在函数调用点,实例化/推导出类型,模板函数再进行编译。
四种类型转换方式
- 语言级别的转换方式
const_cast - 去掉常量属性的一个类型转换
static_cast - 提供编译器认为安全的类型转换(没有任何联系的类型之间的转换不会成功)
reinterpret_cast - 类似于c风格的强制类型转换
dynamic_cast
主要用字继承结构中,可以支持RTTI类型识别的上下类型转化
intmain(){constinta =10;int*p1 =(int*)&a;int*p2 =const_cast<int*>(&a);inta =10;charb =static_cast<int>(a);int*p =nullptr;double*b =reinterpret_cast<double*>(p);}
dynamic_cast
是 C++ 中用于安全地进行基类指针或引用向派生类指针或引用的类型转换的一种运算符。
二:
一个类里面定义了虚函数,那么这个类定义的对象,其运行时,内存中开始的部分,多存储一个vfptr虚函数指针,指向相应类型的虚函数表vfptable。
三:
一个类里面虚函数的个数,不影响对象内存大小(vfptr)影响的是虚函数白表的大小。
一个类型定义的n个对象,他们的vfptr指向的都是同一张虚函数表。
- 函数模板
- 继承
- 虚函数,静态绑定/动态绑定
- 静态绑定/动态绑定
- 如何解释多态
- 抽象类
- 多重继承
- 面试题
- 四种类型转换方式
函数模板
意义:对类型进行参数化
模板的实参推演:可以根据用户传入的实参类型,来推导出模板类型。
然后Derive再构造,同样会执行vfptr 《- &Base::vftable,但是不会调用clear函数,所有此时vfptr 就会指向Derive::vftable,从而正常运行。当程序运行时,每一张虚函数表都会加载到内存的.rodata区。
如何解释多态
静态(编译时期)的多态:函数重载,模板(函数模板,类模板)
boolcompare(int,int){};boolcompare(double,double){};compare(10,20);/在编译阶段就确定了调用的函数版本template<typenameT>boolcompare(T a,T b){};compare<int>(10,30);int实例化一个 compare<int>compare(1.2,5.1);推导出double实例化一个 compare<double>
- 把继承结构,也就是说成从上(基类)到下(派生类)的结构
基类对象 < -派生类对象 类型从下到上的转换(可以)
派生类对象 <- 基类对象 类型从上到下的转换(不可以)
基类指针(引用)<- 派生类对象 类型从下到上的转换(可以)
派生类指针(引用)<-基类对象 类型从上到下的转换(不可以)
- 在继承结构中进行上下的类型转换,默认只支持从下到上的类型转换。
二:
classBase{public:virtualvoidshow(inti =10){cout<<"call Base::show i="<<i <<endl;}};classDerive:publicBase{private:voidshow(inti =20){cout<<"call Derive::show i = "<<i <<endl;}};intmain(){Base *p =newDerive();p->show();deletep;return0;}
对于有默认值的基类和派生类发生多态时,参数压栈是在编译时期就会确定好的,所以派生类的默认参数根本不会起作用,永远用不到
三:
classBase{public:Base(){cout<<"Call Base()"<<endl;clear();}voidclear(){memset(this,0,sizeof(*this));}virtualvoidshow(){cout<<"CAll Base::show()"<<endl;}};classDerive:publicBase{public:Derive(){cout<<"call Derive()"<<endl;}voidshow(){cout<<"Call Derive::show()"<<endl;}};intmain(){Base *pb1 =newBase();pb1->show();deletepb1;Base *pb2 =newDerive();pd2 ->show();deletepb2;return0;}
解释:
Base *pb1 =newBase();pb1->show();deletepb1;
因为pb1调用了clear函数,相当于把vfptr置成0地址了,vfptr已经不再指向Base::vfptable了,当再次调用pb1->show();,从而找不到,发生异常错误。
模板代码是不能在一个文件中定义,在另一个文件中使用
模板代码调用之前,一定要看到模板定义的地方,这样的话,模板才能进行正常的实例化,产生能够被编译器编译的代码。
静态绑定/动态绑定
在编译时期的绑定(函数的调用)
只call指令-静态绑定
在运行时期的绑定(函数的调用)
mov eax,dword ptr[pd] mov ecx,dword ptr[eax] call ecx(虚函数的地址) 动态绑定
那些函数不能实现成虚函数?
虚函数能产生地址,存储在vftable当中
对象必须存在(vfptr -》vftable -》虚函数地址)
构造函数
virtual+构造函数 (错误)
构造函数中(调用的任何函数,都是静态绑定的)调用虚函数,也不会发生静态绑定。它主要用于在运行时检查类型安全性,只能用于具有虚函数的类层次结构中。
#include<iostream>classBase{public:virtual~Base(){}};classDerived:publicBase{public:voidderivedMethod(){std::cout <<"Derived method called."<<std::endl;}};intmain(){Base*basePtr =newDerived();Derived*derivedPtr =dynamic_cast<Derived*>(basePtr);if(derivedPtr){derivedPtr->derivedMethod();}else{std::cout <<"Dynamic cast failed."<<std::endl;}deletebasePtr;return0;}
在这个例子中,basePtr 是一个指向基类 Base 的指针,但实际上指向了一个派生类 Derived 的对象。初始化派生类自己特有的成员
派生类对象的作用域到期了
1.调用派生类的析构函数,释放派生类成员可能占用的外部资源(堆内存,文件)
2.调用基类的析构函数,释放派生类内存中,从基类继承来的成员可能占用的外部资源(堆内存,文件)
重载:一组函数要重载,必须处在同一个作用域当中,而且函数名字相同,参数列表不同
隐藏(作用域的隐藏)的关系:
在继承结构当中,派生类的同名成员,把基类的同名成员给隐藏调用了
覆盖:基类和派生类的方法,返回值,函数名以及参数列表都相同,而且基类的方法是虚函数,那么派生类的方法就自动处理成虚函数,他们之间成为覆盖关系。
模板一般都是放在头文件中的,在源文件中展开
函数模板的非类型参数 必须是整数类型(整数/地址/引用)都是常量,只能使用
继承
- 继承的本质和原理
继承·的·本质·:
a.代码复用
b.在基类中给所有派生类提供统一的虚函数接口,让派生类重写虚函数,然后就可以使用多态
类和类之间的关系:组合and继承
组合:a part of …一部分的关系
继承:a kind of… 一种·的关系
总结:
外部只能访问对象public的成员,protected和private的成员无法直接访问。
多重继承
代码复用 一个派生类有多个基类
virtual可以修饰继承方式,是虚继承,虚继承的类是虚基类
基类指针指向派生类对象,永远指向的是派生类基类部分数据的起始地址。
编译阶段:Base::show (call Base::show (静态绑定)/ call ecx(动态绑定))
也就是说在执行
p->show(); //最终能调用到Derive::show,是在运行时期才确定的
时看的是Base基类的访问权限,不看派生类的权限。