文章目录
- 动态联编的条件:
- 联编的概念:
- 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.函数默认值(很重要)
(函数的默认值在编译结点就已经固定了。)
注意:
- 编译的过程和执行的过程一定要分开!!!
- 变量的生存期是在运行阶段确定的。
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;
}
值在编译阶段,就已经确认。
调用的是派生类方法。
**
问题:
结论:
- 虽然派生类的虚函数会覆盖掉同名的基类虚函数。但是函数默认值在编译阶段就已经确认了。
- 上部分代码,实际上,是因为已经编译完成,故基类指针可以访问到。但是派生类指针是无法访问的。因为派生类的默认值属于私有。
基类指针和派生类指针访问虚函数
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)
结论:
- 基类指指针只能访问派生类同名覆盖掉和继承的基类中的虚函数,基类中没有而派生类中有的虚函数访问不到。
- 而派生类指正均可。如果基类虚函数,派生类中没有,由于是公有继承关系,派生类也可以通过加作用域的方式进行访问。
delete和free的使用条件:
1. 没有析构函数时:
class Object
{
private:
int value;
public:
Object(int x = 0) :value(x) {cout<<"create construct" <<endl;}
//~Object() {}
};
- 创建一个对象
OBject *ip = new Object(10);
- 创建10个对象
OBject *ip = new Object[10];
int main()
{
//创建了10个对象
Object* op2 = new Object[10];
delete[]op2;//ok
delete op2;//ok
free(op2);//ok
}
int main()
{
//创建一个对象
Object* op1 = new Object(10);
free(op1); //ok
delete(op1); //ok
delete[]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对象时,析构隐藏基类对象。