多态与虚函数(补)

这篇具有很好参考价值的文章主要介绍了多态与虚函数(补)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

静态联编与动态联编的深层次理解

我们首先看下面一段代码

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), num(x + 10) {}
	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 + 10) {}
	virtual void add() { cout << "Test: :add()" << endl; }
	virtual void print() const
	{
		cout << "Test: :print()" << endl;
	}
	virtual void show() { cout << "Test: :show()" << endl; }
};

大家可以试着画一下上面三个类的虚表,此处我就不画了
我们试着想一下在上面这三个类型的基础上运行一下代码是否可以运行通过

int main() {
	object* op = nullptr;
	Test test;
	op = &test;
	op->show();
	return 0;
}

有的同学可能会说这是可以运行通过的,因为我们的op指向了test对象,这就是错误的理解,该程序在编译时期就会出现错误,为什么呢?
我们知道编译器在编译时是按照类型识别的,而在编译识别的时候op是我们的objecct类型,而其类型中不存在所谓的show函数,所以呢就会在编译时期爆索,有的同学可能还会说但是我们的op指向了test,这是在运行时进行的,我们编译都通过不了更别提运行时了呀,要怎么运行通过呢?只能通过强转来实现,将op强转为test类型 ((Test*)op)->show();,这种方法不建议大家使用,因为会出现不确定因素导致程序崩溃,比如将以上代码改为

int main() {
	object* op = nullptr;
	object obj;
	Test test;
	op = &obj;
	((Test*)op)->show();
	return 0;
}

这样就会崩掉,因为obj中没有show函数。

多态底层原理

在上次我们讲述了多态底层是通过虚表指针查虚表来实现多态的,而虚表指针是如何查表的呢?
我们通过汇编语言来理解一下,多态与虚函数(补)
这就是运行时的多态,而编译时的多态便是在编译时通过类型名绑定了对象,也就确定了要对ecx赋值的值,不存在查表这一系列操作。这也就是动态联编和静态联编的区别,注意对指针解引用再次通过对象点调用函数仍然是动态联编,((*ob).add())

示例

示例一

多态与虚函数(补)
我们观察以上代码会出现什么问题?
在我们使用memset函数时对this指针进行了操作,这就使得我们该对象的虚表指针也被置为0了,也就是说虚表指针成为了野指针,导致了虚表无法被查找,无法通过指针调用虚函数。所以我们对this指针操作需要谨慎。

示例二

多态与虚函数(补)
我们阅读上面代码,想一下运行结果是什么,是为什么这么调用呢?
我们在调用show函数时,函数中调用print函数使用的是this指针调用,很显然调用的是基类的print函数,但是传过去的this指针是Base指针,而在print函数中调用了add函数,这里也是使用this指针调用,而传过来的指针是Base指针,所以我们查虚表也是查的Base的虚表。
运行结果:
多态与虚函数(补)

示例三

多态与虚函数(补)
多态与虚函数(补)
观察上面代码,想一想运行结果是什么?
很显然我们创建Base对象的时候首先会创建obj对象,所以首先会调用obj的构造函数,此时虚表指针指向obj的虚表,所以add(12)调用的是obj的add函数,然后创建完成之后创建Base对象,此时虚表指针指向Base虚表,所以此处的add查找的是Base的add,然后析构开始进入Base的析构函数(重置虚表指针),在~Base的时候调用add调用的是本类型的add函数,然后析构基类,在析构派生类的时候虚表指针已经被重置,指向了obj的虚表,所以析构基类时查找的也是obj的虚表。注意:::析构函数是静态联编,按照其类型名析构,防止编写程序时派生类对象已经析构了还通过该对象查找该类的虚表。(虚表在数据区)

示例四

多态与虚函数(补)
思考一下以上代码会出现什么样的情况?
我们会发现在Base类中虚函数是私有的,我们会思考 在Base中func函数是私有的,可不可以调用。我们从程序主函数开始看,op是obj类型的,而obj中的func是共有的,所以呢编译时是可以通过的,我们将编译器给骗过去了,而运行的时候又是动态联编,因为编译时我们确定看初始值形参a=10,所以呢在动态联编的时候我们又是从虚表中进行查找不会经过private这一步,所以呢我们仍调用的是Base类型中的func函数,但是b的值变成了10,这就是我们编译时确定了的形参。输出结果:多态与虚函数(补)
这个实例将动态联编和静态联编的使用达到了极致,希望大家可以都可以理解。

对象与内存

