类与对象
多态
1. 简介
一个事物的多种形态,简称多态。
-
物的多态
同一个人在不同人面前,角色不同
如:
- 在父母面前
- 在对象面前
- 在朋友面前
- 在同事面前
-
事的多态
同一种事情,在不同情况下展现不同
如:
-
吃饭
- 中国人 筷子 熟食
- 美国人 刀叉 7分熟
- 印度人 手 咖喱饭
-
睡觉
- 中国人 床上
- 日本人 地上
平躺
侧卧
趴着
-
2. 上行与下行
2.1 上行
子类 转 父类
语法:
父类名 *父类对象指针 = 子类对象指针;
或
父类名& 父类对象名 = 子类对象;
注意:
无风险,无需强转。
2.2 下行
父类 转 子类
语法:
子类名 *子类对象指针 = (子类名 *)父类对象指针;
或
子类名& 子类对象 = (子类名&) 父类对象;
注意:
有风险,需强转。
2.3 示例
#include <iostream>
using namespace std;class Anim{
public:int a;Anim(){}Anim(int a):a(a){}
};class Dog:public Anim{
public:int d;Dog(){}Dog(int a, int d):Anim(a),d(d){}
};
class Cat:public Anim{
public:int c;
};int main(int argc, char *argv[])
{//上行//将子类对象的地址赋值给父类对象的引用或指针Dog d1(10, 100);Anim& a1 = d1;//赋值给指针,记得取地址,不然报错Anim* a2 = &d1;cout << "d1.d=" << d1.d << endl; //d1.d=100cout << "a1.a=" << a1.a << endl; //a1.a=10//子类转父类后, 父类对象不能使用子类所特有的属性
// cout << "a1.d=" << a1.d << endl; //报错//下行Dog d2 = (Dog &)a1;Dog *d3 = (Dog *)a2;cout << "d2.d=" << d2.d << endl; //d2.d=100//父类转换子类后,子类可以使用父类的cout << "d2.a=" << d2.a << endl; //d2.a=10//下行父转子Cat& c1 = (Cat &)a1;cout << "c1.a=" << c1.a << endl; //c1.a=10//d1的内存中没有c,所以c1.c 的值为 d1中d的值cout << "c1.c=" << c1.c << endl; //c1.c=100return 0;
}
分析:
- Dog继承了 Anim,所以d1对象中 有a 也有 d;
- 父转子,c1也指向 d1(可能会报错,编译器优化可能也不报错)
3. 重写
继承关系中,返回值类型相同,函数名形同,形参列表相同,函数体不同 。
重载
同一作用域下,函数名相同,形参列表不同
重定义
继承关系中,函数名相同即可。一旦重定义后,子类会将父类的函数覆盖掉
4. c++多态分类
多态分为:
-
物的多态(上行、下行)
-
事的多态(静态多态,动态多态)
4.1 静态多态(早绑定,静态联编)
概念: 在编译阶段 就确定函数的入口地址
又名: 静态联编,早绑定
如: 函数重载,运算符重载,重定义等
函数在代码区,函数名就是这个函数在代码区的地址,这个地址就是这个函数的地址
4.2 动态多态(晚绑定,动态联编)
概念: 在运行阶段确定程序入口地址
又名: 动态联编,晚绑定
如: 虚函数,重写
5. 引入
要求:设计一个函数,根据传入的对象调用重写的方式。
5.1 示例
需求:
小明开了一个宠物医院,可以给狗看病,可以给猫看病可以给猪看病张女士带着他家的狗旺财去找小明给狗看病李女士带着他家的猫布丁去找小明给猫看病王先生带着他家的猪佩奇去找小明给猪看病分析:对象小明张女士李女士王先生旺财布丁佩奇类动物类属性:姓名狗类猫类猪类人类属性:动物get与set宠物医生类看病
代码:
#include <iostream>
#include <cstring>
using namespace std;class Anim{
private:char name[50];
public:Anim(){}Anim(char *name){strcpy(this->name,name);}char* getName(){return name;}void setName(char *name){strcpy(this->name,name);}void call(){cout << "动物叫" << endl;}
};
class Dog:public Anim
{
public:Dog(){}Dog(char *name):Anim(name){}//重写父类call方法void call(){cout << this->getName() << ":汪汪汪" << endl;}
};
class Cat:public Anim
{
public:Cat(){}Cat(char *name):Anim(name){}//重写父类call方法void call(){cout << this->getName() << ":喵喵喵" << endl;}
};
class Pig:public Anim
{
public:Pig(){}Pig(char *name):Anim(name){}//重写父类call方法void call(){cout << this->getName() << ":哼哼哼" << endl;}
};
class Person:public Anim
{
private:Anim* anim;
public:Person(){anim = NULL;}Person(char *name):Anim(name){}Person(char *name,Anim *anim):Anim(name),anim(anim){}~Person(){if(anim != NULL){delete anim;anim = NULL;}}void setAnim(Anim* anim){this->anim = anim;}Anim* getAnim(){return anim;}//重写父类call方法void call(){cout << this->getName() << ":哇哇哇" << endl;}
};
class AnimDoctor:public Person{
public:AnimDoctor(){}AnimDoctor(char *name):Person(name){}AnimDoctor(char *name,Anim* anim):Person(name,anim){}void cb(Anim* anim){cout << this->getName() << "给" << anim->getName() << "看病" << endl;anim->call();}
};
int main(int argc, char *argv[])
{Dog * dog = new Dog("旺财");Cat * cat = new Cat("布丁");Pig * pig = new Pig("佩奇");Person *p1 = new Person("张女士",dog);Person *p2 = new Person("李女士",cat);Person *p3 = new Person("王先生",pig);AnimDoctor * doctor = new AnimDoctor("小明");doctor->cb(p1->getAnim());return 0;
}//小明给旺财看病
//动物叫
//小明给布丁看病
//动物叫
//小明给佩奇看病
//动物叫
5.2 问题
父类有个函数 void call(){}
,要求是子类继承并重写 父类 该方法,每个子类 动物在看病时,都会叫 “狗:汪汪汪,猫:喵喵喵…” ;
但是,此时现状是,每次打印出来的都是 父类 void call(){}
函数中 的 “动物叫”(子传父 上行
之后调用的依旧是父类的call函数),而我们需要的是 子传父之后,调用的是每个子类特有的方法。
所以需要引入 虚函数
。
6. 虚函数
概念:virtual
修饰的成员函数,就是虚函数
语法:
virtual 返回值类型 函数名(形参列表)
{函数体
}
注意:
- 子类在继承父类时,会生成
虚函数指针
对比如下:以上边动物看病为例
不是虚函数
虚函数
:下面第2幅图可以看出,子类继承了父类的name
和 虚函数指针vfptr
,此时指针指向的是 子类自己的 函数Dog::call
。
特点:
当
子类转换为父类
后:
使用该父类调用 使用virtual修饰的函数,调用的是子类重写后的函数
使用该父类调用普通函数,调用的是父类的该函数
6.1 上边示例修改
#include <iostream>
using namespace std;
#include <cstring>
class Anim{
private:char name[50];
public:Anim(){}Anim(char *name){strcpy(this->name,name);}char* getName(){return name;}void setName(char *name){strcpy(this->name,name);}virtual void call(){cout << "动物叫" << endl;}
};
class Dog:public Anim
{
public:Dog(){}Dog(char *name):Anim(name){}virtual void call(){cout << this->getName() << ":汪汪汪" << endl;}
};
class Cat:public Anim
{
public:Cat(){}Cat(char *name):Anim(name){}void call(){cout << this->getName() << ":喵喵喵" << endl;}
};
class Pig:public Anim
{
public:Pig(){}Pig(char *name):Anim(name){}void call(){cout << this->getName() << ":哼哼哼" << endl;}
};
class Person:public Anim
{
private:Anim* anim;
public:Person(){anim = NULL;}Person(char *name):Anim(name){}Person(char *name,Anim *anim):Anim(name),anim(anim){}~Person(){if(anim != NULL){delete anim;anim = NULL;}}void setAnim(Anim* anim){this->anim = anim;}Anim* getAnim(){return anim;}void call(){cout << this->getName() << ":哇哇哇" << endl;}
};
class AnimDoctor:public Person{
public:AnimDoctor(){}AnimDoctor(char *name):Person(name){}AnimDoctor(char *name,Anim* anim):Person(name,anim){}void cb(Anim* anim){cout << this->getName() << "给" << anim->getName() << "看病" << endl;anim->call();}
};
int main(int argc, char *argv[])
{Dog * dog = new Dog("旺财");Cat * cat = new Cat("布丁");Pig * pig = new Pig("佩奇");Person *p1 = new Person("张女士",dog);Person *p2 = new Person("李女士",cat);Person *p3 = new Person("王先生",pig);AnimDoctor * doctor = new AnimDoctor("小明");doctor->cb(p1->getAnim());doctor->cb(p2->getAnim());doctor->cb(p3->getAnim());/*当子类转换为父类后使用该父类调用使用virtual修饰的函数,调用的是子类重写后的函数使用该父类调用普通函数,调用的是父类的该函数*/// 虚函数所在的类,依据可以直接创建对象Anim a;return 0;
}
//小明给旺财看病
//旺财:汪汪汪
//小明给布丁看病
//布丁:喵喵喵
//小明给佩奇看病
//佩奇:哼哼哼
6.2 动态绑定的条件(重要)
有继承,子类重写父类的虚函数,父类指针或引用指向子类空间(上行)。父类指针或引用才能调用子类重写的虚函数。错误演示:B b;//此时会调用父类的拷贝构造,会产生一个新的父类对象,该 父类对象a 与 子类对象b 是两个独立空间//所以此时使用a对象调用test01依据会执行父类的test01函数A a = b; //拷贝构造a.test01();
6.3 动态绑定原理(机制)(重要)
父类有虚函数
,产生的虚函数指针
指向虚函数表
,表中记录的是父类的虚函数地址
。- 如果子类
继承
父类,那么子类会继承父类的虚函数指针以及虚函数表。 - 如果子类
重写父类的虚函数
,会将将虚函数表纪录的入口地址修改成子类重写的函数入口地址。 - 这时
父类指针指向子类空间
,父类指针调用虚函数就间接
调用子类重写的虚函数。
7. 纯虚函数
概念:父类的虚函数没有函数体
语法:
virtual 返回值类型 函数名(形参列表) = 0;
注意:
- 纯虚函数所在的类不能 直接 创建对象,这种类被称为抽象类
- 子类继承与抽象类,要么重写父类提供的所有纯虚函数,要么自己也是抽象类
示例:
#include <iostream>
#include <cstring>
using namespace std;
//纯虚函数所在的类称为抽象类
/*特点:* 1,抽象类不能直接创建对象* 2,子类继承与抽象类,要么重写所有纯虚函数,要么自己也是抽象类*/
class Anim{
private:char name[50];
public:Anim(){}Anim(char *name){strcpy(this->name,name);}char* getName(){return name;}void setName(char *name){strcpy(this->name,name);}//纯虚函数virtual void call() = 0;virtual void sleep() = 0;
};
class Dog:public Anim{
public:Dog(){}Dog(char *name):Anim(name){}void call(){cout << "汪汪汪" << endl;}
};
class Cat:public Anim{
public:Cat(){}Cat(char *name):Anim(name){}void call(){cout << "喵喵喵" << endl;}void sleep(){cout << "在猫窝睡" << endl;}
};
int main(int argc, char *argv[])
{//有纯虚函数所在的类为抽象类,抽象类不能直接创建对象//Anim anim//子类继承与抽象类,要么重写所有纯虚函数,要么自己也是抽象类//Dog类只重写了call纯虚函数,但是没有重写sleep纯虚函数//所以Dog类也是抽象类,所以不能直接创建对象//Dog dog;//Cat类重写了Anim类所有的纯虚函数,所以Cat类不是抽象类Cat cat;Anim& anim = cat;cout << "Hello World!" << endl;return 0;
}
8. 虚析构造
8.1 问题引入
#include <iostream>using namespace std;
class Anim{
public:Anim(){cout << "父类构造函数" << endl;}~Anim(){cout << "父类析构函数" << endl;}
};class Dog:public Anim{
public:Dog(){cout << "子类构造函数" << endl;}~Dog(){cout << "子类析构函数" << endl;}
};
int main(int argc, char *argv[])
{Dog *dog = new Dog();Anim *anim = dog;delete anim;return 0;
}
//父类构造函数
//子类构造函数
//父类析构函数
问题:没有调用 子类析构函数
8.2 解决方案
将父类的析构函数 设置成 虚析构
虚析构函数是为了解决:基类的指针指向派生类对象,并用基类的指针删除派生类对象。
语法:
virtual ~析构函数()
{}
示例:
#include <iostream>using namespace std;
class Anim{
public:Anim(){cout << "父类构造函数" << endl;}//虚析构virtual ~Anim(){cout << "父类析构函数" << endl;}
};class Dog:public Anim{
public:Dog(){cout << "子类构造函数" << endl;}~Dog(){cout << "子类析构函数" << endl;}
};
int main(int argc, char *argv[])
{Dog *dog = new Dog();Anim *anim = dog;delete anim;return 0;
}
//父类构造函数
//子类构造函数
//子类析构函数
//父类析构函数
9. 纯虚析构(了解)
效果等同于 虚析构
语法:
virtual 析构函数名() = 0;
注意:需要在类外实现析构函数
示例:
#include <iostream>using namespace std;
//class Anim{
//public:
// Anim()
// {
// cout << "父类构造函数" << endl;
// }
// //虚析构
// virtual ~Anim(){
// cout << "父类析构函数" << endl;
// }
//};
class Anim{
public:Anim(){cout << "父类构造函数" << endl;}//纯虚析构//类中定义virtual ~Anim() = 0;
};
//类外实现
Anim::~Anim()
{cout << "父类析构函数" << endl;
}
class Dog:public Anim{
public:Dog(){cout << "子类构造函数" << endl;}~Dog(){cout << "子类析构函数" << endl;}
};
int main(int argc, char *argv[])
{Dog *dog = new Dog();Anim *anim = dog;delete anim;return 0;
}
virtual 析构函数名() = 0;
注意:需要在类外实现析构函数
示例:
#include <iostream>using namespace std;
//class Anim{
//public:
// Anim()
// {
// cout << "父类构造函数" << endl;
// }
// //虚析构
// virtual ~Anim(){
// cout << "父类析构函数" << endl;
// }
//};
class Anim{
public:Anim(){cout << "父类构造函数" << endl;}//纯虚析构//类中定义virtual ~Anim() = 0;
};
//类外实现
Anim::~Anim()
{cout << "父类析构函数" << endl;
}
class Dog:public Anim{
public:Dog(){cout << "子类构造函数" << endl;}~Dog(){cout << "子类析构函数" << endl;}
};
int main(int argc, char *argv[])
{Dog *dog = new Dog();Anim *anim = dog;delete anim;return 0;
}