继承和派生
-
新声明的类如果需要包含原有类的所有成员与属性,除了傻不拉几的复制粘贴代码,还可以采用继承的方式定义新的类,继承也是面向对象的特性之一,即继承指新定义类包含了原有类的所有属性的这一性质,继承根据父类的数量可分为单继承和多继承
-
由基类继承而来的新类,除了具有基类的所有属性之外,还可定义自己特有的属性,这类对原有属性之外的增加称为派生,而新定义的类也可称为派生类(即:派生类是由基类继承、派生而来的类)
-
定义格式:
class 类名1 :继承方式 类名2,继承方式 类名3 …{};
-
设有定义类
class A{private:int a; protected: int b; public: int c};
则可定义以A为基类的三种继承方式的类:
-
公有继承:class B : public A {…};
-
私有继承:class C : private A {…};
-
保护继承:class D : protected A {…};
-
注:继承方式可省略,class默认为私有继承的继承方式,struct默认为公有的继承方式
-
不同继承方式下子类对于父类的数据访问权限改变
公有成员 私有成员 保护成员 公有继承 公有成员 不可访问 保护成员 私有继承 私有成员 不可访问 私有成员 保护继承 保护成员 不可访问 保护成员 即对于父类中的私有成员,以任何方式继承的子类都不可对其进行访问,对于父类中的公有成员和保护成员,公有继承后在子类中不改变父类的原有属性,私有继承后在子类中将全部转化为子类的私有成员,保护继承后在子类中将全部转化为子类的保护成员
-
私有继承与保护继承的区别:对于一重继承而言,私有继承与保护继承没有太大区别,但对于多重继承而言,保护继承下来的成员仍在第N重子类中属性为保护成员,可在类中正常访问,但私有继承由于在第一次继承后转化为私有成员,故对于之后的N-1个子类都将无法再对其中成员访问
-
基类与派生类中的同名成员访问原则(同名访问原则):在派生类中可以定义和基类中同名的成员,在对同名成员进行访问时始终默认访问派生类中的成员,若想要通过派生类对象访问同名成员中的基类定义成员,需要在成员名前加上基类域名空间
//e.g: class A{public:int a;}; class B : public A {public: long a;};
声明 B b1;对于b1.a默认访问类B中的公有成员long a若访问类A中的成员int a需加上类A的域名空间 b1.A::a
-
派生类对象可以直接对基类对象赋值,赋值实际是将派生类对象中属于基类的那一部分赋值给基类对象,但基类对象不可对派生类对象进行赋值,不同派生类之间也不可相互赋值,同理,基类引用也可引用派生类对象,引用实际是派生类对象中的基类部分,基类指针也可指向派生类对象,指向实际是派生类对象中的基类部分,其余部分会被忽略或舍弃(注意:以上行为仅可用于公有继承)
-
多继承中不同基类中同名成员的二义性:对于多继承而言,若所继承的多个基类中包含同名成员则采用同名成员访问原则进行访问,即成员名前加上所属基类的域名空间
-
多重继承下同一基类的重复继承:对于多继承类S而言,若所继承的多个基类中的几个都是由同一基类A继承而来,则在通过S对象访问A中定义的成员时,由于继承路径的不同,会被认定为不同基类中的同名成员,分配不同存储空间,需要使用同名访问原则指定继承路径进行访问
//e.g: class A{public: int a}; class B1 : public A {...}; class B2 : public A {...}; class C : public B2,public B1 {...};
(当想要通过C对象去访问int a时需要指定是由 B1 还是 B2 继承而来的基类A中的int a,即当 C c1; ,是c1.B1::a还是c1.B2::a)
-
虚基类:1.7中由于S中存在两个及以上的基类又是由同一基类A继承而来,实际上在声明实例的时候不同继承路径继承而来的同名成员会为其分配不同的存储空间,访问A中定义的成员时也需要表明所属继承路径,若在定义派生类时不需要这一性质,则可以在定义A的子类B1和B2时声明为虚基类继承,
//即: class B1 : virtual public A {...}; class B2 : virtual public A {...}; class C : public B1,public B2 {...};
则在声明C对象时只会为二重继承的类A成员分配一次空间,访问A中成员时可通过C直接访问(注意:并不是对类C定义本身时使用virtual关键字,而是对其包含相同继承的多个基类定义中使用
-
派生类的构造函数调用顺序:派生类声明对象赋初值时,先调用基类的构造函数,再调用对象成员所属类的构造函数,最后调用自己的构造函数(基类成员和对象成员的赋初值都只能采用参数列表的形式在派生类构造函数头后,函数体前用 :调用构造函数赋初值,而派生类中的其他成员也可以在函数体中用 = 赋初值,当基类或对象成员所属类中存在缺省参数构造函数时,上述行为可省略),当派生类所继承的基类也是派生类时(多重继承时),构造函数的调用顺序也循环嵌套遵循上述顺序,多继承派生类的基类成员构造函数的调用顺序由定义派生类时声明所继承基类的先后顺序决定,与参数列表中的先后书写顺序无关(如1.7中的C类对象声明时对于基类B1、B2的调用顺序是先调用B2再B1,实际上由于B1、B2也是派生类,故在调用B2构造函数前会先调用A类的构造函数),而存在虚基类继承声明的派生类优先虚基类的构造函数调用
-
派生类的析构函数调用顺序:派生类对象生存期满释放时,调用析构函数的顺序和构造函数的顺序相反,先调用自己的析构函数,再调用对象成员所属类的析构函数,最后再调用基类的析构函数,多继承派生类的基类的析构函数调用顺序也与构造函数相反,虚基类的析构函数最后调用,当派生类所继承的基类也是派生类时,析构函数的调用顺序也循环嵌套遵循上述顺序,且也与构造函数相反
-
当不想要一个类能够被继承时,可以使用c++提供的保留字final进行声明,注意声明为final的类如果加入虚函数或纯虚函数则会出现矛盾性错误。
注:c++中结构体也扩展了成员函数和继承与派生的概念,且完全符合上述继承性质与规则