多态
函数覆盖
函数覆盖与函数隐藏比较相似,但是函数隐藏不支持多态。函数覆盖是多态的必要条件。
在编写方式上,函数覆盖比函数隐藏有以下几点区别:
● 被覆盖的函数必须是虚函数
● 在C++11中,可以在派生类的新覆盖的函数上使用overide关键字验证覆盖是否成功。
一个函数使用virtual关键字修饰,就是虚函数,虚函数是函数覆盖的前提。在Qt Creator中虚函数的函数名称使用斜体字。
虚函数具有以下性质:
● 虚函数具有传递性,基类被覆盖的函数是虚函数,派生类中新覆盖的函数也是虚函数。
● 只有普通成员函数与析构函数可以声明为虚函数。
● 如果虚函数的声明与定义分离,virtual关键字只需要修饰声明处。不能只写到定义中。也不能两个都写。
#include <iostream>
using namespace std;
class Animal
{
public:
// 虚函数
virtual void eat();
void test()
{
}
};
void Animal::eat()
{
cout << "动物爱吃饭" << endl;
}
class Dog:public Animal
{
public:
virtual void eat() override
{
cout << "狗爱吃骨头" << endl;
}
// test 不是虚函数
// void test() override
// {
// }
};
int main()
{
Dog d;
d.eat();
return 0;
}
虚函数:允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数。
多态的概念
静态多态:编译时多态,函数重载
动态多态:运行时多态,虚函数实现。
多态可以理解为“一种接口,多种状态”。只需要编写一个函数接口,根据传入的参数类型,执行不同的策略代码。
多态的使用需要具有三个前提条件。
● 公有继承
● 函数覆盖
● 基类的指针/引用指向派生类的对象。
#include <iostream>
using namespace std;
class Animal
{
public:
// 虚函数
virtual void eat()
{
cout << "动物爱吃饭" << endl;
}
void test()
{
}
};
class Dog:public Animal
{
public:
virtual void eat() override
{
cout << "狗爱吃骨头" << endl;
}
};
class cat:public Animal
{
public:
virtual void eat() override
{
cout << "猫爱吃鱼" << endl;
}
};
void test_eat1(Animal *a1)
{
a1->eat();
}
void test_eat2(Animal &a1)
{
a1.eat();
}
int main()
{
// 基类的指针指向派生类对象
Dog *d1 = new Dog;
cat *c1 = new cat;
test_eat1(d1);
test_eat1(c1);
Dog d;
cat c;
test_eat2(d);
test_eat2(c);
return 0;
}
多态原理
具有虚函数的类会存在一张表,每个类的对象内部都会有一个隐藏的虚函数指针成员。指向当前类的虚函数表。
在代码运行时,通过对象的虚函数是指针找到虚函数表,在表中定位到虚函数的调用地址,从而执行对应的虚函数内容。
多态会产生一些额外的开销。优点是代码编写更加灵活高效。缺点会降低代码可读性。和执行速度。
虚析构函数
如果不使用虚析构函数,且基类的指针指向派生类对象,使用delete销毁对象时,只能触发基类的析构函数,如果在派生类中申请内存资源,则会导致无法释放,出现内存泄漏的问题。
#include <iostream>
using namespace std;
class Animal
{
public:
// 虚函数
void eat()
{
cout << "动物爱吃饭" << endl;
}
void test()
{
}
virtual ~Animal()
{
cout << "基类析构函数被调用了" << endl;
}
};
class Dog:public Animal
{
public:
void eat()
{
cout << "狗爱吃骨头" << endl;
}
~Dog()
{
cout << "Dog析构函数被调用了" << endl;
}
};
int main()
{
// 基类的指针指向派生类对象
Animal *al = new Dog;
delete al;
return 0;
}