继承的相关特性

目录

1、对派生类中继承自基类的成员的访问方式的变化

2、继承与友元

3、基类和派生类对象赋值转换

4、继承中的作用域

5、继承体系中调用构造函数和析构函数的顺序

6、派生类的默认成员函数

7、菱形继承

8、在继承体系中完全不需要用户编写的默认成员函数


1、对派生类中继承自基类的成员的访问方式的变化

总而言之,首先看基类的访问方式和派生类的继承方式,哪种方式更小那哪种就是基类成员在派生类中的访问方式,如果发现这么算下来的访问方式为private,那么还需要判断,如果这个private是因为在基类中就是private,那么在派生类中,对这个成员依然不可见,如果这个private是因为继承方式为private,那么在派生类中对该成员是可见的。

1.基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。

2.基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
3.实际上面的表格我们进行一下总结会发现,基类的私有成员在子类都是不可见。基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected > private。
4.使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。比如class A:B等于class A:peivate B。
5.在实际运用中一般使用都是public继承,几乎很少使用protetced / private继承,也不提倡使用protetced / private继承,因为protetced / private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。
6.在整个继承体系种static成员只有一份。static成员是属于类的而不是个别类对象的。
 

2、继承与友元

友元关系不能被继承,比如说:B类为A类的友元,当C类继承B类时,C类和A类没有友元的关系。

3、基类和派生类对象赋值转换

1.派生类对象可以赋值给基类的对象/基类的指针/基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去。

2.基类对象不能赋值给派生类对象。

3.基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才是安全的。这里基类如果是多态类型,可以使用dynamic_cast来进行识别后进行安全转换。(ps:这个我们后面c++11再讲解,这里先了解一下)

4、继承中的作用域

1.在继承体系中基类和派生类都有独立的作用域。

2.需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏,不需要看参数或者返回值,这里不会构成函数重载,因为函数的作用域不相同。

3.派生类和基类中有同名成员时,会隐藏基类成员,也叫重定义基类成员。在子类成员函数中,可以使用基类::基类成员访问被隐藏起来的基类成员。

4.注意在实际中在继承体系里面最好不要定义同名的成员。

5、继承体系中调用构造函数和析构函数的顺序

1.对于派生类来说,构造时是有顺序的,继承自基类的成员先构造,派生类的成员后构造。析构时也有顺序,派生类的成员先析构,继承自基类的成员后析构。即派生类对象初始化先调用基类构造再调派生类构造,派生类对象析构清理先调用派生类析构再调基类的析构。

6、派生类的默认成员函数

1.在派生类的构造函数中必须调用基类的构造函数初始化派生类继承自基类的那一部分成员,当基类没有默认构造函数时,必须在派生类构造函数的初始化列表中显示调用基类的构造函数。为什么不可以在派生类的构造函数的函数体中,也就是花括号中调用基类的构造函数初始化派生类对象中继承自基类的成员呢?因为调用构造函数的格式像在创建一个匿名对象,但实际上不是在创建匿名对象,假如基类的类名为A,则调用A类的构造函数的格式为A(),这是语法规定这么写的,之后编译器会为我们处理。如果在花括号里写A(),编译器就认为你在创建一个局部匿名的A类对象,而不是在初始化派生类对象中继承自基类的成员了。

 初始化派生类对象中继承自基类的成员的错误格式如下

4fbe35ca02e043cd96ae3253dcc770e6.png

 初始化子类对象中的父类成员的正确格式如下

f39179f1cd7d47b589ad5f16af73ab4a.png
 

当基类有默认构造函数(即不需要任何参数的构造函数)时,此时去初始化派生类中继承自基类的成员,可以不在派生类的初始化列表中显示调用基类的构造函数。

没有默认构造还不在派生类的初始化列表中显示调用基类的构造函数就会报错,如下图。

aee33e217982407eba9720c3b1e3464e.png

基类有默认构造,此时无需在派生类的构造函数中显示调用基类的构造函数。

7a4a636517364e849a9f232ebbe22218.png

上面两组结果对照可以得出一个结论:在派生类构造函数的初始化列表中有可能显示写基类的构造函数,也有可能不显示写基类的构造函数,但绝对不可以在派生类构造函数的函数体中写基类的构造函数。

