》什么是继承:C++语言为了实现代码复用最重要的手段,允许我们在原有(基类)功能下扩增,形成一个新类,这个新类称为派生类或者子类。
》继承格式:class 派生类名(子类名):继承类型 基类名(父类名)
继承类型:基类成员在派生类中可见性(public,protected,private)
public继承:基类的非私有成员在子类中访问类型不变。
protected继承:基类的非私有成员在子类中访问类型变为protected属性。
private继承:基类的非私有成员在子类中访问类型都变为private属性。
不论何种继承基类的私有成员在派生类中都是继承下来的了只是都是不可见的。
几点总结:
1.基类私有成员在派生类中是不可访问的,如果想在派生类中访问基类非私有成员,而在类外不能访问就定义为protected继承。
2.public继承是一个接口继承保持is-a原则,每个父类可用的成员对子类也可用,每个子类对象也是一个父类对象。(即父类非私有成员皆为子类所用)
3.protected/private继承是一个实现继承,基类的部分成员并非完全成为子类接口的一部分,是 has-a 的关系原则,所以非特殊情况下不会使用这两种继承关系,在绝大多数的场景下使用的都是公有继承。(即子类从父类继承来的某些成员对外属性是保护或者私有)
4.不管是哪种继承方式,在派生类内部都可以访问基类的公有成员和保护成员,基类的私有成员存在但是在子类中不可见(不能访问)。
5.使用关键字class时默认的继承方式是private,使用struct时默认的继承方式public,不过最好显式的写出继承方式。
6.在实际运用中一般使用都是public继承,极少场景下才会使用protetced/private继承.
》讲一下继承的调用顺序:
基类构造函数->派生类中成员对象构造函数->派生类构造函数
按照继承列表调用 按照派生类中成员对象声明顺序调用
总结:1.基类构造函数没有缺省值,派生类必须要在初始化列表中显示的调用基类构造函数并给出参数。
2.若基类没有定义构造函数则派生类也不需要定义都使用默认的构造函数
3.基类定义了带有形参表的构造函数,则派生类就必须也要定义。
举个菱形继承的例子:(下面会讲到菱形继承)
class A
{
public:
A()
{
cout << "A()" << endl;
}
int data1;
};
class B:public A
{
public:
B()
{
cout << "B()" << endl;
}
private:
int data2;
};
class C:public A
{
public:
C()
{
cout << "C()" << endl;
}
private:
int data3;
};
class D :public B, public C
{
public:
D()
{
cout << "C()" << endl;
}
private:
int data4;
};
int main()
{
B b;
return 0;
}
这里程序执行结果为:A();
B();
创建了一个B类对象,先调用父类即A的构造函数然后再调用自己的构造函数。这里的构造函数什么都没做。
》继承体系的作用域:
1.父类与子类分属于两个作用域(所以父类的私有成员才不能被子类使用因为相当于类外调用父类的私有成员)
2.子类和父类有同名成员,子类成员将屏蔽父类对自己成员的直接访问 称为同名隐藏
在子类中除非使用 基类名::成员名 才会访问基类成员 称为重定义
》继承与转换 --赋值兼容规则(条件:必须是public继承)
1.子类对象可以复制给父类对象 Base b=Derive d;
2.父类对象不可以复制给子类对象。会越界
3.父类的指针,引用可以指向子类对象。Base *B; B=&d;
4.子类的指针,引用不可以指向父类对象。
》友元与继承
友元关系不能继承:即基类友元不能访问子类私有和保护成员。
》继承和静态成员:
若基类定义了一个静态成员,则无论有派生多少个子类,仅有这么一个静态成员。
》三种类型的调用方法:分别是
单继承:即保持直线继承
class A{}; class B:public A{}; class C:public B {};
多继承:即子类可以使用多个继承类型继承多个父类
class A{}; class B{}; class C:public A,public C{};
菱形继承:
class A{}; class B:public A{}; class C:public A {}; class D:public B,public C{};
上面菱形继承程序例子:
如果在主函数中定义D d;结果会是
A();
B();
A();
C();
D();
这样类D中就会有重复的类A中的成员,访问data1就会产生二义性,编译都通不过,因为他不知道你要调用哪一个data1,当然也可以使用d.B::data1;或者d.C::data1;来决定是哪个data1,不过我们在这块有一个更好的方法,那就是虚基类。
》虚继承:如果一个类有多个直接基类,且直接基类又有共同基类,则最底层的派生类中会保留这个间接共同基类的成员多份同名成员。
解决方法:1.在派生对象名前加作用域标识符使他唯一的标识一个成员
2.使用虚基类:虚基类声明:
class 派生类名:virtual 继承方式 类名{}
如果派生类中只存在一份间接基类的拷贝,则没有二义性。
》虚基类的初始化:与普通多继承的初始化在语法上是一样的,只是构造函数的调用不一样
1.虚基类如果定义带有形参的构造函数,或者定义普通的构造函数,则整个继承结构中,所有直接或者间接的派生类都必须在构造函数的成员初始化列表中对虚基类构造函数的调用,以初始化在虚基类中定义的数据成员。
2.建立一个对象时,如果这个对象含有从虚基类中继承来的成员,则虚基类的成员是由最远派生类的构造函数通过调用虚基类的构造函数来进行初始化的。该派生类的其它基类对虚基类构造函数的调用都忽略。
3.若同一层次中同时包含非虚基类和虚基类,应先调用基类的构造函数在调用非虚基类的构造函数。
4.对于多个虚基类,构造函数的执行顺序仍然是先左后右,自上而下。
5.对于非虚基类,构造函数的执行顺序仍然是先左后右,自上而下。
6.若虚基类是由非虚基类派生而来的,则仍然先调用基类构造函数,再调用派生类构造函数。
还有一个问题,virtual这个关键词应该在哪加?所有派生类声明内都加还是只在直接派生类前加?弄清这个问题就要分析派生类的内存里都有些什么东西,是按照什么顺序存储的?
class A
{
public:
A(int a=0)
:data1(a)
{
cout << "A()" << endl;
}
int data1;
};
class B : virtual public A
{
public:
B(int b=0)
:A(1)
, data2(b)
{
cout << "B()" << endl;
}
private:
int data2;
};
int main()
{
B b(2);
return 0;
}
类B内存中有:指针P-》data2-》data1;并且P指向data1.
调试程序,看到在类B内首先是一个指针,其次是类B自己的成员data2最后是类A的成员data1,并且这个指针存放的就是类A中data1相对于类A对象的偏移量。
这也就是说,子类中会保存一个地址指向虚基类的成员。
还是这个例子,只是这里是虚继承:
class A
{
public:
A(int a=0)
:data1(a)
{
cout << "A()" << endl;
}
int data1;
};
class B : virtual public A
{
public:
B(int b=0)
:A(1)
, data2(b)
{
cout << "B()" << endl;
}
private:
int data2;
};
class C : virtual public A
{
public:
C(int c=0)
:A(3)
, data3(c)
{
cout << "C()" << endl;
}
private:
int data3;
};
class D : virtual public B,virtual public C
{
public:
D(int d=0)
:A(2)
,data4(d)
{
cout << "D()" << endl;
}
private:
int data4;
};
菱形虚继承:当直接子类与间接子类定义前都加virtual时:
分析类B的内存:指针P1-》data2-》data1;
类C的内存:指针P2-》data3-》data1;
类D的内存:指针P3-》data4-》data1-》P1-》data2-》P2-》data3;
P1 P2 P3
只有直接子类前有virtual时:
分析类B的内存:指针P1-》data2-》data1;
类C的内存:指针P2-》data3-》data1;
类D的内存:P1->data2-》P2-》data3-》data4-》data1;