首页 > 试题广场 >

下面代码的输出是什么?

[单选题]
下面代码的输出是什么?
class A  
{  
public:  
    A()  {     }  
    ~A() {    cout<<"~A"<<endl;   }  
};  
  
class B:public A  
{  
    public:  
        B(A &a):_a(a)  
        {  
            
        }  
        ~B()  
        {  
            cout<<"~B"<<endl;  
        }  
    private:  
        A _a;  
    };  
      
int main(void)  
 {  
        A a;       //很简单,定义a的时候调用了一次构造函数  
        B b(a); 
}

  • ~B
  • ~B
    ~A
  • ~B
    ~A
    ~A
  • ~B
    ~A
    ~A
    ~A
推荐
前面一位的答案是对的,但是解释不是很赞同。

# include <iostream>
using namespace std;
class A  
{  
public:  
    A()  {  cout<<"create A"<<endl;   }  
    A(const A& other){ cout<<"copy A"<<endl;} //复制构造函数
    ~A() {    cout<<"~A"<<endl;   }  
}; 
class C
{
public:
    C()  {  cout<<"create C"<<endl;   } 
    C(const A& other){ cout<<"copy C"<<endl;} //复制构造函数
    ~C() {    cout<<"~C"<<endl;   }  
};
class B:public A  
{  
public:  
    B()
    {  
        cout<<"create B"<<endl;
    }  
    ~B()  
    {  
        cout<<"~B"<<endl;  
    }  
private:  
    C _a; 
};  
      
int main(void)  
{
        B b; 
        cout<<"------------------------"<<endl;
}
上面的输出结果为

create A
create C
create B
------------------------
~B
~C
~A

我们可以看到,这个地方先是调用parent class的构造函数,然后对成员变量C类型的构造函数,然后再最后执行B类型的构造函数。

析构的过程就是上面的过程反过来。

所以Aesthetic92的解释有一部分不是很准确。我认为。

更加准确的说明应该是,

最开始析构b,~B,这个是没有争议的。
接着是析构b的成员变量_a,所以是~A
接着是b的parent class(基类)的~A
最后才是a的析构~A

不过为了理解这道题,我感觉配上我的例子更好理解一点,原题的成员变量和基类都是相同的类型,比较难以辨认。





编辑于 2015-01-26 15:40:14 回复(17)
要想搞明白该问题,需要理解基类构造析构函数、子类构造析构函数和子类成员变量构造析构函数的调用顺序。
对于构造函数:基类构造函数 > 子类成员变量构造函数 > 子类构造函数
对于析构函数:子类析构函数 > 子类成员变量析构函数 > 基类析构函数
可以看出构造函数的调用过程和析构函数的调用过程正好相反。

main函数中首先构造变量a,然后是b。在构造b时首先调用b的基类A的构造函数,然后调用b中成员变量_a的构造函数,最后调用b的构造函数。
main函数调用结束返回时,变量的释放顺序跟变量的构造顺序正好相反。首先释放变量b,然后是变量a。
在释放变量b时,首先调用b的析构函数,然后析构变量b的成员_a,析构_a时调用_a的析构函数。再调用b的基类的析构函数。
然后是释放变量a,调用a的析构函数。

本例子中应该将A的析构函数更改为virtual的,防止使用多态机制时出现子类对象无法释放的情况,本例子中没有用到多态机制,不存在该问题。

选D
编辑于 2015-01-25 17:29:13 回复(7)
为了便于理清执行过程,我在题目代码的基础上做了适当的修改,完整程序如下:
#include <iostream>
using namespace std;
class A  
{  
public:  
    A()  {  cout << "Create A!" << endl;  }
    A(const A& para)
    {
		cout << "Copy constructor A!" << endl;
    }  
    ~A() {    cout<<"~A"<<endl;   }  
};  
   
