C++ 一道腾讯面试题深入剖析
脚本之家
腾讯面试题:
以下代码是否完全正确,执行可能得到的结果是____。
class A{ int i; }; class B{ A *p; public: B(){p=new A;} ~B(){delete p;} }; void sayHello(B b){ } int main(){ B b; sayHello(b); }
A.程序正常运行
B.程序编译错误
C.程序崩溃
D.程序死循环
分析:
代码中定义了两个类,类A、类B和一个全局函数sayHello。类A中定义一个int类型的成员变量。类B中定义了一个A*类型的成员变量,且类B在初始化的时候构造了类A的一个实例,在类B析构时释放了成员变量p(delete p)。全局函数sayHello的参数是类B的一个实例。
首先程序中没有编译错误,也没有死循环(程序中没有循环哪里来的死循环)。有些初学者可能会说main函数没有写return 0,main函数不显性的写return,编译器也会帮你做的。
那么根据我们这十几年应试教育考试的经验,这段代码出问题的概率很大,不卖关子了,直接说吧:当类中存在指针类型的成员变量时赋值和析构要格外注意,这道题的问题就出在类B对象b中的指针p被析构了两次。
具体分析一下,当执行完成B b这句话后,在b中就构造了一个类A的指针对象p,当调用sayHello(b)函数时系统将会调用类B的赋值构造函数构造一个类B的实例bStep(为了方便下面的叙述随便起了一个名字)传入到sayHello函数中(问题就出在bStep这个实例中),这里当sayHello执行完成后,之前构造的实例bStep将被析构(执行delete p)。然后程序继续开心的执行,直到执行完main函数后系统将会析构b,当b被析构时将再次执行delete p。这样p就被析构了两遍导致程序崩溃。
我们把代码增加一些输出信息后大家就更容易看了:
class B{ A *p; public: B(){ printf("构造\n"); p = new A; } ~B(){ printf("析构\n"); delete p; } B(const B &b){ printf("赋值构造\n"); } }; void sayHello(B b){ } int main() { B b; sayHello(b); }