c++ 派生类的构造函数 与 基类构造函数的关系
《面向对象程序设计基础(第二版》李师贤等,第254页:C++语言的基本规则是:创建一个派生类的对象时,如果基类带有构造函数,则先调用基类的构造函数,然后才调用派生类的构造函数。
《Thinking in C++》,刘宗田等译,第261页:可以看出,构造在类层次的最根处开始,而在每一层,首先调用基类构造函数,然后调用成员对象构造函数。
《C++ Primer Plus(第四版)中文版》,孙建春等译,第399页:记住:创建派生类对象时,程序首先调用基类构造函数,然后再调用派生类构造函数。
基类的成员函数可以被继承,可以通过派生类的对象访问,但这仅仅指的是普通的成员函数,类的构造函数不能被继承。构造函数不能被继承是有道理的,因为即使继承了,它的名字和派生类的名字也不一样,不能成为派生类的构造函数,当然更不能成为普通的成员函数。
在设计派生类时,对继承过来的成员变量的初始化工作也要由派生类的构造函数完成,但是大部分基类都有 private 属性的成员变量,它们在派生类中无法访问,更不能使用派生类的构造函数来初始化。
这种矛盾在C++继承中是普遍存在的,解决这个问题的思路是:在派生类的构造函数中调用基类的构造函数
下面的例子展示了如何在派生类的构造函数中调用基类的构造函数:
#include<iostream>
using namespace std;
//基类People
class People{
protected:
char *m_name;
int m_age;
public:
People(char*, int);
};
People::People(char *name, int age): m_name(name), m_age(age){}
//派生类Student
class Student: public People{
private:
float m_score;
public:
Student(char *name, int age, float score);
void display();
};
//People(name, age)就是调用基类的构造函数
Student::Student(char *name, int age, float score): People(name, age), m_score(score){ }
void Student::display(){
cout<<m_name<<"的年龄是"<<m_age<<",成绩是"<<m_score<<"。"<<endl;
}
int main(){
Student stu("小明", , 90.5);
stu.display();
return ;
}
运行结果为:
小明的年龄是16,成绩是90.5。
请注意第 23 行代码:
Student::Student(char *name, int age, float score): People(name, age), m_score(score){ }
People(name, age)
就是调用基类的构造函数,并将 name 和 age 作为实参传递给它,m_score(score)
是派生类的参数初始化表,它们之间以逗号,
隔开。
也可以将基类构造函数的调用放在参数初始化表后面:
Student::Student(char *name, int age, float score): m_score(score), People(name, age){ }
但是不管它们的顺序如何,派生类构造函数总是先调用基类构造函数再执行其他代码(包括参数初始化表以及函数体中的代码),总体上看和下面的形式类似:
Student::Student(char *name, int age, float score){
People(name, age);
m_score = score;
}
当然这段代码只是为了方便大家理解,实际上这样写是错误的,因为基类构造函数不会被继承,也就是说不能当做当前类的普通的成员函数来调用。换句话说,只能将基类构造函数的调用放在函数头部,不能放在函数体中。
另外,函数头部是对基类构造函数的调用,而不是声明,所以括号里的参数是实参,它们不但可以是派生类构造函数参数列表中的参数,还可以是局部变量、常量等,例如:
Student::Student(char *name, int age, float score): People("小明", ), m_score(score){ }
构造函数的调用顺序
从上面的分析中可以看出,基类构造函数总是被优先调用,这说明创建派生类对象时,会先调用基类构造函数,再调用派生类构造函数,如果继承关系有好几层的话,例如:
A --> B --> C
那么创建 C 类对象时构造函数的执行顺序为:
A类构造函数 --> B类构造函数 --> C类构造函数
构造函数的调用顺序是按照继承的层次自顶向下、从基类再到派生类的。
还有一点要注意,派生类构造函数中只能调用直接基类的构造函数,不能调用间接基类的。以上面的 A、B、C 类为例,C 是最终的派生类,B 就是 C 的直接基类,A 就是 C 的间接基类。
也就是说不能跨越 继承层级关系 调用 基类构造函数
C++ 这样规定是有道理的,因为我们在 C 中调用了 B 的构造函数,B 又调用了 A 的构造函数,相当于 C 间接地(或者说隐式地)调用了 A 的构造函数,如果再在 C 中显式地调用 A 的构造函数,那么 A 的构造函数就被调用了两次,相应地,初始化工作也做了两次,这不仅是多余的,还会浪费CPU时间以及内存,毫无益处,所以 C++ 禁止在 C 中显式地调用 A 的构造函数。
基类构造函数调用规则
事实上,通过派生类创建对象时必须要调用基类的构造函数,这是语法规定。换句话说,定义派生类构造函数时最好指明基类构造函数;如果不指明,就调用基类的默认构造函数(不带参数的构造函数);如果没有默认构造函数,那么编译失败。请看下面的例子:
#include <iostream>
using namespace std;
//基类People
class People{
public:
People(); //基类默认构造函数
People(char *name, int age);
protected:
char *m_name;
int m_age;
};
People::People(): m_name("xxx"), m_age(){ }
People::People(char *name, int age): m_name(name), m_age(age){}
//派生类Student
class Student: public People{
public:
Student();
Student(char*, int, float);
public:
void display();
private:
float m_score;
};
Student::Student(): m_score(0.0){ } //派生类默认构造函数
Student::Student(char *name, int age, float score): People(name, age), m_score(score){ }
void Student::display(){
cout<<m_name<<"的年龄是"<<m_age<<",成绩是"<<m_score<<"。"<<endl;
}
int main(){
Student stu1;
stu1.display();
Student stu2("小明", , 90.5);
stu2.display();
return ;
}
运行结果:
xxx的年龄是0,成绩是0。
小明的年龄是16,成绩是90.5。
创建对象 stu1 时,执行派生类的构造函数Student::Student()
,它并没有指明要调用基类的哪一个构造函数,从运行结果可以很明显地看出来,系统默认调用了不带参数的构造函数,也就是People::People()
。
创建对象 stu2 时,执行派生类的构造函数Student::Student(char *name, int age, float score)
,它指明了基类的构造函数。
在第 27 行代码中,如果将People(name, age)
去掉,也会调用默认构造函数,第 37 行的输出结果将变为:
xxx的年龄是0,成绩是90.5。
如果将基类 People 中不带参数的构造函数删除,那么会发生编译错误,因为创建对象 stu1 时需要调用 People 类的默认构造函数, 而 People 类中已经显式定义了构造函数,编译器不会再生成默认的构造函数。
c++ 派生类的构造函数 与 基类构造函数的关系的更多相关文章
- C++派生类中如何初始化基类对象(五段代码)
今天收到盛大的面试,问我一个问题,关于派生类中如何初始化基类对象,我在想派生类对于构造函数不都是先构造基类对象,然后在构造子类对象,但是如果我们在成员初始化列表先初始化派生类的私有成员,在函数内去调用 ...
- c++,派生类对象可以对基类赋值,基类对派生类不可以赋值
派生类对象可以对基类对象赋值,赋值时属于派生类独有的部分就舍弃不用. #include <iostream> using namespace std; class DemoA { publ ...
- 编写高质量代码改善C#程序的157个建议——建议128:考虑让派生类的名字以基类名字作为后缀
建议128:考虑让派生类的名字以基类名字作为后缀 派生类的名字可以考虑以基类名字作为后缀.这带来的好处是,从类型的名字上我们就知道它包含在哪一个继承体系中. Exception及其子类就是这样一个典型 ...
- C++类继承中,基类/当前对象属性/当前对象的构造顺序
[1]中提到,规范的派生类构造函数三个要点: 首先创建基类对象 应通过成员初始化列表,创建基类对象 应该初始化本派生类新增的成员变量 那在构造派生类实例的过程中,其基类(以及多继承的时候多个基类)/当 ...
- WPF 之 创建继承自Window 基类的自定义窗口基类
开发项目时,按照美工的设计其外边框(包括最大化,最小化,关闭等按钮)自然不同于 Window 自身的,但窗口的外边框及窗口移动.最小化等标题栏操作基本都是一样的.所以通过查看资料,可按如下方法创建继承 ...
- 类转json的基类实现
类转json的基类实现 项目地址 github地址 实现原理 使用反射获取类的属性名和属性内容.具体原理可以自己查一下资料 对一个类调用getClass().getDeclaredFields()可以 ...
- C++primer原书中的一个错误(派生类using声明对基类权限的影响)
在C++primer 第4版的 15章 15.2.5中有以下这样一段提示: "注解:派生类能够恢复继承成员的訪问级别,但不能使訪问级别比基类中原来指定的更严格或者更宽松." 在vs ...
- C++继承和派生练习(一)--关于vehicle基类
Target:定义一个车(vehicle)基类 具有MaxSpeed.Weight等成员变量,Run.Stop等成员函数,由此派生出自行车(bicycle)类.汽车(motorcar)类. 自行车(b ...
- C++_类继承5-抽象基类
abstract base class,ABC 抽象基类 有时候is-a规则并不像看上去那么简单,例如圆和椭圆的关系.圆是椭圆的特殊情况.椭圆可以派生出圆.但是椭圆的数据成员及方法对于圆来说是信息冗余 ...
随机推荐
- c# 是如何对一个可遍历对象实现遍历的
public class Persons:IEnumerable { public Persons(string[] people) { this.people = people; } public ...
- 【原创】大叔经验分享(69)docker启动java应用的时区问题
在docker中启动tomcat或java类应用,获取时间默认是UTC时间,这是因为容器内的locale没有设置为东8区,最简单的方式是增加JAVA_OPTS 如果是java,直接在java命令后增加 ...
- 《Redis高阶应用》讲座总结
数据结构延展 常用数据结构:String,Hash,List,Set,Sorted Set(不聊这些) 高级数据结构:Bitmaps,hyperloglog,GEO 单机拓展到分布式 为什么要分区:性 ...
- js jquery 动态添加表格
for循环将你要添加的标签写上,然后直接var talbeAdd=""for(){ tableAdd+="<tr><td>这儿写你要添加的内容&l ...
- 4.移动端自动化测试-API讲解
一.前置代码 1.导入driver对象 from appium import webdriver 2.声明手机驱动对象 只有声明驱动对象我们才可以让手机完成脚本的操作 driver = ...
- mysql 使用zip包进行安装以及服务启动后立即关闭问题
本实例使用的mysql版本为 mysql-8.0.15-winx64 1.下载zip包 官网地址:https://dev.mysql.com/downloads/mysql/ 2.安 ...
- Delphi 画刷与作图区域
樊伟胜
- 第七章·Logstash深入-收集NGINX日志
1.NGINX安装配置 源码安装nginx 因为资源问题,我们先将nginx安装在Logstash所在机器 #安装nginx依赖包 [root@elkstack03 ~]# yum install - ...
- 工控漏洞利用框架 - ISF(Industrial Security Framework)
一. 框架介绍 本框架主要使用Python语言开发,通过集成ShadowBroker释放的NSA工具Fuzzbunch攻击框架,开发一款适合工控漏洞利用的框架.由于Fuzzbunch攻击框架仅适用于P ...
- Mysql(四)-2:多表查询
一 介绍 本节主题 多表连接查询 复合条件连接查询 子查询 准备表 #建表 create table department( id int, name varchar(20) ); create ta ...