一、概念
从广义上讲,多态可以分为静态多态和动态多态。静态多态(编译时多态)发生在程序的编译阶段,主要包括函数重载和运算符重载,在编译时就能确定调用关系。动态多态(运行时多态),本章讨论的主要是动态多态。因此从狭义上讲,多态指的是动态多态。多态(polymorphism)按照字面的意思是“多种状态”,简单的概括为“一个接口,多种状态”,一个函数接口,在运行时根据传入的参数类型执行不同的策略。多态的实现需要有三个前提条件:
- 公有继承
- 函数覆盖(函数重写)override
- 基类引用/指针指向派生类对象
二、函数覆盖
函数覆盖(函数重写)的前提是虚函数,虚函数使用关键字virtual修饰成员函数实现,普通的虚函数目的是实现函数覆盖。虚函数的格式:virtual 返回值类型 函数名 (参数表){}一句话表达:在之前函数隐藏的前提下,把被隐藏的基类函数使用virtual修饰,就变成了函数覆盖。虚函数具有以下性质:
- 在Qt Creator中斜体表示虚函数
- 虚函数具有传递性,基类被覆盖的虚函数会自动传递给派生类覆盖的新函数,使后者也变为虚函数。
- 成员函数和析构函数可以设置为虚函数,静态成员函数和构造函数不能设置为虚函数。
- 如果函数的声明与定义分离,virtual只需要修饰在声明处。
- C++11中,可以在派生类新覆盖的函数后面使用override关键字修饰,如果函数覆盖成功则不会报错。
#include <iostream>
using namespace std;
class Animal
{
public:
virtual void eat()
{
cout << "吃东西" << endl;
}
virtual void speak();
};
void Animal::speak()
{
cout << "ww" << endl;
}
class Dog:public Animal
{
public:
void eat()
{
cout << "吃" << endl;
}
void speak() override
{
cout << "哼" << endl;
}
};
int main()
{
return 0;
}
三、实现
多态往往伴随着函数的调用和传参,基类引用/指针指向派生类对象通常出现在函数传参中。
nclude <iostream>
using namespace std;
class Animal
{
public:
virtual void eat()
{
cout << "吃东西" << endl;
}
virtual void speak();
};
void Animal::speak()
{
cout << "w'w" << endl;
}
class Dog:public Animal
{
public:
void eat()
{
cout << "吃" << endl;
}
void speak() override
{
cout << "哼" << endl;
}
};
class Cat:public Animal
{
public:
void eat()
{
cout << "吃鱼" << endl;
}
void speak()
{
cout << "害" << endl;
}
};
class Mouse:public Animal
{
public:
void eat()
{
cout << "吃老鼠药" << endl;
}
void speak()
{
cout << "吱吱吱" << endl;
}
};
/**
* @brief test_polymorphysism
* @param a 基类引用,传递栈内存
*/
void test_polymorphysism(Animal& a)
{
a.eat();
a.speak();
}
/**
* @brief test_polymorphysism
* @param a 基类指针,传递堆内存对象
*/
void test_polymorphysism(Animal* a)
{
a->eat();
a->speak();
}
int main()
{
Animal a;
Dog d;
Cat c;
Mouse m;
// 根据传入参数的类型不同,执行不同代码
test_polymorphysism(a);
test_polymorphysism(d);
test_polymorphysism(c);
test_polymorphysism(m);
Animal* a1 = new Animal;
Dog* d1 = new Dog;
Cat* c1 = new Cat;
Mouse* m1 = new Mouse;
// 根据传入参数的类型不同,执行不同代码
test_polymorphysism(a1);
test_polymorphysism(d1);
test_polymorphysism(c1);
test_polymorphysism(m1);
delete a1;
delete d1;
delete c1;
delete m1;
return 0;
}
四、原理
当使用多态时,上面代码中的Animal类对象会增加一个隐藏的成员指针,指向Animal类的虚函数表,虚函数表与之前的虚基类表类似,也是只有一份,属于Animal类持有,而非某个对象持有。
#include <iostream>
using namespace std;
class Animal
{
public:
virtual void eat()
{
cout << "吃东西" << endl;
}
virtual void speak();
};
void Animal::speak()
{
cout << "fgsjkfghklsj" << endl;
}
class Dog:public Animal
{
public:
void eat()
{
cout << "吃" << endl;
}
virtual void sleep()
{
cout << "呼呼呼" << endl;
}
};
class Cat:public Animal
{
public:
void eat()
{
cout << "吃鱼" << endl;
}
void speak()
{
cout << "害" << endl;
}
};
class Mouse:public Animal
{
public:
void eat()
{
cout << "吃药" << endl;
}
void speak()
{
cout << "吱吱吱" << endl;
}
};
/**
* @brief test_polymorphysism
* @param a 基类引用,传递栈内存
*/
void test_polymorphysism(Animal& a)
{
a.eat();
a.speak();
}
/**
* @brief test_polymorphysism
* @param a 基类指针,传递堆内存对象
*/
void test_polymorphysism(Animal* a)
{
a->eat();
a->speak();
}
int main()
{
Animal a;
Dog d;
Cat c;
Mouse m;
// 根据传入参数的类型不同,执行不同代码
test_polymorphysism(a);
test_polymorphysism(d);
test_polymorphysism(c);
test_polymorphysism(m);
Animal* a1 = new Animal;
Dog* d1 = new Dog;
Cat* c1 = new Cat;
Mouse* m1 = new Mouse;
// 根据传入参数的类型不同,执行不同代码
test_polymorphysism(a1);
test_polymorphysism(d1);
test_polymorphysism(c1);
test_polymorphysism(m1);
cout << sizeof(a) << endl; // 4 虚函数表指针
cout << sizeof(d) << endl; // 4 虚函数表指针,存在优化
// delete a1;
// delete d1;
// delete c1;
// delete m1;
return 0;
}
五、隐患
在使用多态时,可能会出现内存泄漏。
#include <iostream>
using namespace std;
class Animal
{
public:
~Animal()
{
cout << "Animal析构" << endl;
}
};
class Dog:public Animal
{
public:
~Dog()
{
cout << "Dog析构" << endl;
}
};
int main()
{
// 为了示例,强行触发多态
Animal* a = new Dog;
delete a; // 跳过了Dog的析构函数,可能出现内存泄漏
return 0;
}
解决的方法是给基类设置虚析构函数,让析构函数也被虚函数表管理。
如果一个类是基类,建议把析构函数设置虚析构函数,因为编译器自动添加的析构函数没有virtual修饰。
#include <iostream>
using namespace std;
class Animal
{
public:
virtual ~Animal()
{
cout << "Animal析构" << endl;
}
};
class Dog:public Animal
{
public:
~Dog()
{
cout << "Dog析构" << endl;
}
};
int main()
{
// 为了示例,强行触发多态
Animal* a = new Dog;
delete a; // 解决了内存泄漏的问题
return 0;
}