派生和继承
一,单重继承的定义形式
class 派生类名:继承访问控制 基类类名{
成员访问控制:
成员声明列表;
};
二,继承中的访问控制
• private(私有的):
在private后声明的成员称为私有成员,私有成员只能通过
本类的成员函数来访问。
• public (公有的):
在public后声明的成员称为公有成员,公有成员用于描述一个类与外部世界的接口,
类的外部(程序的其它部分的代码)可以访问公有成员。
• protected(受保护的):
受保护成员具有private与public的双重角色:对派生类的成员函数而言,它为
public,而对类的外部而言,它为private。即:
protected成员只能由本类及其后代类的成员函数访问。
ps:
-
无论采用什么继承方式,基类的私有成员在派生类中都是不可访问的。
-
“私有”和“不可访问”有区别:私有成员可以由派生类本身访问,不可访问成员即使是派生类本身也不能访问。
-
大多数情况下均使用public继承
三,继承时的构造函数
• 基类的构造函数不被继承,派生类中需要声明自己的构造函数。
• 派生类的构造函数中只需要对本类中新增成员进行初始化即可。
对继承来的基类成员的初始化是通过编译在派生类构造函数初始化器中自动生成默认构造函数(默认拷贝构造)完成。
• 如果基类没有默认构造(包括 =delete),则编译错误
• 派生类的构造函数需要使用基类的
有参构造函数,必须显式在
初始化列表中
申明。注意:不能在构造函数内调用!
#include
<iostream>
using
namespace
std;
class
A
{
public
:
int
n;
A() {
n = 1;
m = 2;
z = 3;
}
A(
int
a
,
int
b
,
int
c
):n(
a
),m(
b
),z(
c
){}
private
:
int
m;
protected
:
int
z;
};
class
A1
:
public
A
{
public
:
void
print()
{
cout
<<
n
<<
" "
<<
" "
<<
z;
}
int
a;
A1()
//同时调用了A无参的构造函数
{
a = 1;
}
A1(
int
x
,
int
b
,
int
c
) :
A
(
x
,
b
,
c
) { a =
x
; }
//调用了有参的构造函数
};
int
main()
{
A1
a1;
A1
a2(0,0,0);
cout
<<
a1.n
<<
" "
<<
a2.n;
return
0;
}
输出:1 0
四,构造函数的调用次序(创建派生类对象时)
• 首先调用其基类的构造函数(调用顺序按照基类被继承时的声明顺序
(从左向右))。
• 然后调用本类对象成员的构造函数(调用顺序按照对象成员在类中的声
明顺序)。
• 最后调用本类的构造函数。
五,撤销派生类对象时析构函数的调用次序与构造函数的调用次序相反
• 首先调用本类的析构函数
• 然后调用本类对象成员的析构函数
• 最后调用其基类的析构函数
五,类中不可继承的成员
-
私有成员
-
构造函数和析构函数
-
赋值运算符的重载( “ 赋值运算符重载函数 ” 不是不能被派生类继承,而是被派生类的默认 “ 赋值运算符重载函数 ” 给覆盖了。 这就是 C++ 赋值运算符重载函数不能被派生类继承的真实原因! )
六,隐藏(overwrite)- 屏蔽基类的函数定义
触发条件:
(1) 派生类的函数与基类的函数
同名,但是参数列表有所差异。
(2) 派生类的函数与基类的函数
同名,参数列表也相同,但是基类函数没有virtual关键字。
如何访问被隐藏的基类成员函数?
基类名::被隐藏的函数
七、恢复访问的控制方式
• 基类中的public或protected成员,因使用protected或
private继承访问控制而导致在派生类中的访问方式发生改变,可以使用“访问声明”恢复为原来的访问控制方式
• 访问声明的形式
1.using 基类名::成员名;(放于适当的成员访问控制后)
1)可被派生类访问的基类成员,都可以在派生类中的任何访问标号下用using声明,对于派生类外部来说,被声明成员的访问权限由using声明所在的
访问标号决定
,
与基类中的访问权限无关
,也与派生列表中的访问标号无关。
2)
在基类中的private成员,不能在派生类中任何地方用using声明。
2.使用访问声明,形式为 base-class::member;, 位置在子类中适当的访问声明处。(注,只能恢复原有访问权限,而不能提高或降低访问权限)
• 使用情景
在派生类中希望大多数继承成员为protected或private,
只有少数希望保持为基类原来的访问控制方式。
#include
<iostream>
#include
<string>
using
namespace
std;
class
A
{
int
data;
public
:
void
set_data(
int
n
) { data =
n
; }
void
disp_data() { cout
<<
"data = "
<<
data
<<
endl; }
};
class
B
:
private
A
{
public
:
A
::set_data;
A
::disp_data;
};
int
main()
{
B
b;
b.set_data(1);
b.disp_data();
return
0;
}
八、赋值运算的类型兼容性
• 可以将后代类的对象赋值给祖先类对象,反之不可。
• 每个派生类对象包含一个基类部分,这意味着可以将派生类对象当作基
类对象使用。
• 指向基类对象的指针也可指向公有派生类对象,反之不可。
•只有公有派生类才能兼容基类类型(上述规则只适用于公
有派生)。
九、对象的类型转换
• Upcasting(向上/基类型转换)
– Assigning a pointer of a derived class type to a
pointer of its base class type.Done implicitly
• Downcasting(向下/派生类型转换)
– Assigning a pointer of a base class type to a pointer
of its derived class type
注:dynamic_cast<强制转化成的目标指针类型>(要转化对象指针)--用于指针变量的转化
static_cast<强制转化成的目标类型>(要转化对象)--用于非指针的变量转化
const_cast<去除const之后的类型>(要转化对象)--用于常量转变量
#include <iostream>
using namespace std;
int main() {
const int i = 100;
const int *p = &i;
int *q = const_cast<int*>(p);
int j = i;
*q =10
cout << i << endl << j<<endl
<< *p << endl << *q <<endl;
return 0;
}
#include <iostream>
using namespace std;
int main() {
const int i = 100;
const int &p = i;
int &q = const_cast<int&>(p);
int j = i;
q=…
cout << i << endl << j << endl
<< p << endl << q <<endl;
return 0;
}
十,多继承
1.
• 多重继承:派生类有多个基类。
• 代表概念:C既是A又是B。
class 派生类名:继承访问控制: 基类名1,继承访问控制: 基类名2,...{
成员声明;
}
2.虚基类
继承基类时,在继承访问控制前添加保留字“virtual”。 那么这个基类就是一个虚拟基类。虚拟基类用于
共享继承。
普通基类与虚基类之间的唯一区别
只有在派生类重复继承了某一基类时才表现出来。
• 若派生类有一个虚基类作为祖先类,则在派生类构造函数中需要列出对虚基类构造函数的调用(否则,调用虚
基类的默认构造函数),
且对虚基类构造函数的调用总是先于普通基类的构造函数。
• 创建后代类对象时,当该后代类列出的虚基类构造函数被调用,Virtual关键字保证了虚基类的唯一副本
只被
初始化一次
。等价于
在派生类的对象中,同名的虚基类只产生一个虚基类子对象,而非虚基类产生各自的子对象
•从虚基类直接或间接派生的派生类中的构造函数的成员初始化列表
中都要列出对虚基类构造函数的调用。但仅仅用建立对象的最远派生类的构造函数调用虚基类的构造函数,而该派生类的所有基类中列出的对虚基类的构造函数的调用在执行中被忽略,从而保证对虚基类子对象只初始化一次。
.
• 创建派生类对象时构造函数的调用次序:
-
最先调用虚基类的构造函数;
-
其次调用普通基类的构造函数,多个基类则 按派生类声明时列出的次序、从左到右调用,而不是初始化列 表中的次序;
-
再次调用对象成员的构造函数, 按类声明中对象成员出现的次序调用,而不是初始化列表中的次序
-
最后执行派生类的构造函数。
-
析构顺序: 与构造顺序相反
1994

被折叠的 条评论
为什么被折叠?



