【C++】动态联编、delete/free【有无析构】的使用,虚析构
创始人
2024-01-18 04:37:20

文章目录

  • 动态联编的条件:
    • 联编的概念:
      • 1. 动态联编:
      • 2. 静态联编:
      • 静态联编时确认了那些属性:
      • 结论:
  • 基类指针和派生类指针访问虚函数
    • 结论:
  • delete和free的使用条件:
    • 1. 没有析构函数时:
      • 结论:
    • 2.有析构函数
      • 申请一个对象
      • 申请10个对象
      • 结论:
      • malloc和delete可不可以混用?
  • 虚析构函数的使用
  • 重置虚表
    • 构造函数:置虚表
    • 析构函数重置虚表

动态联编的条件:

1.必须是公有继承
2.类中的函数必须为虚函数
3.必须使用指针和引用的方式进行。(->,&)
静态联编。,就是我们使用对象名调用我们的函数。函数名和对象的关系在编译阶段就已经确认。

联编的概念:

联编是指计算机程序自身彼此关联的过程,是把一个标识符和存储地址连载在一起的过程,也就是把一个对象的操作相结合的过程。

1. 动态联编:

如果使用基类指针或引用指明派生类对象,并使用该指针调用虚函数,则程序动态地选择派生类的虚函数,称为动态联编(运行时绑定),也叫滞后联编。
如:Obect为基类 Base为派生类

Object *op = *base  op->fun() (动态选择虚函数)

2. 静态联编:

如果使用对象名加.成员函数或者原则运算符“ ”引用特定的一个对象调用虚函数,则被调用的虚函数在编译时是确定的。则是静态联编。
如:

virtual void fun();
Object obj; obj.fun();

不管对象是够是虚的,均采用静态联编,因为对象和成员函数在编译器就已经绑定。

静态联编时确认了那些属性:

1.确认类型(class)
2.访问限定符
3.函数默认值(很重要)
函数的默认值在编译结点就已经固定了。
注意

  1. 编译的过程和执行的过程一定要分开!!!
  2. 变量的生存期是在运行阶段确定的。
class Object
{
public:virtual void print(int x = 20){cout << "object ::print::x:" << x << endl;}
};
class Base :public Object
{public://普通成员函数 virtual void print(int a){cout << "Base ::print::a:" << a << endl;}
};
int main(void)
{Base base;Object* op = &base;//op指向的base对象的地址。op->print();//所以op调用的是base中print()。//但调用的值是在编译期就已经确定的。return 0;
}

请添加图片描述
值在编译阶段,就已经确认。
请添加图片描述
调用的是派生类方法。
**请添加图片描述
问题
请添加图片描述

结论:

  1. 虽然派生类的虚函数会覆盖掉同名的基类虚函数。但是函数默认值在编译阶段就已经确认了。
  2. 上部分代码,实际上,是因为已经编译完成,故基类指针可以访问到。但是派生类指针是无法访问的。因为派生类的默认值属于私有。

