目录
多态
动态多态的产生条件
1.先有继承关系
2.父类中有虚函数 子类重写父类的虚函数
3.父类的指针或引用指向子类的对象
多态原理
1.当父类写了虚函数后,类内部结构发生改变,多了一个vfptr
2.vfptr 虚函数表指针 --->vftable 虚函数表
3.虚函数表中记录着 虚函数的入口地址
4.当父类指针或引用指向子类对象,发生多态,调用是时候从虚函数中找虚函数入口地址
5.虚函数关键字 virtual
静态多态和动态多态的区别
对于父子关系的两个类 指针或者引用是可以直接转换的
静态多态是函数地址是早绑定(静态联编) 函数重载、运算符重载
class Animal
{
public:
void speak()
{
cout<<"动物在说话"<<endl;
}
};
class Cat : public Animal
{
public:
void speak()
{
cout<<"小猫在说话"<<endl;
}
};
void dospeak(Animal& animal) //提前写好了
{
animal.speak();
}
void test_01()
{
Cat c; //这是一个静态多态 地址早绑定
dospeak(c); //动物在说话
}
如果以上程序想要调用小猫在说话 这个时候 函数的地址就不能早绑定(动态联编)
虚函数
动态多态在运行阶段绑定地址,地址晚绑定,动态联编
class Animal
{
public:
//虚函数
virtual void speak()
{
cout<<"动物在说话"<<endl;
}
};
class Cat : public Animal
{
public:
void speak()
{
cout<<"小猫在说话"<<endl;
}
};
class Dog : public Animal
{
public:
void speak()
{
cout<<"小狗在说话"<<endl;
}
};
//传参多态
//父类指向子类空间的方式 是传参的形式实现的
void dospeak(Animal& animal)
{
animal.speak();
}
void test_01()
{
Cat c;
dospeak(c); //小猫在说话
Dog d;
dospeak(d); //小狗在说话
}
纯虚函数(重点)
在多态的应用中 子类调用父类重写的函数 那么父类的虚函数被覆盖了 父类必须有该虚函数
但是具体实现是浪费的 此时我们可以将父类的虚函数写成纯虚函数 只作为模板 告诉子类必须要重写虚函数
注意:父类如果有纯虚函数 该父类一定是抽象类 抽象类不能实例化 只能用于多态应用(父类指针指向子类空间)
父类的纯虚函数 子类必须全部重写 否则 子类也会变成抽象类
class Animal
{
public:
virtual void func() = 0; //纯虚函数格式
};
class Dog:public Animal
{
public:
void func()
{
cout<<"小狗正在叫"<<endl;
}
};
class Cat:public Animal
{
public:
void func()
{
cout<<"小猫正在叫"<<endl;
}
};
void barking(Animal *a)
{
a->func();
}
void test_01()
{
//Animal a; //error 抽象类不能实例化
barking(new Dog); //小狗正在叫
barking(new Cat); //小猫正在叫
}
练习:饮品制作
制作饮品为抽象类 功能有 煮茶 冲泡 倒入 加佐料 具体实现由两个子类完成 为冲咖啡和冲茶叶
class driak
{
public:
virtual void heat() = 0; //煮水
virtual void brew() = 0; //冲泡
virtual void pour() = 0; //倒入
virtual void ingredients() = 0; //辅料
};
class tea:public driak
{
public:
void heat()
{
cout<<"煮水"<<endl;
}
void brew()
{
cout<<"冲泡茶叶"<<endl;
}
void pour()
{
cout<<"倒入杯中"<<endl;
}
void ingredients()
{
cout<<"加柠檬"<<endl;
}
};
class coffee:public driak
{
public:
void heat()
{
cout<<"煮水"<<endl;
}
void brew()
{
cout<<"冲泡咖啡"<<endl;
}
void pour()
{
cout<<"倒入杯中"<<endl;
}
void ingredients()
{
cout<<"加糖和牛奶"<<endl;
}
};
void make(driak *d)
{
d->heat();
d->brew();
d->pour();
d->ingredients();
}
void test_01()
{
cout<<"----茶----"<<endl;
make(new tea);
cout<<"----咖啡----"<<endl;
make(new coffee);
}
纯虚函数和多继承(了解)
绝大多少面向对象语言都不支持多继承 但是绝大多数面向对象的语言都支持接口概念
c++没有接口概念 但是可以通过纯虚函数来实现接口
虚析构函数
虚析构应用(重点)
在多态的应用中 如果需要释放资源时 需要使用到虚析构函数
也就是 由虚构函数的地方 就一定要写虚析构 否则会造成内存泄漏
虚函数的目的:释放子类的析构函数
class Animal
{
public:
Animal()
{
cout<<"Animal构造函数"<<endl;
}
virtual ~Animal()
{
cout<<"Animal析构函数"<<endl;
}
virtual void func()
{
cout<<"动物正在叫"<<endl;
}
};
class Dog:public Animal
{
public:
Dog()
{
cout<<"Dog构造函数"<<endl;
}
~Dog()
{
cout<<"Dog析构函数"<<endl;
}
void func()
{
cout<<"小狗正在叫"<<endl;
}
};
class Cat:public Animal
{
public:
Cat()
{
cout<<"Cat构造函数"<<endl;
}
~Cat()
{
cout<<"Cat析构函数"<<endl;
}
void func()
{
cout<<"小猫正在叫"<<endl;
}
};
void barking(Animal *a)
{
a->func();
delete a;
}
void test_01()
{
//Animal a; //error 抽象类不能实例化
barking(new Dog); //小狗正在叫
barking(new Cat); //小猫正在叫
}
纯虚析构
在虚析构的基础上 将虚虚构的函数去掉 具体实现类外完成
具体场景和虚析构是一样的 都是用于释放子类的析构函数
纯虚析构所在的类一定是抽象类
class Animal
{
public:
Animal()
{
cout<<"Animal构造函数"<<endl;
}
virtual ~Animal() = 0;
virtual void func()
{
cout<<"动物正在叫"<<endl;
}
};
Animal::~Animal() //类外实现
{
cout<<"Animal析构函数"<<endl;
}
class Dog:public Animal
{
public:
Dog()
{
cout<<"Dog构造函数"<<endl;
}
~Dog()
{
cout<<"Dog析构函数"<<endl;
}
void func()
{
cout<<"小狗正在叫"<<endl;
}
};
class Cat:public Animal
{
public:
Cat()
{
cout<<"Cat构造函数"<<endl;
}
~Cat()
{
cout<<"Cat析构函数"<<endl;
}
void func()
{
cout<<"小猫正在叫"<<endl;
}
};
void barking(Animal *a)
{
a->func();
delete a;
}
void test_01()
{
//Animal a; //error 抽象类不能实例化
barking(new Dog); //小狗正在叫
barking(new Cat); //小猫正在叫
}
多态整理
虚函数与纯虚函数
虚函数和纯虚函数 都是为了使用父类指针调用子类函数
虚函数 有函数体 所在的类不是抽象类 可以实例化对象
纯虚函数 =0修饰 没有函数体 所在的类为抽象类 不可实例化对象
虚析构和纯虚析构
使用了虚函数或纯虚函数 必须使用虚析构和纯虚析构来释放空间 否则容易内存泄漏 都是为了通过父类指针来释放子类的所有空间 (子类部分、父类部分)
虚析构 有函数体 所在的类不是抽象类 可实例化对象
纯虚函数 类内没有函数体只有声明 函数体在类外实现 所在的类为抽象类 不可实例化对象
重载重定义重写(重要)
重载: 没有继承关系 函数重载或运算符重载函数名相同 函数形参的个数或类型不同 会构成重载(返回值不同不能构成重载)
重定义: 有继承关系 父类和子类的函数名相同(非虚函数) 返回值和形参可以不同 就会构成重定义
重写: 有继承关系 子类重写父类的虚函数(关键字virtual) 返回值 函数名 形参 必须和父类虚函数相同