C++继承与构造函数、复制控制
每个派生类对象由派生类中定义的(非static)成员加上一个或多个基类子对象构成,因此,当构造、复制、赋值和撤销派生类型对象时,也会构造、复制、赋值和撤销这些基类子对象。
构造函数和复制控制成员不能继承,每个类定义自己的构造函数和复制控制成员。像任何类一样,如果类不定义自己的默认构造函数和复制控制成员,就将使用合成版本。
1:构造函数和继承
派生类的构造函数受继承关系的影响,每个派生类构造函数除了初始化自己的数据成员之外,还要初始化基类。
派生类的合成默认构造函数:除了初始化派生类的数据成员之外,它还初始化派生类对象的基类部分。基类部分由基类的默认构造函数初始化:
class father
{
public:
int publ_i;
father(int a=1, int b=2, int c=3):publ_i(a),priv_i(b), prot_i(c)
{
cout << "father constructor" << endl;
}
virtual void display()
{
cout << "[father]publ_i is " << publ_i << endl;
cout << "[father]priv_i is " << priv_i << endl;
cout << "[father]prot_i is " << prot_i << endl;
}
private:
int priv_i;
protected:
int prot_i;
}; class son: public father
{
public:
void display()
{
cout << "[son]publ_i is " << publ_i << endl;
cout << "[son]prot_i is " << prot_i << endl;
}
};
int main()
{
son ss1;
ss1.display();
}
执行”son ss1;”语句时,调用派生类son的合成的默认构造函数,该函数会首先调用基类的默认构造函数。上述代码的结果是:
father constructor
[son]publ_i is 1
[son]prot_i is 3
如果派生类自己定义了构造函数,则该构造函数会隐式调用基类的默认构造函数:
class son: public father
{
public:
son(int a = 2):mypriv_i(a)
{
cout << "son constructor" << endl;
}
void display()
{
cout << "[son]publ_i is " << publ_i << endl;
cout << "[son]prot_i is " << prot_i << endl;
cout << "[son]mypriv_i is " << mypriv_i << endl;
}
private:
int mypriv_i;
}; int main()
{
son ss1;
ss1.display();
}
执行”son ss1;”语句时,调用派生类son的默认构造函数,该函数首先调用基类的默认构造函数初始化基类部分,然后,使用初始化列表初始化son::mypriv_i,最后,在执行son构造函数的函数体。上述代码的结果是:
father constructor
son constructor
[son]publ_i is 1
[son]prot_i is 3
[son]mypriv_i is 2
派生类构造函数还可以明确的在初始化列表中调用基类的构造函数,来间接的初始化基类成员。注意,在派生类列表中,不能直接初始化基类成员:
class son: public father
{
public:
son(int a = 2):mypriv_i(a),father(100)
{
cout << "son constructor" << endl;
}
void display()
{
cout << "[son]publ_i is " << publ_i << endl;
cout << "[son]prot_i is " << prot_i << endl;
cout << "[son]mypriv_i is " << mypriv_i << endl;
}
private:
int mypriv_i;
};
int main()
{
son ss1;
ss1.display();
}
不管初始化列表中的顺序如何,派生类构造函数,都是首先调用基类构造函数初始化基类成员,然后是根据派生类成员的声明顺序进行初始化。
如果尝试在初始化列表中直接初始化基类成员,会发生编译错误:
son(int a = 2):mypriv_i(a),publ_i(100)
{
cout << "son constructor" << endl;
}
报错如下:
test2.cpp: In constructor ‘son::son(int)’:
test2.cpp:29:29: error: class ‘son’ does not have any field named ‘publ_i’
son(int a = 2):mypriv_i(a),publ_i(100)
^
派生类构造函数不能初始化基类的成员且不应该对基类成员赋值。如果那些成员为 public 或 protected,派生构造函数可以在构造函数函数体中给基类成员赋值,但是,这样做会违反基类的接口。派生类应通过使用基类构造函数尊重基类的初始化意图,而不是在派生类构造函数函数体中对这些成员赋值。
注意,一个类只能初始化自己的直接基类。
2:复制控制和继承
派生类合成的复制控制函数,对对象的基类部分连同派生部分的成员一起进行复制、赋值或撤销,自动调用基类的复制构造函数、赋值操作符或析构函数对基类部分进行复制、赋值或撤销:
class father
{
public:
int publ_i;
father(int a=1, int b=2, int c=3):publ_i(a),priv_i(b), prot_i(c)
{
cout << "father constructor" << endl;
}
father(const father &src):publ_i(src.publ_i), priv_i(src.priv_i), prot_i(src.prot_i)
{
cout << "father copy constructor" << endl;
} father& operator=(const father& src)
{
publ_i = src.publ_i;
priv_i = src.priv_i;
prot_i = src.prot_i; cout << "father operator = " << endl;
return *this;
}
~father()
{
cout << "father destructor" << endl;
}
virtual void display()
{
cout << "[father]publ_i is " << publ_i << endl;
cout << "[father]priv_i is " << priv_i << endl;
cout << "[father]prot_i is " << prot_i << endl;
}
private:
int priv_i;
protected:
int prot_i;
}; class son: public father
{
public:
son(int a = 2):mypriv_i(a)
{
cout << "son constructor" << endl;
}
void display()
{
cout << "[son]publ_i is " << publ_i << endl;
cout << "[son]prot_i is " << prot_i << endl;
cout << "[son]mypriv_i is " << mypriv_i << endl;
}
private:
int mypriv_i;
};
int main()
{
son ss1(3);
son ss2(ss1);
ss2.display(); son ss3;
ss3 = ss1;
ss3.display();
}
“son ss2(ss1);”语句,将调用派生类son的合成的复制构造函数,该构造函数将会自动调用基类的复制构造函数;”ss3 = ss1;”语句,将调用派生类son的合成的赋值操作符函数,该函数会自动调用基类的赋值操作符函数;最后,程序退出之前,会将派生类son的三个对象进行析构,从而自动调用基类的析构函数;上述代码的结果如下:
father constructor
son constructor
father copy constructor
[son]publ_i is 1
[son]prot_i is 3
[son]mypriv_i is 3
father constructor
son constructor
father operator =
[son]publ_i is 1
[son]prot_i is 3
[son]mypriv_i is 3
father destructor
father destructor
father destructor
如果派生类自己定义了复制构造函数或赋值操作符,则应该显示的调用基类的复制构造函数或赋值操作符:
class father
{
public:
int publ_i;
father(int a=1, int b=2, int c=3):publ_i(a),priv_i(b), prot_i(c)
{
cout << "father constructor" << endl;
}
father(const father &src):publ_i(src.publ_i), priv_i(src.priv_i), prot_i(src.prot_i)
{
cout << "father copy constructor" << endl;
} father& operator=(const father& src)
{
publ_i = src.publ_i;
priv_i = src.priv_i;
prot_i = src.prot_i; cout << "father operator = " << endl;
return *this;
}
~father()
{
cout << "father destructor" << endl;
}
virtual void display()
{
cout << "[father]publ_i is " << publ_i << endl;
cout << "[father]priv_i is " << priv_i << endl;
cout << "[father]prot_i is " << prot_i << endl;
}
private:
int priv_i;
protected:
int prot_i;
}; class son: public father
{
public:
son(int a = 2):mypriv_i(a),father(100)
{
cout << "son constructor" << endl;
}
son(const son& src):mypriv_i(src.mypriv_i),father(src)
{
cout << "son copy constructor" << endl;
}
son& operator=(const son& src)
{
father::operator=(src);
mypriv_i = src.mypriv_i; cout << "son operator = " << endl;
return *this;
}
void display()
{
cout << "[son]publ_i is " << publ_i << endl;
cout << "[son]prot_i is " << prot_i << endl;
cout << "[son]mypriv_i is " << mypriv_i << endl;
}
private:
int mypriv_i;
};
int main()
{
son ss1(3);
son ss2(ss1);
ss2.display(); son ss3;
ss3 = ss1;
ss3.display();
}
在派生类son的复制构造函数中,初始化列表中显示调用基类复制构造函数使用src初始化基类部分。这里如果省略调用基类复制构造函数的调用的话,编译器会自动调用基类的默认构造函数初始化基类部分,这就造成新构造的对象会有比较奇怪的配置,它的基类部分保存默认值,而他的派生类部分是src的副本;
在派生类son的赋值操作符函数中,显示调用了基类的赋值操作符函数。如果省略了这一调用,则赋值操作,仅仅使该对象的mypriv_i与src的mypriv_i相同;
上述代码的结果如下:
father constructor
son constructor
father copy constructor
son copy constructor
[son]publ_i is 100
[son]prot_i is 3
[son]mypriv_i is 3
father constructor
son constructor
father operator =
son operator =
[son]publ_i is 100
[son]prot_i is 3
[son]mypriv_i is 3
father destructor
father destructor
father destructor
如果定义了派生类的析构函数,则不同于复制构造函数和赋值操作符:编译器会自动调用基类的析构函数,因此在派生类的析构函数中,只负责清除自己的成员:
~son()
{
cout << "son destructor" << endl;
}
定义了son的析构函数之后,上述代码的结果是:
...
son destructor
father destructor
son destructor
father destructor
son destructor
father destructor
对象的撤销顺序与构造顺序相反:首先运行派生析构函数,然后按继承层次依次向上调用各基类析构函数。
另外,如果基类指针指向了一个派生类对象,则删除该基类指针时,如果基类的析构函数不是虚函数的话,则只会调用基类的析构函数,而不会调用派生类析构函数。比如还是上面的father和son两个类,运行下面的代码:
father *fp = new son;
delete fp;
结果就是:
father constructor
son constructor
father destructor
为了避免这种情况的发生,应该将基类的析构函数定义为虚函数,这样,无论派生类显式定义析构函数还是使用合成析构函数,派生类析构函数都是虚函数。从而可以得到正确的结果。
因此,即使析构函数没有工作要做,继承层次的根类也应该定义一个虚析构函数。
3:构造函数或析构函数中调用虚函数
在运行构造函数或析构函数的时候,对象都是不完整的。为了适应这种不完整,编译器将对象的类型视为在构造或析构期间发生了变化。在基类构造函数或析构函数中,将派生类对象当作基类类型对象对待。
因此,如果是在构造函数或析构函数中调用虚函数,则运行的是为构造函数或析构函数自身类型定义的版本。如果在基类构造函数中调用虚函数,则构造派生类的时候,该虚函数实际上还是调用的基类的版本:
class father
{
public:
int publ_i;
father(int a=1, int b=2, int c=3):publ_i(a),priv_i(b), prot_i(c)
{
cout << "father constructor" << endl;
display();
}
virtual void display()
{
cout << "[father]publ_i is " << publ_i << endl;
cout << "[father]priv_i is " << priv_i << endl;
cout << "[father]prot_i is " << prot_i << endl;
} private:
int priv_i;
protected:
int prot_i;
}; class son: public father
{
public:
son()
{
cout << "son constructor" << endl;
} void display()
{
cout << "[son]publ_i is " << publ_i << endl;
cout << "[son]prot_i is " << prot_i << endl;
}
}; int main()
{
father *fp = new son;
}
在构造son对象时,需要调用father的构造函数,在father的构造函数中调用了虚函数display。此时,虽然构造的是派生类对象,但是这里依然还是调用基类的display函数。因此,上述代码的结果为:
father constructor
[father]publ_i is 1
[father]priv_i is 2
[father]prot_i is 3
son constructor
无论由构造函数(或析构函数)直接调用虚函数,或者从构造函数(或析构函数)所调用的函数间接调用虚函数,都应用这种绑定。
要理解这种行为,考虑如果从基类构造函数(或析构函数)调用虚函数的派生类版本会怎么样。虚函数的派生类版本很可能会访问派生类对象的成员,但是,对象的派生部分的成员不会在基类构造函数运行期间初始化,实际上,如果允许这样的访问,程序很可能会崩溃。
C++继承与构造函数、复制控制的更多相关文章
- C++ Primer 学习笔记_67_面向对象编程 --转换与继承、复制控制与继承
面向对象编程 --转换与继承.复制控制与继承 I.转换与继承 引言: 由于每一个派生类对象都包括一个基类部分,因此能够像使用基类对象一样在派生类对象上执行操作. 对于指针/引用,能够将派生类对象的指针 ...
- C++ 复制控制之复制构造函数
7月26日更新: 过了这么长的时间回过头来看,发现文章中有几个点说错(用红字标出): 构造函数不是只有唯一一个参数,它也可以是多参数形式,其第二参数及后继以一个默认值供应. 不是没有声明复制控制函数时 ...
- C++ Primer 随笔 Chapter 13 复制控制
1.复制控制包含的内容:复制构造函数.赋值操作符.析构函数 2.复制构造函数: a. 定义:只有单个形参,而且该形参是对本类类型的引用,这样的构造函数被成为复制构造函数 b. 适用情况: (1)根据一 ...
- C++Primer笔记之复制控制
复制控制这一节需要注意的地方不多,主要有以下几点: 1.定义自己的复制构造函数 什么时候需要定义自己的复制构造函数,而不用系统提供的,主要遵循以下的经验说明: 某些类必须对复制对象时发生的事情加以控制 ...
- 稍微深入点理解C++复制控制【转】
通过一个实例稍微深入理解C++复制控制过程,参考资料<C++ primer>,介绍点基本知识: 1.在C++中类通过特殊的成员函数:复制构造函数.赋值操作符和析构函数来控制复制.赋值和撤销 ...
- C++拾遗(六)——复制控制
年前忙了几天,到现在才算是有空休息下来.先祝大家新年快乐,心想事成:)我也会发笑脸o.o 这篇博文主要介绍定义一个类型的对象时的复制控制方式,这部分内容之前有一定的了解但又浅尝辄止,始终感觉没能找到要 ...
- 三、Java基础---------关于继承、构造函数、静态代码块执行顺序示例讲解
在上节博客中曾提到过类的继承,这篇文章主要是介绍类的继承.构造函数以及静态代码块的执行顺序. 首先接着分析在黑马基础测试中的一个关于继承的题目,题目描述如下: 声明类Person,包含2个成员变量:n ...
- C++菱形继承的构造函数
网上搜了很多,大多是关于菱形虚继承的构造函数应该怎么写,或者就是最简单的,四个类都不带参数的构造函数. 本文旨在记录一下困扰了博主1h的问题,非常浅显,有帮助固然好,如果侮辱谁的智商还见谅,当然无限欢 ...
- 从零开始学C++之继承(二):继承与构造函数、派生类到基类的转换
一.不能自动继承的成员函数 构造函数 析构函数 =运算符 二.继承与构造函数 基类的构造函数不被继承,派生类中需要声明自己的构造函数. 声明构造函数时,只需要对本类中新增成员进行初始化,对继承来的基类 ...
随机推荐
- 配置android studio环境2
安装android studio 2.1运行 exe 程序 安装截图 备注 :O(∩_∩)O~等了 ,但是还是失败, 完全安装啊,不影响,可以手动运行安装目录下的 如:D:\Program Files ...
- 基于jquery实现图片上传本地预览功能
一.原理 分为两步: 当上传图片的input被触发并选择本地图片之后获取要上传的图片这个对象的URL(对象URL),把对象URL赋值给事先写好的img标签的src属性即可把图片显示出来.在这里,我们需 ...
- HDU 3714
最大值最小问题,三分....竟然排第六当时..... #include<stdio.h> #include<string.h> #define max 10000+10 #de ...
- org.hibernate.service.ServiceRegistryBuilder被弃用
看视频教程是这样写的: //创建配置对象 Configuration config = new Configuration().configure(); //创建服务注册对象 ServiceRegis ...
- 统计py文件或目录代码行数
bug:当遇到3个"""时 可能会将下面的代码不计入代码总行数 import os def count_path(path,countcode): if os.path. ...
- 备忘录模式(Memento、Originator、Caretaker)(状态保存,备份恢复)
定义:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态.这样就可以将该对象恢复到原先保存的状态 类型:行为类 类图: 我们在编程的时候,经常需要保存对象的中间状态,当需要的时 ...
- day18 13.乐观锁介绍
乐观锁是使用版本字段,悲观锁是使用数据库底层的锁机制.mysql的类型timestamp(时间戳)有一个特点:插入数据不用管我,我取系统当前默认值.timestamp插入时间会记录,修改时间也会记录. ...
- linux设置变量的三种方法
1在/etc/profile文件中添加变量对所有用户生效(永久的) 用VI在文件/etc/profile文件中增加变量,该变量将会对Linux下所有用户有效,并且是“永久生效”. 例如:编辑/etc/ ...
- sar网络统计数据
sar是一个研究磁盘I/O的优秀工具.以下是sar磁盘I/O输出的一个示例. 第一行-d显示磁盘I/O信息,5 2选项是间隔和迭代,就像sar数据收集器那样.表3-3列出了字段和说明. 表3-3 ...
- BZOJ2529: [Poi2011]Sticks
2529: [Poi2011]Sticks Time Limit: 10 Sec Memory Limit: 128 MBSec Special JudgeSubmit: 257 Solved: ...