C++中的类继承(2)派生类的默认成员函数
在继承关系里面, 在派生类中如果没有显示定义这六个成员函数, 编译系统则会默认合成这六个默认的成员函数。
1、构造与析构函数的调用关系
调用关系先看一段代码:
class Base
{
public :
Base()
{
cout << "B() " << endl;
}
~Base()
{
cout << "~B() " << endl;
}
private:
int _pri;
protected:
int _pro;
public:
int _pub;
};
class Derived : public Base
{
public :
Derived()
{
cout << "D() " << endl;
}
~Derived()
{
cout << "~D() " << endl;
}
private:
int _d_pri;
protected:
int _d_pro;
public:
int _d_pub;
};
void Test()
{
Derived d;
}
int main()
{
Test();
getchar();
return ;
}
输出结果为:

代码中,我们利用派生类Derived,创建了一个对象d,根据输出结果看到,貌似创建对象d的过程是:先调用基类的构造函数,再调用子类的构造函数;而析构对象时先调用子类的析构函数,再调用基类的析构函数。但是我们不能被表象所迷惑,我们转到反汇编来看看具体是怎么实现的:
我们看到创建对象d的时候是先调用Derived类的构造函数D(),但是在cout << "D() " << endl;这句代码执行之前,编译器还做了一堆的其他工作,其中最重要的是 call Base::Base (0E41041h)这条指令它跳转到了基类中,如图示:

屏幕上输出:

B() 、D()即先执行cout << "B() " << endl;语句,再执行cout << "D() " << endl;语句,所以,才有屏幕上的结果。在析构对象d的时候先调用~D()但是没那么简单,再看图中
在语句cout << "~D() " << endl;执行完之后用掉call Base::~Base (0E410D7h)指令跳转到~B()中 

此时~D()输出,接着~B()输出,然后,~D()才算执行完。

分析:基类是派生类的一部分,创建派生类对象时必须调用派生类构造函数,而派生类构造函数必须使用基类的构造函数。程序首先创建基类对象,所以基类对象在程序进入派生类构造函数之前被创建。实际上C++使用成员初始化列表语法来完成这项工作,即B()相当于在函数D()的初始化列表中被使用,如果不调用基类构造函数,程序将使用默认的基类构造函数。在执行完B()的函数体之后,继承的数据成员被初始化,执行D()函数体初始化新增的数据成员。析构对象时,先调用派生类的析构函数,执行完函数体析构完新增部分之后,使用基类的析构函数析构继承自基类的部分。所以才有上述现象。调用关系总结如下图:
总结:创建派生类对象时程序调用派生类构造函数,然后在初始化列表部分调用基类构造函数初始化继承的数据成员,而派生类构造函数主要初始化新增的数据成员。派生类总是调用一个基类构造函数。可以使用初始化列表语法指明要使用的基类构造函数,否则将使用默认的基类构造函数。派生类对象过期时,程序将先调用派生类析构函数,在函数体执行完之后调用基类析构函数。(可以看到,继承的数据成员生命周期长, 新增的数据成员生命周期短。)
构造函数带参情况
构造派生对象时,派生类构造函数默认调用参数缺省的基类构造函数,若基类构造函数带有参数,则派生类中必须显式定义构造函数,并在初始化列表中传参。本例中,若B()带有参数,则D()中必须显式定义构造函数并传参;

如图,B()带有参数,则D()中构造函数无参时编译不能通过。

传参之后可以编译用过。
当基类中显示定义构造函数,而派生类中没有定义构造函数,则使用默认合成的派生类构造函数,并在默认合成的派生类构造函数调用基类构造函数。即基类Base显式定义了构造函数,而派生类Derived中没有定义,则用默认合成的构造函数D()实例化对象,且在初始化参数列表部分调用基类构造函数B()。
同理当派生类中显示定义构造函数,而基类中没有定义构造函数,则在派生类构造函数中调用默认合成的基类构造函数。
2、拷贝构造函数
使用拷贝构造函数的情况有:
- 将新的对象初始化为一个同类对象
- 按值将对象传递给函数
- 函数按值返回对象
- 编译器生成临时对象
如果程序没有显式定义拷贝构造函数,编译器将自动生成一个。当然,如果想在派生类中构造基类对象,那么不仅仅可以用构造函数,也可以用拷贝构造函数
class Base
{
public :
Base()
{
cout << "B() " << endl;
}
~Base()
{
cout << "~B() " << endl;
}
private:
int _pri;
protected:
int _pro;
public:
int _pub;
};
class Derived : public Base
{
public :
Derived()
{
cout << "D() " << endl;
}
Derived(const Derived &tp)
:Base(tp)//拷贝构造函数
{
cout << "Derive()" << endl;
}
~Derived()
{
cout << "~D() " << endl;
}
private:
int _d_pri;
protected:
int _d_pro;
public:
int _pub;
};
void Test()
{
Derived d;
Derived i(d);
}
int main()
{
Test();
getchar();
return ;
}
运行成功,输出结果为:

