前言

一个人,可以说他是只猴,也可以说他是哺乳动物,也可以说他是动物。但本质上就是个人。


正文


对象多态

在前言之中,人可以推导是动物,但是动物不能百分百推断为人。

  1. 向上转型 父类=>子类
  2. 向下转型 子类=>父类
1
2
3
4
5
6
7
8
9
10
class animal{
public:
int age;
int sex;
};

class pople :public animal{
public:
int money;
};

可以看到这样转型编译器没有报错,但是实际上还是会发生内存切片的问题,毕竟子类继承之后多了属性,传给父类,就有一个地方无法表达。

编译器会给出提示。

内存切片,也就是我们此处,pople是有一个成员的,当由pople继承animal的类,然后实例化,那么其实已经有了三个成员,而animal本身就只有两个,所以用pople实例化的对象传给animal的时候那个多余的成员就很有可能会被抛弃掉。变量还能看得到,函数有的时候就不好说了。

像这样过不去的基本原因就是pople有三个成员内存多一块,而animal只有两块,所以不存在合适和构造函数去转换。

当然因为这种本身就不合理,比价合理的是通过指针或者引用去传值。
以前面的为例:

这样就不存在什么内存切片的问题了。

1
2
animal anm{};
pople *pp = (pople*)&anm;

回到向下转型的问题,虽然强制转换anm为pople类型的指针,但是于本意上,更多的时候不会为了操作anm的内存而改变,可能是需要执行它的函数。


方法多态

静态多态

  1. 函数重载
  2. 函数模板
1
2
3
4
5
6
7
void eat(animal &anm){

}

void eat(pople &pople){

}

像这种写在外部的,依靠传入参数区分的就是我们的函数重载。

1
2
3
4
template<typename T>
void Eat(const T &x){

}

像函数模板感觉还得在里面判断。


动态的像写在类中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class animal{
public:
int age;
int sex;
void eat(){

}
};

class pople{
public:
int money;
void eat(){

}
};

当两个类不想关的时候,调用的eat就是各管各的。不用搞什么特殊。

可当继承的时候,函数也一并被继承,之前说过要么作用域要么using。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class animal{
public:
int age;
int sex;
void eat(animal *ani){

}
};

class pople :public animal{
public:
int money;
void eat(pople *pop){

}
};

往函数里加形参固然是解决的问题之一,但从理解的角度则不是很好。
首先就是他是类的方法,本身就需要类去调用不说,你还要往里面传指针。

如果依照自上向下转型,制作一个基类的指针,然后if判断某个值,让这个指针指向派生类去做这个操作。
同样也是因为结果有了可变性不是固定死的,这种就被称为动态多态。


除了虚基类,还有虚函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class animal{
public:
int age;
int sex;
void virtual eat(animal *ani){

}
};

class pople :public animal{
public:
int money;
void virtual eat(pople *pop){

}
};

具体放后面讲


结语

next