149-C++学习第十二弹(多态,虚函数)

多态与虚函数

晚绑定:编译时不能确定函数名和地址的关系,在运行的过程中确定
需要用到运行时的查表方案
在这里插入图片描述
virtual

没有继承关系,不要把成员函数定义成虚函数

就近原则(同名隐藏)

class Object
{
public:
	int value;
public:
	Object(int x = 0) :value(x) {}
};
class Base : public Object
{
public:
	int value;
	int num;
public:
	Base(int x = 0) :Object(x), value(x + 10), num(x + 20) {}
	void fun()
	{
		value = 10;//访问的是Base的value(就近原则) 
		num = 20;
		Object::value = 20;//访问的是Object的value
	}
};
int main()
{
	Base base;
	base.value = 10;//访问的是Base的value(就近原则) 
	base.Object::value = 20;//访问的是隐藏父对象的value
}

同名隐藏和可访问属性没有任何关系

class Object
{
public:
	void fun(int x,int y,int z) {
		cout << "Object::fun " << x << endl;
	}
};
class Base : public Object
{
public:
	void fun(int x) {
		cout << "Base::fun " << x << endl;
	}
	void fun(int a, int b) {
		cout << "Base::fun(int,int): " << a << " " << b << endl;
	}
};

int main()
{
	Base base;
	base.fun(12);//调动base自己的
	base.fun(1, 2);//调动base自己的
	//base.fun(1, 2, 3);//编译不通过,因为Base里的fun函数把隐藏基类的fun函数隐藏掉了
	base.Object::fun(1,2,3);//编译通过,告诉编译器调动的是隐藏基对象的fun函数
	return 0;
}

对于Base这个类型我们有两个fun函数,参数和类型不一致,这两个函数在Base中是一种重载关系。
在这里插入图片描述
但一定要注意,Base里的两个fun函数和父类Object的fun函数不具有重载关系
在这里插入图片描述
跨类型之间,没有函数重载的概念,这叫同名隐藏
函数重载:全局函数或者同属于一个类的

虚函数

class Animal
{
private:
	string name;
public:
	Animal(const string& na) :name(na) {}
	~Animal() {}
	virtual void eat() {}
	virtual void walk() {}
	virtual void talk() {}
	virtual void PrintInfo() const{}
	const string& GetName() const { return name; }
};
class Dog : public Animal
{
private:
	string owner;
public:
	Dog(const string& own, const string& na) :Animal(na), owner(own) {}
	~Dog() {}
public:
	virtual void eat() { cout << "Dog Eat: bone " << endl; }
	virtual void walk() { cout << "Dog Walk: run " << endl; }
	virtual void talk() { cout << "Dog talk: wang wang .." << endl; }
	virtual void PrintInfo() const {
		cout<<"Dog Owner: " << owner << endl;
		cout << "Dog Name: " << GetName() << endl;
	}
};

class Cat : public Animal
{
private:
	string owner;
public:
	Cat(const string& own, const string& na) :Animal(na), owner(own) {}
	~Cat() {}
public:
	virtual void eat() { cout << "Cat Eat:  fish " << endl; }
	virtual void walk() { cout << "Cat Walk: silent " << endl; }
	virtual void talk() { cout << "Cat talk: Miao miao .." << endl; }
	virtual void PrintInfo() const {
		cout << "Cat Owner: " << owner << endl;
		cout << "Cat Name: " << GetName() << endl;
	}
};

void fun(Animal& animal)
{
	animal.eat();
	animal.walk();
	animal.talk();
	animal.PrintInfo();
}
int main()
{
	Dog dog("yhping", "hashiqi");
	Cat cat("tulun", "Persian");

	fun(dog);
	fun(cat);
	return 0;
}

先看下面这个例子:
有一个动物,有走的方法
派生出狗,狗把这个方法重写
派生出人,人把这个方法重写
狗是动物,人是动物,都是继承来的
狗走的方法是四条腿
人走的方法是两条腿
不同对象的生成,它体现的走法也不一样
在这里插入图片描述
当我们在动物,定义一个引用,当我们引用狗对象的时候,调动狗的虚方法,当引用人对象,调用人的虚方法
在这里插入图片描述
回到主题上
我们有动物(名字,说话声,走路方式)
派生出狗和猫(是一个的意思)
我们把虚方法重写了
动物的引用,引用猫,调用的是猫,引用狗,调用的是狗,体现多态性
在这里插入图片描述
赋值兼容规则:可以把子对象给父引用,可以把子对象给父指针
在这里插入图片描述
看下面:
在这里插入图片描述
学生是一个人,可以用人里面的方法
人不一定是学生,不能调动show()

