派生类构造函数次序矛盾?

laraya 2009-03-14 09:54:39
#include <iostream.h>
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:该书解释说首先调用基类构造函数A(int x),这个没问题,
然后说按照B类的数据成员b,d,x,y依次构造,如果按照这个解释的话确实没什么问题,程序运行正确。
2:其他书的解释:一般派生类构造顺序:(1):是先基类,顺序按继承顺序:(2)再内嵌对象,对象顺序的是按照他们在派生类中定义的顺序,
(3)最后是派生类自己的数据成员,自己数据成员的顺序是正常的,既是说程序先给哪个数据成员赋值哪个就先被构造了。
那么在这个程序里面,显然先构造基类,没问题,然后应该说是构造内嵌对象x,y,但问题是按照这个顺序的话,B的自己数据成员b还并没有构造,也就是值不确定,那你的对象x和y的参数应该也不确定,为什么实际上程序是正确的,也就是说b值先得到了1.
输出是1 2 3 1 1
那么b是怎么先得到参数v的值,这不是先构造了b吗?
...全文
518 32 打赏 收藏 转发到动态 举报
写回复
用AI写文章
32 条回复
切换为时间正序
请发表友善的回复…
发表回复
y309307896 2011-06-24
  • 打赏
  • 举报
回复
顶了!!!
caiguangsong90 2010-11-03
  • 打赏
  • 举报
回复
// 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” 关系! 不
可进行赋值!
另外,如下的两种赋值操作都是不允许的,因为它们的类型不匹配,属于整
体与部分的关系,不可相互赋值:
<对象成员所属类的对象> = <组装类对象>;
<组装类对象> = <对象成员所属类的对象>;
*/
hacker_sx 2009-03-14
  • 打赏
  • 举报
回复
貌似上面多打了个1
hacker_sx 2009-03-14
  • 打赏
  • 举报
回复
#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
*/
aime99 2009-03-14
  • 打赏
  • 举报
回复
应该是无论初始化列表怎么写,都是先 基类, 后 成员, 最后 这个类本身的构造函数
构造基类时:以上面提到的构造顺序一致
构造成员时(无论是对象成员 还是 原始类型成员 都是成员):按照这些成员在类中声明的顺序依次构造,与参数列表顺序无关
最后是本身的构造函数执行
B(int v): b(v),y(b+2),x(b+1),d(b),A(v) 正确
B(int v): b(v),y(b+2),x(b+1),d(b),A(b) 错误,因为在构造基类A(b)是,对象b还没有被初始化

laraya 2009-03-14
  • 打赏
  • 举报
回复
[Quote=引用 18 楼 fibbery 的回复:]
2:其他书的解释:一般派生类构造顺序:(1):是先基类,顺序按继承顺序:(2)再内嵌对象,对象顺序的是按照他们在派生类中定义的顺序,
(3)最后是派生类自己的数据成员,自己数据成员的顺序是正常的,既是说程序先给哪个数据成员赋值哪个就先被构造了。
这个说法不够准确。

1、2、3并不是顺序。

1、是大体的顺序,而2、3说的是1种的每一个构造函数调用的函数体内的顺序。不知道我这么说你是否明白,参考2楼我的回…
[/Quote]
有点道理啊
我多试了几次
就是说派生类成员也是按照声明的顺序做的
即b, c,d,x,y
但是c在函数体内,所以它最后被赋值,所以如果写成这样就不对了
B(int v): c(v),y(b+2),x(b+1),b(c),d(b),A(v)
并非先给c赋值了
仍然是先给b赋值,但是b的参数c不确定。
  • 打赏
  • 举报
回复
[Quote=引用 15 楼 laraya 的回复:]
引用 12 楼 rickyzhang2008 的回复:
引用 2 楼 fibbery 的回复:
分成两个顺序比价容易理解:
1、继承链中的顺序:从基类到派生类;
2、类的数据成员定义顺序:这个顺序是在同一个构造函数中,其数据成员的构造函数调用顺序。如果数据成员定义了默认构造函数或者在初始化列表中调用了数据成员的构造函数,那么,编译器会按照数据成员声明的顺序调用其构造函数。

2只是1的一个步骤,也就是说,1中的每次构造是由2中更微…
[/Quote]
点错了,呵呵

看下面的这句话:
如果数据成员定义了默认构造函数或者在初始化列表中调用了数据成员的构造函数,那么,编译器会按照数据成员声明的顺序调用其构造函数。
  • 打赏
  • 举报
回复
[Quote=引用 14 楼 hemiya 的回复:]
C/C++ code
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 b, c;
const int d;
A x, y;
1,这个是对象的创建顺序,
2,B(int v): b(v),y(b+2),x(b+1),d(b),A(v) ,这个是类内部数据的初始化循序,两个不是一个…
[/Quote]

看下面这句话:

