c++ 多态与虚函数

c++中多态分为静态多态和动态多态,静态多态是函数重载,在编译阶段就能确定调用哪个函数。动态多态是由继承产生的,指不同的对象根据所接收的消息(成员函数)做出不同的反应。例如,动物都能发出叫声,但不同的动物能发出不同的叫声,这就是多态。

虚函数声明格式:

class A{
权限控制符:
    virtual 函数返回值类型 函数名 (参数列表);
};

c++多态满足条件

(1)基类声明虚函数

class Animal {
public:
	virtual void Sound();
};

(2)派生类重写基类的虚函数

class Dog:public Animal {
public:
	virtual void Sound();
	//void Sound();等价于virtual void Sound();
};

注意:派生类中重写的虚函数前是否添加virtual,均被视为虚函数

(3)将派生类对象赋值给基类指针或引用,通过基类指针或引用访问虚函数

    Dog dog;
	Animal* animal =  &dog;//通过指针
	animal->Sound();
	Animal& animal_1 = dog;//通过引用
	dog.Sound();

声明虚函数注意以下几点:

(1)构造函数不能声明为虚函数,因为构造函数执行时,对象还没有创建,但析构函数可以声明为虚函数

(2)虚函数不能是静态成员函数。因为静态成员函数是对象共享的

(3)友元函数不能声明为虚函数,但虚函数可以作为另一个类的友元函数


 c++11 final关键字

(1)修饰类,表示该类不可以被继承

class A final{};

(2)修饰虚函数,表示该虚函数不能在派生类中重写

virtual void f() final;

c++虚函数实现多态的原理

虚函数是通过动态绑定实现多态的,当编译器在编译过程中遇到virtual关键字时,他不会对函数进行绑定,而是为包含虚函数的类建立一张虚函数表Vftable.编译器按照虚函数的声明依次保存虚函数地址。同时,编译器会在类中添加一个隐藏的虚函数指针Vfptr,指向虚函数表。在创建对象时,将虚函数指针Vfptr放置在对象的起始位置,为其分配内存空间,而虚函数表不占用对象内存空间。

派生类继承基类时,也继承了基类的虚函数指针。当创建了派生类对象时,派生类对象的虚函数指向自己的虚函数表。如果,派生类重写了基类的虚函数,则派生类虚函数会覆盖基类的同名函数。当通过基类指针操作派生类对象时,已派生类对象内存为准,从对象中获取Vfptr,通过Vfptr找到Vftable,从而调用相应的虚函数,实现了动态绑定。

示例介绍:

class Cattle {
public:
	virtual void walk();
	virtual void sound1();
	virtual void eat1();
};
class Horse {
public:
	virtual void walk();
	virtual void sound2();
	virtual void eat2();
};
class CattleHorse :public Cattle,public Horse {
public:
	virtual void walk();
	virtual void sound1();
	virtual void eat2();
};

其中声明了Cattle类,Horse类,CattleHorse类。CattleHorse重写了walk()方法,Cattle的sound1()方法,Horse的eat2()方法

我们来看一下vftable和vfptr

当创建派生类对象ch赋值给Cattle类时

    CattleHorse ch;
	Cattle* cattle = &ch;

 基类指针cattle从对象ch中获得虚函数指针Vfptr从而获得虚函数表

  调用虚函数时

	cattle->walk();
	cattle->sound1();
	cattle->eat1();

 我们要理解虚函数被重写之后,派生类虚函数会覆盖基类的同名虚函数的原理


c++纯虚函数和抽象类

有时基类并不需要实现函数,只需声明即可,实现交由派生类即可,这样的函数成为纯虚函数

声明格式:

virtual 返回值类型 函数名(参数列表) = 0;

注意:纯虚函数后面"=0",并不是函数的返回值为0,它只是告诉编译器这是一个纯虚函数

纯虚函数的几个说明

(1)如果一个类中包含了纯虚函数,这样的类称为抽象类。抽象类特点是:不能实例化对象,但可以定义抽象类的指针或引用。

如声明了抽象类Animal

class Animal {
public:
	virtual void walk() = 0;
	virtual void eat();
};
	//Animal animal;不允许
    //Animal* animal = new Animal;不允许
	Animal* animal;

其中不能写Animal animal,也即不能实例化对象,但可以定义抽象类的指针Animal *animal;这时如果不用派生类对象为其赋值,尽管实现了eat()方法,也是不可以调用eat()方法的

	animal->eat();//错误,未初始化animal局部变量

 (2)派生类都应该实现基类的纯虚函数,如果不实现,则该函数在派生类中仍然是纯虚函数,该派生类也是抽象类,也不能实例化对象。


c++虚析构函数与纯虚析构函数

若派生类中有开辟到堆区的数据,而基类没有声明虚析构函数,在析构派生类对象时,编译器只会调用基类的析构函数,不会调用派生类析构函数,导致派生类对象申请的资源不能正确的释放。所以需要声明虚析构函数

虚析构函数声明格式:

virtual ~析构函数();

纯虚析构函数声明格式:

virtual ~析构函数() = 0;

简单示例:

class Animal {
public:
	Animal();
	virtual ~Animal();
	virtual void sound()=0;
};
class Dog :public Animal {
public:
	string *name;
public:
	Dog(string name);
	virtual void sound();
	virtual ~Dog();
};
Dog::Dog(string name) {
	cout << "调用Dog子类的构造函数:" << endl;
	 this->name=new string(name);
}
Dog::~Dog() {
	if (this->name != NULL) {
		cout << "调用派生类Dog析构函数" << endl;
		delete name;
		this->name = NULL;
	}
}
int main() {
	Animal* animal = new Dog("小黄");
	animal->sound();
	delete animal;
	return 0;
}

 上图部分代码声明了基类Animal以及构造函数和析构函数,派生类Dog以及构造函数与析构函数,其中派生类为小狗起名声明了name堆区数据,如果不声明基类析构函数为虚析构函数,则派生类堆区数据name就无法释放而造成内存泄漏。

关于虚析构函数的注意事项

(1)在基类声明虚析构函数之后,基类的所有派生类析构函数都自动成为虚析构函数

(2)在析构派生类对象时,先调用派生类析构函数,在调用基类析构函数(栈)

(3)虚析构函数和纯虚析构函数都要在基类中实现,因为假若基类中有堆区开辟的数据,也是需要实现虚析构函数释放资源的

(4)虚析构函数和纯虚析构函数区别:声明了纯虚析构函数后,该类为抽象类(只要该类中有虚函数,该类就是抽象类),不能实例化对象。而声明虚析构函数不会成为抽象类。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值