【C++】继承 上篇
文章目录
- 1、概念及定义
-
- 1.1 概念
- 1.2 定义
- 2、class 与 struct 的区别
- 3、赋值兼容规则
- 4、继承中的作用域问题
- 5、派生类(子类)的默认成员函数
-
- 5.1 构造函数
- 5.2 拷贝构造函数
- 5.3 赋值运算符重载
- 5.4 析构函数
- 6、基类中哪些成员被子类继承了?
-
- 6.1 成员变量
- 6.2 成员方法
1、概念及定义
1.1 概念
继承主要的工作就是-----共性抽取
具体地讲:
①继承机制是面向对象程序设计使代码可以复用的最重要的手段;
②允许程序员在保持原有类特性的基础上进行扩展,增加功能。这样实现的类称为派生类/子类。基于实现该类的原有类称为基类/父类
③继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。(比如:animal—>dog---->kinds of dogs)
④继承是类层次设计的复用
1.2 定义
定义方式:class 派生类:继承方式 基类
继承方式可以是 public、protected、private
三种,他们在继承基类时,所具有特性以及表现出的结果也有所不同,具体如下:
- 以public的方式继承基类
结论:
在public的继承方式下:
①父类中的成员变量的访问权限,到子类中不会发生改变
②父类中的私有访问权限的变量在子类中不可见(不能直接被访问)
问题:类在设计的时候,访问权限应该如何选择?
应该遵循以下3点原则:
-
以protected的方式继承基类
结论:
在protected的继承方式下:
①基类中public修饰的 成员在子类中访问权限为protected
②基类中protected修饰的成员在子类中的访问权限依旧是protected
③父类中的private访问权限的变量在子类中不可见(不能直接被访问) -
以private的方式继承基类
结论:
在private的继承方式下:
①基类中public修饰的 成员在子类中访问权限为private
②基类中protected修饰的成员在子类中的访问权限为private
③父类中的private访问权限的变量在子类中不可见(不能直接被访问)
上面详细分析了每一种个情况,下面我们针对上面的结论进行汇总:
注意:
1、基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。
2、基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的
3、 在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。
2、class 与 struct 的区别
主要有以下3点区别:
3、赋值兼容规则
前提:一定在public的继承方式下才满足
- 可以直接使用子类对象给父类对象赋值,反过来不行
这个很好理解,具体和可以通过两个方面理解:
①子类和父类的关系是is–a的关系,因此使用子类给父类赋值时可以的
②从对象模型来说。
对象模型可以简单理解为成员变量在内存中的布局情况;
- 可以使用基类的指针指向子类的对象,反过来不行
如果一定要指向,必须强转,不推荐,仅仅是能通过编译,但是在使用的时候可能会造成程序崩溃
分析如下:
- 可以使用基类的引用去引用子类对象,反过来不行
引用在底层本质上就是使用指针实现的,因此它和指针的理解思路是一致的,这里就不再赘述。
4、继承中的作用域问题
明确:派生类和基类隶属于不同的作用域
那么,现在有这样一种情况:
基类和派生类中出现了同名的成员变量或成员方法。这种情况要如何去理解呢?
首先,他一定不是函数重载,因为函数重载的前提必须是在同一作用域。
其实它就是我们本模块要介绍的----同名隐藏(重定义)问题
基类和派生类中出现同名的成员时,会有如下问题的存在:
那么,该如何解决呢?
只需要在访问的时候加上 基类名称和作用域限定符即可,这样做的目的是明确告诉编译器被调用成员所处的作用域
建议:一般情况下,在继承体系中最好不要定义同名的成员
5、派生类(子类)的默认成员函数
5.1 构造函数
主要取决于基类的情况,分为两大类进行讨论:
- 基类没有显式定义任何构造函数
子类可以提供构造函数,也可以不提供构造函数 是否提供根据子类中完成的功能或者具体情况决定
- 基类显式定义了构造函数
①基类的构造函数是无参或者全缺省的
子类可以提供构造函数,也可以不提供构造函数
是否提供根据子类中完成的功能或者具体情况决定②基类的构造函数是非默认构造函数
子类必须要定义自己的构造函数
在子类构造函数初始化列表位置显式调用基类的构造函数(完成从基类中继承下来的成员的初始化工作)
- 基类和子类构造函数的调用先后顺序是怎样的?
把握一点:
创建那个类的对象,编译器就会调用这个类的构造函数
例如:创建子类对象,本质上调用的是子类的构造函数,但是在子类的构造函数的初始化列表处会调用基类的构造方法来初始化从基类继承下来的对象。然后再去执行子类构造函数的函数体。
因此,从结果上来看是基类对象的构造函数先执行完毕,子类构造函数后执行完毕。
5.2 拷贝构造函数
取决于基类的情况,主要分为两类:
- 基类的拷贝构造函数未定义
子类的拷贝构造函数可定义可不定义,根据子类的实际情况决定
- 基类的拷贝构造函数定义了
子类也需要定义拷贝构造函数,并且需要在子类的拷贝构造函数初始化列表的位置显式调用基类的拷贝构造函数
5.3 赋值运算符重载
- 基类的赋值运算符重载未定义
子类可定义可不定义
- 基类的赋值运算符重载显式定义了
子类也需要定义,分为两个大的步骤:
①调用基类的赋值运算符重载给基类部分成员赋值base::operator= (d);
②给子类自己新增的部分进行赋值注意:基类的operator= 与子类自己的 operator= 构成了同名隐藏,因此要加作用域限定符指定调用基类的operator=,否则默认调用子类自己的operator=,就会陷入无限递归
正确示范:
错误示范:
5.4 析构函数
编译器将子类的析构函数编译完成之后,会自动在子类析构函数的最后一条语句之后插入一条调用基类析构函数的汇编语句call ~Base();!
问题:基类和子类析构函数调用先后顺序?
6、基类中哪些成员被子类继承了?
6.1 成员变量
普通成员变量,全部被继承!
这个我们在本文的1.2 定义这个模块已经全部验证!
静态成员变量也被继承了
注意:静态成员变量在整个继承体系中只有一份
验证:通过静态变量来记录创建对象的个数
class Base{public:Base(int a,int b){_a = a;_b = b;++_count;}Base(const Base& b){_a = b._a;_b = b._b;++_count;}Base& operator=(const Base& b){_a = b._a;_b = b._b;return *this;}~Base(){cout << "Base::~Base()" << endl;--_count;}public:int _a;int _b;static int _count;};int Base::_count = 0;class Derived : public Base{public:Derived():Base(1,2){}Derived(int a,int b,int c):Base(a,b), _c(c){}Derived(const Derived& d):Base(d){_c = d._c;}Derived& operator=(const Derived& d){Base::operator=(d);_c = d._c;return *this;}~Derived(){cout << "Derived::~Derived()" << endl;}public:int _c;};void Test(){cout << &Base::_count << endl;cout << &Derived::_count << endl;}
6.2 成员方法
普通成员方法,被子类继承了。
前面的代码均有体现,这里不再验证~
静态成员方法—也被子类继承了
验证:
本篇文章到这里就结束了,感觉有所帮助的读友,可以转发分享给身边的朋友并留下你们的足迹!
下篇我们讲讲C++中一些不同的继承体系~,我们下篇再见!