class B:public A  
{  
    public:  
        B(A &a):_a(a)  
        {  
             cout << "Create B!" << endl;
        }  
        ~B()  
        {  
            cout<<"~B"<<endl;  
        }  
    private:  
        A _a;  
};      
int main(void)  
{  
        A a;       //很简单,定义a的时候调用了一次构造函数  
        B b(a); 
} 
输出结果:
Create A!
Create A!
Copy constructor A!
Create B!
~B
~A
~A
~A
结果分析:
main函数中
A a;// 调用A类的构造函数,输出:Create A!
B b(a);
创建派生类对象时,调用构造函数的顺序如下:
先是父类的构造函数,然后如果类成员变量中有某类的对象,调用其相应的构造函数,最后调用派生类自身的构造函数,析构函数的调用顺序正好相反。
所以,我们来分析B b(a);的执行顺序:
1.调用父类A的构造函数,输出:Create A!
2.我们注意到在B的构造函数中,它的成员变量_a被初始化为:_a(a),注意这里调用了A类的拷贝构造函数 A(const A & para);所以输出了:Copy constructor A!
3.调用派生类B自身的构造函数,输出:Create B!
析构时顺序正好反过来。
发表于 2016-01-19 16:30:42 回复(2)
在vs上运行之后发现,输出顺序确实如@Aesthetic92所说,然而@赖聪林说的也没有错,可是@赖聪林举的例子并没有用到拷贝构造函数。
 原题----------------------------------------------------------------------------- 
 class A 
{ 
    public: 
    A() { cout<<"A"<<endl; } 
    ~A() { cout<<"~A"<<endl; } 
}; 
class B:public A 
{ 
     public: B(A &a):_a(a) 
    {     
    cout<<"B"<<endl; 
    } 
     ~B() { cout<<"~B"<<endl; }
 private: A _a; 
}; 
 void main(void) 
{     A a; 
    B b(a); 
}

 结果为
A 
A 
B 
~B 
~A
~A 
~A 
 加上拷贝构造函数----------------------------------------------------------------------
  class A 
{
public: A(){ cout<<"A"<<endl; } 
        A(const A& other){ cout<<"copy A"<<endl;} 
        ~A() { cout<<"~A"<<endl; } }; 
class B:public A 
{
 public:
     B(A &a):_a(a) 
    {
     cout<<"B"<<endl; 
    } 
     ~B() { cout<<"~B"<<endl; } 
private: A _a; 
}; 
 void main(void) 
{
     A a;     
     B b(a);
 } 

结果显示 
A 
A 
copy A
B
~B 
~A 
~A 
~A 
 修改后----------------------------------------------------------------------------------class A {
 public: 
     A() { cout<<"A"<<endl; } 
     A(const A& other){ cout<<"copy A"<<endl;} 
     ~A() { cout<<"~A"<<endl; } };
 class B:public A {
 public:
     B(A &a) { cout<<"B"<<endl; } 
    ~B() { cout<<"~B"<<endl; }
 private: A _a; 
};
 void
  main(void)
 { 
   A a; 
   B b(a);
 } 

结果为 
A 
A
A
B
~B
~A
~A
~A

发表于 2015-04-11 17:53:21 回复(6)
答案:选D
答案解析:答案看起来可能比较怪,其实给默认构造函数补上输出,然后再在基类里写个复制构造函数,这样结果就很明朗了;
首先 A a;这个调用A的默认构造函数,
B b(a); 因为A &a,形参为引用,不需要调用基类复制构造函数(其实基类也没写);_a(a)首先创建对象_a,调用基类A的默认构造函数,然后调用基类复制构造函数(测试的时候可以写出来),把a对象赋给_a,结束之后调用B的析构函数,输出~B;然后调用基类A的析构函数撤销复制构造函数,输出~A;调用基类A的析构函数撤销_a,输出~A;最后调用基类A的析构函数撤销一开始创建的A a,输出~A
发表于 2014-12-30 17:08:22 回复(5)
析构顺序:B的析构函数 -> B的成员_a的析构函数 -> B的基类A的析构函数 -> A的析构函数
输出:~ B ~A ~A ~A
编辑于 2015-03-31 22:01:48 回复(0)
推荐答案的总结:
构造函数:基类构造 > 成员构造 >  派生构造
析构函数:  派生析构 > 成员析构 > 基类析构

