C/C++程序设计
1
四、派生类的三种继承方式五、继承和不继承的语义六、派生与继承的算例七、构造和析构的次序
C/C++程序设计
2
四、派生类的三种继承方式关键字 [ private,public,protected ]具有双重含义:
1,明确界定同一类层次中各成员的访问控制属性 ;
2.是指类上下关系中派生类对于基类成员的访问控制属性的隐含演化规则。派生类的继承方式就是指第二个含义而言。首先抓住下面几个主要的概念:
a,继承不影响基类的数据成员分配,不破坏基类的独立性 ;
b,所有的数据成员都占有内存,静态数据成员被唯一地放置在全局数据区。
C/C++程序设计
3
c,派生类对象占有的内存是基类对象占有的内存和派生类新增数据成员的内存之和。
d,类层次本身的访问控制属性和继承方式对于对象的内存分配无影响。
通过继承无论是私有继承还是公共继承,派生类的对象既潜在地继承上层基类的所有数据状态也潜在地拥有基类所有的成员函数。
派生类对于基类的成员访问控制属性受制于两个因素,
基类本身成员的访问控制属性,派生类相对于基类的继承方式控制。
C/C++程序设计
4
下面是关于编译器继承方式的描述,
1),对于任意继承方式,基类的私有成员对于派生类是

