文章目录
- 构成多态的条件
- 虚函数
- 作用:完成重写
- 重写
- 重载 重写 隐藏
- 为什么析构函数要搞成符合多态?
- 原理
- 预热
- 对于基类指针或引用指向父类或者子类的成员函数是如何调用不同的函数呢?
一个类如果是基类,它的析构函数最好加上virtual
构成多态的条件
- 必须通过基类的指针或者引用调用虚函数
- 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写
多态,不同对象传递过去,调用不同函数
多态调用看的指向的对象
普通对象(普通调用(不构成多态)),看当前者类型
虚函数
只有成员函数才可以加virtual
加上virtual它就叫做虚函数
作用:完成重写
重写
虚函数重写的一些细节:
// 重写的条件本来是虚函数+三同(即派生类虚函数与基类虚函数的
返回值类型、函数名字、参数列表完全相同),但是有一些例外
// 1、派生类的重写虚函数可以不加virtual – (建议大家都加上)
// 2、协变,返回的值可以不同,但是要求返回值必须是父子关系指针和引用
重载 重写 隐藏
函数重载发生在同一作用域
重写和隐藏 发生在基类和派生类
隐藏只要函数名一样就符合条件
为什么析构函数要搞成符合多态?
场景一
class Person
{
public:
~Person()
{
cout << "~Person()" << endl;
}
};
class Student:public Person
{
public:
~Student()
{
cout << "~Student()" << endl;
}
};
int main()
{
Person ps;
Student st;
return 0;
}
这种情况下程序走的好着呢
也没上多态,不是也可以吗?为什么非得把父类和子类的析构函数搞成虚函数重写呢?
因为下面的场景下,不构成重写会造成内存泄漏
class Person
{
public:
~Person()
{
cout << "~Person()" << endl;
}
};
class Student:public Person
{
public:
~Student()
{
cout << "~Student()" << endl;
delete[] _a;
}
private:
int* _a = new int[10];
};
int main()
{
/*Person ps;
Student st;*/
Person* p = new Person;//基类指针既可以指向基类,也可以指向派生类
delete p;
p = new Student;
delete p; //p->destructor() + operator delete(p)(free)
//p是基类指针,指向Student,多态条件一满足
//这里我们期望p指向谁,调用谁的析构
//如果析构函数不构成多态,那么p->destructor() 是普通调用,只看当前者p的类型,永远只调用~person
return 0;
}
运行结果
delete p 做了2件事
p->destructor() + operator delete§(free)
如果析构函数不构成多态,那么p->destructor() 是普通调用,只看当前者p的类型,永远只调用~person
这不是我们期望的,这里我们期望p指向谁,调用谁的析构
基类指针既可以指向基类,也可以指向派生类
那么我们就需要满足多态的条件
p是基类指针,指向Student,调用析构函数。多态条件一满足
编译器帮助我们把类析构函数都被处理成destructor这个统一的名字
形参类型一样,函数名一样,析构又没有返回值,那么就需加上virtual即可
class Person
{
public:
virtual ~Person()
{
cout << "~Person()" << endl;
}
};
class Student:public Person
{
public:
virtual ~Student()
{
cout << "~Student()" << endl;
delete[] _a;
}
private:
int* _a = new int[10];
};
int main()
{
/*Person ps;
Student st;*/
Person* p = new Person;//基类指针既可以指向基类,也可以指向派生类
delete p;
p = new Student;
delete p; //p->destructor() + operator delete(p)(free)
//p是基类指针,指向Student,多态条件一满足
//这里我们期望p指向谁,调用谁的析构
//如果析构函数不构成多态,那么p->destructor() 是普通调用,只看当前者p的类型,永远只调用~person
return 0;
}
原理
预热
Base的大小是多少呢?
那么虚函数存在哪里呢?
存在了代码段(常量区)
回顾我们类和对象大小时的知识,成员函数也不保存在对象中,而是存放在公共的代码段
这里的vfptr是虚函数表指针,存的只是虚函数的地址!
成员函数加了virtual就会放到虚函数表指针里面
对于基类指针或引用指向父类或者子类的成员函数是如何调用不同的函数呢?
父类指针或者引用指向子类,发生切片,拿到的仍然是一个父类
指向父类还是父类
那么他们是如何调用不同的成员函数的呢?
class Person {
public:
virtual void BuyTicket() { cout << "买票-全价" << endl; }
int _a = 1;
};
class Student : public Person {
public:
virtual void BuyTicket() { cout << "买票-半价" << endl; }
int _b = 1;
};
void Func(Person& p)
{
// 符合多态,运行时到指向对象的虚函数表中找调用函数的地址
p.BuyTicket();
}
int main()
{
Person Mike;
Func(Mike);
Student Johnson;
Func(Johnson);
return 0;
}
Mike和Johnson的监视中看到他们的虚函数表里面存的地址不同,因为子类对父类的虚表进行了覆盖(子类拷贝了父类的虚表再进行重写)
P指针指向基类和指向子类时调用的函数是不同的地址
符合多态的话,P指向父类就是父类,P指向子类,完成切片后看到的还是一个子类,那么P到底是指向父类还是子类就不得而知了。
但是我不管,运行时,符合多态,运行时到指向对象的虚函数表中找调用函数的地址
在编译时就确定成员函数的地址
如果不符合多态,那么就看调用者P的类型,去Person里去找这个函数,在用函数名修饰规则就可以找到函数的地址