class object {
private: int value;
   public:
	   object(int x = 0) : value(x) {}
	   void func(int a = 10) {
		   cout << "object::func  a" <<a<<"  value:" << value << endl;
	   }
	   void print() const
	   {
		   cout << "object::print:" <<endl;
	   }
};
int main() {
	object* op = nullptr;
	//object obj;
	//op = &obj;
	op->print();
	return 0;
}

我们观察上面代码,才可是否可以运行?
答案是可以运行的,因为在print函数中,不存在对this指针的使用,直接输出。
多态与虚函数(补)
而通过op调用func函数时就会报错,this指针为空。
我们对以上代码进行了修改如下:

class object {
private: int value;
   public:
	   object(int x = 0) : value(x) {}
	   void func(int a = 10) {
		   cout << "object::func  a" <<a<<"  value:" << value << endl;
	   }
	   virtual void print() const//2
	   {
		   cout << "object::print:" <<endl;
	   }
};
int main() {
	object obj(11);//3
	object* op = (object*)malloc(sizeof(object));//1
	(*op) = obj;//3
	new(op) object(obj);//4
	op->print();
	op->func();
	return 0;
}

我们首先知道当使用op调用print函数时会出现崩溃的情况,因为不存在this指针为nullptr,所以呢我们加入了1操作,申请了空间给op指针,这样我们就可以运行出结果,只是输出的value是随机值,因为我们只申请了空间但是没有赋值。为此我们进行了2操作,将print改成了虚函数,这样呢程序就会崩溃,因为不存在虚表指针来调用虚函数。然后我们添加了3操作,希望可以创建一个对象,通过对象与对象之间赋值来改变,但是这样呢程序仍然会崩溃,因为默认的对象与对象之间的赋值函数不会对虚表指针也进行赋值。所以我们使用了定位new,也就是通过系统操作把新的obj对象赋值给op,这样也就实现了对虚表指针的赋值,程序也就不会崩溃。

虚析构函数

首先我们给出一段代码,思考下面代码出现的问题。

class object {
private: int value;
   public:
	   object(int x = 0) : value(x) {}
	   ~object() {}
	   virtual void print(int x) //2
	   {   cout << "object::print:" <<x<<endl; }
};
class Base :public object {
	int num;
public:
	Base(int x = 0) :object(x + 10), num(x) { }
	~Base() {}
	void print(int x) { cout << "Base::print:" << x << endl; }
};
int main() {
	object* op = new Base(10);
	op->print(1);
	delete op;
	return 0;
}

上面代码我们会发现在调用print的时候调用的是Base的print函数,但是我们delete时就会只析构object对象导致出现内存泄漏,怎么解决这种问题呢?就是将obj的析构函数设置成虚析构函数,就可以实现动态析构。
根据赋值兼容规则,可以用基类的指针指向派生类的对象,如果使用基类指针指向动态创建的派生类对象,由该基类指针撤销派生类对象,则必须将析构函数定义为虚函数,实现多态性,自动调用派生类析构函数,可能存在内存泄漏问题。也就是上面代码出现的问题。
总结:在实现运行时多态,不管怎样调用析构函数都必须保证不出错,所以必须把析构函数定义为虚函数。类中没有虚函数就不要把析构函数定义为虚。文章来源地址https://www.toymoban.com/news/detail-446579.html

构造函数为什么不能是虚函数?

  • 构造函数的用途:创建对象,初始化对象中的属性,类型转换。
  • 在类中定义了虚函数就会有一个虚函数表,对象模型中就含有一个指向虚表的指针。在定义对象时构造函数设置虚表指针指向虚表。
  • 构造函数的调用属于静态联编,在编译时必须知道具体的类型信息。
  • 如果构造函数可以定义为虚构造函数,使用指针调用虚析构造函数,如果编译器采用静态联编,构造函数就不能为虚函数。如果采用动态联编,运行时指针指向具体对象,使用指针调用构造函数,相当于已经实例化的对象在调用构造函数这是不允许的,对象的构造函数只能执行一次。
  • 如果指针可以调用需构造函数,通过查虚表调用构造函数,那么指针为nullptr就会出现错误。
  • 构造函数在编译时确定,如果是虚函数,编译器怎么知道你想构建是继承树上的哪一种呢?