如果数据成员定义了默认构造函数或者在初始化列表中调用了数据成员的构造函数,那么,编译器会按照数据成员声明的顺序调用其构造函数。
fibbery 2009-03-14
  • 打赏
  • 举报
回复
从另外一个角度来理解,一个构造函数被调用,该构造函数执行些什么呢?不正是初始化自己的成员吗?成员如何被初始化呢?不正是成员的构造函数吗?统统地说来都是构造函数的调用而已,顺序很简单,从基类到派生类,也就是,基类先初始化自己的成员(这里就开始形式上的递归了),基类的成员开始调用其自己的基类的构造函数,直到所有基类的成员被初始化完成,在进入基类的子类构造函数,构造子列的成员,依次类推。
fibbery 2009-03-14
  • 打赏
  • 举报
回复
2:其他书的解释:一般派生类构造顺序:(1):是先基类,顺序按继承顺序:(2)再内嵌对象,对象顺序的是按照他们在派生类中定义的顺序,
(3)最后是派生类自己的数据成员,自己数据成员的顺序是正常的,既是说程序先给哪个数据成员赋值哪个就先被构造了。
这个说法不够准确。

1、2、3并不是顺序。

1、是大体的顺序,而2、3说的是1种的每一个构造函数调用的函数体内的顺序。不知道我这么说你是否明白,参考2楼我的回复。

举例子说明:
B继承A,那么,按理说,B z(1);应该先调用A的构造函数。根据B(int v): b(v),y(b+2),x(b+1),d(b),A(v){}
调用顺序是:A(v)-->a(v), b(v), d(v), x(b+1), y(b+2)
laraya 2009-03-14
  • 打赏
  • 举报
回复
[Quote=引用 14 楼 hemiya 的回复:]
C/C++ code
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(b)
{
c=v;
cout < <b < <" " < <c < <" " < <d;

}
};



int b, c;
const int d;
A x, y;
1,这个是对象的创建顺序,
2,B(int v): b(v),y(b+2),x(b+1),d(b),A(b) ,这个是类内部数据的初始化循序,两个不是一个…
[/Quote]
你这个越说越乱了
如果把b放在函数体内赋值的话,
那么基类A(b)中的b就是不确定值了
而不是你说的那样啊
先把v传给b,
那样的话b就存在值了,而不会在调用基类构造函数时候参数不确定了。
fibbery 2009-03-14
  • 打赏
  • 举报
回复
[Quote=引用 15 楼 laraya 的回复:]
我试过了
就是说把对b的赋值放在x的后面或者干脆就放到函数体里面赋值
得到的就是不确定值,
这个显然是对的啊,不论是根据书上的步骤或者说是显然是这样的
所以还是说为什么b放在前面就得到值了,
那么不是和书上说的第二步矛盾了,它并没有立即调用内嵌对象的构造函数,而是先把非内嵌对象的成员值按照先遇到的顺序给赋值了,构造内嵌对象x的时候b已经得到值了。
解释下哈.[/Quote]
这里面的顺序,指编译器自动生成的代码的顺序,不存在人为干预的代码顺序。所以,这里面所说的,是指继承、构造函数初始化列表(变量声明的顺序)、默认构造函数的声明(如果没有在初始化列表中显示调用)所影响的编译器生成的代码。也因此,不能包括,在函数体内人为编写的代码,对构造顺序产生的人为干预。
laraya 2009-03-14
  • 打赏
  • 举报
回复
[Quote=引用 12 楼 rickyzhang2008 的回复:]
引用 2 楼 fibbery 的回复:
分成两个顺序比价容易理解:
1、继承链中的顺序:从基类到派生类;
2、类的数据成员定义顺序:这个顺序是在同一个构造函数中,其数据成员的构造函数调用顺序。如果数据成员定义了默认构造函数或者在初始化列表中调用了数据成员的构造函数,那么,编译器会按照数据成员声明的顺序调用其构造函数。

2只是1的一个步骤,也就是说,1中的每次构造是由2中更微小的调用构成。


对的

C/C++…
[/Quote]
我试过了
就是说把对b的赋值放在x的后面或者干脆就放到函数体里面赋值
得到的就是不确定值,
这个显然是对的啊,不论是根据书上的步骤或者说是显然是这样的
所以还是说为什么b放在前面就得到值了,
那么不是和书上说的第二步矛盾了,它并没有立即调用内嵌对象的构造函数,而是先把非内嵌对象的成员值按照先遇到的顺序给赋值了,构造内嵌对象x的时候b已经得到值了。
解释下哈.
hemiya 2009-03-14
  • 打赏
  • 举报
