目录树
1.继承
1.1 基类成员在派生类中的访问属性
1.2继承时导致的二义性
1.3 多基继承
2.虚函数的多态
2.1虚函数的定义
2.2派生类中可以根据需要对虚函数进行重定义
2.3 虚函数的访问
2.4哪些函数不能定义为虚函数
2.5虚函数表指针(vptr)和虚基类表指针(bptr)
2.5.1 虚函数表指针vptr
2.5.2含静态变量、虚函数的类的空间计算
2.5.3虚基类表指针
2.5.4 虚拟继承时构造函数的书写
2.5.5虚函数
3.运行时类型识别与显示转换
3.1 typeid
3.2 显式转换
1.继承
如果一个类有多个直接基类,而这些直接基类又有一个共同的基类,则在最底层的派生类中会保留这个间接的共同基类数据成员的多份同名成员。提出虚继承,虚继承时,公共基类在对象模型中只有一份拷贝。
1.1 基类成员在派生类中的访问属性
这里一定要区分清楚派生类对象和派生类中的成员函数对基类的访问时不同的。
1.2继承时导致的二义性
1)在公有继承下(私有,保护继承时,不能隐式转换)下,派生类的对象/对象指针/对象引用可以赋值给基类的对象/对象指针/对象引用(发生隐式转换)。但基类的对象/对象指针/对象引用不能赋值给派生类的对象/对象指针/对象引用。因为派生类包含了基类的所有信息,而基类缺乏派生类中的信息。
2)c++容许把基类的对象指针/对象引用强制转换为(显式)派生类的对象指针/对象引用。
3)一个指向基类的指针可以用来指向该基类公有派生类的任何对象,这是c++实现程序运行时多态性的关键。
1.3 多基继承
当继承基类时,在派生类中就获得了基类所有数据成员的副本,该副本称为子对象。
类mi会包含的的d1的子对象和d2的子对象。如果多个基类中存在同名成员的情况,造成编译器无从判断具体要访问那个基类的成员,则称对基类成员访问的二义性问题。(解决办法:可以加限定符基类)
2.虚函数的多态
多态是面向对象的精髓。可以概括为一个接口,多种方法。通俗来讲,多态是指同一个操作作用于不同的对象就会产生不同的响应。多态性分为静态多态和动态多态。其中的函数重载和运算符重载属于静态多态。虚函数属于动态多态性。c++是通过虚函数实现动态多态的。
2.1虚函数的定义
虚函数的定义是在函数原型前加一个关键字virtual即可。
如果一个基类成员定义为虚函数,那么,他在所有派生类中也保持为虚函数,即使在派生类中省略了virtual关键字,也任然是虚函数。
2.2派生类中可以根据需要对虚函数进行重定义,重定义的格式要求:
1)与基类的虚函数有相同的参数个数;
2)与基类的虚函数有相同的参数类型;
3)与基类的虚函数有相同的返回类型。或者与基类的虚函数相同,或者返回指针(引用),并且派生类虚函数所返回的指针(引用)类型是基类中被替换的虚函数所返回的指针(引用)类型的子类型(派生类型)。
2.3 虚函数的访问
虚函数可以通过对象名访问,此时编译器采用的是静态编译。
1)通过对象名访问虚函数时,调用哪个类的函数取决于定义对象名的类型。对象类型是基类时,就调用基类的函数。对象类型是子类时就调用子类的函数。
2)使用指针访问非虚函数时,编译器根据指针本身的类型决定要访问那个函数,而不是根据指针指向的对象类型。
3)使用指针访问虚函数时,编译器根据指针所指对象的类型决定要访问那个函数,而不是根据指针本身的类型。
4)使用引用访问虚函数,与使用指针访问虚函数类型,不同的是,引用一经声明,引用变量本身无论如何改变其调用的函数都不会改变,始终指向开始定义时的函数。在一定程序上提高了代码的安全性。(受限制的指针)
总结:
c++中函数调用默认不使用动态绑定,要触发动态绑定需满足两个条件:
a.只用指定为虚函数的成员函数才能进行动态绑定,成员函数默认为非虚函数,非虚函数不能进行动态绑定。
b.必须通过基类类型的引用或指针进行函数调用。
2.4哪些函数不能定义为虚函数
普通函数(类的非成员函数),静态成员函数,构造函数,友员函数。而内联成员函数和赋值操作符重载函数即使声明为虚函数也毫无意义。
2.5虚函数表指针(vptr)和虚基类表指针(bptr)
2.5.1 虚函数表指针vptr
1)每个类产生一堆指向virtual functions 的指针,放在表格中,这个表格称为虚函数表。
2)每个对象被添加一个指针,指向相关的virtual table,通常这个指针被称为vptr(虚函数表指针)vptr的设定和重置由每一个类的构造函数,析构函数和复制构造函数自动完成。每个类所关联的type_info信息(用以支持rtti)也由virtual table 指出。通常放在表格的第一个slot处。
2.5.2含静态变量、虚函数的类的空间计算
sizeof应用在类和结构的处理情况是相同的。但是在类中的静态成员不对结构或类的大小产生影响。
2.5.3虚基类表指针
c++支持单一和多重继承。
class iostream:public istream,public ostream{...};
继承关系也可以指定为虚拟(virtual 共享的意思)
class istream :virtual public ios{...};
class ostream :virtual public ios{...}; //菱形继承
在虚拟继承中,基类不管在继承串链中被派生多少次,永远只会存在一个实体。
在虚拟继承基类的之类中,子类会增加某种形式的指针,或者指向虚基类子对象,或者指向一个相关的表格,表格中存放的不是虚基类子对象的地址,就是其偏移量。此指针叫bptr.
2.5.4 虚拟继承时构造函数的书写
从A类直接虚拟派生(B和C)和间接派生(D)的类中,其构造函数的初始化列表中都要列出对基类A构造函数的调用。这种机制保障不管有多少层继承,虚基类的构造函数必须且只能被调用一次。
若在初始化列表中没有显式调用虚基类的构造函数,则将调用虚基类的默认构造函数。若虚基类没有定义默认构造函数,则编译错误。
2.5.5虚函数
在基类中不能给出虚函数有意义的实现,而把它声明为纯虚函数,他的实现留给基类的派生类去做。
a.两种常见的抽象类
凡是有纯虚函数 的类叫抽象类(抽象接口),这种类不能声明对象,只是作为基类为派生类服务。除非在派生类中完全实现基类中的所有纯虚函数,否则,派生类也是抽象类,不能实例化对象。
只定义了protected型构造函数的类也是抽象类。对于一个类,如果只定义了protected构造函数而没有提供public构造函数,无论是外部还是派生类中都不能创建该类的对象,但能派生出新类,这种能派生出新类,但不能创建自己对象的类叫另外一种形式的抽象类。
3.运行时类型识别与显示转换
3.1 typeid
c++通过下面两种操作符提供RTTI
1)typeid 操作符,返回指针或引用所指对象的实际类型。
2)dynamic_cast操作符,将基类类型的指针或引用安全的转换为派生类型的指针或引用。
只有当typeid的操作数是带虚函数的类类型的对象时,才返回动态类型信息。
3.2 显式转换
c++中的显式转换也称强制类型转换。包括static_cast、dynamic_cast、 const_cas、 reinterpret_cast。
在引用命名的强制类型转换操作符之前,显式强制类型转换用圆括号将类型括起来实现。
1) reinterpret_cast
int *ip;
char *pc=(char*) ip;
效果与reinterpret_cast相同。
int *ip;
char *pc=reinterpret_cast<char*> ip;
2) const_cast
除了添加和删除const特性,用const_cast符执行其他任何类型转换都会引起编译错误。
3)static_cast
编译器隐式执行的任何类型转换都可以由static_cast显式完成。
只有当类型之间可隐式转换时(除类层次间的下行转化除外),static_cast的转换才是合法的。否则出错。类层次间的下行转化不能通过隐式转换完成。
使用static_cast完成下行转换(把基类指针或引用转换为子类指针或引用),由于没有动态类型检查,所以不安全。
c++的基本类型的指针之间不含隐式转换(除void*)需要显示转换。故char* 不能隐式转换为int*。
4)dynamic_cast
把expression转换为type类型的对象,type必须是类的指针,类的引用或void*
a.tpye和expression的类型需要一致,都为指针或引用。
与其他转换类型不同,dynamic_cast 涉及运行时类型检查,而这个运行时类型信息存储在类的虚函数表里,只有定义了虚函数的类才有虚函数表,对没有虚函数表的类在使用时会导致dynamic_cast编译错误。如果绑定到引用或指针的对象的类型不是目标类型,则dynamic_cast失败。如果转换到指针类型的dynamic_cast失败,则dynamic_cast的结果为0.如果转换到dynamic_cast失败,则抛出一个bad_cast类型的异常。dynamic_cast操作符执行两个操作。首先验证请求的转换是否有效,操作符执行的验证是在运行时进行的。
b.dynamic_cast主要用于类层次间的上行转换和下行转换
dynamic_cast运算符可以在执行期决定真正的类型。如果下行转换时安全的(基类的指针或引用执行一个派生类对象)这个运算符会传回转型过的指针。如果downcast不安全,这个运算符会传回空指针(基类的指针或引用没有指向一个派生类对象)
在类间进行上行转换时,dynamic_cast和static_cast的效果一样。
在类间进行下行转换时,dynamic_cast具有类型检查功能,比static_cast更加安全。
在运行dynamic_cast时必须包括多态类型。static_cast则没有这个限制。