两道腾讯的面试真题,考验C++对象模型的理解

1、原题如下:

#include <iostream>
using namespace std;
class Test
{
public:
    static int getA();
    int getB();
    virtual int getC();
    int a;
    static int c;
};
int Test::getA()
{
    return 0;
}
int Test::getB()
{
    return 0;
}
int Test::getC()
{
    return 0;
}
int main()
{
    Test *ptr = (Test*)malloc(sizeof(Test));
    //下面的几个调用,哪些会出现问题?
    ptr->getA();
    ptr->getB();
    ptr->getC();
    ptr->a;
    ptr->c;
    return 0;
}

答案:所有语句都能通过编译,但是运行时,ptr-c#>getC();这条语句会出错,crash。

分析:考察的地方有malloc和虚函数。

虚函数调用会crash,其他正常。因为其他在编译期间可以确定地址。

malloc调用,不会像new一样调用构造函数(free不会调用析构函数),所有虚函数指针都不会分配。

参考文献:《深度探索C++对象模型

2、原题如下:

以下代码是否完全正确,执行可能得到的结果是____。

A.程序正常运行

B.程序编译错误

C.程序崩溃

D.程序死循环

#include <iostream>
using namespace std;
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);
}

答案:C.首先程c#面试序中没有编译错误,也编译器原理没有死循环(程序中没有循环哪里来的死循环)。有些初学者可能会说main函c#委托数没有写return 0,main函数不显性的写return,编译器也会帮你做的。当类中存在指针类型编译器的堆空间不足的成员变量时赋值和析构要格外注意,这道题的问题就出在类B对象b中的指针p被析构了两次。

分析:当执行完成B b这句话后,在b中就构造了一个类c#A的指针对象p,当调用sayHello(b)函数时系统将会调用类B的赋值构造函数构造一个类B的实例bStep(为了方便下面的叙述随便起了一个名字)传入到sayHello函数中(问题就出在bSc#和javatep这个实例中),这里当sayHello执行完成后,之前构造的实例bStep将被析构(c#接口;执行delete p)c#。然后程序继续开心的执行,直到执行完main函数后系统将会析构b,当b被析构时将再次执行delete p。这样p就被析构了两遍导致程序崩溃。

我们把代码增加一些输出信息后大家就更容易看了:

#include <iostream>
using namespace std;
class A
{
    int i;
};
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);
}

控制台输出如下(2次构造函数,2次析构函数):

构造

拷贝构造

析构

析构

正确的方法应该把sayHello函数写成这样:

void sayc#面试Hello(const B&c#菜鸟教程 b)

{

}

控制台输出编译器的分析模型和综合模型如下(1次构c#造函数,1次析构函数)ÿc#怎么读1a;

构造

析构

使用引用的方法效率高,没有拷贝构造函数被调用,因为没有新对象被创建编译器和解释器的区别。const目的是避免对象被改变。

参考文献&编译器原理#x编译器ff1a;《c#教程Effective C++ 第三版》条款20:宁以pass-by-reference-to-const替换pass-by-value