C++之多态与虚函数

初识多态

  • 多态性是面向对象程序设计的关键技术之一。若程序不支持多态,不能称为面向对象的语言
  • 编译时多态:通过函数重载实现,早期绑定
  • 运行时多态:在程序执行过成中,动态的确定,它是通过类继承关系public和虚函数实现的,目的也是建立一种通用的程序。
  • 运行时多态的设计思想:
  • 对于相关的类型,确定他们之间的一些共同特征,(属性和方法),将共同特征被转移到基类中,然后在基类中,把这些共同的函数或方法声明为公有的虚函数接口,然后使用派生类继承基类,并且在派生类中重写这些虚函数,已完成具体的功能。

虚函数定义:

class Animal
{
private:
	string _name;
public:
	Animal(const string& name):_name(name){}
	~Animal(){}
	virtual void eat() { cout << "eat..." << endl; }
	virtual void walk() { cout << "walk..." << endl; }
	virtual void talk() { cout << "walk..." << endl; }
	void PrintInfo()const
	{
		cout << _name << endl;
	}
};

class Dog:public Animal
{

public:
	Dog(const string& name):Animal(name){}
	~Dog(){}
	void eat() { cout << "Dog eat bone" << endl; }
	void walk() { cout << "Dog walk Dog" << endl; }
	void talk() { cout << "Dong walk :wang wang" << endl; }
};

class Cat:public Animal
{
public:
	Cat(const string &name):Animal(name){}
	~Cat(){}
	void eat() { cout << "cat eat:fish" << endl; }
	void walk() { cout << "cat walk silent" << endl; }
	void talk() { cout << "Cat talk miao maio " << endl; }
};
void fun(Animal * ap) //打印的是狗
{
	if (ap == nullptr)return;
	ap->eat();
	ap->walk();
	ap->talk();
}
void funa(Animal& ma)  //打印的是猫
{
	ma.eat();
	ma.talk(); 
	ma.walk();
	ma.PrintInfo(); //不具有多态性
}
void func(Animal animal) //不具有多态,打印的是动物本身
{
	animal.eat();
	animal.talk();
	animal.walk();
	animal.PrintInfo();
}
int main()
{
	Animal* ap = nullptr;
	Cat cat("bsm");
	Dog dog("erha");
	fun(&dog);
	funa(cat);
	func(cat);
}

总结:运行时多态性,公有继承+虚函数+(指针或引用调用虚函数)

  • 静态联编和动态联编

函数的重写,函数参数,函数名,函数返回类型必须相同(函数重写是同名覆盖的一种特殊情况)

class Object
{
public:
	virtual void add(int x) { cout << "Object::add(int)" << endl; }
};
class Base:public Object
{
public:
	virtual void add(int a) { cout << "Base::add(int)" << endl; }
};

int main()
{
	Base base;
	Object* op = &base;
	Object& obj = base;
	op->add(10); //拿指针或者引用调用虚方法,是动态联编,要通过查虚表的方式
	obj.add(10);

	Object os;//静态联编
	os.add(20);
	os = base;
	os.add(20);
}

协变

class Object
{
public:
	virtual Object* Get() { return this; }
};
class Base :public Object
{
public:
	virtual Base* Get() { return this; }
};
int main()
{
	Base base;
	Object* op = &base;
	op->Get();
}
  • 只有类的成员函数才能说明虚函数,这是因为虚函数仅适用于有继承关系的类对象,友元函数和全局函数不能做为虚函数
  • 静态成员函数,是所有同一类对象共有,不受限于某个对象,不能作为虚函数
  • 构造函数和拷贝构造函数不能作为虚函数,构造函数和拷贝构造函数是设置虚表指针
  • 析构函数可定义为虚函数,构造函数不能定义虚函数,因为在调动构造函数时对象还没有完全实例化(虚表指针没有设置),在基类中及其派生类中都动态分配内存空间,必须把析构函数定义为虚函数,实现撤销对象时的多态性
  • 运行时的多态,函数执行速度要慢一些,为了实现多态性,每一个派生类中均要保存相应的虚函数的入口地址表,函数的调用机制也是间接实现,所以多态性总要付出一定的代价,但通用性是更高的目标
  • 如果定义放在类外,virtual只能加载函数的声明前面,不能加在函数的定义前面。正确的定义必须不包括virtual
    在这里插入图片描述

运行时多态的原理

  • 虚函数指针表简称为虚表,虚表就是虚函数指针的集合
  • 虚表本质是一个存储虚函数指针的指针数组
  • 这个数组的首元素之上存储RTTI(运行时类型识别信息的指针)
  • 从数组下标0开始一次存储虚函数地址,最后面放了一个nullptr
  • 虚表存储在只读数据段(.rodata)
  • 类型设计中定义了虚函数,此类型就有了对应的虚表
  • v代表virtual,f代表function,table代表表,数组
  • 使用此类型定义的对象就含有一个指向虚标的指针,名字是vfptr
  • vfptr存储在对象中

函数的重写:(同名覆盖)

using namespace std;

class Object
{
private:
	int value;
public:
	Object(int x=0):value(x){}
	virtual void add() { cout << "object::add()" << endl; }
	virtual void fun(){ cout << "object::fun()" << endl; }
	virtual void print(){ cout << "object::print()" << endl; }
};
class Base :public Object
{
private:
	int num;
public:
	Base(int x=0):Object(x),num(x){}
	virtual void add() { cout << "Base::add()" << endl; }
	virtual void fun() { cout << "Base::fun()" << endl; }
	virtual void show() { cout << "Base::show()" << endl; }
};
class Test :public Base
{
private:
	int count;
public:
	Test(int x = 0) :Base(x),count(x) {}
	virtual void add() { cout << "Test::add()" << endl; }
	virtual void print() { cout << "Test::print()" << endl; }
	virtual void show() { cout << "Test::show()" << endl; }
};
int main()
{
	Object* op = nullptr;
	Object obj;
	cout << sizeof(obj) << endl;
	Base base;
	Test test;
	op = &test;
	op->print();
	return 0;
}

在这里插入图片描述
在这里插入图片描述

查虚表:

void fun(Object* op)
{
	op->add();
	op->fun();
	op->print();
	op->show(); //error
}
void func(Base* base)
{
	base->add();
	base->fun();
	base->print();
	base->show();
}

int main()
{
	Object obja;
	Base base(10);
	Test test(10);
	fun(&obja);
	fun(&base);
	fun(&test);
	func(&base);
	func(&test);
}

静态联编和动态联编

在这里插入图片描述

class Object
{
private:
	int value;
public:
	Object(int x = 0) :value(x)
	{
		memset(this, 0, sizeof(Object));
	}
	void func() { cout << "Object::fun" << value << endl; }
	virtual void add(int x) { cout << "Object::add" << x << endl; }
};

int main()
{
	Object obj;
	obj.add(10);//静态联编
	Object* op = nullptr;
	op->add(10);//动态联编,运行的时候进行查表,但是运行时程序出错,原因是this为nullptr,对空指针进行了查表操作
}

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

淡蓝色的经典

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值