文章目录
- 动态联编的条件:
- 联编的概念:
- 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对象时,析构隐藏基类对象。
 



