问题:上面都是调用基类的构造函数初始化派生类对象中继承自基类的成员,那可不可以不调用构造函数,而是自己手动初始化一个个成员呢?

答案:不可以。如下图写法是错误的,可以理解为派生类初始化时是合成的,派生类的成员(这里的成员不包括从基类继承的成员)由派生类的构造函数初始化,如果在派生类构造函数的初始化列表里显示初始化基类成员,会报错,如下图中红色下划波浪线处报错。

3ba75769876749a994ef9ebcb5a0f2b3.png

2.派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。派生类的拷贝构造函数需要独属于派生类的成员调用派生类的拷贝构造,继承自基类的成员调用基类的拷贝构造。如下图,派生类拷贝构造的写法和派生类构造函数的写法一样,只是参数变成了对派生类对象的引用。tips:Person中只有一个成员_name。

6a4a293b30664f0e93476426a0922e91.png

3.必须在派生类的operator=()中调用基类的operator=()完成对基类成员的复制。对于赋值运算符 operator=()来说,注意它也是函数,由于派生类和基类的函数名相同,所以派生类中隐藏了基类的赋值运算符,如果不突破类域访问基类的运算符,会造成无限递归自己的赋值运算符,最后造成栈溢出,所以编写派生类的赋值运算符的方法应该如下图。

3a6a6259546144d4975e8de7345a0fc1.png

4.派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。对于析构函数来说,这里和上面的其他默认成员函数有些不一样,虽然派生类和基类的析构函数名不相同,但这里派生类的析构和基类的析构会构成隐藏,原因是多态需要,派生类和基类的析构函数的名字都会统一成destructtor(),并且不需要在派生类的析构函数中手动调用基类的析构函数,而在派生类的其他默认成员函数中都是需要手动调用基类对应的成员函数的。

析构函数没有初始化列表,所以释放派生类对象中继承自基类的成员时只能在派生类的析构函数体中调用基类的析构函数。还有一个特殊的点就是不管你显不显示在派生类的析构函数体中写基类的析构,派生类析构结束时都会自动调用父类的析构,换言之如果在派生类显示写了基类的析构,那么基类的析构次数会是派生类的双倍次。

71f81232f8d1482d840a63e6e66e1e2f.png

5.因为后续一些场景析构函数需要构成重写,重写的条件之一是函数名相同。那么编译器会对析构函数名进行特殊处理,处理成destrutor(),所以父类析构函数不加virtual的情况下,子类析构函数和父类析构函数构成隐藏关系。基类与派生类的析构函数应该为虚函数(即加virtual关键字),如下图实验。

#include<iostream.h>
classBase
{
public:
virtual ~Base(){cout<<"~Base"<<endl;}
};


classDerived:publicBase
{
public:
virtual ~Derived(){cout<<"~Derived"<<endl;}
};


void main(void)
{
Base*pB=newDerived;//upcast
delete pB;
}

///
输出结果为:
~Derived
~Base
如果析构函数不为虚,那么输出结果为:
~Base

6.基类的构造函数、析构函数、赋值函数都不能被派生类继承。

7、菱形继承

有ABCD四个类,假如A类成员只有一个整形变量_a,B类成员只有一个整形变量_b,其他类同理。假如B类和C类都继承A类,然后D类再继承B和C,此时D类成员中就有两个整形变量_a,假设D的实例叫x,那么正确访问两个_a的格式为x.B::_a和x.C::_a。错误格式为x._a,它会导致二义性,编译器不知道该访问哪个。这是菱形继承的第一个缺陷,第二个缺陷就是数据冗余,毕竟D类间接继承了两份A类的所有成员。

如何解决呢?通过虚拟继承。拿上面情景举例,只需要将B和C类继承A类时的继承方式都改成virtual继承即可。D类继承B和C时不用加virtual。
 

被虚拟继承的基类也被称为虚基类,只有当出现了菱形继承的情况时,才需要在继承列表中的类前面加virtual,虚基类也是可以被实例化的。

8、在继承体系中完全不需要用户编写的默认成员函数

对于其他默认成员函数,如取地址重载(&)一般是不用自己写的,因为就算是派生类,也不需要取父类对象的地址,只需要自己类对象的地址就行。

f7c251ce0d5d48448ae6fbdfacaec5c8.png

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值