这里我没有给基类定义拷贝构造函数,但是编译器自动给基类生成了一个拷贝构造函数,因为我基类中定义的没有指针成员,所以浅拷贝可以满足我的要求,但是如果在基类成员中有指针变量,必须要进行显式定义拷贝构造函数,即进行深拷贝。不然会造成同一块内存空间被析构两次的问题。
3、赋值操作符
class Base
{};
int main()
{
Base a;
Base b = a;//初始化
Base c;
c = a;//赋值
}
默认的赋值操作符用于处理同类对象之间的赋值,赋值不是初始化,如果语句创建新的对象,则使用初始化,如果语句修改已有对象的值,则为赋值。 赋值运算符是不能被继承的,原因很简单。派生类继承的方法的特征与基类完全相同,但赋值操作符的特征随类而异,因为它包含一个类型为其所属类的形参。
如果编译器发现程序将一个对象赋给同一个类的另一个对象,它将自动为这个类提供一个赋值操作符。这个操作符的默认版本将采用成员赋值,即将原对象的相应成员赋给目标对象的每个成员。
如果对象属于派生类,编译器将使用基类赋值操作符来处理派生对象中基类部分的赋值,如果显示的为基类提供了赋值操作符,将使用该操作符。
注意:赋值运算和拷贝构造是不同的,赋值是赋值给一个已有对象,拷贝构造是构造一个全新的对象
将派生类对象赋给基类对象时:
class Base
{
public:
int data;
};
class Derive:public Base
{
public:
int d;
};
int main()
{
Base a;
Derive dd;
a = dd;
}
上面的a=dd;语句将使用谁的赋值操作符呢。 实际上,赋值语句将被转换成左边的对象调用的一个方法
a.operator=(dd);//左边的为基类对象
简而言之,可以将派生对象赋给基类对象,但这只涉及到基类的成员。如图示

