基类对象 < -派生类对象 类型从下到上的转换(可以) 派生类对象 <- 基类对象 类型从上到下的转换(不可以) 基类指针(引用)<- 派生类对象 类型从下到上的转换(可以) 派生类指针(引用)<-基类对象 类型从上到下的转换(不可以)
总结一: 如果类里面定义了虚函数,那么编译阶段,编译器给这个类类型产生一个唯一的vftable虚函数表,虚函数表中主要存储的内容就是RTTI指针(运行时的类型信息)和虚函数的地址。当程序运行时,每一张虚函数表都会加载到内存的.rodata区。
二: 一个类里面定义了虚函数,那么这个类定义的对象,其运行时,内存中开始的部分,多存储一个vfptr虚函数指针,指向相应类型的虚函数表vfptable。 一个类型定义的n个对象,他们的vfptr指向的都是同一张虚函数表。
三: 一个类里面虚函数的个数,不影响对象内存大小(vfptr)影响的是虚函数白表的大小。
四: 如果派生类中的方法和基类继承来的某个方法,返回值,函数名,参数列表都相同,而且基类的方法是virtual虚函数,那么派生类的这个方法,自动处理成虚函数。
在编译时期的绑定(函数的调用) 只call指令-静态绑定
在运行时期的绑定(函数的调用)
mov eax,dword ptr[pd]
mov ecx,dword ptr[eax]
call ecx(虚函数的地址) 动态绑定
静态(编译时期)的多态:函数重载,模板(函数模板,类模板)
bool compare(int , int){};
bool compare(double,double){};
compare(10,20); /在编译阶段就确定了调用的函数版本
template<typename T>
bool compare(T a,T b){};
compare<int>(10,30); int 实例化一个 compare<int>
compare(1.2,5.1); 推导出double实例化一个 compare<double>
拥有纯虚函数的类,叫做抽象类 抽象类不能再实例化对象了,但是可以定义指针和引用变量。 一般情况会把基类定义成抽象类。
代码复用 一个派生类有多个基类
virtual可以修饰继承方式,是虚继承,虚继承的类是虚基类
基类指针指向派生类对象,永远指向的是派生类基类部分数据的起始地址。
#include <iostream>
// 虚基类
class Base {
public:
int data;
};
// 第一个派生类
class Derived1 : virtual public Base {
public:
void setData(int value) {
data = value;
}
};
// 第二个派生类
class Derived2 : virtual public Base {
public:
void displayData() {
std::cout << "Data: " << data << std::endl;
}
};
// 最终派生类
class FinalDerived : public Derived1, public Derived2 {
public:
// 使用虚基类的成员函数和数据
void accessData() {
setData(42); // 通过Derived1访问Base的成员函数
displayData(); // 通过Derived2访问Base的数据
}
};
int main() {
FinalDerived obj;
obj.accessData();
return 0;
}
虚基类是用于解决多重继承中的菱形继承问题的一种机制。当一个类同时继承了两个或更多个共同基类,而这些基类又继承自同一个共同的基类时,就会形成菱形继承结构。为了解决由此可能产生的二义性和数据重复的问题,可以将这些共同的基类声明为虚基类。
在声明虚基类时,需要在派生类的继承列表中使用关键字 virtual。这样做可以确保每个派生类只包含一份虚基类的实例,从而避免了数据重复和二义性。
一:
class Base
{
public:
virtual void show(){ cout<<"call Base::show"<<endl;}
};
class Derive : public Base
{
private: //编译阶段不看它
void show()
{
cout<<"call Derive::show"<<endl;
}
};
int main()
{
Base *p = new Derive();
p->show(); //最终能调用到Derive::show,是在运行时期才确定的
delete p;
return 0;
}
对于private:
void show()
{
cout<<"call Derive::show"<<endl;
}可以正常调用
成员方法能不能调用,就是说方法的访问权限是不是public的,是在编译阶段就需要确定的。
编译阶段:Base::show (call Base::show (静态绑定)/ call ecx(动态绑定))
也就是说在执行 p->show(); //最终能调用到Derive::show,是在运行时期才确定的 时看的是Base基类的访问权限,不看派生类的权限。
二:
class Base
{
public:
virtual void show(int i = 10){ cout<<"call Base::show i="<< i <<endl;}
};
class Derive : public Base
{
private: //编译阶段不看它
void show(int i = 20)
{
cout<<"call Derive::show i = "<< i <<endl; //i的值是10
}
};
int main()
{
Base *p = new Derive();
/*
因为p是Base类型
push 0Ah 参数压栈(Base里面的参数)
mov eax,dword ptr[p]
mov ecx,dword ptr[eax]
call ecx
*/
p->show(); //动态绑定 p->Derive vfptr -》Derive vftable
delete p;
return 0;
}
对于有默认值的基类和派生类发生多态时,参数压栈是在编译时期就会确定好的,所以派生类的默认参数根本不会起作用,永远用不到
三:
class Base
{
public:
Base()
{
/*
push ebp
mov ebp , esp
sub esp,...开辟空间
rep stos esp <->ebp (windows下默认初始化为0×CCCCCCCC)(Linux g++/gcc不做此步骤)
vfptr 《- &Base::vftable
*/
cout<<"Call Base()"<<endl;
clear();
}
void clear(){memset(this,0,sizeof(*this));}
virtual void show()
{
cout<<"CAll Base::show()"<<endl;
}
};
class Derive : public Base
{
public:
Derive()
{
cout<<"call Derive()"<<endl;
}
void show()
{
cout<<"Call Derive::show()"<<endl;
}
};
int main()
{
//错误代码
Base *pb1 = new Base();
pb1->show();
delete pb1;
//正确
Base *pb2 = new Derive();
pd2 ->show();
delete pb2;
return 0;
}
解释:
//错误代码
Base *pb1 = new Base();
pb1->show();
delete pb1;
因为pb1调用了clear函数,相当于把vfptr置成0地址了,vfptr已经不再指向Base::vfptable了,当再次调用pb1->show();,从而找不到,发生异常错误。
//正确
Base *pb2 = new Derive();
pd2 ->show();
delete pb2;
首先vfptr同样被置0地址,因为Base先进行构造函数,vfptr 《- &Base::vftable,再调用clear函数,vfptr被置成0地址。 然后Derive再构造,同样会执行vfptr 《- &Base::vftable,但是不会调用clear函数,所有此时vfptr 就会指向Derive::vftable,从而正常运行。
主要用字继承结构中,可以支持RTTI类型识别的上下类型转化
int main()
{
const int a = 10;
int *p1 = (int*)&a;
int *p2 = const_cast<int*>(&a);
/*不考虑const,左右两边类型要保持一致,体现了安全性
const_cast《》里面必须是指针或引用类型
*/
int a = 10;
char b = static_cast<int>(a); //char与int有联系
int*p = nullptr;
double *b = reinterpret_cast<double*>(p);//可以转换,但是不安全
}
dynamic_cast 是 C++ 中用于安全地进行基类指针或引用向派生类指针或引用的类型转换的一种运算符。它主要用于在运行时检查类型安全性,只能用于具有虚函数的类层次结构中。如果尝试转换失败,dynamic_cast 将返回一个空指针(对指针进行转换)或引发 std::bad_cast 异常(对引用进行转换)。
下面是 dynamic_cast 的一般语法:
dynamic_cast<new_type>(expression)
其中 new_type 是要转换的目标类型,expression 是要转换的指针或引用。
#include <iostream>
// 基类
class Base {
public:
virtual ~Base() {} // 虚析构函数确保多态性
};
// 派生类
class Derived : public Base {
public:
void derivedMethod() {
std::cout << "Derived method called." << std::endl;
}
};
int main() {
Base* basePtr = new Derived();
// 使用 dynamic_cast 进行类型转换
Derived* derivedPtr = dynamic_cast<Derived*>(basePtr);
if (derivedPtr) {
// 转换成功,可以安全地调用 Derived 类的方法
derivedPtr->derivedMethod();
} else {
// 转换失败
std::cout << "Dynamic cast failed." << std::endl;
}
delete basePtr;
return 0;
}
在这个例子中,basePtr 是一个指向基类 Base 的指针,但实际上指向了一个派生类 Derived 的对象。通过使用 dynamic_cast 将 basePtr 转换为 Derived* 类型的指针 derivedPtr,我们可以安全地调用 Derived 类的方法。如果转换失败(例如 basePtr 指向的对象不是 Derived 类型的),dynamic_cast 将返回 nullptr。