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中属于基类的那部分
posted @ 2022-07-30 15:15  nanfengnan  阅读(80)  评论(0编辑  收藏  举报