到了这里,关于多态与虚函数(补)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C++三大特性—多态 “抽象类与虚函数表”

    抽象类和虚函数表是 C++中实现多态性的重要概念,它们对于学习 C++非常重要。 掌握抽象类和虚函数表的使用方法对于理解 C++的多态性是非常重要的。在 C++中,通过使用抽象类和虚函数表,可以实现基于多态性的各种功能,如继承、多态、模板等。同时,在实际应用中,抽

    2024年02月07日
    浏览(38)
  • 【C++学习】第六章多态与虚函数案例实现

    虚函数的作用就是为了实现多态,和php的延时绑定是一样的。 函数重载是静态的,在横向上的功能, 虚函数是类继承上的功能,是动态的。

    2024年02月09日
    浏览(37)
  • C++学习Day07之动态联编和静态联编

    C++ 中的联编(Binding)分为动态联编(Dynamic Binding)和静态联编(Static Binding)两种方式。它们分别指的是在运行时和编译时确定函数或方法的调用方式的过程。 静态联编是指在编译时确定函数或方法的调用方式。在编译阶段,编译器根据调用函数或方法的类型和参数类型来

    2024年02月21日
    浏览(35)
  • 【C++】虚函数表 & 多态的原理 & 动态绑定和静态绑定

    梳理虚函数表、多态原理、动静态绑定的知识 目录 一、虚函数表 二、多态的原理 三、动态绑定和静态绑定 在学习多态原理之前,我们需要了解一下虚函数表的概念  我们先一起来看下下面这段代码 通过测试我们发现b对象是8bytes, 除了_b成员,还多一个__vfptr指针放在对象

    2024年02月03日
    浏览(41)
  • C++——多态与虚表

    目录 1.多态的实现 2.虚表 2.1虚函数重写是怎么实现的 2.2多态的原理 2.3静态绑定与动态绑定 3.单继承体系中的虚函数表 ​编辑4.多继承体系中的虚函数表 5.菱形继承的虚函数表 6.菱形虚拟继承的虚函数表 在C++中,要想实现多态,必须满足以下几个条件: 有继承关系 有虚函数

    2024年02月07日
    浏览(37)
  • 嵌入式培训机构四个月实训课程笔记(完整版)-C++和QT编程第四天-C++动态联编和虚函数练习(物联技术666)

    链接:https://pan.baidu.com/s/1KayCjn6Vem9YFucS8lpCFg?pwd=1688 提取码:1688 设计一个动物类:动物有一个name成员,另外有三个函数sleep(), eat(), play(); 从动物这个类派生出狗类和猫类,在对应的三个函数中实现输出如下信息的功能: dog(or cat) (name) is sleepping...! dog(or cat) (name) is eatting...! dog

    2024年01月18日
    浏览(39)
  • 【库函数】Linux下动态库.so和静态库.a的生成和使用

    目录 🌞1. Linux下静态库和动态库的基本概念 🌞2. 动态库 🌊2.1 动态库如何生成 🌍2.1.1 文件详情 🌍2.1.2 编译生成动态库 🌊2.2 动态库如何使用 🌍2.2.1 案例 🌍2.2.2 动态库错误记录 🌞3. 静态库 🌊3.1 静态库如何生成 🌍3.1.1 文件详情 🌍3.1.2 编译生成动态库 🌊3.2 静态库如

    2024年04月25日
    浏览(28)
  • [C++] 多态(下) -- 多态原理 -- 动静态绑定

    上一篇文章我们了解了虚函数表,虚函数表指针,本篇文章我们来了解多态的底层原理,更好的理解多态的机制。 [C++] 多态(上) – 抽象类、虚函数、虚函数表 下面这段代码中,Func函数传Person调用的Person::BuyTicket,传Student调用的是Student::BuyTicket,这就是多态调用,但是这里我

    2024年02月04日
    浏览(46)
  • 小程序 多层次对象数组的赋值、动态赋值

    对单个属性赋值  动态赋值 field 是wxml上通过data-field传过来的  对单个属性赋值:

    2024年02月15日
    浏览(52)
  • <c++>虚函数与多态 | 虚函数与纯虚函数 | 多态的实现原理 | 虚析构函数

    🚀 个人简介:CSDN「 博客新星 」TOP 10 , C/C++ 领域新星创作者 💟 作    者: 锡兰_CC ❣️ 📝 专    栏: 从零开始的 c++ 之旅 🌈 若有帮助,还请 关注➕点赞➕收藏 ,不行的话我再努努力💪💪💪 在上一篇文章中,我们介绍了 c++ 中类与对象的继承,继承可以根据一个或

    2023年04月18日
    浏览(38)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包