C++面试题目集合(持续跟新)

与我前面写的C语言进阶知识点遥相呼应。

这才是C++面试,网上的面试题有些太简单了。

C++面试题目最多集中在对象的内存模型,记住了:如果用c/c++,内存都不清楚,还写个屁的程序!

1.C++的虚函数是怎样实现的?

      C++的虚函数使用了一个虚函数表来存放了每一个虚函数的入口地址,这个虚函数表又使用一个虚函数指针来进行访问。通常,虚函数指针都放在对象模型的第一个位置存放,这样访问徐函数指针的速度最快,没有偏移量。通过虚函数指针,找到虚函数表,进而再做一个次偏移量计算,来得到真实虚函数的入口地址,从而访问了虚函数。这样看来,访问一个虚函数将使用两次间接计算,故要失去一些时间效率。另外,使用了虚函数,那么就要消耗一些空间,存放虚函数指针。但是,这都是值得的,为了实现多态。

2. 虚函数表是每个对象一个还是每个类一个呢?

     这个事搜狗的面试题目。其实讨论过,我之前看到这个问题,立马就想到了this指针。http://topic.csdn.net/u/20081031/16/6784c211-a475-4af5-a331-c3df887a2d0c.html比较详细的讨论。

    每个类只有若干个虚拟函数表,每个对象可以有若干个虚函数表指针。(这都是在多重继承的情况下出现的。单重继承的情况下,就是一个对象有一个虚函数表指针,每一个类有一个虚函数表)函数调用的时候会将this指针作为参数发过去,所以没有必要为每一个对象做一个虚拟函数表。
     “每个类有一个或多个虚函数表,单纯的继承下来的类有一个虚函数表,虚继承下来的类就有可能有多个虚函数表(只是有可能),对象没有虚函数表,但是有指向虚函数表的指针。对象之所一能调用“自己的函数”,是因为类的成员函数不知不觉的在编译时多了一个this指针参数来识别成员数据属于哪个对象。”

    关于这个问题,在《more effective c++》中的条款24有详细叙述,的确是每一个类的一个虚函数表。至于放的的位置,书中也有讲述。

3.函数指针和指针函数的区别?

     就想通过这个问题来判断我的C水平?C语言我看的数都有三本。

    函数指针,一个指针,指向了函数的调用地址。  void (*fun)(void)

  指针函数,就是一个函数的返回值是指针。 int *  fun(void)

  这道题目我真的很冤,指针函数,我真的没有听说过。

   数组指针和指针数组我知道什么区别

4. C++ 深拷贝和浅拷贝的区别?如果要delete一个浅拷贝,需要注意什么条件?

   现在有一个指针p1指向了一个内存空间m1;

   浅拷贝就是再用一个新的指针p2指向这片内存空间m1;

   深拷贝就是用一个新的指针p3指向m1的副本m2

   delete一个浅拷贝,首先要测试是不是有其它的指针还在指向这片空间。不然,直接就是野指针了。为什么野指针那么是绝对要禁止的?野指针现在指向了一片内存区间,这片内存区间以前是有意义的,现在被释放了,操作系统可能会讲这边区间放上其它程序数据。那么,野指针仍然指向了这片区间。如果此时使用野指针,改变了这片内存的数据,那么程序应该直接崩溃。而且,这样子的崩溃可能出现,可能等一会儿出现,不定。带来的bug很难查找。

5.  dynamic_cast的用法?

http://blog.csdn.net/wingfiring/article/details/633033

作为四个内部类型转换操作符之一的dynamic_cast和传统的C风格的强制类型转换有着巨大的差别。除了dynamic_cast以外的转换,其行为的都是在编译期就得以确定的,转换是否成功,并不依赖被转换的对象。而dynamic_cast则不然。在这里,不再讨论其他三种转换和C风格的转换。
首先,dynamic_cast依赖于RTTI信息,其次,在转换时,dynamic_cast会检查转换的source对象是否真的可以转换成target类型,这种检查不是语法上的,而是真实情况的检查。
先看RTTI相关部分,通常,许多编译器都是通过vtable找到对象的RTTI信息的,这也就意味着,如果基类没有虚方法,也就无法判断一个基类指针变量所指对象的真实类型, 这时候,dynamic_cast只能用来做安全的转换,例如从派生类指针转换成基类指针.而这种转换其实并不需要dynamic_cast参与.
也就是说,dynamic_cast是根据RTTI记载的信息来判断类型转换是否合法的。

struct B1{
    virtual ~B1(){}
};
struct B2{
    virtual ~B2(){}
};
struct D1 : B1, B2{};
int main()
{
    D1 d;
    B1* pb1 = &d;
    B2* pb2 = dynamic_cast<B2*>(pb1);//L1
    B2* pb22 = static_cast<B2*>(pb1);  //L2
    return 0;
}

