本篇博客中的编译环境:vs2017,x86程序。
同一动作的不同状态,当去完成某个行为时,由不同对象去完成时会产生不同的状态。
例如:
构成多态时,这两个条件缺一不可。
被virtual
修饰的函数成为虚函数。
虚函数的重写:
virtual
关键字,一般情况下都加上;例如:
class People//基类
{
public:
virtual void BuyNote()
{
cout << "购买全票" << endl;
}
};
class Student : public People//派生类,学生
{
virtual void BuyNote()
{
cout << "购买半票" << endl;
}
};
class Child : public People//派生类,child
{
virtual void BuyNote()
{
cout << "免费" << endl;
}
};
void Test(People& D)//测试
{
D.BuyNote();
}
int main()
{
People A;
Student B;
Child C;
Test(A);
Test(B);
Test(C);
return 0;
}
上述代码的测试条件:
public
,派生类为private
结果:
虚函数重写的两个例外:
例:
class A
{
};
class B : public A
{
};
class C
{
public:
virtual A* func()
{
return new A;
}
};
class D
{
public:
virtual B* func()
{
return new B;
}
};
virtual
关键字,都对基类析构函数进行了重写;注意:在继承体系中,最好将基类中的析构函数设计为虚函数,如果子类涉及到资源管理,一定要将基类析构函数设计为虚函数。
在虚函数后面加上 = 0
,该虚函数就为纯虚函数。包含纯虚函数的类叫做抽象类(也称为接口类),抽象类不能实例化出对象。 派生类继承之后也不能实例化出对象,只有重写虚函数,派生类才能实例化为对象。纯虚函数规范了派生类必须重写,接口继承。
实现继承:普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。
接口继承:虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。所以如果不实现多态,不要将函数定义为虚函数。
如果类中包含虚函数,用户没有显式定义构造函数,编译器会给用户生成一份默认的构造函数,在生成构造函数中会填充对象前4个字节的内容;如果用户显式定义了构造函数,编译器会修改用户定义的构造函数,增加一条给对象前4个字节赋值的语句。
什么情况下编译器会给类生成默认的构造函数?
编译器填充的前4个字节为虚表指针。
例:
class People
{
public:
virtual void BuyNote()
{
cout << "购买全票" << endl;
}
private:
int _b;
};
int main()
{
People A;
return 0;
}
vs2017中,观察其对象模型为:
即:
在派生类中:
class People
{
public:
virtual void BuyNote()
{
cout << "购买全票" << endl;
}
private:
int _b;
};
class Student : public People
{
private:
int _c;
};
int main()
{
People A;
Student B;
return 0;
}
2. 如果派生类重写了基类某个虚函数,编译器就会将派生类虚函数替换相同偏移量位置的基类虚函数。
class People
{
public:
virtual void BuyNote()
{
cout << "购买全票" << endl;
}
virtual void BuyAirTicket()
{
cout << "购买全票" << endl;
}
virtual void BuyFerryTicket()
{
cout << "购买全票" << endl;
}
private:
int _b;
};
class Student : public People
{
public:
virtual void BuyNote()
{
cout << "购买半票" << endl;
}
virtual void BuyAirTicket()
{
cout << "少量优惠" << endl;
}
private:
int _c;
};
int main()
{
People A;
Student B;
return 0;
}
3. 若派生类中有新增虚函数,vs2017的监视窗口中,虚表不会显示新增的虚函数。
class People
{
public:
virtual void BuyNote()
{
cout << "购买全票" << endl;
}
virtual void BuyAirTicket()
{
cout << "购买全票" << endl;
}
virtual void BuyFerryTicket()
{
cout << "购买全票" << endl;
}
private:
int _b;
};
class Student : public People
{
public:
virtual void BuyNote()
{
cout << "购买半票" << endl;
}
virtual void BuyAirTicket()
{
cout << "少量优惠" << endl;
}
virtual void BuyAmusementParkTicket()
{
cout << "游乐园购买半票" << endl;
}
private:
int _c;
};
其实新增的虚函数也在该虚表中,只是编译器的监视窗口隐藏了该新增的虚函数。
打印虚表中的函数:
typedef void(*VFPTR) ();
void PrintVTable(VFPTR vfptr[])
{
for (int i = 0; vfptr[i] != nullptr; ++i)
{
cout << vfptr[i] << " ";
vfptr[i]();
}
}
int main()
{
People A;
Student B;
VFPTR* vftable = (VFPTR*)(*(int*)(&B));//取到了前4个字节中的内容,整形内容,强转为VFPTR*
PrintVTable(vftable);
return 0;
}
虚表中放置的是虚函数的入口地址,如果能拿到虚表中的内容,即拿到了虚函数的入口地址,则可以将该函数调用起来。
基类虚表构建过程:编译期间在编译期间,按照虚函数在类中声明的先后次序依次添加到虚表中;
派生类虚表的构建过程:
注意:
虚函数调用:
满足多态以后的函数调用,不是在编译期间确定的,而是在运行起来以后到对象中去找的,不满足多态的函数调用是在编译时确定的。
class People
{
public:
virtual void BuyNote()
{
cout << "购买全票" << endl;
}
virtual void BuyAirTicket()
{
cout << "购买全票" << endl;
}
virtual void BuyFerryTicket()
{
cout << "购买全票" << endl;
}
private:
int _b;
};
class Student
{
public:
virtual void BuyAirTicket()
{
cout << "少量优惠" << endl;
}
virtual void BuyAmusementParkTicket()
{
cout << "游乐园购买半票" << endl;
}
private:
int _c;
};
class Child : public People, public Student
{
public:
virtual void BuyTrainTicket()
{
cout << "免费" << endl;
}
private:
int _d;
};
int main()
{
People A;
Student B;
Child C;
return 0;
}
派生类新增的虚函数放在了第一个继承基类的虚函数表中。其余规则,遵守单继承时虚表规则。
class A
{
public:
virtual void fun1()
{
}
virtual void fun2()
{
}
};
class B : public A
{
public:
virtual void fun1()
{
}
virtual void fun3()
{
}
};
class C : public A
{
public:
virtual void fun2()
{
}
virtual void fun4()
{
}
};
class D : public B, public C
{
public:
virtual void fun4()
{
}
virtual void fun5()
{
}
};
typedef void(*VFPTR) ();
void PrintVTable(VFPTR vfptr[])
{
for (int i = 0; vfptr[i] != nullptr; ++i)
{
cout << vfptr[i] << endl;
}
}
int main()
{
D d;//那么在第一个虚表中,应该存在4个虚函数
VFPTR* vftable = (VFPTR*)(*(int*)(&d));
PrintVTable(vftable);
return 0;
}
派生类菱形继承下的多态,派生类的新增虚函数,存在与第一个继承的虚表下面。菱形继承在多态中,仍会产生二义性问题。
class A
{
public:
virtual void fun1()
{
}
virtual void fun2()
{
}
};
class B : virtual public A
{
public:
virtual void fun1()
{
}
virtual void fun3()
{
}
};
class C : virtual public A
{
public:
virtual void fun2()
{
}
virtual void fun4()
{
}
};
class D : public B, public C
{
public:
virtual void fun4()
{
}
virtual void fun5()
{
}
};
typedef void(*VFPTR) ();
void PrintVTable(VFPTR vfptr[])
{
for (int i = 0; vfptr[i] != nullptr; ++i)
{
cout << vfptr[i] << endl;
}
}
int main()
{
D d;//那么在第一个虚表中,应该存在2个虚函数地址
VFPTR* vftable = (VFPTR*)(*(int*)(&d));
PrintVTable(vftable);
return 0;
}
.
派生类菱形继承下的多态,派生类的新增虚函数,存在与第一个继承的虚表下面。菱形虚拟继承在多态中,不会有二义性问题。
静态成员函数不能是虚函数。