多态是C++面向对象的三大特性之一,多态是指同一种行为对应不同的实现,即不同子类创建的对象在执行父类的同一成员方法时有不同的表现形式。采用多态技术的程序具有代码组织结构清晰、可读性强、利于前期和后期的扩展以及维护等优点。
一、多态的概念
1.多态的分类
多态根据函数地址的绑定时机可分为两类,静态多态和动态多态。函数重载和运算符重载等复用函数名的方式就属于静态多态,在编译阶段确定函数地址;动态多态即通过虚函数实现运行时多态,在运行阶段才能确定函数地址,C++在类和对象中所用到的多态技术即是指动态多态 。
程序执行时,函数的地址都会在编译阶段就确定完毕,但C++引入了关键字virtual,当在函数前添加关键字virtual后,该函数称为虚函数,虚函数的地址在运行阶段才会确定。
2.多态的使用
在实际开发中,提倡“开闭原则”,即对源码扩展进行开放、对源码修改进行关闭,体现在代码编写中就是不对已有的父类和子类进行修改,而是通过增加子类以实现扩展与维护,而这就需要用到多态技术。
使用多态技术有两个前提,一是需要建立继承关系,二是需要子类重写父类中的虚函数。重写虚函数,即令父类中虚函数和子类中相应函数的返回值类型、函数名以及参数列表完全一致。
使用多态的方式则较为固定,一般是令实现行为的函数将父类的引用或指针作为函数形参,当调用该函数时,可以将子类所创建的对象作为实参传递给它。
class Animal
{
public:
//函数action()的地址会在编译阶段就确定完毕
void action()
{
cout << "动物行动" << endl;
}
//C++引入了关键字virtual,当在函数speak()前添加关键字virtual后,函数speak()称为虚函数
//虚函数speak()的地址在运行阶段才会确定
virtual void speak()
{
cout << "动物鸣叫" << endl;
}
};
//多态的使用前提:
//建立继承关系,子类为Rat、Ounce,父类为Animal
class Rat :public Animal
{
public:
void action()
{
cout << "老鼠钻下水道" << endl;
}
//多态的使用前提:
//子类Rat重写父类Animal中的虚函数speak()
void speak()
{
cout << "老鼠:吱~吱~吱~~~" << endl;
}
};
class Ounce :public Animal
{
public:
void action()
{
cout << "雪豹下山捕猎" << endl;
}
//多态的使用前提:
//子类Ounce重写父类Animal中的虚函数speak()
void speak()
{
cout << "雪豹闭嘴!!!" << endl;
}
};
void DoAction(Animal& animal)
{
//函数action()的地址会在编译阶段就确定完毕
//因此无论接收到的实参对象由哪个子类创建,都会执行父类Animal创建的对象animal的成员方法action()
animal.action();
}
//多态的使用方式:
//令实现行为的函数DoSpeak()会将父类Animal的引用或指针作为函数形参
void DoSpeak(Animal& animal)
{
//虚函数speak()的地址在运行阶段才会确定
//因此会根据接收到的实参对象由哪个子类创建,选择执行相应子类创建的对象的成员方法speak()
animal.speak();
}
void test()
{
Rat rat;
Ounce ounce;
DoAction(rat);
DoAction(ounce);
cout << endl << "-------------------------------------" << endl << endl;
//多态的使用方式:
//当调用函数DoSpeak()时,可以将子类Rat、Ounce所创建的对象rat、ounce作为实参传递给函数DoSpeak()
DoSpeak(rat);
DoSpeak(ounce);
}
int main()
{
test();
return 0;
}
3.多态的本质
利用VS提供的开发人员命令提示符查看各个类的内部结构。虚函数表指针vfptr中,v为virtual,f为function,ptr为pointer;虚函数表中,v为virtual,f为function。
若使用多态技术,父类Animal创建对象时,会创建一个虚函数表指针vfptr指向虚函数表vftable,当vfptr加上vftable中的偏移量后,指向的即是父类Animal的成员方法speak()的具体内容。
在建立继承关系且子类重写父类中的虚函数后,当子类Rat、Ounce创建对象时,会创建属于自身的新虚函数表指针vbptr和新虚函数表vftable,当两个对象的vfptr分别加上自身vftable中的偏移量后,指向的分别是子类自身的成员方法speak()的具体内容。
当父类Animal的引用或指针指向子类Rat、Ounce的对象rat、ounce时,实际执行的就是对象rat、ounce中重写的内容各异的虚函数,这就是所谓同一种行为对应不同的实现。
二、多态的优化使用
1.纯虚函数和抽象类
使用多态技术时,通常父类中虚函数的内容是无意义的,主要都是执行子类重写的内容各异的虚函数,因此可以将虚函数改为纯虚函数。类中只要有一个纯虚函数,即被称为抽象类。
抽象类有两个特点,一是无法通过抽象类创建对象,二是子类必须重写抽象类中的纯虚函数,否则也属于抽象类。
//类中只要有一个纯虚函数,即被称为抽象类。
//理塘动物
class LiTangAnimal
{
public:
//使用多态技术时,通常父类中虚函数的内容是无意义的,主要都是执行子类重写的内容各异的虚函数,因此可以将虚函数改为纯虚函数
//出生
virtual void Born() = 0;
//命名
virtual void Name() = 0;
//任务
virtual void Action() = 0;
//死亡
virtual void Die() = 0;
//理塘动物的一生
void Life()
{
Born();
Name();
Action();
Die();
}
};
//小马
class Horse : public LiTangAnimal
{
public:
virtual void Born()
{
cout << "小马出生在理塘!" << endl;
}
virtual void Name()
{
cout << "将小马命名为纠纠!" << endl;
}
virtual void Action()
{
cout << "寻找瑞克五!" << endl;
}
virtual void Die()
{
cout << "纠纠小马走了捏!" << endl;
}
};
//雪豹
class Ounce : public LiTangAnimal
{
public:
virtual void Born()
{
cout << "雪豹出生在理塘!" << endl;
}
virtual void Name()
{
cout << "将雪豹命名为芝士!" << endl;
}
virtual void Action()
{
cout << "闭嘴!" << endl;
}
virtual void Die()
{
cout << "芝士雪豹走了捏!" << endl;
}
};
//猞猁
class Lynx : public LiTangAnimal
{
};
//描述理塘动物的一生
void DescribeLife(LiTangAnimal* animal)
{
animal->Life();
cout << "—————————————————" << endl;
delete animal;
}
void test()
{
//无法通过抽象类创建对象
//LiTangAnimal Dingzhen;
//子类必须重写抽象类中的纯虚函数,否则也属于抽象类。
//Lynx ErEr;
DescribeLife(new Horse);
DescribeLife(new Ounce);
}
int main()
{
test();
return 0;
}
2.虚析构和纯虚函数
使用多态技术时,由于指向子类对象的是父类的引用或指针,如果子类对象中有成员属性开辟到堆区,那么使用delete命令释放父类引用或指针指向的堆区空间时无法调用到子类对象的析构函数,这会导致内存泄漏,将父类中的析构函数改为虚析构函数和纯虚析构函数可以解决此问题。
虚析构函数和纯虚析构函数都可以实现释放父类指针时释放子类对象,且都必须有具体的函数实现。二者区别在于,如果是父类内使用纯虚析构函数,该类即为抽象类,无法通过它创建对象。
//如果是父类内使用纯虚析构函数,该类即为抽象类,无法通过它创建对象
class Animal
{
public:
Animal()
{
cout << "Animal 构造函数调用" << endl;
}
virtual void Lose_8000() = 0;
//虚析构和纯虚析构都可以实现释放父类指针时释放子类对象
//虚析构函数,即令析构函数加上关键字virtual,必须有具体的函数实现
//virtual ~Animal()
//{
// cout << "Animal 虚析构函数调用" << endl;
//}
//纯虚析构函数,即令析构函数加上关键字virtual且类内仅声明其为0
virtual ~Animal() = 0;
};
//纯虚析构函数必须有具体的函数实现
Animal::~Animal()
{
cout << "Animal 纯虚析构函数调用" << endl;
}
class Rat : public Animal
{
public:
string* Name;
Rat(string name)
{
cout << "Rat 构造函数调用" << endl;
Name = new string(name);
}
virtual void Lose_8000()
{
cout << endl << *Name << "于双流机场 -8000" << endl << endl;
}
~Rat()
{
cout << "Rat 析构函数调用" << endl;
if (this->Name != NULL)
{
delete Name;
Name = NULL;
}
}
};
void test()
{
Animal* animal = new Rat("Xiaochuan_Sun");
animal->Lose_8000();
//使用多态技术时,由于指向子类对象的是父类的引用或指针,如果子类对象中有成员属性开辟到堆区
//那么使用delete命令释放父类引用或指针指向的堆区空间时无法调用到子类对象的析构函数
//这会导致内存泄漏,将父类中的析构函数改为虚析构和纯虚析构可以解决此问题
delete animal;
}
int main()
{
test();
return 0;
}