多态简介
多态(Polymorphism)是一种允许不同类的对象对同一消息做出响应的能力,这个响应的确切行为取决于对象的实际类型。多态是面向对象编程的核心概念之一,它使得代码更加灵活和可扩展。C++中的多态主要有两种形式:编译时多态(也称为静态多态),编译阶段确定函数的地址;运行时多态(也称为动态多态),在运行阶段确定函数的地址。
静态多态(Static Polymorphism)
-
函数重载(Overloading):
- 允许在同一作用域内声明多个同名函数,只要它们的参数列表不同。
-
运算符重载(Operator Overloading):
- 允许为自定义类型重新定义运算符的行为。
-
模板(Templates):
- 允许创建泛型类和函数,它们可以在编译时根据给定的类型参数实例化。
动态多态(Dynamic Polymorphism)
-
虚函数(Virtual Functions):
- 当一个基类的成员函数被声明为虚函数时,派生类可以覆盖(Override)它。运行时,通过基类的指针或引用调用虚函数时,将调用对象实际类型的版本。
-
动态绑定(Dynamic Binding):
- 与虚函数一起工作,确保调用正确的函数版本,即使基类指针或引用指向的是派生类对象。
-
抽象类(Abstract Classes):
- 不能实例化的类,通常包含至少一个纯虚函数。它们用作接口或基类。
基本语法
实现动态多态:父类的引用指向子类的对象,实现地址晚绑定。
1.有继承
2.子类有重写父类的虚函数,与重载不同,重写完全一样,类型,名称,参数都一样
测试案例:
运行测试代码,发现传入的是cat,(传入的是父类的指针,指向子类)执行的却是父类中的函数。因为地址早绑定了父类的地址,若想执行子类中的执行函数,则需要在父类函数前加virtual即可,这样操作即可实现地址晚绑定。
//建立一个动物了类
class Animal
{
public:
void say()
{
cout << "动物在叫唤" << endl;
}
};
class Cat :public Animal
{
public:
void say()
{
cout << "猫在叫唤" << endl;
}
};
void dosay(Animal &animal)
{
animal.say();
}
int main()
{
//
// test();
Animal animal;
Cat cat;
dosay(cat);
system("pause");
return 0;
}
本质分析
int main()
{
class Animal
{
public:
void say()
{
cout << "动物在叫唤" << endl;
}
};
Animal animal;
cout <<" sizeof(animal) ==" << sizeof(animal) << endl;;
system("pause");
return 0;
}
查看anmial对象的内存大小可知,所占只有一个字节,因为这个类是个空类
随后在成员函数前加上virtual关键字后打印
void virtual say()
此时说明该类变成了一个指针,指向了内部虚函数表,表类记录着虚函数的地址。当子类重写父类中的虚函数,子类中的虚函数表内部会替换成子类的虚函数地址,父函数中的虚函数表并没有发生变化。
纯虚函数和抽象类
多态中,通常父类中的虚函数实现是没有意义的,主要是调用子类重写的内容
因此,可以将虚函数改写为纯虚函数
virtual 返回值类型 函数名(参数列表)=0;
当类中有了纯虚函数,这个类也称为抽象类
特点:
无法实例化对象
子类必须重写抽象类中的纯虚函数,否则也属于抽象类
代码示例:
//构建父类
class Base
{
public:
//纯虚函数
//只有有一个纯虚函数,此类成为抽象类
//1.无法示例化对象
//2.抽象类的子类必须重写父类中的纯虚函数,否则也属于抽象类,无法实例化对象
virtual void func() = 0;
};
class Son:public Base
{
public:
virtual void func()
{
cout << "func函数调用!" << endl;
};
};
void test()
{
//利用多态:父类指针指向子类对象
Base* base = new Son;
base->func();
}
虚析构与纯虚析构
解决:多态使用时,若子类中有属性开辟在堆区,那么父类指针在释放时无法调用到子类的析构代码,此时可以将父类中的析构函数改为虚析构与纯虚析构!
共性:
都可以解决父类指针释放子类的对象
都需要具体的函数实现
区别:
若为虚析构,则该类属于抽象类,无法实例化对象
虚析构:virtual ~类名(){}
纯虚析构: virtual ~类名()=0;
~类名()
{}
class Anmial
{
public:
Anmial()
{
cout << "ANMIAL构造函数!" << endl;
}
~Anmial()
{
cout << "ANMIAL析构函数!" << endl;
}
//纯虚函数
virtual void func() = 0;
};
class Cat:public Anmial
{
public:
Cat(string name)
{
cout << "CAT构造函数调用" << endl;
//创建在堆区返回一个指针
m_Name=new string(name);
}
//子类需要重写,否则视为抽象类
virtual void func()
{
cout <<*m_Name <<"小猫在说话" << endl;
}
~Cat()
{
if (m_Name != NULL)
{
cout << "CAT析构函数调用" << endl;
delete m_Name;
m_Name = NULL;
}
}
string* m_Name;
};
void test()
{
Anmial*animal = new Cat("TOM!");
animal->func();
delete animal;
}
运行结果:
由此可见,cat类的析构函数并没有被执行,是因为父类指针在析构的时候,不会调用子类中的析构函数,导致子类如果有堆区的属性,导致内存泄漏问题。
解决措施:利用虚析构解决父类指针释放子类对象时不干净的问题
将父类析构改为虚析构 virtual~Anmial()
纯虚析构需要函数声明和具体的函数实现
class Anmial
{
public:
Anmial()
{
cout << "ANMIAL构造函数!" << endl;
}
virtual~Anmial() = 0;
virtual void func() = 0;
};
Anmial::~Anmial()
{
cout << "Anmial纯虚析构函数" << endl;
}
虚析构与纯虚析构使用来解决通过父类指针释放子类对象的
若子类中没有堆区的数据,可以不写纯虚析构或者虚析构
拥有纯虚析构的函数的类属于抽象类,无法实例化对象