64,637
社区成员
发帖
与我相关
我的任务
分享
// program 8_3.cpp 派生类对象的创建和初始化与基类对象的创建和初始化有关。
# include<iostream.h>
class CA{
int a;
public:
CA(int n){ a=n; cout<<"CA::a="<<a<<endl; };
~CA(){cout<<"CAobj is destructing."<<endl;};
};
class CB{
int b;
public:
CB(int n){ b=n; cout<<"CB::b="<<b<<endl; };
~CB(){cout<<"CBobj is destrcting."<<endl;};
};
class CC:public CA{
int c;
public:
CC(int n1,int n2):CA(n2){ c=n1; cout<<"CC::c="<<c<<endl; };
~CC(){cout<<"CCobj is destructing"<<endl;};
};
class CD:public CB,public CC{
int d;
public:
CD(int n1,int n2,int n3,int n4):CC(n3,n4),CB(n2){
d=n1; cout<<"CD::d="<<d<<endl;
};
~CD(){cout<<"CDobj is destructing"<<endl;};
};
void main(void){
CD CDobj(2,4,6,8);
}
/*
这个程序只有一个对象说明语句,但却要运行四个构造函数和四个析构函
数。从下面的运行结果,可以看出调用这些函数的先后次序。输出为:
CB::b=4
CA::a=8
CC::c=6
CD::d=2
CDobj is destructing
CCobj is destructing
CAobj is destructing.
CBobj is destrcting.
从这个例子可知:
(1) 派生类构造函数的参数不仅要为自己的数据成员提供初始化数据, 还要
为基类,以及基类的基类提供初始化数据。
(2)由初始化符表指明哪些参数用于本类,哪些参数用于基类。
(3)在多数多重继承的情况下,初始化工作先基类(多个基类则按基类说明
表处的自左至右顺序,而并不按初始化符表处的顺序!) ,再对象成员,最后是自
身。如果基类又是一个派生类,那么它的初始化又同样按本条指出的顺序,这是
个递归过程。
//一般性顺序:基类>>对象成员>>自身初始化//
如类CD的对象CDobj的初始化顺序为:
CD的基类CB的初始化;
CD的基类CC的初始化;
CD的对象成员的初始化(无);
CDobj自身的初始化。
其中:
CD的基类CB的初始化——执行CB();
CD的基类CC的初始化又分为三步:
CC的基类CA的初始化;
CC的对象成员初始化(无);
CCobj自身的初始化。
所以上述初始化过程为:
CB初始化;
CA初始化;
CC初始化;
CD初始化。
故在设计派生类构造函数时应注意参数的分配。
读者可能已经注意到了如下事实:通过继承可使派生类中“拥有”一个基类
的对象; 通过将类中的数据成员说明成是另一个类的对象时, 也使得在该类中“拥
有”了那一个类的对象。但两者在概念和使用上既有关联又有较大的区别。
当类的数据成员为另一个类的对象时,意味着包含,是指A类对象中总含有
一个B类对象(对象成员) 。它们属于整体与部分的关系(“has a”关系) ,如汽
车和马达,马达是汽车的一部分。不妨称包含类对象的类为“组装类”。
当使用类的继承产生派生类后, 派生类对象中也总 “拥有” 基类的对象成员,
这意味着派生类的对象必然是一个基类对象(“is a”关系) ,如汽车和轿车,首
先轿车就是汽车(具有汽车的所有特征) ,另外它又比汽车有所特殊(还具有另
外一些特殊属性)。
在使用它们时有两点需要注意。 一是注意构造函数和析构函数的执行次序以
及“组装类”或派生类构造函数应负有的“责任” — 既要对所包含的每一个对
象成员的初始化负责(若含有对象成员的话) ,又要对其直接基类的初始化负责
(若又为派生类的话) 。二是注意由于“组装”关系与继承关系的不同,决定了
对其对象成员 (或基类成员) 的访问方式以及对其对象可施加操作的某些不相同。
例如,由于派生类的对象必然是一个基类对象,通过派生类对象,也就可以直接
调用(或存取)其基类的公有或保护成员函数(或公有及保护数据成员) 。如最
常用的调用方式为:<派生类对象>.<基类的公有或保护成员>。但通过“组装类”
的类对象调用其对象成员的公有成员函数(或公有数据成员)时,则必须使用另
外的调用方式:<组装类对象>.<对象成员>.<对象成员所属类的公有成员>。
又比如,如下的赋值操作是允许的:
<基类对象> = <派生类对象>;
因为派生类对象必然是一个基类对象,它包含着基类对象所需要的一切数据(另
外还有“富余” ,但“富余”部分被“甩掉”不进行赋值)。
但反方向的赋值则不被允许( <派生类对象> = <基类对象>;) 。可这样来理
解:基类对象不具有派生类对象所需的一切数据,反方向不具有“is a” 关系! 不
可进行赋值!
另外,如下的两种赋值操作都是不允许的,因为它们的类型不匹配,属于整
体与部分的关系,不可相互赋值:
<对象成员所属类的对象> = <组装类对象>;
<组装类对象> = <对象成员所属类的对象>;
*/
#include <iostream>
using namespace std;
class A
{
private:
int a;
public:
A(int x):a(x) { cout <<a <<" "; }
};
class B: A
{
private:
int b, c;
const int d;
A x, y;
public:
B(int v): b(v),y(b+2),x(b+1),d(b),A(v)
{
c=v;
cout <<b <<" " <<c <<" " <<d;
}
};
int main(void)
{
B z(1);
return 0;
}
/*
1.定义一个派生类对象,首先初始化它的基类成员(基类部分)
即调用基类的构造函数(如果是多继承,则按继承的先后顺序调用基类的构造函数)
2.基类部分初始化完之后,初始化派生类部分,派生类的成员初始化依赖它的声明顺序
并不依赖它的初始化列表的顺序初始化派生类成员,总结来说:就是派生类成员的初始化
依赖它的声明顺序而不是依赖初始化列表的顺序。
3.调用派生类的构造函数,可以理解为就是执行派生类构造函数的函数体而已
4.特别注意:但是,请注意:上面两点调用构造函数或者其他的参数传递是参考初始化列表给出的参数的
详细解释:
首先:B z(1);则依据1,调用基类的构造函数,但是这里不知道该调用基类的哪个构造函数
因为基类有默认的构造函数(即没有参数)和你定义的A(int x)这个构造函数,所以,编译器
要进行选择。依据4,参考到初始化列表b(v),y(b+2),x(b+1),d(b),A(v)中有A(v),所以编译器
选择调用你定义的构造函数A(int x),所以打印输出a的值,输出 1,然后,依据2,派生类自身定义的
部分是按它的定义顺序初始化的,即按下面这个顺序,b,c,d,x,y.
int b, c;
const int d;
A x, y;
所以,依据4,分别参考初始化列表b(v),y(b+2),x(b+1),d(b),A(v) 给出的参数信息,可知道
初始化b,使用b(v),b被初始化为1。然后,初始化c,由于初始化列表中没有指定c的初始化,所以
暂时c不被初始化,然后初始化d,根据初始化列表中的d(b),d被初始化为b的值,即为1。然后初始化
A类对象x和y,依据初始化列表中的x(b+1)初始化x,由于b的值为1,所以即相当于x(2),给除了一个参数
2,则调用你定义的构造函数A(int x),打印输出类A的x对象中的a的值,即输出2,同理,依据y(b+2)
初始化y,打印输出3。
最后,依据3,调用派生类构造函数,即
B(int v)
{
c=v;
cout <<b <<" " <<c <<" " <<d;
}
这时,直接忽略初始化列表了,执行这个派生类的构造函数,那么执行函数体
c=v;则把那个没初始化的c被赋值为v的值,即c的值为1。最后打印输出b和c的值
所以再输出两个1。
综上所述:输出1 2 3 1 1 1
*/
class B: A
{
private:
int b, c;
const int d;
A x, y;
public:
B(int v): b(v),y(b+2),x(b+1),d(b),A(v)
{
c=v;
cout < <b < <" " < <c < <" " < <d;
}
};
#include <iostream>
using namespace std;
class A1
{
private:
int a;
public:
A1(int x):a(x) { cout << a << " "; }
};
class B: A1
{
private:
int b, c;
const int d;
A1 x, y;
public:
B(int v): b(30),y(b+2),x(b+1),d(b),A1(v) //B(int v):y(b+2),x(b+1),d(b),A1(v) ,如果改成这样,会输出lz想要的结果
{
c=v;
cout << b<< " "<< c<< " "<< d;
}
};
int main(void)
{
B z(1);
return 0;
}