一、多态:同样的调用语句有多种不同的表现形式。
通俗的说就是根据传入对象类型的不同,调用不同的派生类的相应函数
多态的实现条件:
1、要存在继承关系
2、对虚函数的重写
3、基类指针 (引用) 指向派生类对象
二、静态联编与动态联编
静态联编:程序匹配,连接在编译阶段实现,也称为早期联编(在编译的时候,就知道了该去调用谁)
例如:函数重载
动态联编:程序联编推迟到运行时进行,也称为晚期联编(在执行时,才会知道调用哪一个函数)
例如:switch 和 if 语句
#include <iostream>
using namespace std;
// 动物: 基类
class Animal
{
public:
Animal(int age, char *name)
{
this->age = age;
this->name = name;
}
virtual void sleep()
{
printf ("动物睡觉\n");
}
void print()
{
printf ("age = %d, name = %s\n", age, name);
}
private:
int age;
char *name;
};
class Cat:public Animal
{
public:
Cat(int age, char *name):Animal(age, name)
{
}
// 函数重定义
void sleep()
{
printf ("猫 趴着睡觉\n");
}
};
class Fish:public Animal
{
public:
Fish(int age, char *name):Animal(age, name)
{
}
void sleep()
{
printf ("鱼 睁着眼睡觉\n");
}
};
void func(Animal *p)
{
// 1、p 是一个Animal 类型指针
// 2、print 是一个普通的成员函数
// 3、结论:调用 Animal 的 print 函数
// 编译阶段完成 ----> 静态联编 ----> 早期联编
p->print();
}
// 问题:
// 根据传入的对象类型的不同,调用不同派生类的相应函数
// 函数根据指针类型,只能调用该类型的函数,也就是只能调用基类的函数
// 期望的结果:根据传入的对象类型的不同,调用不同派生类的相应函数
// 解决的办法:虚函数,在基类函数之前 加上 virtual 关键字,将该函数变为虚函数
// 则当基类指针调用 虚函数的时候 会根据对象的不同调用不同的函数
void func1(Animal *p)
{
// 1、p 是一个Animal 类型指针
// 2、sleep 是一个 虚函数
// 3、结论:调用 谁的sleep? 因为不知道 p 的类型,所以不知道调用哪个 sleep
// 何时确定调用谁? ------> 运行的时候 -----> 动态联编 晚期联编 迟绑定
p->sleep(); // 多态
}
// 多态 实现条件:
// 1、继承
// 2、虚函数
// 3、父类指针指向子类对象
int main2_1()
{
Animal *pa = new Animal(2, "动物");
Cat *pc = new Cat(3, "猫");
Fish *pf = new Fish(2, "鱼");
//func(pa); 动物睡觉
//func(pc); 动物睡觉
//func(pf); 动物睡觉
func1(pa); 动物睡觉
func1(pc); 猫 趴着睡觉
func1(pf); 鱼 睁着眼睡觉
return 0;
}
分析:不是虚函数之前,func的形参为 Animal 类的指针,基类指针指向派生类对象,基类指针
的本质依旧是 Animal* ,因此在编译的时候就明确了,该调用哪个函数,这是个静态联编。
而加了 virtual 来修饰基类的成员函数,以此来实现多态,成为虚函数之后,再通过基类指针
去调用函数时,会根据传入对象的不同,找到相应的函数来执行。
二、虚析构函数
通过基类指针释放派生类对象,基类的析构函数一定是虚析构函数
构造函数:从当前类往上找 父类 => 最上层的父类,从最上层的父类开始构造
(调用构造函数),类似于前序递归
析构函数:从当前类开始析构 析构完 沿着继承路径往上找父类 析构父类 => 找到
最上层的父类析构,类似于后序递归
#include <iostream>
using namespace std;
// 基类
class A
{
public:
A()
{
printf ("A 的构造函数\n");
}
~A()
{
printf ("A 的析构函数\n");
}
};
class B: public A
{
public:
B()
{
p = new char[20];
printf ("B 的构造函数\n");
}
~B()
{
if (p != NULL)
delete[] p;
printf ("B 的析构函数\n");
}
private:
char *p;
};
// abcdefg
void printS(char *p)
{
if (*p == '\0')
return ;
printf ("%c", *p);
printS(p+1); // 递归
//printf ("%c", *p);
}
// 先递归 后执行
// 先执行 后递归
// 构造 从当前类网上找 父类 ------> 最上层的父类, 从最上层的父类开始构造(调用构造函数) 前序递归
// 析构 从当前类开始析构 析构完 沿着继承路径网上找父类 析构父类 -----> 找到最上层的父类 析构 后序递归
void func(A *pa)
{
// 指针是 A 所以认为析构的是A 对象
// 通过基类指针释放派生类对象, 基类的析构函数一定要是 虚析构函数
delete pa;
}
int main()
{
//A *pa = new A;
//func(pa);
A *pa = new B;
func(pa);
return 0;
}
// 运行结果:
// A的构造函数
// B的构造函数
// A的析构函数
// 没有B的析构函数,会造成内存的泄漏
原因:A *pa = new B 定义了一个B类的对象,会先调用A类的构造函数,再调用B类的构造函数,再调用 func() 函数,释放的是基类的指针
被认为析构的是基类的对象,所以要想基类指针释放派生类对象,基类的析构函数一定要是虚析构函数
三、多态的原理
多态通过一个虚函数指针 (vfptr) 来实现,这个虚函数指针指向一个虚函数表,这张表里存放了该类对应的虚函数。
基类有虚函数,基类的虚函数指针就会指向一张虚函数表,这张表由编译器保管。
派生类中有与基类虚函数同名的函数也是虚函数,但与基类有所不同的是,派生类中的虚函数指针不在指向基类中
的虚函数表,而是指向自己的虚函数表
因此,当调用函数时,编译器会根据对象的虚函数指针找到对应的虚函数表,再找到虚函数。
虚函数在编译的时候,并不知道谁在调用,在运行的时候才会确定,因此是动态联编,效率
会低于一般的函数
四、虚函数和虚继承
1、基类有虚函数,派生类没有虚函数,普通继承
vfptr -> AA ::print();
a
b
2、基类有虚函数,派生类没有虚函数,虚继承
vbptr -> 当前对象指针 虚基类指针
b
vfptr -> 基类指针
a
3、基类没有虚函数,派生类有虚函数,普通继承
vfptr -> BB::show()
a
b
4、基类没有虚函数,派生类中有虚函数,虚继承
vfptr -> 当前对象指针
vbptr
b
a
5、基类有虚函数,派生类中有虚函数,普通继承
派生类中的虚函数 与 基类中的虚函数 不同名时
vfptr
a
b
派生类中的虚函数 与 基类中的虚函数 同名时
对象 b
vfptr -> BB::print() BB ::show()
a
b
6、基类有虚函数,派生类中有虚函数,虚继承时
派生类中的虚函数 与 基类中的虚函数 不同名时
vfptr -> 当前对象指针 -> BB::show() AA::print()
vbptr
b
vfptr -> 基类指针 -> AA::print()
a
派生类中的虚函数 与 基类中的虚函数 同名时
vfptr -> 当前对象指针 BB::print() BB::show() AA::print()
vbptr
b
00 00 00 00
vfptr
a
五、构造函数中的虚函数调用
虚函数指针是分步初始化的
构造的顺序是先构造父类再构造子类
当调用父类的构造函数时,虚函数指针vfptr指向父类的虚函数表
当父类构造完,调用子类的构造函数的时候,虚函数指针vfptr指向子类的虚函数表
结论:构造函数中无法实现多态
六、用基类操作派生类数组
一般来讲,基类和派生类中的成员参数是不一样的
导致基类指针和派生类指针的步长不一致,所以不要
用基类指针操作派生类数组
七、纯虚函数和抽象类
纯虚函数:虚函数,只有声明,不需要实现,函数体部分改成 = 0
抽象类:拥有纯虚函数的类叫抽象类
抽象类不能实例化对象,但可以定义抽象类指针,用来操作派生类对象
派生类必须实现抽象类的所有 纯虚函数, 如果不实现,则派生类是一个抽象类
#include <iostream>
using namespace std;
// 图形的基类
// 抽象类:拥有纯虚函数的类叫 抽象类
// 抽象类不能实例化对象,但可以定义 抽象类指针, 用来操作派生类对象
// 派生类必须实现抽象类的所有 纯虚函数, 如果不实现, 则派生类将是一个抽象类
class Shape
{
public:
// 纯虚函数:虚函数,只有声明,不需要实现 函数体部分改成 = 0;
virtual double getS() = 0;
};
class Circle:public Shape
{
public:
Circle(int r)
{
this->r = r;
}
virtual double getS()
{
return r*r*3.14;
}
private:
int r;
};
class Rectangle :public Shape
{
public:
Rectangle(int a = 0, int b = 0)
{
this->a = a;
this->b = b;
}
private:
int a;
int b;
};
void func (Shape *p)
{
printf ("s = %.2f\n", p->getS());
}
int main()
{
// Shape s;
Rectangle r;
Circle c(2);
func(&c);
return 0;
}
八、抽象类接口
#include <iostream>
using namespace std;
// 加法接口
class IAdd
{
public:
virtual void add() = 0;
virtual void printResult() = 0;
};
// 乘法接口
class IMul
{
public:
virtual void mul() = 0;
virtual void printResult() = 0;
};
class A:public IAdd, public IMul
{
public:
A(int a = 0, int b = 0)
{
this->a = a;
this->b = b;
}
virtual void add()
{
res = a + b;
}
virtual void mul()
{
res = a * b;
}
virtual void printResult()
{
printf ("res = %d\n", res);
}
private:
int a;
int b;
int res;
};
void add(IAdd *p)
{
p->add();
p->printResult();
}
void mul(IMul *p)
{
p->mul();
p->printResult();
}
int main()
{
//A b(2, 3);
//b.add();
//b.printResult();
//b.mul();
//b.printResult();
A *pb = new A(2,3);
add(pb);
mul(pb);
return 0;
}