[BoolanC++微专业] Week5笔记

来源:互联网 发布:php租房管理源码 编辑:程序博客网 时间:2024/05/22 11:33

一、虚函数和虚表:
上周的学习中学习了虚函数,在学习的时候,我一直有一个疑问,明明在派生类中定义相同名字的函数,就可以覆盖基类的函数,那么virtual的意义是什么呢?就单纯的显式表明这个函数在将来会被派生类的函数给override?实际上并不是这样,虚函数是实现多态的重要步骤下面给一个程序:

class fruit{public:    void func()    {        cout << "Fruit" <<endl;    }    virtual void vfunc()    {        cout << "virtual Fruit" << endl;    }};class apple:public fruit{public:    void func()    {        cout<< "Apple" <<endl;    }    void vfunc()    {        cout << "Virtual Apple" <<endl;    }};int main(int argc, char* argv[]){    fruit* f = new apple();     f->func();    f->vfunc();    return 0;}

最后输出的结果是

FruitVirtual Apple

这就牵涉到静态绑定和动态绑定的问题,当我们使用存在继承关系的类型时,必须将一个变量或者其他表达式的静态类型与该表达式表示对象的动态类型区分开来。表达式的静态类型在编译时总是已知的,它是变量声明时的类型表达式生成的类型,动态类型是变量或者表达式表示的内存中的对象的类型,动态类型直到运行时才可知。
以上面的程序为例:
因为func()并没有被声明为虚函数。所以在调用f->func()时候,它虽然指向的是一个apple类的内存快,但是f的类型还是 fruit*,所以调用的是 fruit类中的func()。
而vfunc()调用的时候,它使用的便是class中的vtbl(虚表)中动态指向的函数Apple::vfunc()。
需要注意的是,动态绑定只有我们通过指针或者引用调用虚函数才会发生。

OOP的核心思想便是多态性,我们把具有继承关系的多个类型称为多态类型,因为我们能使用这些内容的多种形式,而无需在意他们的差异。引用或者指针的静态类型与动态类型不同这一事实正是C++语言支持多态性的根本所在。
当我们使用基类的引用或者指针调用基类i定义中的一个函数时,我们并不知道该函数真正作用的对象是什么类型,因为它可能是一个基类对象也可能是一个派生类对象,如果该函数是虚函数,则直到运行时才会决定到底执行哪个版本,判断的依据是引用或者指针绑定的对象的真实类型。
另一方面,对非虚函数的调用在编译时进行绑定,类似的,同过对象进行的函数调用也在编译时进行绑定。

这里写图片描述

虚函数的调用,用c代码来实现的话,就是上图中这一句:

(*(p->vptr)[n])(p);(* p->vptr[n] )(p);

另附上侯捷老师的动态绑定汇编代码:
这里写图片描述

这里写图片描述

二、重载new和delete:
首先我们来分析一下 new和delete的工作机制。
new:
第一步,调用一个名为operator new 或者 operator new[]的标准库函数。
第二部,编译器运行相应的构造函数以构造这些对象,并传入初始值。
第三步,对象被分配了空间并构造完成,返回一个指向该对象的指针。
delete:
第一步,对指针所指向的对象或者指针所指向的数组中的元素执行对应的析构函数。
第二部,编译器调用operator delete或者operator delete[]的标准库函数释放空间。

如果希望控制内存分配过程,则它们需要定义自己的operator new和operator delete函数。编译器并不会对 这种重复的定义提出异议,反而会用我们的版本替换标准库定义的版本。

operator new和operator delete可以定义在类内也可以定义在全局作用域内,在使用时优先在类内搜索new和delete重载。但是我们可以使用::new和::delete来使用全局作用域内的重载函数。

标准库一共有8个delete和new的版本,在这里我们讨论其中4个:

void *operator new(size_t);void *operator new[](size_t);void operator delete(void *) noexcept;void operator delete[](void *) noexcept;

我们可以重载上述函数的任意一个,当我们将上述运算符函数定义成类的成员时,它们是隐式静态的。
因为new作用在对象构造之前,而delete作用在对象析构之后。

我们可以自定义具有任何形参的operator new,但是有一个函数无论如何不可以被用户重载:

void *operator new(size_t,void*);

下面是一个简单的delete和new的重载:

void *operator new(size_t size){    if(void *mem = malloc(size))    {        return mem;    }else    {        throw bad_alloc();    }}void operator delete(void *mem) noexcept{    free(mem);}
0 0
原创粉丝点击