在继承关系里面, 在派生类中如果没有显示定义这六个成员函数, 编译系统则会默认合成这六个默认的成员函数。

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、拷贝构造函数

使用拷贝构造函数的情况有:

  1. 将新的对象初始化为一个同类对象
  2. 按值将对象传递给函数
  3. 函数按值返回对象
  4. 编译器生成临时对象

如果程序没有显式定义拷贝构造函数,编译器将自动生成一个。当然,如果想在派生类中构造基类对象,那么不仅仅可以用构造函数,也可以用拷贝构造函数

 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)类的默认成员的更多相关文章

  1. android开发中关于继承activity类中方法的调用

    android开发中关于继承activity类中的函数,不能在其他类中调用其方法. MainActivity.java package com.example.testmain; import and ...

  2. 《前端之路》- TypeScript (三) ES5 中实现继承、类以及原理

    目录 一.先讲讲 ES5 中构造函数(类)静态方法和多态 1-1 JS 中原型以及原型链 例子一 1-2 JS 中原型以及原型链中,我们常见的 constructor.prototype.**prot ...

  3. C++中的类继承(2)派生类的默认成员函数

    在继承关系里面, 在派生类中如果没有显示定义这六个成员 函数, 编译系统则会默认合成这六个默认的成员函数. 构造函数. 调用关系先看一段代码: class Base { public : Base() ...

  4. Java中关于继承、类、多态、接口的知识点

    继承 含义:在面向对象编程中,可以通过扩展一个已有的类,并继承该类的属性和行为,来创建一个新的类 优点:1)代码的重用性:2)子类扩展父类的属性和方法:3)父类的属性和方法可用于子类:4)设计应用程序 ...

  5. C++中虚继承派生类构造函数的正确写法

    最近工作中某个软件功能出现了退化,追查下来发现是一个类的成员变量没有被正确的初始化.这个问题与C++存在虚继承的情况下派生类构造函数的写法有关.在此说明一下错误发生的原因,希望对更多的人有帮助. 我们 ...

  6. C++模板编程中只特化模板类的一个成员函数

    模板编程中如果要特化或偏特化(局部特化)一个类模板,需要特化该类模板的所有成员函数.类模板中大多数成员函数的功能可能是一模一样的,特化时我们可能只需要重新实现1.2个成员函数即可.在这种情况下,如果全 ...

  7. C++模板编程中只特化模板类的一个成员函数(花样特化一个成员函数)

    转自:https://www.cnblogs.com/zhoug2020/p/6581477.html 模板编程中如果要特化或偏特化(局部特化)一个类模板,需要特化该类模板的所有成员函数.类模板中大多 ...

  8. python多继承中子类访问祖先类的同名成员

    子类调用父类的同名成员 方式1: class A: def f_a(self): print("----A----") class B: def f_a(self): print( ...

  9. SSM-SpringMVC-09:SpringMVC中以继承MutiActionController类的方式实现处理器

    ------------吾亦无他,唯手熟尔,谦卑若愚,好学若饥------------- MutiActionController类,多行动处理器,简单来说,就是可以一个处理器中有多个处理方法,分支 ...

随机推荐

  1. MT【328】向量里的最佳逼近

    已知平面向量$\overrightarrow {a},\overrightarrow {b}$满足$|\overrightarrow {a}|=4,|\overrightarrow {b}|=2$.若 ...

  2. 关于Vue修改默认的build文件存放的dist路径

    原文地址:http://www.cnblogs.com/JimmyBright/p/7681086.html Vue默认build路径是项目的dist目录下,有时候我们可能希望build之后的文件自动 ...

  3. 【BZOJ4002】[JLOI2015]有意义的字符串(数论,矩阵快速幂)

    [BZOJ4002][JLOI2015]有意义的字符串(数论,矩阵快速幂) 题面 BZOJ 洛谷 题解 发现我这种题总是做不动... 令\(A=\frac{b+\sqrt d}{2},B=\frac{ ...

  4. 超越村后端开发(2:新建models.py+xadmin的引入)

    1.新建Model 1.users数据 1.在apps/users/models.py中: from datetime import datetime from django.db import mo ...

  5. echarts纵坐标使用科学计数法表示

    最近做项目使用echart画图,发现纵坐标的刻度太大或太小的情况,导致页面十分难看,甚至出现遮挡的情况,所以想办法用科学计数法表示 代码如下: var option = { title: Echart ...

  6. 分析web.xml

    <?xml version="1.0" encoding="UTF-8"?>  //xml的版本:1.0 和 编码:utf-8 <web-ap ...

  7. python之路(4)高阶函数和python内置函数

    前言 函数式编程不用变量保存状态,不改变变量 内置函数 高阶函数 把函数当作参数传给另一个对象 返回值中包含函数 使用的场景演示: num_test = [1,2,10,5,8,7] 客户说 :对上述 ...

  8. 字符串join函数跟+号测试

    字符串join函数for循环+区别: 原因是这样的,字符串是不可变对象,当用操作符+连接字符串的时候,每执行一次+都会申请一块新的内存,然后复制上一个+操作的结果和本次操作的右操作符到这块内存空间,因 ...

  9. Android屏幕设置只允许上下旋转

    android:screenOrientation = ["unspecified" | "behind" |                          ...

  10. 常见JS写法

    1.在DIV中找某个CLASS $('.doc_input', 'div')