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

一、继承中的对象模型

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. 透过“锁”事看InnoDB对并发的处理?

    一. 并发场景下的问题 相对于串行处理方式,并发的事务处理可显著提升数据库的事务吞吐量.提高资源利用率.在MySQL实际应用中,根据场景的不同,可以分为以下几类: 读读并发 读写并发 写写并发 在这些 ...

  2. [BD] HBase

    NoSQL数据库 关系型数据库:用表格的行-列来保存数据,OLTP,写入多,行式存储 非关系型数据库:只用来存储数据,业务逻辑由应用程序处理,OLAP,查询多,列式存储 常见NoSQL数据库 Redi ...

  3. 佳能m62套机5500 佳能EOS M50 M6 MARK2 II二代 最低到过5800

    佳能m62套机5500 佳能EOS M50 M6 MARK2 II二代

  4. iPhone手机怎么和电脑互传文件,一条数据线搞定

    官方的方法是,通过iTunes进行文件的传输.传个文件还要特意安装个iTunes,实在是麻烦. 其实我们只需要在苹果应用商店app store下载Documents这个文件就可以. 另外,Docume ...

  5. 002.Ansible之Inventory文件

    一 简介 在使用Ansible来批量管理主机的时候,通常我们需要先定义要管理哪些主机或者主机组,而这个用于管理主机与主机组的文件就叫做Inventory,也叫主机清单.该文件默认位于/etc/ansi ...

  6. 大数据 什么是 ETL

    ETL 概念 ETL 这个术语来源于数据仓库,ETL 指的是将业务系统的数据经过抽取.清洗转换之后加载到数据仓库的过程.ETL 的目的是将企业中的分散.零乱.标准不统一的数据整合到一起,为企业的决策提 ...

  7. shell基础之变量及表达式

    本节内容 1. shell变量简介 2. 定义变量 3. 使用变量 4. 修改变量的值 5. 单引号和双引号的区别 6. 将命令的结果赋值给变量 7. 删除变量 8. 变量类型 9. 特殊变量列表 1 ...

  8. 嵌入式Boa服务器上CGI开发-(转自Bryce.Xiao)

    嵌入式WEB服务器常见的有lighttpd shttpd thttpdboa mathopd minihttpdappwebgoahead=============================== ...

  9. 5.8-12 watch、which、whereis、locate、updatedb

    5.8 watch:监视命令执行情况 watch命令可以以全屏的方式动态显示命令或程序的执行情况.   -n    命令执行的间隔时间,默认为2s -d    高亮显示命令结果的变动之处 -t     ...

  10. 如何在 Docker 环境下自动给 .NET 程序生成 Dump

    前言 之前"一线码农"大佬有写文章介绍了如何在 windows 下自动 dump,正好手里有个在 docker 环境下 dump 的需求,所以在参考大佬文章的基础上,有了本篇. ​ ...