pb2将会为NULL指针,pb22将会编译出错。


http://baike.baidu.com/view/1745213.htm

dynamic_cast < type-id > ( expression )  

该运算符把expression转换成type-id类型的对象。Type-id必须是类的指针、类的引用或者void*;  

如果type-id是类指针类型,那么expression也必须是一个指针,如果type-id是一个引用,那么expression也必须是一个引用。  dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换。  

在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;  

在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。

http://blog.csdn.net/wingfiring/article/details/633033

dynamic_cast 确实很好地解决了我们的问题,但也需要我们付出代价,那就是与 typeid 相比,dynamic_cast 不是一个常量时间的操作。为了确定是否能完成强制类型转换,dynamic_cast`必须在运行时进行一些转换细节操作。因此在使用 dynamic_cast 操作时,应该权衡对性能的影响


5. 请阅读以下的代码,试试看,你能发现错误或者不恰当地方吗?(提示:   不止一处)
http://topic.csdn.net/t/20061129/14/5194019.html

class   Wheel{};
class   Color{};

class   Car
{
private:
    Wheel*   wheels;
public:
    Car(int   wheel_count){wheels   =   new   Wheel[wheel_count];}
    ~Car(){delete   wheels;}
};

class   Roadster   :   public   Car
{
public:
    Color   color;

    Roadster(){};
    Roadster(const   Roadster&   rs)
    {
        this-> color   =   rs.color;
    }
    ~Roadster(){};
    Roadster&   operator=(const   Roadster&   rhs)
    {
        this-> color   =   rhs.color;
    }
    const   Roadster&   clone()
    {
        Roadster   the_new   =   *this;
        return   the_new;
    }
};

int   main(int   argc,   char*   argv[]){
    Roadster*   yours   =   new   Roadster;
    Roadster   mine;     mine   =   yours-> clone();
    Car*   pCar   =   yours;
    delete   pCar;
    return   0;
}; 

修改如下:

class   Wheel{};
class   Color{};

class   Car
{
private:
    Wheel*   wheels;
    //1.   没有保存wheels的下标
    //2.   最好用vector
public:
    Car(int   wheel_count){wheels   =   new   Wheel[wheel_count];}    
    ~Car(){delete   wheels;}
    //3.   析构函数要虚拟
    //4.   delete   wheels;   ->   delete   []wheels;
    //5.   要提供拷贝构造函数
    //6.   要重载赋值操作符
};

class   Roadster   :   public   Car
{
public:
    Color   color;
    //7.   成员变量要私有

    Roadster(){};
    //8.   要调用父类的构造函数
    Roadster(const   Roadster&   rs)
    {
        this-> color   =   rs.color;
    }
    //9.   要调用父类的拷贝构造函数
    ~Roadster(){};
    //10.   析构函数要虚拟(原则:析构函数要么虚拟且公有,要么保护且非虚拟)
    Roadster&   operator=(const   Roadster&   rhs)
    {
        this-> color   =   rhs.color;
    }
    //11.   需要调用父类的赋值操作符
    //12.   没有返回值
    //13.   返回的类型最好是const   Roadster&,   而不是Roadster&
    const   Roadster&   clone()
    {
        Roadster   the_new   =   *this;
        return   the_new;
    }
    //14.   不能返回局部变量的引用
    //15.   按照这里的逻辑,   其实可以直接返回*this
};

int   main(int   argc,   char*   argv[]){
    //16.   这里的{}风格与前面的不一致
    Roadster*   yours   =   new   Roadster;
    Roadster   mine;     mine   =   yours-> clone();
    //17.   最好是一行只有一条语句,   不要两条语句放在一行
    //18.   其实这里可以直接是Roadster   mine   =   yours-> clone();
    Car*   pCar   =   yours;
    delete   pCar;
    //我认为这里是没有问题的,作为Car类的使用者,他在这里用是没有问题的,错误的只是Car类的代码
    return   0;
}; 

6.C++程序进入main函数之前,退出main函数之后会做些什么?

 
 main函数执行之前,主要就是初始化系统相关资源:
1.设置栈指针
2.初始化static静态和global全局变量,即data段的内容
3.将未初始化部分的赋初值:数值型short,int,long等为0,bool为FALSE,指针为NULL,等等,即.bss段的内容
4.运行全局构造器,C++中构造函数
5.将main函数的参数,argc,argv等传递给main函数,然后才真正运行main函数

main 函数之后会执行相反的工作。


你可能感兴趣的:(C++,面试,struct,delete,Class,fun)