不可访问的”。
2),对于公共继承方式,基类的公共成员为派生类的公共成员,基类的保护成员为派生类的保护成员。
3),对于保护继承方式,基类的公共的和保护的成员变为派生类的保护成员。
4),对于私有继承方式,基类的公共的和保护的成员变为派生类的私有成员。
C/C++程序设计
5
除开禁止派生类访问的基类的私有成员以外,无论是继承的成员还是新增的成员,派生类的成员访问控制属性只有三种,1),可以在任何位置访问的公共成员。
2),可以出现在派生类中的保护成员。
3),不出现在派生类的私有成员。
保护成员和私有成员仅由类的成员函数或者友元函数访问,禁止派生类对象外部访问。
公共成员允许对象外部访问。访问控制对于类作用域所有的名称是同等适用的。这些名称包括成员名、类中的枚举常数名称和嵌套类。
C/C++程序设计
6
五、继承和不继承的语义
C++语言在关于继承的特殊函数时指出:不参与继承的特殊函数是构造函数、析构函数、赋值运算符函数和作为特权地位的友元函数。前已指出派生类继承了基类的数据状态。
即便是私有继承,基类的私有成员依然成为派生类对象内存的一部分。
友元函数不继承的含义是指:友元函数形参列表的基类的对象指针只能够无禁锢的访问基类的所有成员,这种特权地位不因派生类的继承关系而使得基类的特权函数也获得对派生类成员的无条件访问。
C/C++程序设计
7
继承的含义:基类的数据成员和函数成员在派生类可访问的环境下可当作派生类的一个有机组成部分,就好像上下层次的区分并不存在一样。
继承的含义指形如 obj.m_n 或 pobj->f()访问格式,如果派生类中存在相应的成员直接采用派生类的成员。
如果派生类没有提供相关的成员,则使用从基类继承的可访问成员。
派生类成员函数的定义遵循基类的一般规则,但派生类继承了基类的数据成员和成员函数。根据继承的方式和继承的语义操作相应的信息。
C/C++程序设计
8
构造函数和析构函数不能继承表现一种对象分而制之的思想,基类负责构造自身的数据成员,派生类负责构造继承的和新添补的数据成员,这样有利于派生类缺省构造函数自动调用基类的缺省构造函数。相应的析构函数执行各自的清理工作。
这种对象处理当前类数据的思想并非不适应于其它可以继承的成员函数,但由于构造和析构函数被系统自动调用的特殊性,上下类层次之间的操作有必要加以严格的界定。
C/C++程序设计
9
基类的构造函数恰恰是可以被派生类构造函数以基类名显式调用的函数,不能继承的特殊函数实际上是要求重点处理的函数,对于不继承的函数应该精心提交派生类的相应版本,以分别处理数据的变动情况。在这里不继承的含意是指派生类的对象自动调用的是自身类的构造函数,而不隐含地向上借用。
赋值运算符函数的作用类似拷贝构造函数,不参入继承机制。特别地如果类中存在指针成员,程序员应仔细处理指针的动态内存资源。
如果用户未提交构造函数和析构函数或赋值运算符函数,编译器生成相应的简单版本满足最低化的要求。
C/C++程序设计
10
继承的含义是指基类的数据成员和函数成员在派生类可访问的环境下可当作派生类的一个有机组成部分,就好像上下层次的区分并不存在一样。
继承的含义指形如 obj.m_n 或 pobj->f()访问格式,如果派生类中存在相应的成员直接采用派生类的成员。
如果派生类没有提供相关的成员,则使用从基类继承的可访问成员。
派生类成员函数的定义遵循基类的一般规则,但派生类继承了基类的数据成员和成员函数。根据继承的方式和继承的语义操作相应的信息。
C/C++程序设计
11
六、派生与继承的算例
[例 ] B类公共地继承 A类,A类的公共成员是 B类的公共成员
#include <stdio.h>
class A
{ public,long& X() { return x; }
protected,long x; };
class B:public A { public,B() { x=1; } };
class C:public B { /* public:B::x;*/ };
void main()
{ C b;
long* p=& (b.X() +=1);
printf ("%d\n",*p);
} //输出,2
C/C++程序设计
12
[例 ] B类保护地继承 A类,A类保护的或公共的成员是 B类的保护成员
#include <stdio.h>
class A { protected,long x; };
class B,protected A { public,long& X() { return x;} };
class C,protected B { public,B::X; }; //C类保护地继承 B类
void main() //public,B::X;界定成员的控制属性优于
//继承方式 protected 的控制属性
{ C b; //public,B::X; 表示将基类中保护的成员在
//派生类中界定为 public属性
long* p=& (b.X()=1);
printf ("%d\n",*p);
} //输出,1
C/C++程序设计
13
[例 ] B类私有地继承 A类,A类的公共的或保护的成员是 B
类的私有成员
#include <stdio.h>
class A { private,long x;
public,long& X() { return x;} };
class B,A{ public,long y; long& Y() { return X();} };
class C,private B { public,B::Y; };
void main() //public,B::Y;将基类中公
//共的成员在派生类中界定为 public属性
{ C c;
long* p=& (c.Y ()=1);
printf ("%d\n",*p);
} //输出,1
C/C++程序设计
14
[例 ] B类保护地继承 A类,C类私有地继承 B类。
#include <stdio.h>
class A
{ protected,long x; };
class B,protected A
{ public,long& operator+=(int n) { return x+=n;} };
class C,private B
{ public,B::operator+=; public:B::x; };
void main()
{ C b; b.x=1;
long* p=&(b+=2);
printf ("%d,%d\n",*p,b.x);
} //输出,3,3
C/C++程序设计
15
七、构造和析构的次序生成派生类的对象时编译器按照如下的次序调用构造函数:
1,基类构造函数。多继承情形先声明的先调用即从左到右的次序调用基类构造函数。
2,同一类层次中嵌入对象的构造函数,嵌入对象构造函数按照在类中的声明次序依次调用,与成员初始化语法的排放次序无关。嵌入对象如果存在才调用。
3,派生类的构造函数。确保继承树层次的构造函数全被调用一次。
C/C++程序设计
16
4,总的原则是从左到右从上到下由内向外调用构造函数。
下面是构造函数成员初始化在单继承含一个嵌入对象时的语法格式,
CDerived::CDerived (t1 v1,t2 v2,...,tn vn ):
CBase (v1,v2,v3),objEmbed (v2,v3,vn)
{ 派生类部分子集合成员初始化; }
冒号后的成员初始化列表中 CBase (v1,v2,v3)是对于基类构造函数的显式调用而 objEmbed (v2,v3,vn) 是嵌入对象调用自身所隶属的构造函数。
语法上基类名、嵌入对象名两者之间的次序可以互换。
C/C++程序设计
17
派生类构造函数的形参作用域开始于冒号处结束于构造函数的外层右花括号。因此冒号后构造函数中的实参可以直接采用派生类的形参和其它全局变量名。
如下的对象定义语句:
CDerived objd(v1,v2,...,vn );
CDerived* pObjd=new CDerived(v1,v2,...,vn);
导致派生类含 n个形参的构造函数的调用,由此诱发一系列基类构造函数的启动。实参从最晚派生类层层向上传递,直到顶层基类构造函数中的代码首先得到执行。
为确保此种传递机制的环环相扣,派生类负责直接基类的初始化。
C/C++程序设计
18
派生类缺省的构造函数激发基类相应缺省构造函数的调用。成员函数可以作递归调用。 例如,
[void CDerived:,Line (){Line();}]
是递归调用,递归函数的形参是反复入栈的,隐含的 this指针形参出现于递归函数时需要精心设计算法。
为避免递归调用发散,派生类 CDerived成员函数中显式地调用基类的成员函数。 形式为,
void CDerived:,Line() { CBase:,Line(); ;...; }
对于虚掉类域分辨符的索引方式 m_n,派生类优先使用自身类新增的名称。如果派生类本身未交付这个名称,则上溯索引可访问的基类中的名称。
这是优先采用派生类成员名称的支配原则。
C/C++程序设计
19
[例 ] 基类和嵌入对象的初始化
#include <stdio.h>
struct SData{ long nx; long ny; };
typedef struct tagPOINT { long x; long y; } POINT;
class CPoint,public tagPOINT
{ public:CPoint (){ }
CPoint (POINT initPt) {x=initPt.x; y=initPt.y; } };
class CBase
{ public,CBase(){ } ~CBase() {delete [ ] m_p; }
protected:
CBase (SData* pData)
{ m_n=pData->nx; m_p=new long [m_n]; }
C/C++程序设计
20
void Line(){for (int j=0;j<m_n;j++) m_p[j]=j;}
protected,long m_n; long* m_p;
};
class CDerived:public CBase
{ public,CDerived ( SData* pData,CPoint pt);
CDerived(){}
~CDerived() {delete [ ] m_p;}
void Line(); void Show();
protected,CPoint m_pt;
long m_n; long* m_p;
};
CDerived::CDerived (SData* pData,CPoint pt),
CBase (pData),m_pt(pt)
C/C++程序设计
21
{ m_n=pData->ny; m_p=new long [m_n]; }
void CDerived::Line()
{ CBase::Line(); for (int j=0;j<m_n;j++) m_p[j]=j; }
void CDerived::Show()
{ Line();
long sumx=0; long sumy=0; int k;
for ( k=0; k<CBase::m_n; k++)
sumx+=CBase::m_p[k];
for( k=0;k<m_n;k++) sumy+=m_p[k];
printf ("{nx,ny}={%d,%d};",CBase::m_n,m_n);
printf ("sumx=%d,sumy=%d,m_pt={%d,%d}\n",
sumx,sumy,m_pt.x,m_pt.y); }
C/C++程序设计
22
void main()
{ SData d={4,5};
POINT pt={60,70};
CPoint cpt (pt);
CDerived obj (&d,cpt);
(&obj)->Show ();
}
//输出,{nx,ny}={4,5};sumx=6,sumy=10,m_pt={60,70}
C/C++程序设计
23
成员函数常作为公共的成员,以便对象指针外部访问。
本题中基类的构造函数处理为保护的属性,这个构造函数可在友元函数或 (派生 )类的成员函数中访问。
obj(&d,cpt)在外部调用构造函数
CDerived(SData*,CPoint )
实参由此向上转入到顶层的基类的保护构造函数。
对于其间卷入的构造函数调用链,编译器在内部会履行一个代码的展开,以保证基类构造函数中的代码首先得到执行。
数据结构 SData负责启动 CDerived类的初始数据描述,根据 SData可以张成 CDerived类的动态数据。
C/C++程序设计
24