C++面向对象之继承
C++继承
1.专有名词
1.基类(父类):已存在的类称为基类
2.派生类(子类):新建立的类称为派生类
3.继承:一个新类从已有的类那里获得已有特性,这种现象称为类的继承
4.单继承:一个派生类只从一个基类派生,这称为单继承
5.多继承:一个派生类有两个或多个基类的称为多继承
6.派生:从已有的类(父类)产生一个新的子类,称为类的派生
2.声明派生类的方式
class 派生类名:[继承方式] 基类名{ //派生类新增的成员 };
继承方式:
公用继承public 私有继承private 保护继承protect
3.继承的一些问题
1.派生类只能继承基类的所有成员,不能只继承部分成员
很容易多了一些不使用的数据,目前C++标准还未解决
2.派生类无法继承基类的构造函数和析构函数
在派生类中应该重新定义自己的constructor和decoder,而且还要调用基类的构造函数
3.派生类可以调整继承方式来改变基类成员在派生类中的访问属性
4.派生类成员的访问属性
访问属性的几种情况:
1.基类的成员函数访问基类成员 2.基类的成员函数访问派生类的成员 3.派生类的成员函数访问派生类的成员 4.派生类的成员函数访问基类的成员 5.在派生类外访问派生类的成员 6.在派生类外访问基类成员 派生类外看的时派生类内部成员的属性 ---------------------------------------------------------------------------- 1,3,5情况一定成立,并且访问属性只和本类中定义的属性相关 2情况不成立,基类无法访问派生类的成员 4,6情况复杂,他们的成员的访问属性和基类的成员访问属性相关,还和继承方式有关
5.继承方式
有继承时后,讨论成员访问属性要考虑是在基类中的,还是派生类中的访问属性,不同范围访问属性不同,也体现了多态性。
5.1 公有继承(public)
基类的公有成员和保护成员在派生类中保持原有访问属性,其私有成员仍为基类私有--派生类不可访问。
5.2 私有继承(private) -- 作用:不想让子类的子类访问基类的属性
基类的公有成员和保护成员在派生类中成了私有成员。其私有成员仍为基类私有--派生类不可访问。
5.3 受保护的继承(protected) -- 区别于private:还可以被派生类操作,
基类的公有成员和保护成员在派生类中成了保护成员。其私有成员仍为基类私有--派生类不可访问。
口诀:private继承,全private;protected继承全protected;public继承不变;
5.4 不可访问成员
基类的私有成员对于派生类来说就是不可访问成员
6.多级派生时的访问属性 【重点】
必须会分析,分析时候可以画每个类的盒图,同时标出相应的private,public,protected的成员
// 单继承例子 class A{ public: int i; protected: int j; void f1(); private: int k; }; class B: public A{ public: void f2(); protected: void f3(); private: int m; }; class C: protected B{ public: void f4(); private: int n; };
继承方式 public成员 protected成员 private成员 不可访问成员 A i f1(),j k B public i,f2() f1(),j,f3() m k C protected f4() i,j,f1(),f2(),f3() n k,m
7. 单继承派生类的构造函数
由于派生类无法继承基类的构造函数和析构函数,因此基类成员的初始化工作也要有派生类的构造函数承担
question1:在设计派生类的构造函数时要考虑如何对基类的成员进行初始化
解决办法:在执行派生类的构造函数时,调用基类的构造函数
1.派生类构造函数的一般形式:
1.1 构造函数在类内声明和定义
派生类构造函数名(总参数表):基类构造函数名(参数表) --- 这个就是初始化列表 {派生类中新增数据成员初始化语句} // :后面也可以添加本类成员的初始化列表
1.2 构造函数在类内声明,类外定义
类内声明 -- 不需要加基类的构造函数部分
派生类构造函数名(总参数表) {派生类中新增数据成员初始化语句}
类外定义 -- 需要加上基类构造函数部分
派生类名::派生类构造函数(总参数表):基类构造函数(参数表){ 派生类中新增成员的初始化语句 }
2.对象的构造函数和析构函数执行顺序:
1.先调用基类的构造函数 2.在调用派生类的构造函数 3.派生类释放对象时,先调用派生类的析构函数,在执行基类的析构函数
7.1 简单的派生类的构造函数
1.简单的派生类只有一个基类,而且只有一级派生
2.派生类的成员不包括基类的对象
7.2 有子对象的派生类的构造函数
子对象:就是类对象,可以是基类的对象等
1.有子对象的派生类的构造函数的格式:
有多个子对象,就在派生类构造函数中写多个---子对象名(参数表)---初始化
1.1 在类的内部定义和初始化
// 一般形式 派生类构造函数(总参数表):基类构造函数(参数表),子对象名(参数表){ 派生类成员初始化 } // 总参数表中还包含子对象的参数 // 这里使用子对象名,是为了防止子对象和基类是同一个类,这样就会出现二义性 // 全参数列表形式 派生类构造函数(总参数表):基类构造函数(参数表),子对象名(参数表),派生类的成员初始化列表{}
1.2 在类的内部声明,在类的外面定义
// 类内声明 派生类构造函数(总参数表){} // 类外定义 派生类构造函数(总参数表):基类构造函数(参数表),子对象名(参数表),派生类的成员初始化列表{ //语句 }
2.执行派生类构造函数顺序
1.调用基类构造函数 2.调用子类构造函数 3.在执行派生类构造函数本身
7.3 多层派生时的构造函数
多层派生构造函数格式: -- 这里只列举两层,也有多种格式同上,自己写吧,太累了
派生类构造函数1(总参数表1):基类构造函数(参数表),派生类1的成员初始化列表{} 派生类构造函数2(总参数表2):派生类1构造函数(参数表),派生类2的成员初始化列表{} // 类1继承基类 // 类2继承类1
构造函数执行顺序
1.调用基类构造函数 2.在调用第二级构造函数 3.执行本派生类的构造函数
8.派生类的析构函数
9.多重继承(multiple inheritance) -- 不提倡使用多继承,建议使用单继承
9.1 多继承语法和构造函数
1.多继承类定义的语法:
class 派生类名:[继承方式] 基类1,[继承方式] 基类2,...,[继承方式] 基类n{}
2.多继承构造函数语法:-- 类内和类外同上(类外定义时,类内声明不加:后面的东西)
派生类构造函数(总参数表):基类1构造函数(参数表),基类2构造函数(参数表),...,基类n的构造函数(参数表),派生类的成员初始化列表{}
3.多继承基类构造函数的执行顺序;
按照派生类构造函数定义时的顺序依次执行
4.当多继承的派生类中出现多个同名的变量名时,要使用时编译器无法识别:
解决办法;
在同名变量前面加上相应的"类名::",就是加入相应的作用域,可以解决二义性问题.
9.2 多继承引发的二义性问题
继承的成员同名而产生的二义性问题:同名成员是在基类之间的不包括派生类
引用同名成员时会出现二义性问题,编译器无法识别 解决办法; 在同名成员前面加”类名::",来区分同名的成员
继承的成员同名而产生的二义性问题:基类和派生类都有共同的同名成员
// 无需解决,引用同名成员时,默认引用的是派生类的同名成员,把基类同名的成员覆盖了
10. 虚基类 -- 解决有共同间接基类的子类中保存多份基类数据的问题
1.产生原因:
如果一个派生类有多个直接基类,而这些直接基类又有一个共同的基类,则最终的派生类中会保留该间接共同基类数据成员的多份同名成员。
2.作用
C++提供虚基类(virtual base class)的方法,使得在继承间承接共同基类时只保留一份成员。
3.虚基类的声明格式
class A{} class B:virtual public A{} class C:virtual public A{} // 声明基类为虚基类的格式 class 派生类名:virtual 继承方式 基类名{}
注意: 1.虚基类不是在声明基类时声明的,而是在声明派生类时,指定继承方式时声明的。 (因为一个基类可以在生成一个派生类时做虚基类,而在生成另一个派生类时不作为虚基类) 2.派生类在经过虚基类继承声明后,当基类通过多条派生路径被一个派生类继承时,该派生类只继承该基类一次,也就是说,基类成员只保留一次。
4.虚基类的初始化
class A{ public: A(int n){} }; class B:virtual public A{ public: B(int n):A(n){} }; class C:virtual public A{ public: C(int n):A(n){} }; class D: public B, public C{ public: D(int n):A(n),B(n),C(n){} };
注意: 1.在以前,在派生类的构造函数中必须负责对其直接基类初始化,再由其直接基类负责对间接基类初始化。 2.现在,由于虚基类在派生类中只有一份数据成员,所以规定:在最后的派生类中不仅要负责对其直接基类进行初始化,还要负责对虚基类进行初始化。 3.C++编译系统只执行最后的派生类对虚基类的构造函数的调用,而忽略其他派生类(B,C)对虚基类的构造函数的调用,这就保证了虚基类的数据成员不会被多次初始化。
eg:
class Person{ public: Person(string nam,char s,int a):name(nam),sex(s),age(a){} protected: string name; char sex; int age; }; class Teacher:virtual public Person{ public: Teacher(string name,char sex,int age,string title):Person(name,sex,age),title(title){} protected: string title; }; class Student:virtual public Person{ public: Student(string name,char sex,int age,float score):Person(name,sex,age),score(score){} protected: float score; }; class Graduate: public Teacher, public Student{ public: Graduate(string name,char sex,int age,string title,float score,float wage):Person(name,sex,age),Teacher(name,sex,age,title),Student(name,sex,age,score),wage(wage){} void show(); private: float wage; }; void Graduate::show(){ cout<<name<<" "<<sex<<" "<<age<<" "<<title<<" "<<score<<" "<<wage<<endl; }
11. 基类和派生类之间的赋值转换问题
1.自动转换:派生类->基类,因为派生类中有基类的数据成员,而基类中没有派生类特有的数据成员。 2.把子类赋值给基类(或基类引用,基类指针),则基类对象只可以引用子类中数据基类的成员,无法引用子类特有的成员。
1.派生类对象可以向基类赋值
注意:
这里派生类向基类赋值,是指在赋值时舍弃派生类自己特有的成员,把继承的数据成员赋值给基类对象。
基类无法向派生类赋值,因为派生类的特有数据成员,基类中没有,所以无法赋值成功。
// 基类A,派生类B A a; B b; a = b; // 把派生类b对象中a有的成员赋值给a
2.派生类对象可以替代基类对象向基类对象的引用进行赋值或初始化
A a1; B b1; A& r = a1; // r和a1共享同一内存空间,r是a1的别名 // 用派生类对象对基类的引用赋值 A& r = b1; // r不是b1的别名,也不共享同一段存储空间,r只是b1中属于基类部分的别名.
3.函数的形参是基类对象或基类对象的引用,实参可以使用子类对象
A a; B b; void fun(A &a); fun(b); // 形参a只能引用b中基类部分成员
4.派生类对象的地址可以赋值给指向基类对象的指针变量,也就是说,指向基类对象的指针变量也可以用来指向派生类对象
A *p = &b; // 则指针p还是只可以引用b中属于基类的那部分