简介
一个事物的多种形态 , 简称多态
物的多态
同一个人在不同人面前展现是不同的
如 :
在我面前
在对象面前
在朋友面前
在父母面前
事的多态
吃饭
中国人 筷子 熟食
美国人 刀叉 7 分熟
日本人 筷子 生食
印度人 手
睡觉
中国人 床上
日本人 榻榻米
侧卧
平躺
趴着
上行与下行
上行
子类转父类
语法
父类名 * 父类对象指针 = 子类对象指针 ;
父类名& 父类对象名 = 子类对象 ;
注意 :
无风险, 无需强转
下行
父类转换为子类
语法
子类名 * 子类对象指针 = ( 子类名 *) 父类对象指针 ;
子类名& 子类对象 = ( 子类名 &) 父类对象 ;
注意 :
有风险, 需强转
重写
重载
同一作用域下, 函数名形同相同 ,形参列表不同
重定义
函数继承时,继承关系中,函数名相同即可
重写
继承关系中,返回值类型相同,函数名形同,形参列表相同,函数体不同
c++多态分类
静态多态 ( 早绑定 , 静态联编 )
概念:在编译阶段 就确定函数的入口地址.
又名: 静态联编 , 早绑定
如: 函数重载 , 运算符重载 , 重定义等
动态多态 ( 晚绑定 , 动态联编 )
概念: 在运行阶段确定程序入口地址
又名: 动态联编 , 晚绑定
如: 虚函数 , 重写
引入
要求 : 设计一个函数 , 根据传入的对象调用重写的方式
代码
#include <iostream>
using namespace std;
class Anim{
public:void test01(){cout << "动物test01" << endl;}
};
class Dog:public Anim{
public:void test01(){cout << "狗类test01" << endl;}
};
class Cat:public Anim{
public:void test01(){cout << "猫类test01" << endl;}
};
void method01(Anim& a)
{
a.test01();
}
int main(int argc, char *argv[])
{Dog d;Cat c;method01(d);method01(c);return 0;
}
运行结果
原因
父类指针只能操作子类空间中的父类部分。
虚函数
概念
virtual修饰的成员函数 , 就是虚函数
语法
virtual 返回值类型 函数名 ( 形参列表 )
{
方法体
}
特点
当子类转换为父类后
使用该父类调用使用 virtual 修饰的函数 , 调用的是子类重写后的
使用该父类调用普通函数 , 调用的是父类的该函数
补充概念
重写 : 在继承关系中 , 子类方法与父类方法 , 返回值类型一致 , 函数名相同 , 形参列表相同
注意 : 子类重写父类虚函数 , 子类该函数默认为虚函数
代码
#include <iostream>
using namespace std;
class Anim{
public:virtual void test01(){cout << "动物test01" << endl;}
};
class Dog:public Anim{
public:void test01(){cout << "狗类test01" << endl;}
};
class Cat:public Anim{
public:void test01(){cout << "猫类test01" << endl;}
};
void method01(Anim& a)
{a.test01();
}
int main(int argc, char *argv[])
{Dog d;Cat c;method01(d);method01(c);return 0;
}
动态绑定条件(重要)
有继承,子类重写父类的虚函数,父类指针或引用指向子类空间。父类指针或引用才能调用子类重写的虚函数。
错误演示
B b;
//此时会调用父类的拷贝构造 , 会产生一个新的父类对象 , 该父类对象 a 与子类对象b是两个独立空间 ,所以此时使用a 对象调用 test01 依据会执行父类的 test01 函数
//A a = b;
A& a = b;
a.test01();
动态绑定原理(重要)
父类有虚函数,产生虚函数指针指向虚函数表,表中纪录的是父类的虚函数地址。
如果子类继承父类,那么子类会继承父类的虚函数指针以及虚函数表。
如果子类重写父类的虚函数,会将将虚函数表纪录的入口地址修改成子类重写的函数入口地址。
这时父类指针指向子类空间,父类指针调用虚函数就间接调用子类重写的虚函数。
纯虚函数
概念
父类的虚函数没有函数体
语法
virtual 返回值类型 函数名(形参列表) = 0;
示例
#include <iostream>
using namespace std;
class Anim{
public:
//纯虚函数virtual void test01() = 0;
};
class Dog:public Anim{
public:void test01(){cout << "狗类test01" << endl;}
};
class Cat:public Anim{
public:void test01(){cout << "猫类test01" << endl;}
};
void method01(Anim& a)
{a.test01();
}
int main(int argc, char *argv[])
{Dog d;Cat c;method01(d);method01(c);return 0;
}
注意
纯虚函数所在的类不能直接创建对象 , 这种类被称为抽象类
子类继承与抽象类 , 要么重写父类提供的所有纯虚函数 , 要么自己也是抽象类
虚析函数
引入
#include <iostream>
using namespace std;
class Anim{
public:virtual void test01() = 0;Anim(){cout << "父类构造" << endl;}~Anim(){cout << "父类析构" << endl;}
};
class Dog:public Anim{
public:void test01(){cout << "狗类test01" << endl;}Dog(){cout << "子类Dog构造" << endl;}~Dog(){cout << "子类Dog析构" << endl;}
};
int main(int argc, char *argv[])
{Dog *d = new Dog();Anim *a = d;delete a;return 0;
}
结果
问题: 没有子类析构函数调用
解决方案: 将父类的析构函数 设置成 虚析构
语法
virtual ~ 析构函数 ()
{
}
代码
#include <iostream>
using namespace std;
class Anim{
public:virtual void test01() = 0;Anim(){cout << "父类构造" << endl;}virtual ~Anim(){cout << "父类析构" << endl;}
};
class Dog:public Anim{
public:void test01(){cout << "狗类test01" << endl;}Dog(){cout << "子类Dog构造" << endl;}~Dog(){cout << "子类Dog析构" << endl;}
};
int main(int argc, char *argv[])
{Dog *d = new Dog();Anim *a = d;delete a;return 0;
}
结果:
纯虚析构(了解)
语法
virtual 析构函数名 () = 0;
注意
需要在类外实现析构函数
如
#include <iostream>
using namespace std;
class Anim{
public:virtual void test01() = 0;Anim(){cout << "父类构造" << endl;}virtual ~Anim() = 0;
};
Anim::~Anim()
{cout << "父类析构" << endl;
}
class Dog:public Anim{
public:void test01(){cout << "狗类test01" << endl;}Dog(){cout << "子类Dog构造" << endl;}~Dog(){cout << "子类Dog析构" << endl;}
};
int main(int argc, char *argv[])
{Dog *d = new Dog();Anim *a = d;delete a;return 0;
}
结果
总结
1、虚函数和纯虚函数的区别?
虚函数和纯虚函数都是为了通过父类指针调用子类重写的虚函数。
虚函数 不=0 修饰 , 有函数体 , 所在的类不是抽象类 , 可以实例化对象。
纯虚函数 =0 修饰 , 没有函数体 , 所在的类是抽象类 , 不可以实例化对象。
2、虚析构和纯虚析构的区别
虚析构和纯虚析构都是为了通过父类指针释放子类的所有空间( 父类部分 , 子类部分 )
虚析构不=0 修饰 , 所在的类不是抽象类 , 可以实例化对象。
纯虚析构=0 修饰 , 类外实现函数体 , 所在的类是抽象类 , 不可以实例化对象。
3、重载、重定义、重写的区别
重载:没有继承,函数重载和运算符重载。函数名相同,参数的个数,顺序、类型可以不同,返回值类型不能作为重载条件。
重定义:有继承,子类重定义父类同名函数(非虚函数)。返回值类型,形参可以相同 和 不同。
重写:有继承,子类重写父类的虚函数。函数名,返回值类型,形参必须和父类同名函数一致。