之前对于虚函数(以及纯虚函数)和多态都只是有一个基础的认识,这次想着做一个知识点的总结和辨析
1.动态绑定与静态绑定
1.1静态类型与动态类型
静态类型: 声明变量时的变量类型 如 int a; 编译时确定
动态类型: 一般指指针或引用指向的类型, 如 int& b=a; 运行时确定
1.2动态绑定与静态绑定
动态绑定: 绑定动态类型, 对象的成员依赖于动态类型 运行时确定
静态绑定: 绑定静态类型, 对象的成员依赖于静态类型 运行时确定
看例子:
class People{
public:
virtual void func(int age=0){
cout<<"People age: "<<age<<endl;
}
};
class Student:public People{
public:
void func(int age){
cout<<"Student age: "<<age<<endl;
}
};
int main(){
People p; //静态绑定 静态类型为People
Student t; //静态绑定 静态类型为Student
//对于peo要注意静态绑定始终是People
People* peo; //没有动态类型
peo=NULL; //没有动态类型
peo=new People(); //动态绑定 动态类型为People
peo=new Student(); //动态绑定 动态类型为Student
peo->func();
// t.func(); //编译时这一行会报错
}
注意静态类型不可变,可以变化的只用动态类型.
还有一个不常见的小知识点: 对于上述例子我们直接Student类中的func函数,如果我们直接使用对象比如t来调用,会报错(因为没传参) 但是如果我们使用 父类的多态来调用 如peo,则不会报错,并且会正常调用Student里面的函数
虚函数采用的是动态绑定 这也是虚函数之所以可以实现多态的基础 也只有虚函数采用的是动态绑定,其他采用的都是静态绑定
2.多态
不同对象对于同一行为有不同的反应
意义:多态一个比较明显的好处就是使得代码更容易复用,更新版本增加新功能时,只用重写一下函数就可以了,当然也有利于代码的维护,代码的模块化,隐藏内部细节.....ect.
分类:1.编译时多态: 静态绑定 例如函数重载
2.运行时多态: 动态绑定 例如使用基类指针或者引用指向派生类
条件: 1. 使用父类指针或者引用指向派生类
2. 派生类中有实现的同名虚函数 (没有实现会调用父类的虚函数)
3.虚函数
使用virtual进行修饰的成员方法(函数)
特点: 虚函数所在类的派生类 中的同名函数 均为虚函数. 加不加virtual无所谓
本质: 虚函数是多态的基础,没有虚函数就没有多态.
引入:
我们知道在子类中如果想要修改父类的public or protected成员变量或者成员方法(函数)是比较容易的,可直接修改,就算同名加上作用域就可以student1.People::age=12; student1.People::func();(Student是People的子类).
但是如果我们当我们想要使用多态(使用基类指针或引用指向派生类,一般来说这样的操作更为常见,因为变量指针或者引用可多次修改指向,节省变量和空间)对进行操作,会发现因为静态绑定导致, 指向派生类的指针或者变量只能使用的父类成员方法和成员变量,当我们想要使用派生类的同名函数时,在父类函数前加入virtual,让他在运行时进行动态编译即可(具体更加详细内容和虚函数表相关,可自行查阅).
特殊解决:
需要注意的是,即使是指向派生类的指针或者引用可以访问到派生类的虚函数,访问不到派生类的其他非虚函数(非虚成员方法),访问不到派生类任何成员变量(无论是否重名都只能访问自己域内的成员变量),大致也是因为静态绑定,但是针对这种特殊情况是可以使用向下兼容的,即把该指针或者引用强制转化成派生类即可
注意:我们一般会设父类的析构函数为虚函数,这样的话,在派生类析构时,就会调用自己的析构函数,减少OOM的可能
class base {
public:
int data = 0;
~base() {
cout << "base的虚构函数" << endl;
}
void func() {
cout << "base的func函数" << endl;
}
};
class fromBase :public base {
public:
int data = 1;
~fromBase() {
cout << "fromBase的虚构函数" << endl;
}
void func() {
cout << "fromBase的func函数" << endl;
}
};
int main() {
//base* base2fromBase = &fromBase1;
base* base2fromBase=new fromBase();
fromBase* fromBase2fromBase = new fromBase();
cout << "基类指针不可使用派生类的成员变量和非虚函数,而可以使用派生类实现了的虚函数" << endl;
cout << "base2fromBase:" << base2fromBase->data << endl;
cout << "fromBase2fromBase:" << fromBase2fromBase->data << endl;
base2fromBase->func();
fromBase2fromBase->func();
cout << "基类指针不可直接使用派生类的析构函数,除非该函数被virtual修饰" << endl;
delete fromBase2fromBase;
delete base2fromBase;
return 0;
}
4.纯虚函数与抽象类:
纯虚函数: 前面加上virtual标识, 没有函数体, 写完参数后写=0 如virtual void func(int a)=0;
抽象类: 含有纯虚函数 或者 继承的父类含有纯虚函数且该函数未被重写 的类
抽象类不能实例化,(即含有纯虚函数或者含有继承的未被重写的纯虚函数的类不可实例化),个人感觉这是为了避免忘记重写纯虚函数而设置的,
5.虚函数与纯虚函数的区别:
1.虚函数可以在类外创建 纯虚函数只能类内创建
2.虚函数可以有函数体 纯虚函数一定没有
3.纯虚函数必须被重写