回复

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 b, c;
const int d;
A x, y;
1,这个是对象的创建顺序,
2,B(int v): b(v),y(b+2),x(b+1),d(b),A(v) ,这个是类内部数据的初始化循序,两个不是一个东西.
在执行1的时候类内部对象根据声明的顺序在内存中几经有了自己的空间了,也就是确确实实存在了.
调用b的构造函数,根据构造函数的初始化列表,进行初始化工作,
1,把v值传给b;
2,调用y的构造函数A(int x),记住这时候的y几经存在了,只是没有进行初始化工作,也就是在调用y的构造函数A(int x)之间y已经存在,现在只是调用y的构造函数A(int x)
3,同2
4,把b值传给d;
5,调用基类的构造函数.
laraya 2009-03-14
  • 打赏
  • 举报
回复
[Quote=引用 10 楼 chin_chen 的回复:]
引用 7 楼 laraya 的回复:
我知道和构造列表顺序没关系
我也说清楚了啊
我的困惑是:
书上说第二步是内嵌对象
那么这里第二步应该构造对象x和y,
同样的道理x(b+1),这里面的b难道已经得到值1了吗
不是说最后再对自己的成员赋值吗?

你理解有问题啊。
class B: A
{
private:
int b, c;//它在x的前面申明的,编译器先给它初始化了,它有值了啊,当然你在初始化列表里面,可以随后对x(b+1)进行构造了…
[/Quote]
是我理解问题吗?
书上明确说明派生类构造函数执行顺序是一调用基类构造函数;
二如果派生类中含有内嵌成员对象,则调用内嵌对象的构造函数;
三再执行派生类自己的构造函数体;
那么这里第二步就是先后调用内嵌对象x和y的构造函数;
其次才会给其他的什么bcd赋值,他们的赋值可以在函数体内,也可以放在初始化列表里面
他么的顺序应该就是遇到谁就给谁先赋值;
所以说问题是b怎么就先得到了参数v的值,这不互相矛盾吗?
  • 打赏
  • 举报
回复
[Quote=引用 2 楼 fibbery 的回复:]
分成两个顺序比价容易理解:
1、继承链中的顺序:从基类到派生类;
2、类的数据成员定义顺序:这个顺序是在同一个构造函数中,其数据成员的构造函数调用顺序。如果数据成员定义了默认构造函数或者在初始化列表中调用了数据成员的构造函数,那么,编译器会按照数据成员声明的顺序调用其构造函数。

2只是1的一个步骤,也就是说,1中的每次构造是由2中更微小的调用构成。
[/Quote]

对的

#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;
}


lz可以试下上述代码,输出就是不确定的
laraya 2009-03-14
  • 打赏
  • 举报
回复
[Quote=引用 8 楼 stormlk1983 的回复:]
补充一点,除了要在初始化表里先初始化几类外,初始化派生类成员的顺序应该和成员在类内部声明的顺序一致
因为在西够的时候,是有严格的顺序的
[/Quote]
大家都没说清楚哎
你意思是说等基类构造完后
就按照派生类的成员顺序来吗
那也应该是 b, c;
const int d;
A x, y;
那么显然c应该在d前面就被赋值了,事实上c是最后再被赋值的
难道这样理解:
不管是内嵌对象还是单个数据成员,
都是先遇到就先赋值,
只不过在对内嵌对象赋值即构造内嵌对象的时候是按照内嵌对象在派生类里面声明的顺序。
有人能讲清楚一点吗?
chin_chen 2009-03-14
  • 打赏
  • 举报
回复
[Quote=引用 7 楼 laraya 的回复:]
我知道和构造列表顺序没关系
我也说清楚了啊
我的困惑是:
书上说第二步是内嵌对象
那么这里第二步应该构造对象x和y,
同样的道理x(b+1),这里面的b难道已经得到值1了吗
不是说最后再对自己的成员赋值吗?
[/Quote]
你理解有问题啊。
class B: A
{
private:
int b, c;//它在x的前面申明的,编译器先给它初始化了,它有值了啊,当然你在初始化列表里面,可以随后对x(b+1)进行构造了!
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;

}
};
jn989 2009-03-14
  • 打赏
  • 举报
回复
[Quote=引用 7 楼 laraya 的回复:]
我知道和构造列表顺序没关系
我也说清楚了啊
我的困惑是:
书上说第二步是内嵌对象
那么这里第二步应该构造对象x和y,
同样的道理x(b+1),这里面的b难道已经得到值1了吗
不是说最后再对自己的成员赋值吗?
[/Quote]
在基类的构造函数之后才会调派生类的构造函数,但初始化列表又不是构造函数啊,在A构造函数之后,已经通过b(v)给b赋值了,而后再依次构造x,y
夹心饼干 2009-03-14
  • 打赏
  • 举报
回复
补充一点,除了要在初始化表里先初始化几类外,初始化派生类成员的顺序应该和成员在类内部声明的顺序一致
因为在西够的时候,是有严格的顺序的
加载更多回复(12)

64,637

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