多态
静态多态---------->函数重载、泛型编程
动态多态---------->虚函数
好处:
1.应用程序不必为每一个派生类编写功能调用,只需要对抽象基类进行处理即可。大大提高程序的可复用性。//继承
2.派生类的功能可以被基类的方法或引用变量所调用,这叫向后兼容,可以提高可扩充性和可维护性。//多态的真正作用
虚函数 (virtual)
- 作用:
虚函数是运行时多态,若某个基类函数声明为虚函数,则其公有派生类将定义与其基类虚函数原型相同的函数,这时,当使用基类指针或基类引用操作派生类对象时,系统会自动用派生类中的同名函数代替基类虚函数。 - 原理(动态联编):
派生类根据自身要求,继承并改写了基类同名函数speak(),这种改变在静态联编的条件下编译器并不知道,造成了上诉结果,若想要通知编译器这种改变,则需要通过动态动态联编实现,其方法就是在基类中将可能发生改变的成员函数声明为虚函数。 - 条件:
1.虚函数只能时类中的函数,但不可以时静态成员函数。
2.派生类对基类虚函数重新定义时,必须与基类中虚函数的原型完全一致。
class A{
public:
void func1(){cout<<"class A";}
};
class B:public A{
void func1(){cout<<"class B";}
};
int main()
{
A *a=new B;
a->func1(); //调用 A::func1() 而不是 B::func1()
//要调用B的func1实现多态 需要将基类的函数定义为虚函数
}
虚函数指针:
class A{
//函数代码所占用的存储空间不包含在对象中
//(具体看'C++成员函数在内存中的存储方式')
void func(){}
};
class B{
virtual void func(){}
};
int main()
{
cout<<sizeof(A)<<" "<<sizeof(B); //结果为 1 4
}
虚基类
作用:公共基类只产生一份拷贝
在多重继承中,若一个类声明为虚基类,则能保证一个派生类间接地多次继承该类时,派生类中只能继承该基类的一份成员,避免了派生类中访问公共基类公有属性多份拷贝的二义性。
对于虚基类的构造函数,C++编译器的调用方法是:由最后定义的派生类,即类的层次结构中最底层的派生类在定义对象时完成虚基类构造函数的调用,该派生类的其他基类对虚基类构造函数的调用被忽略。
实现方式:
通过对虚基类的继承派生出新类,定义新类对象时,对象中数据成员的排列形式与普通非虚基类派生出的新类数据成员的排列形式不同,派生类对象中将增加一个隐藏的"虚基类表指针"(vbptr)成员变量,从而达到间接计算虚基类成员的位置的目的。该变量指向一个全类共享的偏移量表,表中项目记录了对于该类而言,“虚基类表指针”与虚基类之间的偏移量。
总结:
含有虚基类时构造函数调用顺序:
1.虚基类的构造函数 //有虚基类多出的步骤
2.调用基类构造函数。按它们在派生类中定义的先后顺序,依次调用
3.其次调用对象成员的构造函数。按它们在派生类中定义的先后顺序,依次调用。
4.最后调用派生类的构造函数。
成员函数定义顺序:
1.定义虚基类的"虚基类指针”成员(遇到虚基类就直接跳到第二步,而不是继续查看访问其他基类)
2.定义该类的数据成员
3.定义派生类的成员
4.定义步骤1遇到的虚基类的成员
5.如果虚基类的成员中还含有虚基类返回第一步
例子:
class E{
public:
int e;
};
class A:virtual public E{
public:
int a;
};
class B:public A{
public:
int b;
};
class C:virtual public B{
public:
int c;
};
class D:public C{
public:
int d;
};
int main()
{
D *d=new D;
printf("vbptr\n"); //vbptr
printf("C:%X\n",&d->c); //27D0E14
printf("D:%X\n",&d->d); //27D0E18
printf("vbptr\n"); //vbptr
printf("A:%X\n",&d->a); //27D0E20
printf("B:%X\n",&d->b); //27D0E24
printf("E:%X\n",&d->e); //27D0E28
}
重载与重写
重载:
发生在同一个类中
相同的方法名
参数
列表不同
不看返回值,如果出现了只有返回值不同的“重载”,是错的。
重写:
发生在子类与父类中
相同的方法名
相同的参数列表
返回值相同 或者 子类方法的返回值是父类方法返回值类型的子类
访问修饰符相同 或者 子类方法的修饰符范围 大于 父类
抛出的异常相同 或者 子类方法抛出的异常 小于父类
储存区
- 内存栈区: 存放局部变量名;
- 内存堆区: 存放new或者malloc出来的对象;
- 常数区: 存放局部变量或者全局变量的值;
- 静态区: 用于存放全局变量或者静态变量;
- 代码区:二进制代码。
派生类构造函数的调用顺序
1.首先调用基类构造函数。按它们在派生类中定义的先后顺序,依次调用
2.其次调用对象成员的构造函数。按它们在派生类中定义的先后顺序,依次调用。
3.最后调用派生类的构造函数。
C++成员函数在内存中的存储方式
博客:https://blog.csdn.net/fuzhongmin05/article/details/59112081
C++ 虚继承实现原理(虚基类表指针与虚基类表)
博客:https://blog.csdn.net/longlovefilm/article/details/80558879