# 极客班C++OOP(下)第五周笔记

极客班C++OOP(下)第五周笔记


0.关于vptr和vtbl

虚指针vptr和虚表vtbl,通俗的说,二者主要用途就是,在继承关系中肯定虚函数具体调用哪一个函数时用的。c++

1 继承关系内存布局

子类必定含有父类的部分(part)数组

对于函数,继承的话,是继承调用权,而非函数的空间。函数

2 静态绑定

函数初始化的时候就知道成员的地址了,形如布局

//汇编代码
call xxxxxx

3 动态绑定 ,以及 this的解释

C++看到一个函数,有两个选择:学习

3.1.静态绑定

//汇编形式:
//__call_@ 0xABDI398A4@func1

3.2.动态绑定

若是符合某些条件:ui

  • a) 经过指针
  • b) 指针是向上转型的关系
  • c) 调用的是虚函数

只要符合上面的三个条件,编译器就把代码编译成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())。直到查找虚函数表,编译器才最终肯定是哪一个调用。因此这种虚函数和对象的绑定关系,就叫作动态绑定。指针

3.3 this指针

其中,n 就是这个虚函数在虚表中的顺序索引,须要了解的是:code

编译器在编译初期就把虚函数定义的顺序记录了下来,并对作了索引关联

因此当咱们是使用指针p(向上转型)查找虚函数(p->ptr[n])的时候,虚指针就肯定了对应虚函数的地址,而后再调用该虚函数*(p->ptr[n])(),实参为p,即*(p->ptr[n])(p)

说了那么多,其实p就是this

3.4 多态

上面3.3小节中说到的动态绑定(即,调用虚函数的时候才能肯定是哪一个对象来调用),实际上就是继承关系中的多态。

  • vptr 虚指针 , 虚函数的指针
  • vtbl 虚表 , 虚函数地址表,顺序存储了对象虚函数的地址

其中,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();//虚函数,调用派生类本身的虚函数

4. new && delete

new 和 delete 在C++中分别是建立和回收堆内存的配对操做符。既然是操做符,那么通常就能够重载。

可是,下面这段话,并非操做符new真正的调用,此处只是一个关键字表达式,C++会将该关键字分配到具体的operator new()上。

String *ps = new String("Hello world");
delete ps;
//数组
String *p = new String[3];
delete [] p;

上面的例子,就是表达式,表达式分解以后就是operator newoperator 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);//释放内存

5. 重载 new和delete

既然new和delete是操做符,那么就能够重载(c++规定能够重载的范围内)。 重载操做符,分两大类

  • 全域重载
    • ::operator new , ::operator delete
    • ::operator new[], ::operator delete[]
  • 成员重载
    • A::operator new(), A::operator delete()
    • A::operator new[], A::operator delete[]

对于全域范围内重载,有一个条件是,不能在某个特定的namespace重载,必须是全域的。

同时,new操做符返回的必须是void *类型,第一个参数必须是size_t类型。

inline void * ::operator new(size_t size);

delete操做符则相似

inline void ::operator delete(void * ptr);

6. Basic_String 扩充申请量

在这个示例中,咱们学习了new的主要用途,就是用于自定义的特殊的内存分配。好比 basic_string 类型的内存分配,并非一般咱们看到的new String()对象就完了,该类还作了扩展,即在基本类型占用空间的基础上,还增长了extra大小的扩展空间,以做特殊用途。

这类特性,得以给咱们极大的内存管理上的便利。

另外,new 和delete在内存分配上是配对的,可是并不意味着,new以后必然会调用delete来释放空间。这一点,在ppt中,咱们已经足够了解,这里就很少作说明了。

相关文章
相关标签/搜索