有些类与类之间存在特殊的关系,有共性也有特性,比如动物类可以细分为猫,狗等。下级别的成员除了拥有上一级的共性,还有自己的特性,这个时候就可以考虑继承的技术,减少重复代码。

一、继承中的对象模型

1.1 子类继承父类中的成员变量

子类从父类继承的成员变量,是属于子类呢还是属于父类呢?我们定义如下示例:

class father{
public:
int f_a;
protected:
int f_b;
private:
int f_c;
};
class son : public father{ // 从son是father的子类
public:
int s_a;
};
son S1;
cout << "son所占的内存为:" << sizeof(S1) << endl;

输出结果为:

son所占的内存为:16

因此可以看出,父类中的所有变量都被子类给继承了下来,都属于子类的一部分。虽然父类中 private 访问权限的成员不能被子类访问,但是仍然属于子类的一部分。同理,在子类继承父类时,除了继承父类中所有的成员变量,也同时继承了除了父类构造函数外的所有成员函数,这样便可以有效节省代码量,提高代码复用效率。至于子类与父类构造函数之间的关系,将在后文进行解释。

1.2 子类与父类构造函数的原则

上面提到,子类可以继承父类中所有的成员变量和成员方法,但不继承父类的构造函数。因此在创建子类对象时,为了初始化从父类继承来的成员变量,系统需要调用父类的构造方法。我们都知道,任何一个类都要有构造函数,那么子类的构造函数和父类的构造函数之间的关系是怎样的?

  1. 如果子类没有定义构造函数,则调用父类的无参构造函数;

  2. 如果子类定义了构造函数(不管是无参还是有参),在创建子类对象时首先执行父类的无参构造函数,然后执行自己的构造函数;

    这两种情况下,不管子类是否定义了新的构造函数,只要没有显示的调用父类中的构造函数,都只会调用父类中的无参构造函数。

  3. 如果父类中自己定义了有参构造函数,那么编译器便不会再提供默认无参构造函数,子类便只能在构造函数中显示的调用父类的构造函数来对父类成员进行初始化。

仅仅通过文字会比较抽象,下面我们通过几个具体的例子来解释一下上面的构造原则:

class father{
public:
string f_a;
// 与默认构造函数一样,都是无参构造函数
father(){
cout << "我是父类的无参构造函数" << endl;
}
// 自定义的有参构造函数
father(string f_a){
this->f_a = f_a;
cout << "我是父类的有参构造函数" << endl;
}
};
class son : public father{
public:
string f_a;
// 子类中的构造函数,在没有显示调用父类构造函数的情况下,默认调用父类中的无参构造函数
son(string f_a){
cout << "我是子类的构造函数" << endl;
}
};
son S1("abc"); // 如果此时创建对象,那么将会调用父类中的无参构造函数,然后调用子类的构造函数

输出结果如下:

我是父类的无参构造函数	//调用父类的无参构造函数
我是子类的构造函数 // 调用子类的构造函数

假如父类中只定义了有参构造函数,在创建子类对象时编译器便找不到父类中的无参构造函数,于是便会报错。解决这类问题的办法就是在子类构造函数中显示地调用父类的有参构造函数来对父类的成员初始化。

class father{
public:
string f_a;
// 自定义的有参构造函数
father(string f_a){
this->f_a = f_a;
cout << "我是父类的有参构造函数" << endl;
}
};
class son : public father{
public:
string f_a;
// 子类中的构造函数,由于父类中没有提供无参构造函数,导致出错
//son(string f_a){
// cout << "我是子类的构造函数" << endl;
//}
son(string f_a):father("father"){ // 显示调用父类中的有参构造函数
cout << "我是子类的构造函数" << endl;
}
};
son S1("abc"); // 如果此时创建对象,那么将会调用父类中的无参构造函数,然后调用子类的构造函数

输出结果如下:

我是父类的有参构造函数	//显示调用父类的有参构造函数
我是子类的构造函数 // 调用子类的构造函数

