经典C++笔试题解析5

本文深入解析C++中的构造函数,包括缺省构造函数、编译器默认构造函数、虚函数与内联函数的区别,以及如何正确使用拷贝构造函数和赋值运算符。同时,介绍了构造函数初始化列表的优势,并讨论了定位new与普通new的区别。

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


From: http://blog.youkuaiyun.com/sailor_8318/archive/2008/10/31/3188958.aspx


1、预处理器

#include"..."和#include<...>的区别

"..."一般是用户提供的头文件,从当前目录开始查找

<...>一般是系统提供的头文件,从环境变量值开始查找

还有常见的预定义名字:

__cplusplus  编译c++

__STDC__  编译标准C

__FILE__  当前编译文件

__LINE__  当前行

__TIME__

__DATA__

文件名和行号在打印调试时非常方便,通常将打印函数定义两个版本,调试时带上文件名和行号便于快速定位

 

2、 class X{

        public:

        X(int a=3){

                printf("this is X(int a=3)");

        }; //是不是缺省构造函数?(是)

 

        X(){

                printf("this is X()");

        };

};

 

void main()

{

        X a[3];

}

//warning C4520: 'X' : multiple default constructors specified

//error C2668: 'X::X' : ambiguous call to overloaded function

 

分清什么是缺省构造函数,什么是编译器默认构造函数

 

缺省构造函数就是没有指定任何参数时调用的构造函数。编译器默认的构造函数本身就是无参的,而不是具有默认参数的。默认构造函数(指没有参数的构造函数)在C++语言中是一种让你无中生有的方法。构造函数能初始化对象,而缺省构造函数则可以不利用任何在建立对象时的外部数据就能初始化对象。

 

从上面编译的提示信息可以看出,X a[3]未提供任何参数,从X(int a=3)的角度理解可以是默认参数,从X()的角度可以理解为无参数,因此调用时出现了无法确定的情况。默认参数函数经常和重载函数发生混淆,因此应避免。

 

multiple default constructors specified可以看出X(int a=3)是缺省构造函数。

 

若调用X a(4),则将明确调用X(int a=3)

 

class X{

        public:

        X(int a=3){

                printf("this is X(int a=3)");

        }; //是不是缺省构造函数?(是)

};

 

void main()

{

        X a[3];

        X a(4);

}

因为定义了X(int a=3),编译器不会自动生成无参构造函数,因此X a[3]和X a(4)都可以通过。因此有参构造函数提供默认参数是一种非常好的方式,可以适应很多情况。

 

3、所谓构造函数名符其实吗?是用来构造对象的吗?

不是,构造函数只是初始化对象数据。只所以需要构造函数是因为C中经常出现对象忘记了初始化导致的潜在问题,C++为了避免这种问题,将其扼杀在摇篮中,故提出了构造函数的概念,来保证每个对象在建立后能正确的初始化。

 

4、虚函数能不能内联? 不能,为什么?

在建立虚函数表时,需要指定虚函数的地址,而内联函数只是在编译时提供的一些符号,进行一些检查后替换了相关的代码,编译后就不存在内联函数了,因此也不存在一个地址。故虚函数不能是内联函数。

 

5、explicit 关键字有何作用?

explicit只能用作单参数构造函数,指明该构造函数不能用作隐式转换。

class X{

    public:

    int ix;

    explicit X(int i){ix=i;}

};

void f(X){};

void g(int I){

    //! f(I);

}

 

6、看这个宏:  #define max(a,b)  ((a)>(b)?(a):(b))

举一个例子使这个宏崩溃

max(++a,b)

该怎么做?

使用内联 inline int max(int a, int b){return a>b?a:b;}

为使它能像宏一样接受多类型,模板:  template<class T>

inline const T& max(const T& a, const T& b){return a>b?a:b;}

 

7、为什么有必要重载new和delete?

为了改善缺省的new,delete的性能,缺省的new在分配对象时还分配了额外的空间用于管理内存,因此在分配大量而又小体积的的对象时这种缺省的内存管理存在效率问题,需要重载。

 

New和delete采用的内存分配机制只适用于一般情况,对于某些特定情况可能满足不了要求,通常有以下几种情况:

效率问题,创建大量某些对象造成系统速度瓶颈;

堆的分段问题,反复创建释放造成堆大量分段,没有大块内存满足系统需求;

实时嵌入式系统中,要求内存分配时间一定,不允许内存耗尽和分段问题。

对于上述情况,需要定制的new和delete等操作符。

 

8、一般为了安全将类声明中声明一个私有的拷贝构造函数和一个赋值运算符,但若非要实现拷贝构造函数也一定要实现赋值运算符。

因为将一个现有对象赋值给一个初建的对象时,是调用拷贝构造函数,而后续进行赋值时将调用赋值运算符

 

9、构造函数的初始化列表比在函数体内部赋值的好处?

a.效率,想想,基类构造函数可能会被调用两次

b.const的初始化必须在此处

c.基类无缺省构造函数

 

10、强制类型转换会引发拷贝构造函数的发生

class X { 

        public:

        X(){};

        X(X& a){cout<<"haha..."<<endl;};

        ~X(){cout<<"~X()..."<<endl;};

};

 

class Y:public X{

        public:

        ~Y(){cout<<"~Y()..."<<endl;};

};

void main(){

    Y y;

        (X)y;

}

/*

haha...

~X()...

~Y()...

~X()...

*/

由上述输出可以看出,(X)y实际上是创建了一个X类型的临时对象,将已有的对象Y的基类部分赋值给此临时对象,此时需要调用X类型的拷贝构造函数。根据构造和析构的相反顺序,后构造的临时对象将先被析构,即第一个~X()。然后析构y,先调用派生类的析构函数再调用基类的析构函数。

 

11、定位new 可以将对象创建在已经分配好的缓冲池里

class X{

public:

        X(){cout<<"constructed"<<endl;}

        ~X(){cout<<"destructed"<<endl;}

};

void main(){

   char* buf=new char[sizeof(X)*20];

   X* px=new(buf) X;  调用构造函数

   px->~X();  需要显式析构

   delete[] buf;    不需要对定位new的对象调用delete,因为定位new并不分配空间,只是构造对象,但是要手工调用析构函数。

}

 

12、extern "C" 的函数能与c++函数重载吗? 

可以。重载的本质就是在编译时根据参数个数和类型来为函数添加一些附加信息,以生成不同的函数标识符,这样在调用时根据参数就可以自动识别并调用对应的函数。而C中并没有参数类型和个数的附加信息,因此无法实现重载。

 

但是两个extern "C"引入的函数之间不能重载


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值