首先,我们来认识一下类与类之间的关系有哪几种:
1、has_A 组合 包含关系,类中包含另一个类的对象
2、use_A 使用关系,类中使用了另一个类的成员函数
3、is_A 继承关系,B是特殊的A,B从A继承过来,A中有的B中都有
继承的语法: class 类名 :访问控制权限 要继承的类的名字
class C_XY:public XY {}; // C_XY是继承自XY的子类(派生类),XY是C_XY的父类(基类)
1、访问控制权限:public 、protect 、private 主要讲一下protect属性,是保护属性,类的内部可用,外部不可用,其派生类的内部可用,外部不可用
1)公有继承(public)下,父类→子类:公有→公有(内外可用),保护→保护(内可用),私有→私有(不可用)。
2)保护继承(protect)下,父类→子类:公有→保护(内可用),保护→保护(内可用),私有→私有(不可用)。
3)私有继承(private)下,父类→子类:公有→私有(内可用),保护→私有(内可用),私有→私有(不可用)。
判断子类的访问权限可以依次判断:类的内外→继承方式→基类属性
2、对象模型:子类的排列方式是基类成员在前,本身的成员在后
3、类型兼容性原则:在需要基类对象的任何地方,都可以使用公有派生类的对象来替代,在替代之后,派生类对象就可以作为基类的对象使用,但是只能使用从基类继承的成员。
1)子类对象可以当作父类对象使用
2)子类对象可以直接赋值给父类对象
3)子类对象可以直接初始化父类对象
4)父类指针可以直接指向子类对象
5)父类引用可以直接引用子类对象
4、继承中的构造和析构:
1) 派生类在进行构造时要调用父类的构造函数对父类变量进行初始化;
派生类新增的变量要调用自己的构造函数进行初始化;
对父类的成员参数初始化,在初始化列表中调用父类的构造函数。
2) 继承和组合混搭时的顺序:先父类,再子类对象,最后之类的构造函数。
5、派生类中有和基类同名的成员变量会屏蔽基类的成员,当然真正需要使用时,可以使用域解析符::来进行区分,如
b.A::a = 10;
6、重载和重定义:
1) 类中的函数重载必须发生在同一个类中;
派生类不能重载基类的函数,重载会屏蔽基类的所有同名函数,但同样,还是可以使用域解析符的。
2) 派生类的函数和基类的函数 函数原型一样,叫函数的重定义。重定义是发生在基类和派生类之间的。
7)static 关键字的继承
基类的静态变量是所有派生类共享的,因为它存在数据域中。 静态变量也遵循继承的访问控制。
8)多继承:一个类有多个基类的继承关系
class C : public A, public B{};
1) 派生类的基类的构造顺序和在继承列表中的声明顺序有关。
2) 多继承中的指针偏移:
A *pa = &c;
B *pb = &c;
C *pc = &c;
此时,pa 和 pc 指向 继承类中父类A成员的起始地址,但pb则指向的是继承类中父类B成员的起始地址,与pa和pc相差一个A的距离
3)但是,当一个派生类从多个基类派生,而这些基类又有一个共同的基类,则在对该基类声明的名字进行访问时会产生二义性问题。
公共基类的调用不明确(::可以解决);
虽说调用不明确问题可以解决,但在构造派生类的时候公共基类的构造函数却还是会被调用2次,产生问题。
4)为了解决二义性问题,C++便引入了虚继承的概念,使公共基类在派生类中只产生一个子对象。
关键字: virtual
class B1: virtual public A{};
运用virtual 关键字之后,构造函数就只会被调用一次。但是当我们用sizeof 来测试类的大小时,发现类不仅没有变小,反而变大了。
经过一系列的测试,发现,在基类的父类成员中多了一个vbpter这样的虚基类指针:
该指针包含2个内容:当前类指针距离虚基类指针的偏移量 、 基类指针和虚基类指针的偏移量。
class A
{
public:
int a;
};
class B1 : virtual public A
{
public:
int b1;
};
class B2 : virtual public A
{
public:
int b2;
};
class C :public B1,public B2
{
public:
int c;
};
int main()
{
C c;
c.a = 10;
c.b1 = 20;
c.b2 = 30;
c.c = 40;
A *pa = &c;
B1 *pb1 = &c;
B2 *pb2 = &c;
C *pc = &c;
printf("pa = %p, pb1 = %p, pb2 = %p, pc = %p\n",pa,pb1,pb2,pc);
return 0;
}
运行结果为:
pa = 00CFF90C, pb1 = 00CFF8F8, pb2 = 00CFF900, pc = 00CFF8F8
这样的一个测试程序,从运行结果我们可以看出,pc和pb1 确实是指向B1首地址,而pb2与其相差8个字节,pa与其相差20个字节,所以vbptr确实是20和12。