p能否调动show()?
在这里插入图片描述
p->show();不能编译通过 p指针是父类型的,p里面没有show函数,编译不能通过

干货来了
在这里插入图片描述
父指针指向子对象调动虚方法,可以转向去调动子类的虚方法,多态性
三同:返回类型,函数名,参数列表相同
在这里插入图片描述
并不认为子类重写了父类的虚函数
也不认为重载
而是认为同名隐藏

唯一的一个例外:
在这里插入图片描述
这种情况也称作多态

只有类的成员函数才能声明成虚函数
下面这三种函数不能被声明成虚函数

virtual static void show() {}//错误,静态函数无this指针
virtual friend void print();//错误,无this指针
virtual inline void f();//错误,调用内联,代码在调用点展开了,编译时已把代码展开了

构造函数,拷贝构造函数,移动构造函数,不能定义成虚函数
构造函数是在置虚表指针
析构函数可以定义成虚函数,析构函数是在重置虚表指针

指针调动虚函数,或者引用调动虚函数,才能实现多态性(有查表代码)

虚表

在这里插入图片描述
从头开始看代码
定义了一个Object类,定义了一个私有成员value;还有Object的构造函数,定义了add,fun,print这样的虚函数
Base类继承Object,定义了一个私有成员num,然后还有构造函数,重写了add,fun,添加了自己的虚函数show
Test类继承Base:定义了一个私有成员count,然后还有构造函数,重写了add,print,show函数
在这里发生了同名覆盖(虚函数)
我们先编译Object类型,发现有3个虚函数:add,fun,print,会做一个虚表,虚表在只读的数据区,填进去的是add,fun,print的函数地址
在这里插入图片描述

然后编译Base类型,会把上面这个虚表拷贝一份,虚表的名字改为Base vftable,Base重写了add函数,把原本的Object的add覆盖掉,fun重写了,把原本Object的fun覆盖了,Object的print没有重写,就留在原来位置上。然后又新写了一个虚函数show添加进去
在这里插入图片描述
当编译Test类型的时候,把Base的虚部拷贝下来,Test重写add函数,覆盖Base的add函数,Base的fun函数没有重写,仍然是Base的fun函数,print重写了,覆盖Object的print函数,show函数重写了,覆盖Base的show函数
在这里插入图片描述

以上就是同名覆盖
在这里插入图片描述
上面这种情况就没有虚表(以上是同名隐藏)
只有虚函数,编译时才有虚表!
在这里插入图片描述
test对象的大小是多大?
test对象里有Object,Object由两部分构成,虚表指针和value构成
Base继承Object,只有自己的一个成员:num
test继承Base:只有自己的一个成员:count
在这里插入图片描述
当我们构建test,首先得调动Object的构造函数,value=0;虚表指针指向Object,当我们构建好Object对象时,Object的构造函数返回到Base的构造函数,我们构建Base对象,num=0+10=10;重置虚表,虚表指针指向Base的虚表。构建完成Base,回到构造Test对象,count=10;重置虚表,虚表指针指向Test的虚表
在这里插入图片描述
如果是只构建Base对象
最后的虚表指向Base的虚表

如果是只构建Object对象
最后的虚表指向Object的虚表

在这里插入图片描述
虚表指针只有一个哦

#include<iostream>
#include<string>
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() const { cout << "Object::print()" << endl; } 
};
class Base : public Object {
private: int num;
public: Base(int x = 0) :Object(x + 10), num(x)
{
}
	  virtual void add() { cout << "Base::add()" << endl; }
	  virtual void fun() { 
		  cout << "Base::fun()" << endl;
		  this->show();
	  }
	  virtual void show() { cout << "Base::show()" << endl; }
};
class Test : public Base {
private: int count;
public: Test(int x = 0) :Base(x), count(x + 10) 
      {
      }
	  virtual void add() { cout << "Test::add()" << endl; }
	  virtual void print() const { cout << "Test::print()" << endl; }
	  virtual void show() { cout << "Test::show()" << endl; }
	  void call() {
		  cout << "Test::call()" << endl;
		  this->fun();
	  }
};

在这里插入图片描述
这里的op不能调动show函数,op指向函数,编译时受类型限制,op是Object类型,不能编译通过
yhp函数通过对象地址查虚表,调动
如果是对象点调动,则不能查虚表
在这里插入图片描述
调动的各自对象的虚表

