1.当自己的类指针指向自己类的对象时,无论调用的是虚函数还是实函数,其调用的都是自己的(那必须的啊):
2.当指向父类对象的父类指针被强制转换成子类指针时候,子类指针调用函数时,只有非重写函数是自己的,虚函数是父类的;
3.当指向子类对象的子类指针被强制转换成父类指针的时候,也就是父类指针指向子类对象,此时,父类指针调用的虚函数都是子类的,而非虚函数都是自己的;
class A1
{public:
int a = 0;
virtual int fu()
{
return 0;
};
virtual int fu2() {
return 0;
};
};
class B1 :A1
{
public:
int num = 2;
int fu()
{
return num;
}
};
int main()
{
A1 *aa = new A1();
int sizea = sizeof(A1);
B1 *b=new B1();
A1 *a = (A1*)b;
return 0;
}
如图为子类B1的类内布局,及其虚函数表
可以看出,对于子类B1,其中重写过的fu才是B1的,fu2没重写过,所以如果调用的话,只会调用父类的fu2函数。
需要注意的是一个类不管有多少个虚函数,虚函数指针(可以理解为虚函数表的表头指针)对于类的一个对象来说只有一个,所以sizeof(A1),32位下是8(4+4,4字节对齐), 64位下为16,(4+8,还要再算上8字节对齐,所以是16)
sizeof(B1),32位下为sizeof(A1) 8+4=12
sizeof()是运算符,由于在编译时计算,因此sizeof不能用来返回动态分配的内存空间的大小。实际上,用sizeof来返回类型以及静态分配的对象、结构或数组的空间,返回值跟这些里面所存储的内容没有关系。 对于类来说,sizeof只会考虑到变量和虚函数,以及基类,不考虑成员函数和静态变量。对子类sizeof的操作,先计算基类再+上子类独有的
此处涉及虚函数表的原理,或者说是类内布局:
需要主要的是子类转换为父类,会被屏蔽(内存布局是先父类的虚函数表指针、父类成员变量,然后才是子类的成员变量,如果父类没有虚函数,子类有的话,这里还要考虑子类的虚函数表指针,所以理论上很好屏蔽)掉子类独有的成员函数和成员变量,父类指针,只能操作子类的虚函数和父类拥有的变量和父类的成员函数。
4.虚函数与函数重载,多态:
什么是多态:同一个实体,同时具有多种形式
向不同的对象发送同一条消息(即函数调用)
不同的对象在接收时会产生不同的行为(即不同的函数实现)
多态的两种方式:
1.函数重载的方式(静态多态或编译时多态)
2.函数重写的方式(动态多态或运行时多态)
2.1 要实现C++函数重写,必须要先把父类的成员函数设定为虚函数
注意1:
派生类重写基类方法时可以加上override关键字表示重写
override关键字为C++11标准后新加入的,用来明确派生类重写基类继承过来的虚函数
注意2:
为了能够让同一个函数操作不同类型的子类对象,所以我们把参数类型定义成了基类对象
当传递子类类型时,参数类型可以自动转化(子类对象可以赋值给父类对象)
子类转换为父类:向上转型,使用dynamic_cast<type_id>(expression),这种转换相对来说比较安全不会有数据的丢失;
父类转换为子类:向下转型,可以使用强制转换,这种转换时不安全的,会导致数据的丢失,原因是父类的指针或者引用的内存中可能不包含子类的成员的内存。
如图,父类指针aa转换成子类,但是父类中没有子类独有的一些函数,这造成编译通过,但是运行会出错。
虚函数也是类的成员函数
虚函数和函数重载都实现了C++的多态性,但表现形式不一样,函数重载调用根据参数个数、参数类型等进行区分,而虚函数则是根据动态联编来确定调用什么,
函数重载可以是类的成员函数也可以是非成员函数,比如:
1 2 |
int fun(int a); int fun(int a, int b); |
这就是非成员重载,虚函数必须是成员函数了,否则就失效了
函数重载的定义:
两个以上的函数,具有相同的函数名,但是形参的个数或者类型不同,编译器根据实参和形参的类型及个数的最佳匹配,自动确定调用哪一个函数,这就是函数的重载。
重载函数的四个注意,前两个可以混合使用:
个数不同: int add(int x, int y); int add(int x, int y, int z);
类型不同:int add(int x, int y); int add(float x, float y);
不以形参名来区分函数
不以返回值来区分函数