二、继承中的构造和析构顺序

在上面一节中也简单提到了继承过程中的构造顺序,原则是:

  1. 父类先执行构造函数,子类然后执行构造函数
  2. 子类先执行析构函数,父类然后执行析构函数

下面通过一个简单的例子来简要说明一下:

class father{
public:
string f_a;
father(){
cout << "我是父类的构造函数" << endl;
}
~father(){
cout << "我是父类的析构函数" << endl;
}
};
class son : public father{
public:
string f_a;
son(){
cout << "我是子类的构造函数" << endl;
}
~son(){
cout << "我是子类的析构函数" << endl;
}
};

输出结果如下:

我是父类的构造函数
我是子类的构造函数
我是子类的析构函数
我是父类的析构函数

二、同名成员的处理

当子类与父类中出现同名的成员,如何通过子类对象访问到子类或者父类中的同名数据呢?首先我们先理解一下为什么子类和父类中会出现同名的成员变量和成员函数。这是因为变量和函数都有它的作用域,在同一个作用域中不能出现两个重名的变量,但是在不同的作用域中可以出现重名。这就相当于每个学校都有一个校长,但是在同一所学校内只能有一个校长。由于父类和子类是两个不同的作用域,所以可以出现重名的变量或者函数。

class father{
public:
string f_a;
int f_b;
father(){ // 父类中的无参构造函数,初始化父类成员
f_a = "father";
f_b = 10;
}
void show(){
cout << " 我是father!" << endl;
}
};
class son : public father{
public:
string f_a;
son(){ // 子类中的无参构造函数,初始化子类成员
f_a = "son";
}
void show(){
cout << " 我是son!" << endl;
}
};
son S1;
cout << "f_b = " << S1.f_b << endl; // 子类对象可以像访问自己的成员一样访问从父类继承下来的成员
cout << "f_a = " << S1.f_a << endl; // 但是,从父类继承了一个变量 f_a , 子类自己也有一个 f_a , 那么访问的是哪个呢?

输出结果为:

f_b = 10
f_a = son // 访问的是子类中的成员

通过上述实例可以看出,当子类和父类中有同名的成员时,子类对象优先访问子类中的成员,若想访问父类中的成员,应该指定父类成员的作用域:

cout << "f_a = " << S1.father::f_a << endl; // 给 f_a 加上一个父类的作用域

输出结果为:

f_a = father	// 访问到了父类中的同名成员

对成员函数的访问也是一样,比如:

S1.show();	// 访问子类中的show
S1.father::show(); // 访问父类中的show

输出结果为:

我是son!
我是father!

详解C++中继承的基本内容的更多相关文章

  1. 详解C++中的多态和虚函数

    一.将子类赋值给父类 在C++中经常会出现数据类型的转换,比如 int-float等,这种转换的前提是编译器知道如何对数据进行取舍.类其实也是一种数据类型,也可以发生数据转换,但是这种转换只有在 子类 ...

  2. Python 在子类中调用父类方法详解(单继承、多层继承、多重继承)

    Python 在子类中调用父类方法详解(单继承.多层继承.多重继承)   by:授客 QQ:1033553122   测试环境: win7 64位 Python版本:Python 3.3.5 代码实践 ...

  3. 详解Javascript的继承实现(二)

    上文<详解Javascript的继承实现>介绍了一个通用的继承库,基于该库,可以快速构建带继承关系和静态成员的javascript类,好使用也好理解,额外的好处是,如果所有类都用这种库来构 ...

  4. (转载)详解Javascript中prototype属性(推荐)

    在典型的面向对象的语言中,如java,都存在类(class)的概念,类就是对象的模板,对象就是类的实例.但是在Javascript语言体系中,是不存在类(Class)的概念的,javascript中不 ...

  5. 详解Springboot中自定义SpringMVC配置

    详解Springboot中自定义SpringMVC配置 WebMvcConfigurer接口 ​ 这个接口可以自定义拦截器,例如跨域设置.类型转化器等等.可以说此接口为开发者提前想到了很多拦截层面的需 ...

  6. jQuery:详解jQuery中的事件(二)

    上一篇讲到jQuery中的事件,深入学习了加载DOM和事件绑定的相关知识,这篇主要深入讨论jQuery事件中的合成事件.事件冒泡和事件移除等内容. 接上篇jQuery:详解jQuery中的事件(一) ...

  7. 图文详解Unity3D中Material的Tiling和Offset是怎么回事

    图文详解Unity3D中Material的Tiling和Offset是怎么回事 Tiling和Offset概述 Tiling表示UV坐标的缩放倍数,Offset表示UV坐标的起始位置. 这样说当然是隔 ...

  8. 【转】详解C#中的反射

    原帖链接点这里:详解C#中的反射   反射(Reflection) 2008年01月02日 星期三 11:21 两个现实中的例子: 1.B超:大家体检的时候大概都做过B超吧,B超可以透过肚皮探测到你内 ...

  9. 详解Webwork中Action 调用的方法

    详解Webwork中Action 调用的方法 从三方面介绍webwork action调用相关知识: 1.Webwork 获取和包装 web 参数 2.这部分框架类关系 3.DefaultAction ...

