虚成员变量?!

本文通过一个具体的C++代码示例,展示了当基类和派生类中存在同名成员变量时可能出现的问题。强调了这与虚函数的多态行为不同,并提醒开发者要注意变量隐藏的现象。

 【原文写于2005年5月4日03:11星期三,

“虚成员变量”?!
注意!!没有“虚成员变量”,C++不会帮你多态
所以,如果在基类和派生类中定义了命名一样的成员变量,就要十分小心了

代码

#include
using namespace std ;

class A
{
public:
A() { m_c = 'A' ; }
char m_c ;
} ;

class B : public A
{
public:
B() { m_c = 'B' ; }
char m_c ;
} ;

int main()
{
A a ;
B b ;

A * p = &b ;
cout << p->m_c << endl ;
cout << ((B*)p)->m_c << endl ;
return 0 ;
}

/************输出***************/
A
B


派生类中保存了自己的成员变量和基类的成员变量,具体引用哪个,根据类型决定
这与虚函数是完全不同的

<think>好的,我现在需要详细分析用户在继承体系中包含基类和成员变量时构造函数的执行顺序的问题。用户已经提供了几个引用内容,我需要结合这些引用和之前的知识,整理出一个清晰的回答。 首先,用户的问题是关于C++基类和成员变量在构造函数中的执行顺序。根据引用[4]和[5],基类的构造函数会在所有非基类之前被调用,且构造顺序由继承声明顺序决定,而不是初始化列表中的顺序。此外,成员变量的构造顺序则按照类内声明的顺序进行,同样不受初始化列表顺序影响。 接下来,我需要确认几个关键点: 1. 基类的构造函数何时被调用,顺序如何? 2. 非基类的构造函数顺序? 3. 成员变量的构造顺序? 4. 派生类自身构造函数的执行时机? 根据引用[4],基类的构造函数由最底层的派生类直接调用,并且优先于所有非基类。例如,如果类D继承自基类A和普通基类B、C,那么A的构造函数先执行,然后是B和C(按声明顺序),接着是成员变量,最后是D的构造函数体。 成员变量方面,引用[5]指出成员的构造顺序只和类内声明顺序有关,初始化列表的顺序不影响。例如,类中先声明成员s1,后声明s2,即使初始化列表先写s2,也会先构造s1再s2。 此外,必须注意,对于基类,所有中间派生类(如B和C继承自A)在它们的初始化列表中调用基类的构造函数会被忽略,只有最底层的派生类(如D)调用基类的构造函数,确保基类只被构造一次。这可以防止多次初始化的问题。 现在需要将这些信息整合成一个结构化的回答,涵盖基类、普通基类、成员变量的构造顺序,以及可能的示例代码和常见错误。同时,用户可能关心如何正确编写初始化列表,以及为什么某些情况下构造顺序会出问题。 可能还需要讨论析构函数的顺序是否与构造函数相反,但用户的问题主要集中在构造阶段,所以可以简要提及析构顺序,但重点放在构造上。 最后,根据用户提供的引用[1]中的例子,类A的成员变量构造顺序是声明的顺序,即使初始化列表中的顺序不同,成员变量仍然按照声明顺序构造。这进一步验证了成员变量构造顺序的规则。 总结回答的结构: 1. 总述构造顺序规则 2. 分点详细说明各部分的顺序 3. 示例代码及输出 4. 常见错误和注意事项 5. 相关问题扩展 需要确保回答正确引用提供的参考文献,特别是关于基类初始化顺序和成员变量声明顺序的部分。同时,注意使用正确的LaTeX格式和代码块,符合用户之前的指示。</think>--- ### C++基类与成员变量的构造函数执行顺序详解 --- #### 一、构造函数的执行顺序总规则 在包含基类和非基类的继承体系中,构造顺序遵循以下优先级: **基类 → 非基类 → 成员变量 → 派生类自身构造函数** 顺序由**声明顺序**决定,与初始化列表无关[^4][^5]。 --- #### 二、具体执行顺序分析 ##### 1. 基类构造 - **基类的构造函数**由**最底层的派生类直接调用**,且优先于所有非基类[^4]。 - 若存在多个基类,按继承声明的顺序依次执行。 - **关键特性**:基类仅构造一次,避免多次初始化冲突[^4]。 **示例**: ```cpp class A { public: A() { cout << "A构造\n"; } }; class B : virtual public A { public: B() { cout << "B构造\n"; } }; class C : virtual public A { public: C() { cout << "C构造\n"; } }; class D : public B, public C { public: D() { cout << "D构造\n"; } }; int main() { D d; // 输出顺序:A构造 → B构造 → C构造 → D构造 return 0; } ``` ##### 2. 非基类构造 - 非基类的构造函数按**派生类继承声明顺序**执行[^5]。 - 若同时存在基类和非基类,基类优先。 **示例**: ```cpp class Base1 { public: Base1() { cout << "Base1构造\n"; } }; class Base2 { public: Base2() { cout << "Base2构造\n"; } }; class Derived : public Base1, virtual public Base2 { public: Derived() { cout << "Derived构造\n"; } }; int main() { Derived d; // 输出顺序:Base2构造(基类) → Base1构造 → Derived构造 return 0; } ``` ##### 3. 成员变量构造 - **成员变量**按**类内声明顺序**初始化,与初始化列表顺序无关[^1]。 - 若成员变量包含对象,其构造函数在基类构造完成后执行。 **示例**: ```cpp class Member1 { public: Member1() { cout << "Member1构造\n"; } }; class Member2 { public: Member2() { cout << "Member2构造\n"; } }; class MyClass { Member2 m2; // 先声明 Member1 m1; public: MyClass() : m1(), m2() {} // 实际顺序:m2 → m1 }; int main() { MyClass obj; // 输出:Member2构造 → Member1构造 return 0; } ``` ##### 4. 派生类自身构造函数 - 所有基类和成员构造完成后,执行派生类自身的构造函数体。 --- #### 三、常见错误与注意事项 1. **基类构造函数必须由最底层派生类调用** 中间派生类(如`B`和`C`)对基类构造函数的调用会被忽略[^4]: ```cpp class A { public: A(int) {} }; class B : virtual public A { public: B() : A(1) {} // 此处调用A的构造函数会被D的调用覆盖 }; class D : public B { public: D() : A(2) {} // 必须显式调用基类构造函数 }; ``` 2. **成员变量声明顺序错误** 若初始化列表顺序与声明顺序不一致,可能导致逻辑错误[^1]: ```cpp class Data { int x; // 先声明x int y; public: Data() : y(5), x(y + 1) {} // x初始化时y尚未构造,实际x值为随机数 }; ``` 3. **无法直接初始化基类成员** 派生类不能直接初始化基类的私有成员,必须通过基类构造函数[^3]: ```cpp class Base { protected: int value; }; class Derived : public Base { public: Derived() : value(10) {} // 错误!必须调用Base的构造函数 Derived() : Base(), value(10) {} // 仍然错误,应通过基类构造函数传递参数 Derived() : Base(10) {} // 正确(假设Base有接受int的构造函数) }; ``` --- #### 四、完整示例与输出 **代码**: ```cpp #include <iostream> using namespace std; class VirtualBase { public: VirtualBase() { cout << "基类构造\n"; } }; class Base1 { public: Base1() { cout << "Base1构造\n"; } }; class Base2 { public: Base2() { cout << "Base2构造\n"; } }; class MemberA { public: MemberA() { cout << "MemberA构造\n"; } }; class MemberB { public: MemberB() { cout << "MemberB构造\n"; } }; class Derived : virtual public VirtualBase, public Base1, public Base2 { MemberB mB; // 声明顺序:mB在前 MemberA mA; public: Derived() : Base2(), Base1(), mA(), mB() { cout << "Derived构造\n"; } }; int main() { Derived d; return 0; } ``` **输出顺序**: ``` 基类构造 → Base1构造 → Base2构造 → MemberB构造 → MemberA构造 → Derived构造 ``` --- #### 五、析构函数顺序 析构顺序与构造顺序**严格相反**: 1. 派生类析构函数 2. 成员变量逆序析构 3. 非基类逆序析构 4. 基类析构 --- ### 总结 - **构造顺序优先级**:基类 > 非基类(按声明顺序) > 成员变量(按声明顺序) > 派生类自身 - **关键规则**:基类由最底层派生类初始化,成员顺序由声明决定[^4][^5] - **常见错误**:错误调用基类成员初始化、依赖未构造的成员变量赋值[^3][^1] --- ### 相关问题 1. 基类的析构函数是否会被多次调用? 2. 如何验证成员变量的构造顺序是否与声明顺序一致? 3. 多继承中如何避免基类构造函数被中间类覆盖? 4. 如果基类没有默认构造函数,应如何处理? [^1]: 成员变量初始化顺序由类内声明顺序决定。 : 派生类无法直接初始化基类成员变量。 [^4]: 基类构造函数由最底层派生类调用。 [^5]: 基类构造顺序由继承声明顺序决定。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值