类与对象
多态
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=100
cout << "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=100
return 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;
}