1. C++的多态性:
在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的时间类型来调用相应的函数。如果对象类型时派生类,就调用派生类的函数;如果对象类型时基类,就调用基类的函数。
1)用virtual 关键字声明的函数叫做虚函数,虚函数肯定是类的成员函数;
2)存在虚函数的类都有一个一维的虚函数表叫做虚表,类的对象有一个指向虚表的开始指针。虚表是和类对应的,虚表指针是和对象对应的;
3)多态性是一个接口多种实现,是面向对象的核心,分为类的多态性和函数的多态性;
4)多态用虚函数来实现,结合动态绑定;
5)纯虚函数是虚函数再加上 = 0 ;
6)抽象类是指包括至少一个纯虚函数的类。
纯虚函数:virtual void fun()= 0 ;即抽象类,必须在子类型实现这个函数,即先有名称,没有内容,在派生类实现内容。
2. C++多态的三种类型:
重载:在同一个类,函数名相同,参数个数相同或者类型不同;
示例代码:
int Exam()
{
std::cout << "Ex01" << std::endl;
return 0;
}
int Exam(int a, int b)
{
std::cout << "Ex02" << std::endl;
return 0;
}
int Exam(float a, float b)
{
std::cout << "Ex03" << std::endl;
return 0;
}
int main()
{
int a{}, b{};
float c{}, d{};
Exam();
Exam(a, b);
Exam(c, d);
}
//注* 重载不能通过函数的返回类型来完成重载
重写(覆盖):具有继承关系的俩个类,子类重新编写父类的方法,父类函数必有virtual修饰,即使定义父类的对象,也会调用子类的方法;因为父类的方法已经呗重写了;如果需要调用父类的方法,就在调用时加上一个作用域;
隐藏:具有继承关系的俩个类,子类重新编写了父类的方法;父类函数没有virtual修饰。
示例代码:
class Examples
{
public:
virtual void Exam01()
{
std::cout << "Ex01" << std::endl;
}
void Exam02()
{
std::cout << "Ex02" << std::endl;
}
};
class Examples_T :public Examples
{
public:
virtual void Exam01()//此函数重写了Exam01
{
std::cout << "Ex03" << std::endl;
}
virtual void Exam02()//此函数隐藏了Exam02
{
std::cout << "Ex04" << std::endl;
}
};
int main()
{
Examples_T ls;
ls.Exam01();
ls.Exam02();
}
3. 不规范的重写行为:
在派生类中重写的成员函数可以不加virtual关键字,也是构成重写,因为继承后基类的虚函数被继承下来,在派生类中依旧保持虚函数的属性,我们只是重写了它。这是非常不规范的,在平时尽量不要这样使用。
#include <iostream>
class Examples
{
public:
virtual void Exam01()
{
std::cout << "Ex01" << std::endl;
}
void Exam02()
{
std::cout << "Ex02" << std::endl;
}
};
class Examples_T :public Examples
{
public:
virtual void Exam01()
{
std::cout << "Ex03" << std::endl;
}
void Exam02()
{
std::cout << "Ex04" << std::endl;
}
};
int main()
{
Examples_T ls;
ls.Exam01();
ls.Exam02();
}
4. 析构函数重写问题
基类中的析构函数如果是虚函数,那么派生类的析构函数就重写了基类的析构函数。这里他们的函数名不相同,看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor,这也说明的基类的析构函数最好写成虚函数。
将析构函数定义为虚函数的原因:
因为基类指针可能指向派生类,当delete的时候,如果不定为虚函数,系统会直接调用基类的析构函数,这个时候派生类就有一部分没有被释放,就会造成可怕的内存泄漏问题。
若定义为虚函数构成多态,那么就会先调用派生类的析构函数然后派生类的析构函数会自动调用基类的析构函数,这个结果满足我们的本意。
所以在继承的时候,尽量把基类的析构函数定义为虚函数,这样继承下去的派生类的析构函数也会被变成虚函数构成多态。
5. override 和 final
override:
override是用来检查函数是否重写,是在virtual void fun() override {}这里加上,然后来检查的。实际中,建议这样写。
final:
final是在class A final {};这里加上,目的是为了不让这个类被继承。
或者,在一个函数后加,表示这个函数不能被重写。void fun() final {}。
注* final虽然是关键字 但是可作为变量名使用
示例:
int main()
{
int final = 100;
std::cout << final;
}