C++ 知识
C++ 虚函数
C++ 虚函数是定义在基类中的函数,子类必须对其进行覆盖。
虚函数的作用
- 定义子类对象,并调用对象中未被子类重写的基类函数
A
。同时在该函数A
中,又调用了已被子类重写的基类函数B
。那此时将会调用基类中的函数B
,可我们本应该调用的是子类中的函数B
。虚函数即能解决这个问题。
1 |
|
Father::display()
用虚函数:
1 |
|
Son::display()
- 在使用 指向子类对象的基类指针,并调用子类中的重写函数 时,如果该函数不是虚函数,那么将调用基类中的该函数;如果该函数是虚函数,则会调用子类中的该函数。
1 |
|
Father::display()
1 |
|
Son::display()
虚函数的原理
虚函数的本质是一个简单的 虚函数表 。当一个类存在虚函数时,通过该类创建的对象实例,会在内存空间的前 4 个字节保存一个指向虚函数表的指针__vfptr
。__vfptr
指向的虚函数表是类独有的,而且该类的所有对象共享。虚函数表的实质是一个虚函数地址的数组,它包含了类中每个虚函数的地址,既有当前类定义的虚函数,也有重写父类的虚函数,也有继承而来的虚函数。
当子类重写了父类的虚函数时,子类虚函数表将包含子类虚函数的地址,而不会有父类虚函数的地址。同时,当用基类指针指向子类对象时,基类指针指向的内存空间中的 __vfptr
依旧指向了子类的虚函数表。所以,基类指针依旧会调用子类的虚函数。
定义一个有虚函数的类
1 |
|
定义两个对象:
1 |
|
两个对象的内存空间分配如下:
定义一个子类
1 |
|
定义一个子类对象:
1 |
|
其内存空间如下:
c++ 中的纯虚函数
纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加=0
1 |
|
在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。而针对每种动物的方法又有所不同,此时需要使用多态特性,也就需要在基类中定义虚函数。
纯虚函数是在基类中声明的虚函数,它要求任何派生类都要定义自己的实现方法,以实现多态性。实现了纯虚函数的子类,该纯虚函数在子类中就变成了虚函数。
定义纯虚函数是为了实现一个接口,用来规范派生类的行为,也即规范继承这个类的程序员必须实现这个函数。派生类仅仅只是继承函数的接口。纯虚函数的意义在于,让所有的类对象(主要是派生类对象)都可以执行纯虚函数的动作,但基类无法为纯虚函数提供一个合理的缺省实现。所以类纯虚函数的声明就是在告诉子类的设计者,“你必须提供一个纯虚函数的实现,但我不知道你会怎样实现它”。
含有纯虚函数的类称之为抽象类,它不能生成对象(创建实例),只能创建它的派生类的实例。抽象类是一种特殊的类,它是为了抽象和设计的目的为建立的,它处于继承层次结构的较上层。抽象类的主要作用是将有关的操作作为结果接口组织在一个继承层次结构中,由它来为派生类提供一个公共的根,派生类将具体实现在其基类中作为接口的操作。
抽象类只能作为基类来使用,其纯虚函数的实现由派生类给出。如果派生类中没有重新定义纯虚函数,而只是继承基类的纯虚函数,则这个派生类仍然还是一个抽象类。如果派生类中给出了基类纯虚函数的实现,则该派生类就不再是抽象类了,它是一个可以建立对象的具体的类。
虚析构函数
主要功能就是确保继承体系中的对象正确释放。
1 |
|
如果没有虚析构函数,则只会调用基类的析构函数,那么派生类中分配的内存就没办法释放,造成泄露。所以,如果发生继承,一定要把基类析构函数定义为虚函数。
C++ 中函数重载、重写和重定义
重载(overload):函数名相同,参数列表不同,重载只存在类的内部
重写(override):子类重新定义父类中有相同名称和参数的虚函数,存在于继承关系之间
- 被重写的函数不能是
static
的,必须是virtual
的 - 重写函数必须有相同的返回值类型、名称和参数列表
- 重写函数的访问修饰符可以不同
- 被重写的函数不能是
重定义(redefining):子类重新定义父类有相同名称的非虚函数
编译器如何解决命名冲突
编译后,重载的函数名各不相同。变名的机制:作用域 + 返回类型 + 函数名 + 参数列表,来解决命名冲突的问题
智能指针 shared_ptr
、weak_ptr
、unique_ptr
shared_ptr
只有指向动态分配的对象的指针才能交给 shared_ptr
对象托管。
不能用下面的方式使得两个 shared_ptr
对象托管同一个指针:
1 |
|
sp1
和 sp2
并不会共享同一个 p
的托管技术,而是各自将对 p
的的托管计数都记为 1。这样,当 sp1
消亡时要析构 p
,sp2
消亡时要再次析构p
,这会导致程序崩溃。
weak_ptr
weak_ptr
是一种用于解决 shared_ptr
相互引用时产生死锁问题的智能指针。weak_ptr
时对对象的一种弱引用,它不会增加对象的引用计数,weak_ptr
和 shared_ptr
可以相互转化,shared_ptr
可以直接赋值给 weak_ptr
,weak_ptr
也可以通过 lock
函数来获得shared_ptr
。
weak_ptr
指针通常不单独使用,只能和shared_ptr
类型指针搭配使用。将一个weak_ptr
绑定到一个shared_ptr
不会改变shared_ptr
的引用计数。一旦最后一个指向对象的shared_ptr
被销毁,对象就会被释放。即使有weak_ptr
指向对象,对象也还是会被释放。weak_ptr
并没有重载operator ->
和operator *
操作符,因此不可直接通过weak_ptr
使用对象,典型的用法时调用其lock
函数来获得shared_ptr
实例,进而访问原始对象。
unique_ptr
unique_ptr
是一个独享所有权的智能指针,unique_ptr
对象包装一个原始指针,并负责其生命周期。当该对象被销毁时,会在其析构函数中删除关联的原始指针。unique_ptr
重载了 ->
和*
运算符,因此可以像其他普通指针一样使用。
引用计数是在对上还是栈上
多个 shared_ptr
或weak_ptr
可以指向同一个被引用计数的对象。而其中一个或几个离开作用域被析构,强引用计数未归 0,指向的对象不能释放。因此不能放到栈上。