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++之继承(二):继承与构造函数、派生类到基类的转换
一.不能自动继承的成员函数 构造函数 析构函数 =运算符 二.继承与构造函数 基类的构造函数不被继承,派生类中需要声明自己的构造函数. 声明构造函数时,只需要对本类中新增成员进行初始化,对继承来的基类 ...
随机推荐
- 微服务开源生态报告 No.10
「微服务开源生态报告」,汇集各个开源项目近期的社区动态,帮助开发者们更高效的了解到各开源项目的最新进展. 社区动态包括,但不限于:版本发布.人员动态.项目动态和规划.培训和活动. 非常欢迎国内其他微服 ...
- android的AIDL
一.AIDL的意义: AIDL全称是Android Interface Definition Language,是android接口定义语言.AIDL就是为了避免我们一遍遍的写一些 ...
- 浏览器在IE8 以下时显示提示信息,提示用户升级浏览器
<!--[if lt IE 8]> <div style="background: #eeeeee;border-bottom: 1px solid #cccccc;col ...
- How to class-dump iPad apps?
http://stackoverflow.com/questions/4776593/how-to-class-dump-ipad-apps The issue here is that the bi ...
- Orleans 整体介绍
背景 Orleans 是微软开源的Actor模型开发框架. Actor模型 此模型解决了并发编程时对资源竞争使用的问题,将对同一个业务数据的访问从并行变为串行执行,降低了多线程编程的难度,使普通编程人 ...
- config.js配置页面中的样式和图片路径
这个文章用在什么地方,我先说一下,上周啊,我接到一个任务.因为公司业务要对接不同的银行,例如在工行下颜色是红色的,在其他银行下默认为蓝色,所以在页面一致的情况下,保证页面中的按钮和ICON是可以配置的 ...
- Ajax--同源策略,jsonp跨域传输原理(callback),
什么是同源策略? 阮一峰的博客 同源策略 同源策略的解决方法: 跨域传输 img 标签的src是可以引入其他域名下的图片 script标签的src属性同理 ,也可以引入其他域名下的js文件,并执行 1 ...
- javascript函数式编程和链式优化
1.函数式编程理解 函数式编程可以理解为,以函数作为主要载体的编程方式,用函数去拆解.抽象一般的表达式 与命令式相比,这样做的好处在哪?主要有以下几点: (1)语义更加清晰 (2)可复用性更高 (3) ...
- hdu 1251 统计难题(trie树入门)
统计难题 Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 131070/65535 K (Java/Others)Total Submi ...
- Leetcode914.X of a Kind in a Deck of Cards卡牌分组
给定一副牌,每张牌上都写着一个整数. 此时,你需要选定一个数字 X,使我们可以将整副牌按下述规则分成 1 组或更多组: 每组都有 X 张牌. 组内所有的牌上都写着相同的整数. 仅当你可选的 X > ...