C++构造函数以及析构函数的若干面试问题

本文探讨了C++中的构造函数和析构函数的相关面试问题,包括构造函数的重载、析构函数为何通常声明为虚函数、何时需要定义拷贝构造函数以及构造与析构函数的调用顺序。解释了构造函数不能被继承,析构函数为虚函数的原因,以及在多态场景下析构函数的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、模块一

Q1:构造函数能否重载,析构函数能否重载,为什么?
A1:函数重载就是同一函数名的不同实现,并且能在编译时能与一具体形式匹配,这样参数列表必须不一样。由于重载函数与普通函数的差别是没有返回值,而返回值不能确定函数重载,所以构造函数可以重载; 析构函数的特点是参数列表为空,并且无返回值,从而不能重载。

Q2:析构函数为什么一般情况下要声明为虚函数?
A2:虚函数是实现多态的基础,当我们通过基类的指针是析构子类对象时候,如果不定义成虚函数,那只调用基类的析构函数,子类的析构函数将不会被调用。如果定义为虚函数,则子类父类的析构函数都会被调用。

Q3:什么情况下必须定义拷贝构造函数?
A3:当类的对象用于函数值传递时(值参数,返回类对象),拷贝构造函数会被调用。如果对象复制并非简单的值拷贝,那就必须定义拷贝构造函数。例如大的堆 栈数据拷贝。如果定义了拷贝构造函数,那也必须重载赋值操作符。

http://jaden.blog.51cto.com/1549175/324480

二、模块二

例题:C++类体系中,不能被派生类继承的有?
正确答案: A

A、构造函数
B、静态成员函数
C、非静态成员函数
D、赋值操作函数

解析:
构造函数不能被继承的,但是可以被调用,如果父类重新定义了构造函数,也就是没有了默认的构造函数,子类创建自己的构造函数的时候必须显式的调用父类的构造函数。而其余的三个在我们平常的使用中都可以被继承使用。

编译器总是根据类型来调用类成员函数。但是一个派生类的指针可以安全地转化为一个基类的指针。这样删除一个基类的指针的时候,C++不管这个指针指向一个基类对象还是一个派生类的对象,调用的都是基类的析构函数而不是派生类的。如果你依赖于派生类的析构函数的代码来释放资源,而没有重载析构函数,那么会有资源泄漏。所以建议的方式是将析构函数声明为虚函数。
也就是delete a的时候,也会执行派生类的析构函数。
一个函数一旦声明为虚函数,那么不管你是否加上virtual 修饰符,它在所有派生类中都成为虚函数。但是由于理解明确起见,建议的方式还是加上virtual 修饰符。

构造方法用来初始化类的对象,与父类的其它成员不同,它不能被子类继承(子类可以继承父类所有的成员变量和成员方法,但不继承父类的构造方法)。因此,在创建子类对象时,为了初始化从父类继承来的数据成员,系统需要调用其父类的构造方法。

如果没有显式的构造函数,编译器会给一个默认的构造函数,并且该默认的构造函数仅仅在没有显式地声明构造函数情况下创建。

构造原则如下:

1. 如果子类没有定义构造方法,则调用父类的无参数的构造方法。

2. 如果子类定义了构造方法,不论是无参数还是带参数,在创建子类的对象的时候,首先执行父类无参数的构造方法,然后执行自己的构造方法。

3. 在创建子类对象时候,如果子类的构造函数没有显示调用父类的构造函数,则会调用父类的默认无参构造函数。

4. 在创建子类对象时候,如果子类的构造函数没有显示调用父类的构造函数且父类自己提供了无参构造函数,则会调用父类自己的无参构造函数。

5. 在创建子类对象时候,如果子类的构造函数没有显示调用父类的构造函数且父类只定义了自己的有参构造函数,则会出错(如果父类只有有参数的构造方法,则子类必须显示调用此带参构造方法)。

6. 如果子类调用父类带参数的构造方法,需要用初始化父类成员对象的方式,比如:
#include <iostream.h>
class animal
{
public:
    animal(int height, int weight)
    {
        cout<<"animal construct"<<endl;
    }
};
class fish:public animal
{
public:
    int a;
    fish():animal(400,300), a(1)
    {
        cout<<"fish construct"<<endl;
    }
};
void main()
{
    fish fh;
}

三、模块三

例题:下面代码的输出是什么?

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

正确答案: D
A、

~B

B、

~B
~A

C、

~B
~A
~A

D、

~B
~A
~A
~A

要想搞明白该问题,需要理解基类构造析构函数、子类构造析构函数和子类成员变量构造析构函数的调用顺序。

对于构造函数:基类构造函数 > 子类成员变量构造函数 > 子类构造函数
对于析构函数:子类析构函数 > 子类成员变量析构函数 > 基类析构函数

可以看出构造函数的调用过程和析构函数的调用过程正好相反。

main函数中首先构造变量a,然后是b。在构造b时首先调用b的基类A的构造函数,然后调用b中成员变量_a的构造函数,最后调用b的构造函数。

本题中涉及到构造函数和析构函数的有:基类、子类成员变量、子类3种情况。

main函数调用结束返回时,变量的释放顺序跟变量的构造顺序正好相反。首先释放变量b,然后是变量a。

也就是,a,b析构时都会按照:子类析构函数 > 子类成员变量析构函数 > 基类析构函数顺序进行。但是先释放b,再有a。

在释放变量b时,首先调用b的析构函数,然后析构变量b的成员_a,析构_a时调用_a的析构函数。再调用b的基类的析构函数。
然后是释放变量a,调用a的析构函数。
如下:

b:~B > ~A > ~A
a:~A

按顺序连起来就是~B ~A ~A ~A

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

按照以上思想理解下题,可迎刃而解:

# 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
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值