基类指针和派生类指针访问虚函数

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; }
};
class Base :public Object
{private:int num;
public:Base(int x = 0) :num(x), Object(x + 10) {}virtual void add() { cout << "Base::add" << endl; }virtual void fun(int x) { cout << "Base::fun" << endl; }};
int main()
{Base base;Object* op = &base;Base* bp = &base;op->fun();op->add();op->fun(12); error //无法访问,通过隐藏基类指针,查找派生类虚表,访问不到派生类中有,而基类中没有的虚方法。op->Base::fun(12);error//访问不到,如下图:bp->add();bp->fun();   error   //通过查表的方式,直接访问不到,但是可以通过加作用域范围,进行访问。因为派生类继承它没有bp->Object::fun();                                                                    //而基类有的虚函数bp->fun(10);}

请添加图片描述
op能访问:add() fun()
bp能访问:add() Object::fun() fun(10)
请添加图片描述

结论:

  1. 基类指指针只能访问派生类同名覆盖掉和继承的基类中的虚函数,基类中没有而派生类中有的虚函数访问不到。
  2. 而派生类指正均可。如果基类虚函数,派生类中没有,由于是公有继承关系,派生类也可以通过加作用域的方式进行访问。

delete和free的使用条件:

1. 没有析构函数时:

class Object
{
private:int value;
public:Object(int x = 0) :value(x) {cout<<"create construct" <
  1. 创建一个对象
OBject *ip = new Object(10);
  1. 创建10个对象
OBject *ip = new Object[10];
int main()
{//创建了10个对象Object* op2 = new Object[10];delete[]op2;//okdelete op2;//okfree(op2);//ok
}
int main()
{//创建一个对象Object* op1 = new Object(10);free(op1); //okdelete(op1); //okdelete[]op1;  //ok
}

结论:

在没有析构函数时,不论申请多少个对象,free,delete detete [] 随便用

2.有析构函数

申请一个对象

OBject *op1 = new Object(10);

在申请堆空间:有上越界和下越界标志。
free(op1);delete(op1);不会读取计数位置。
delete[]op1; 会把上越界当做计数位读取,从而导致错误。
请添加图片描述

申请10个对象

OBject *op1 = new Object[10];

申请10个空间,还会申请计数位置和头部信息。如下图:
如果使用:free(op1);delete(op1);会导致读取头部信息失败。(因为不会把记录的个数也认为是头部信息,最终在读的时候出问题)
只有使用 delete[]op1; 才能够获取头部信息和计数位置。

请添加图片描述

结论:

free无法调动析构函数来析构对象,delete无法将记录的对象个个数也认为是头部信息,所以只能使用delete[]来释放多个对象。

malloc和delete可不可以混用?

如果是内置类型,就可以。
如果是自己设置的类型,要看有没有析构函数。
如果有,如果申请多个对象,就需要调用10次析构函数。

虚析构函数的使用

class Object
{
private:int value;
public:Object(int x = 0) : value(x) { cout << "Obejct::Create: " << this << endl; }~Object() { cout << "Obejct::destroy: " << this << endl; }
};class Base : public Object
{
private:int num;
public:Base(int x = 10) : Object(x + 10), num(x){cout << "Base::Create: " << this << endl;}~Base() { cout << "Base::Destroy: " << this << endl; }
};int main(void)
{Object* op = new Base(10);delete op;return 0;
}

请添加图片描述
当有基类指针指向派生类的对象,如果基类和派生类都有析构函数,那么析构op指向的base对象时,使用的是基类的析构函数,。如果想连级调用base中的析构函数,必须将基类中的析构函数设置为虚函数,那么派生类中的析构函数就会覆盖掉基类中的析构函数,从而调用派生类中的析构函数。
采用虚析构

 virtual ~Object() { cout << "Obejct::destroy: " << this << endl; }virtual~Base() { cout << "Base::Destroy: " << this << endl; }

请添加图片描述

重置虚表

重置虚表的意义
当析构完Base中的资源,如果不把虚表指针指向Object的虚表,那么析构Object的时候,会再次析构Base中的add(),从而导致重复析构。

  • 在之前的代码析构函数中加入add()
class Object
{
private:int value;
public:Object(int x = 0) : value(x) { cout << "Obejct::Create: " << this << endl; }virtual void add() { cout << "Object::add" << endl; }~Object() {add();cout << "Obejct::destroy: " << this << endl; }
};class Base : public Object
{
private:int num;
public:Base(int x = 10) : Object(x + 10), num(x){cout << "Base::Create: " << this << endl;}virtual void add() { cout << "Object::add" << endl; }~Base(){add();cout << "Base::Destroy: " << this << endl; }
};
int main()
{Object* op = new Base(10);delete op;return 0;
}

请添加图片描述

构造函数:置虚表

:调用object类中的构造函数,创建隐藏基类对象,让该对象内保存的虚表指针,指向虚表。即为置虚表
上面的程序;是先new一个base对象,先构造隐藏基类对象,使其内的虚表指针指向Object的虚表,当在此对base实例化时,基类的虚表指针会指向Base的虚表。
如下如:
请添加图片描述

析构函数重置虚表

在析构过程中,析构函数没第一部是先将虚表指针重新指向该对象的虚表。
因此会出现这个结果:请添加图片描述
很明显,虚表指针指向Base的虚表时,析构base对象
虚表指针指向Object对象时,析构隐藏基类对象。
请添加图片描述

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
苁蓉的食用方法 鲜苁蓉的吃法有... 肉苁蓉号称“温而不热,补而不峻,暖而不燥,滑而不泄,故有从容之名。”历来也是传统药食同源的滋补佳品。...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
阳澄湖在哪里哪个省的 阳澄湖是... 点击题目下方苏州生活指南有用 有趣 有态度近期的阳澄湖度很高,3月24日,央视二套《第一时间》直播连...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...