tips:如果使用父类指针指向子类对象,析构时只会调用父类的构造函数,从而产生内存泄漏。
        这时,如果把父类声明为虚基类,析构时就会调用派生类的析构函数

发表于 2019-02-26 15:30:57 回复(0)
其实这道题的重点在于:b中的成员变量_a调用的是拷贝构造函数。
我们在题目代码的基础上,给class A 加上拷贝构造函数。
#include <iostream>
#include <cstring>

using  namespace std;

class A
{
public:
    A()  {  cout << "create A" << endl;   }
    A(const A& a) { cout << "copy constructer" << endl;}
    ~A() {    cout<<"~A"<<endl;   }
};

class B:public A
{
    public:
        B(A &a):_a(a)
        {
             cout << "create B" << endl;
        }
        ~B()
        {
            cout<<"~B"<<endl;
        }
    private:
        A _a;
    };

int main(void)
{
        A a;       //很简单,定义a的时候调用了一次构造函数
        B b(a);

    return 0;
}
然后,我们观察输出:

编辑于 2016-08-27 15:33:15 回复(0)
主要把握这一点:构造函数的初始化顺序,①基类的成员变量,基类的构造函数。②派生类的成员变量,派生类的构造函数。注意值传递形式的函数的对象参数和函数的返回值是对象都需要调用拷贝初始化构造函数。析构函数按相反的方向析构。
发表于 2015-04-15 10:12:34 回复(0)
先构析派生类对象,再构析派生类成员,再构析派生类的基类,最后构析基类对象
发表于 2018-02-14 12:12:48 回复(0)
D    先是
A a;       a
B b(a)     复制构造a   b的成员a    然后 b    析构相反    ~b   ~a   ~a   ~a
发表于 2015-09-13 12:35:48 回复(0)
B(A &a):_a(a)  是什么意思
发表于 2017-05-25 10:49:56 回复(1)
三个A对应了派生类析构->派生类对象析构->基类析构
编辑于 2023-12-27 20:11:07 回复(0)
改了题目能不能清空一下评论区啊, 云里雾里的都不知道在说什么
发表于 2022-04-19 13:20:27 回复(0)

构造函数执行顺序:基类成员初始化列表->基类构造函数体->派生类成员初始化列表->派生类构造函数体

析构函数执行顺序:派生类析构函数->派生类成员变量->基类析构函数->基类成员变量


构造的顺序与析构相反,注意成员初始化列表一定在函数体内的代码之前执行,执行顺序为成员变量在类中声明的顺序,和成员初始化列表中的顺序无关:

class Base {
public:
    Base(int a, int b) : _b(b), _a(a) { cout << "Base\n"; }
private:
    int _a;
    int _b;
};

执行顺序:

先是成员初始化列表:初始化_a,初始化_b

然后是函数体:若有virtual函数,则先初始化vptr,然后执行用户代码cout << "Base\n";


main函数中,变量的释放顺序与定义的顺序相反,因此本题先析构b,再析构a

最终顺序为:

  • b:B的析构函数->A的析构函数(B中数据成员_a)->A的析构函数(子对象)
  • a:A的析构函数(main中的a)
编辑于 2022-03-09 23:01:52 回复(0)
发表于 2022-02-09 19:29:22 回复(1)
这题按照栈思想,倒着看。 先a木问题,所以最后一个~A,再接着B开始构造,先调用A构造,所以一个~A没问题,然后B接着创建成员使用引用初始化,又有且仅有一个~A,最后进入B构造,一个~B。
发表于 2020-09-18 23:54:41 回复(0)
不是引用么 在销毁b时 _a已经销毁了 为什么a还会被销毁一次
发表于 2020-03-25 22:56:12 回复(0)
只有我一个人没看到a是A类型的吗。。😩
发表于 2020-02-29 16:02:18 回复(0)

-a是什么时候构造和析构的呢?

发表于 2019-05-13 23:43:45 回复(0)
不要忘了复制构造函数也对应一次析构
发表于 2018-08-26 18:16:57 回复(0)