虚指针vptr
和虚表vtbl
,通俗的说,二者主要用途就是,在继承关系中肯定虚函数具体调用哪一个函数时用的。c++
子类必定含有父类的部分(part)数组
对于函数,继承的话,是继承调用权,而非函数的空间。函数
函数初始化的时候就知道成员的地址了,形如布局
//汇编代码 call xxxxxx
C++看到一个函数,有两个选择:学习
//汇编形式: //__call_@ 0xABDI398A4@func1
若是符合某些条件:ui
只要符合上面的三个条件,编译器就把代码编译成this
class A{ public: virtual void vfun1(); void func2(); }; class B:public A{ public: virtual void vfun1(); void fun2(); }; //调用 A p=new B();//B是A的派生类,而且调用了虚函数 p->vfun1();//虚函数,覆盖了父类的虚函数,动态绑定 p->fun2(); //普通函数,静态绑定,无需动态肯定,毕竟B里面原本就有了
编译器看来,p->vfun1() 的形式以下:spa
(*(p->vptr)[n])(p); //或者 (* p->vptr[n])(p);
即,调用虚函数以前,咱们仍然不知道该函数对应的是哪一个调用(究竟是调用A::vfun1()
呢,仍是B::vfun1()
)。直到查找虚函数表,编译器才最终肯定是哪一个调用。因此这种虚函数和对象的绑定关系,就叫作动态绑定。指针
其中,n
就是这个虚函数在虚表中的顺序索引,须要了解的是:code
编译器在编译初期就把虚函数定义的顺序记录了下来,并对作了索引关联
因此当咱们是使用指针p
(向上转型)查找虚函数(p->ptr[n])
的时候,虚指针就肯定了对应虚函数的地址,而后再调用该虚函数*(p->ptr[n])()
,实参为p
,即*(p->ptr[n])(p)
说了那么多,其实p就是this
上面3.3
小节中说到的动态绑定(即,调用虚函数的时候才能肯定是哪一个对象来调用),实际上就是继承关系中的多态。
其中,vptr
是当基类定义了虚函数的时候,若是子类继承了基类
,那么子类在实例化的时候,虚指针就能够指出调用的是哪一个函数了。
举个例子:
有时候咱们须要在容器中存放不少不一样的水果,香蕉、苹果、梨,可是容器只能存放一种东西,因此只好存指针(指针才是无差异的)。那么,存储什么类型的指针呢?咱们这么多种类的东西,都是水果,因此,应该放水果的指针进去,根据以前的向上转型的例子,能够看出,具体水果能够转型为基类水果类型。
std::list<Fruit*> myList;
咱们定义了一个myList,该容器存放的是 Fruit*
这种类型的指针。
class Fruit{ public: virtual print(){ std::cout << "I'am Fruit!";} }; class Apple:public Fruit{ public: virtual print(){ std::cout << "I'am Apple!";} }; class Banana:public Fruit{ public: virtual print(){ std::cout << "I'am Banana!";} }; class Pear:public Fruit{ public: virtual print(){ std::cout << "I'am Pear!";} }; //那咱们就能够在里面放各类派生类型了 myList.push_back(new Apple()); myList.push_back(new Banana()); myList.push_back(new Pear()); Fruit pApple = new Apple(); pApple->print();//虚函数,调用派生类本身的虚函数
new 和 delete 在C++中分别是建立和回收堆内存的配对操做符。既然是操做符,那么通常就能够重载。
可是,下面这段话,并非操做符new真正的调用,此处只是一个关键字表达式,C++会将该关键字分配到具体的operator new()
上。
String *ps = new String("Hello world"); delete ps; //数组 String *p = new String[3]; delete [] p;
上面的例子,就是表达式,表达式分解以后就是operator new
和operator delete
两个函数调用,以及相应内存管理过程。 ``
其中,new String("Hello world")
,这是表达式,能够分解为如下几个动做。
try { void *mem =operator new (sizeof(String));//操做符函数,能够重载 String *ps = static_cast<String*>(mem);//转型 ps->String::String("Hello world");//构造 }
而,delete
操做符,扮演的是
p->~String(); //调用析构函数 operator delete(p);//释放内存
既然new和delete是操做符,那么就能够重载(c++规定能够重载的范围内)。 重载操做符,分两大类
对于全域范围内重载,有一个条件是,不能在某个特定的namespace重载,必须是全域的。
同时,new操做符返回的必须是void *
类型,第一个参数必须是size_t
类型。
inline void * ::operator new(size_t size);
delete操做符则相似
inline void ::operator delete(void * ptr);
在这个示例中,咱们学习了new的主要用途,就是用于自定义的特殊的内存分配。好比 basic_string 类型的内存分配,并非一般咱们看到的new String()对象就完了,该类还作了扩展,即在基本类型占用空间的基础上,还增长了extra大小的扩展空间,以做特殊用途。
这类特性,得以给咱们极大的内存管理上的便利。
另外,new 和delete在内存分配上是配对的,可是并不意味着,new以后必然会调用delete来释放空间。这一点,在ppt中,咱们已经足够了解,这里就很少作说明了。