继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类或子类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用。
下面我们看到Person是父类,也称作基类。Student是子类,也称作派生类。
C++类的访问限定符用于控制类的成员(包括成员变量和成员函数)在类的外部的可访问性。C++中有以下三种访问限定符:
需要注意的是,访问限定符只在类的内部起作用,在类的外部没有直接的影响。同时,访问限定符可以用于类的成员变量和成员函数的声明中,默认情况下,成员变量和成员函数的访问限定符是private。
C++类的继承方式有以下几种:
总结如下:
类成员/继承方式 | public继承 | protected继承 | private继承 |
---|---|---|---|
基类的public成员 | 派生类的public成员 | 派生类的protected成员 | 派生类的private成员 |
基类的protected成员 | 派生类的protected成员 | 派生类的protected成员 | 派生类的private成员 |
基类的private成员 | 在派生类中不可见 | 在派生类中不可见 | 在派生类中不可见 |
①基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected > private
这些继承方式可以根据具体的需求选择合适的方式
②基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。
例如:
结果如下:
上述父类Person中成员有三种访问限定分别是public、protected、private,而子类Student使用public继承父类,那么对于父类的公有成员在子类中的访问方式还是public,protected成员访问方式选择继承方式public和protected中较小的protected,同理父类的private成员继承到子类中也是选择private方式,在子类中不可访问
对于私有成员也是被继承到子类中,只是不可访问:
③基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
④ 使用关键字class
时默认的继承方式是private
,使用struct
时默认的继承方式是public
,不过最好显示的写出继承方式。
⑤在实际运用中一般使用都是public
继承,几乎很少使用protetced/private继承,也不提倡使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。
如下图所示:
例如下面代码:
当一个类继承另一个类时,它可以重定义继承的成员函数或者成员变量。 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
父类::父类成员
来显示访问注意在实际中在继承体系里面最好不要定义同名的成员。
例如:
结果如下:
我们发现当子类与父类有隐藏关系时,对于同名变量_num的调用,除非显示使用
Person::_num
调用的是父类的成员变量,其他情况_num表示的都得子类中定义的变量,这是因为它们有不同的作用域,在子类中调用变量都是先从子类这个作用域中寻找。
再看下面的例子:
这里 B中的fun和A中的fun不是构成重载,因为不是在同一作用域 B中的fun和A中的fun构成隐藏,成员函数满足函数名相同就构成隐藏。
结果如下:
如果Test函数中:
结果如下:
使用对象b调用fun()没有给参数,这样编译是不通过的,因为这样调用是调用的类B中的成员函数fun是需要传参的,如果要调用基类中的fun函数就必须显示调用,代码如下:
结果如下:
6个默认成员函数,“默认”的意思就是指我们不写,编译器会变我们自动生成一个,那么在派生类中,(先不考虑取地址重载)这几个成员函数是如何生成的呢?
例如如下父类:
结果如下:
我们发现对于父类中的成员它会自动调用父类Person的默认构造函数与析构函数
当我们将基类的默认构造函数中的缺省值"tutu",去掉,它就不再是默认构造函数,那么在创建子类Student对象时就不会自动调用默认构造函数,会保错,那么这时我们就需要在初始化列表里显示调用
代码如下:
结果如下:
还有一种显示调用情况:
这种情况是不可取的,这是因为规定在初始化列表中是不可以使用父类的成员的
默认生成拷贝构造一般情况下够用,只有当子类成员涉及深拷贝时就必须自己实现拷贝构造
这里也可以自己显示实现一下拷贝构造:
注意这里
Person(st)
中调用Person中的拷贝构造实现赋值兼容
operator=
必须要调用基类的operator=
完成基类的复制。如果自己显示写析构函数:
因为多态的原因,析构函数的名字会被统一处理为destructor(),所以这里调用会构成隐藏,会循环调用,所以要指定作用域:
但是我们发现Person的析构函数居然调用了两次:
这是因为析构函数具有特殊性,在子类析构函数调用完之后会自动调用父类的析构函数,所以即便是自己显示实现了子类的析构函数也不需要自己主动调用父类的析构函数
所以不需要自己主动调用父类的析构函数,否则会报错
其核心原因在于初始化时先构造父类再构造子类,而析构时先析构子类再析构父类,因为子类析构时是可能用到父类成员的,先父后子可能会出错
所以为了保证先析构子类再析构父类,编译器会在析构了子类后自动调用父类的析构函数
总结如下:
默认成员函数\子类成员 | 内置成员 | 自定义成员 | 子类中的父类成员(整体) |
---|---|---|---|
默认生成的构造 | 不做处理 | 调用自定义类型的默认构造 | 调用父类的默认构造 |
默认生成的拷贝构造 | 值拷贝 | 调用自定义类型的拷贝构造 | 调用父类的拷贝构造 |
默认生成的赋值重载 | 直接赋值 | 调用自定义类型的赋值重载 | 调用父类的赋值重载 |
默认生成的析构函数 | 不做处理 | 调用自定义类型的析构函数 | 自动调用父类的析构函数 |
对于构造和析构: 派生类对象初始化先调用基类构造再调派生类构造。 派生类对象析构清理先调用派生类析构再调基类的析构
继承可以分为公有继承(public inheritance)、保护继承(protected inheritance)和私有继承(private inheritance)。继承在C++中的应用非常广泛,可以用于构建复杂的类层次结构,提供代码的复用性和灵活性。但是,在使用继承时也需要注意避免多层次的继承导致的类关系复杂性增加,以及合理设计基类和派生类之间的关系。以上就是今天的所以内容啦~ 完结撒花~ 🥳🎉🎉