随机推荐

  1. [转载]虚拟化之KVM配置

    虚拟化之KVM配置 2017-11-06 [TOC] 虚拟化:通过虚拟化工具把cpu,内存,硬盘等真实硬件资源给模拟成更少的虚拟硬件资源.进行虚拟化的好处是,最大限度的利用硬件资源.也是云计算的基础. ...

  2. YAML/YML文件一直提示格式错误解决方法

    第一次接触yml文件,各种格式报错,但是看了几次也没看出来.其实有一个好方法,那就是直接通过yml在线格式检查 可以将yml具体内容复制到以下网址进行查询.具体报错位置会更加详细 https://ww ...

  3. 【玩转PDF】贼稳,产品要做一个三方合同签署,我方了!

    一.前言 事情是这个样子的,小农的公司,之前有个功能需要签署来进行一系列的操作,于是我们引入了一个三方平台的签署--上上签,但是有一个比较尴尬的点就是,它不支持合同在浏览器上和附件一起预览的,我们想要 ...

  4. system verilog内建数据类型

  5. 去除HTML中的标签内容

    采集后的数据都带有'<>'html标签: <img src="http://i4.hdfimg.com/www/images/giftrans/3d/da/7b/18414 ...

  6. Sparse R-CNN: End-to-End Object Detection with Learnable Proposals 论文解读

    前言 事实上,Sparse R-CNN 很多地方是借鉴了去年 Facebook 发布的 DETR,当时应该也算是惊艳众人.其有两点: 无需 nms 进行端到端的目标检测 将 NLP 中的 Transf ...

  7. kafka之二:手把手教你安装kafka2.8.0(绝对实用)

    前面分享了kafka的基本知识,下面就要对kafka进行实操,先看如何安装. kafka需要zookepper的支持,所以要安装kafka需要有zookeeper的环境,zookeeper安装请参见& ...

  8. THINKPHP_(8)_修改TP源码,支持基于多层关联的任一字段进行排序

    之前博文 前述博文THINKPHP_(1)_修改TP源码,支持对中文字符串按拼音进行排序,其解决的主要问题是,对于查询出的think\collection数据,按指定字段对数据进行排序,从而在页面上进 ...

  9. MegEngine基本概念

    MegEngine基本概念 基本概念 MegEngine 是基于计算图的深度神经网络学习框架. 本文内容会简要介绍计算图及其相关基本概念,以及在 MegEngine 中的实现. 计算图 结合一个简单的 ...

  10. MinkowskiEngine实用函数和类

    MinkowskiEngine实用函数和类 sparse_quantize MinkowskiEngine.utils.sparse_quantize(coords, feats=None, labe ...