void yhp(Object* op)
{
	op->add();
	op->fun();
	op->print();
	printf("\n");
}

int main()
{
	Object obj;
	Base base;
	Test test;
	yhp(&obj);
	yhp(&base);
	yhp(&test);
	return 0;
}

在这里插入图片描述

void tulun(Base* bp)
{
	bp->add();
	bp->fun();
	bp->print();
	bp->show();//对于编译器来说,Base有4个虚函数,都可以放进去
}
int main()
{
	Object obj;
	Base base;
	Test test;
	tulun(&base);
	tulun(&test);

	return 0;
}

在这里插入图片描述

#include<iostream>
#include<string>
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() const { cout << "Object::print()" << endl; } 
};
class Base : public Object {
private: int num;
public: Base(int x = 0) :Object(x + 10), num(x)
{
}
	  virtual void add() { cout << "Base::add()" << endl; }
	  virtual void fun() { 
		  cout << "Base::fun()" << endl;
		  this->show();
	  }
	  virtual void show() { cout << "Base::show()" << endl; }
};
class Test : public Base {
private: int count;
public: Test(int x = 0) :Base(x), count(x + 10) 
      {

      }
	  virtual void add() { cout << "Test::add()" << endl; }
	  virtual void print() const { cout << "Test::print()" << endl; }
	  virtual void show() { cout << "Test::show()" << endl; }
	  void call() {
		  cout << "Test::call()" << endl;
		  this->fun();
	  }
};
int main()
{
	Test test;
	test.call();
	return 0;
}

此主函数打印结果是什么?
Test::call()
Base::fun()
Test::show()

原因:先打印Test::call(),Test没有改写fun虚函数,转向调用Bse的fun虚函数,打印Base::fun(),this->show()此时查的虚表仍然是test的虚表,调用test的show虚函数,打印Test::show(),因为构建的是test对象,已经把base的虚表覆盖了,base是test的隐藏父对象,是test对象的一部分,base对象的this和test对象的this一样,都是指向一样的位置地址,都是指向test对象的空间的最顶部的位置地址

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

在这里插入图片描述
ecx获取的是test的地址
在这里插入图片描述
edx存放的是虚表的地址
在这里插入图片描述
调动第一个函数,add函数
在这里插入图片描述
[edx]意思是取它所指之物
edx指向虚表的首地址,调动add,[edx]就是取add这个函数的地址,调动完add函数,走了。
调动print函数
在这里插入图片描述
edx+8
add函数相当于0位置,每个指针4个字节,edx加8就是print函数的地址了。
在这里插入图片描述

为什么会形成多态?

在这里插入图片描述
虚表里的函数顺序永远和定义的基类里的虚函数的顺序一致
不按字典序

编译器编译的时候,产生Object的虚表
在这里插入图片描述

编译Base的时候,把虚表拷贝下来,产生Base的虚表,已定义的就用指针修改了,变成Base的虚表
在这里插入图片描述
编译Test的时候,拷贝虚表,改成Test的虚表
在这里插入图片描述
在这里插入图片描述

怎么调动?
首先根据这个指针p,指向base,把base的虚表抽出来,放到edx(虚表首地址),调动fun,从edx的第0下标抽取,调用print,edx+4,调动show,edx+8,
如果给的是&test,test对象的地址,p指针调用,把test的虚表的指针给edx,和上面方法一致。
父指针指哪个子对象,把哪个对象的虚表地址(前4个字节)给edx。
抽内容,内容就是函数地址
**子对象重写函数,只是覆盖掉,顺序不变的!**这就是为什么先生成基类的虚表
为了可以edx+4,edx+8
顺序不变!!!

在这里插入图片描述
调动构造函数构建obj1
调动构造函数obj2
进入fun函数,调动构造函数构obja
fun函数结束,把obja析构掉
回退到主函数结束,要析构2个对象地址,调动2次析构函数
程序怎么知道调动两个对象?
在栈帧规则中
给主函数分配栈帧
在底层信息(或者在上层),调动一次构造函数就记一次
在这里插入图片描述
当主函数要退出时,它就在这看记了几次,2个,调用2次析构函数
仍然把创建对象个数记录起来
没有写析构函数,程序就不记录。
fun函数中也有记录创建对象的个数

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

林林林ZEYU

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

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

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

打赏作者

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

抵扣说明:

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

余额充值