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

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


0.关于vptr和vtbl

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

1 继承关系内存布局

子类一定含有父类的部分(part)

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

2 静态绑定

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

//汇编代码
call xxxxxx

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

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

3.1.静态绑定

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

3.2.动态绑定

如果符合某些条件:

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

只要符合上面的三个条件,编译器就把代码编译成

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() 的形式如下:

(*(p->vptr)[n])(p);
//或者
(* p->vptr[n])(p);

即,调用虚函数之前,我们仍然不知道该函数对应的是哪个调用(到底是调用A::vfun1()呢,还是B::vfun1())。直到查找虚函数表,编译器才最终确定是哪个调用。所以这种虚函数和对象的绑定关系,就叫做动态绑定。

3.3 this指针

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

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

所以当我们是使用指针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中,我们已经足够了解,这里就不多做说明了。

转载于:https://my.oschina.net/lxrm/blog/659969

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值