基类对象赋给派生类对象。
class Base
{
public:
int data;
};
class Derive:public Base
{
public:
int d;
};
int main()
{
Base a;
Derive dd;
dd = a;
}
上述赋值语句将被转换为:
d.operator=(a); //Derive::operator=(const Derive&)左边的对象为派生类对象,不过派生类引用不能自动引用基类对象,所以上述代码不能运行。或者运行出错。除非有函数Derive(const Base&){}
总结:
- 是否可以将基类对象赋给派生类对象,答案是也许。如果派生类包含了转换构造函数,即对基类对象转换为派生类对象进行了定义,则可以将基类对象赋给派生对象。
- 派生类对象可以赋给基类对象。
4、类的成员初始化列表
(1)类的成员变量总是在构造函数执行前创建完毕,但有此成员变量只能在初始化时赋值 – 如const型常量 和 引用;
(2)使用初始化表可以使指定构造函数中的参数或常量作为成员的初始值;
Derived::Derived(int i, int j): x(i), y(j) {}
(3)初始化表只能用于构造函数;
(4)必须使用初始化表来初始化const型常量和引用;
(5)成员初始化的顺序与它们出现在类声明中的位置有关,与初始化表中的顺序无关;
(6)C++11允许类内初始化,但初始化表会覆盖类内初始化:
class Derived {
private:
int x = ;
int y = ;
static const int num = ;
};
C++中的类继承(2)派生类的默认成员函数的更多相关文章
- cc31a_demo--CppPrimer_静态成员与继承-在派生类中访问基类中的static成员的方法
//*基类中的static成员,在整个继承层次中只有一个实例 //*在派生类中访问基类中的static成员的方法 //1.基类名::成员名 //2.子类名::成员名 //3.对象.成员名 //4.指针 ...
- 不可或缺 Windows Native (21) - C++: 继承, 组合, 派生类的构造函数和析构函数, 基类与派生类的转换, 子对象的实例化, 基类成员的隐藏(派生类成员覆盖基类成员)
[源码下载] 不可或缺 Windows Native (21) - C++: 继承, 组合, 派生类的构造函数和析构函数, 基类与派生类的转换, 子对象的实例化, 基类成员的隐藏(派生类成员覆盖基类成 ...
- ES6里关于类的拓展(二):继承与派生类
继承与派生类 在ES6之前,实现继承与自定义类型是一个不小的工作.严格意义上的继承需要多个步骤实现 function Rectangle(length, width) { this.length = ...
- C++//菱形继承 //俩个派生类继承同一个基类 //又有某个类同时继承俩个派生类 //成为 菱形继承 或者 钻石 继承//+解决
1 //菱形继承 2 //俩个派生类继承同一个基类 3 //又有某个类同时继承俩个派生类 4 //成为 菱形继承 或者 钻石 继承 5 6 #include <iostream> 7 #i ...
- C++ //多继承语法 C++中允许一个类继承多个类
1 //多继承语法 C++中允许一个类继承多个类 2 #include <iostream> 3 #include <string> 4 using namespace std ...
- C++中的默认成员函数
一般而言,对于一个用户自定义的类类型,以下四个函数在用户没有自定义的情形下,会由编译器自动生成: 1.default constructor 2.copy constructor Someclass: ...
- C++ 继承 - 在派生类中对基类初始化
构造函数与基类的其他成员不同,不能被派生类继承,因此为了初始化基类中的成员变量,需要在派生类中调用基类的构造函数(即显式调用),如果派送类没有调用则默认调用基类的无参构造函数(即隐式调用). 显式调用 ...
- C++primer原书中的一个错误(派生类using声明对基类权限的影响)
在C++primer 第4版的 15章 15.2.5中有以下这样一段提示: "注解:派生类能够恢复继承成员的訪问级别,但不能使訪问级别比基类中原来指定的更严格或者更宽松." 在vs ...
- python 全栈开发,Day20(object类,继承与派生,super方法,钻石继承)
先来讲一个例子 老师有生日,怎么组合呢? class Birthday: # 生日 def __init__(self,year,month,day): self.year = year self.m ...
随机推荐
- MongoDB【第一篇】安装
第一步:准备 1. 操作系统 CentOS-7-x86_64-Everything-1511 2. MongoDB 版本 mongodb-linux-x86_64-rhel70-3.4.2 3. 设置 ...
- 【CNMP系列】CNMP之路,系统起步。
简单的来理解,我所说的CNMP,不是CNM+P,而是CentOs+Nginx+MySql+PHP,也可以单纯的理解为LNMP,但是系统是我们自己选的,虽说是Linux的一个分支,但我就喜欢CentOs ...
- 每天一个linux命令(37)--iostat命令
Linux 系统中的iostat是I/O statistics (输入/输出统计)的缩写,iostat工具将对系统的磁盘操作活动进行监视.它的特点是汇报磁盘活动统计情况,同时也会汇报出CPU使用情况. ...
- jQuery_小测试
1.在div元素中,包含了一个<span>元素,通过has选择器获取<div>元素中的<span>元素的语法是? 提示使用has() 2.在<ul>元素 ...
- leetcode HouseRobber Dp Code
#include <malloc.h> int MAX(int x,int y){ return x>y?x:y;} int rob(int* nums, int numsSize) ...
- Hibernate学习笔记①
---恢复内容开始--- 1.JavaEE的三层结构 WEB层--Service层--DAO层 2.Hibernate版本 Hibernate3.x 4.x 5.x 4版本是过渡版本 不使用 5 ...
- BMP图片格式模型(2)
因为公司的主要业务是图像识别相关的,因此对图像处理.识别是我学习的重点.虽然写程序也不少年了,但是对于图像处理领域,我还是一个新兵.对很多基础的概念也还是存在盲区,所以想在边学边做的过程中,对一些概念 ...
- 九度oj题目1207:质因数的个数
题目描述: 求正整数N(N>1)的质因数的个数. 相同的质因数需要重复计算.如120=2*2*2*3*5,共有5个质因数. 输入: 可能有多组测试数据,每组测试数据的输入是一个正整数N,(1&l ...
- 第一章 开发简单Java应用程序
1. 什么是程序? 程序一词来自生活,通常指完成某些事务的一种既定方式和过程(一组有序指令的集合). 2. Java的三个领域? Java SE:java技术核心(指桌面应用程序)如:QQ.百 ...
- Delphi的Hint介绍以及用其重写气泡提示以达到好看的效果
Delphi中使用提示是如此简单,只需将欲使用Hint的控件作如下设置: ShowHint := True; Hint := ‘提示信息’; 不必写一行代码,相当方便. 但有时我们又想自己定制提示的效 ...
