1

语言面试题

#include <iostream>

using namespace std;

class A
{
public:
    A() : m_ival(0)
    {
        test();
    }

    virtual void func()
    {
        cout << m_ival << endl;
    }

    void test()
    {
        func();
    }

public:
    int m_ival;
};

class B : public A
{
public:
    B()
    {
        test();
    }

    void func()
    {
        ++ m_ival;
        cout << m_ival << endl;
    }
};

int main()
{
    A* p = new B;

    p->test();
    
    delete p;

    return 0;
}
问 1:分析下面的程序,给出运行结果

输出:

0
1
2
问 二 :在子类与父类构造函数中都调用test(), 那么编译器是怎么确定应该调用的哪个 func() 函数版本呢?

相关概念:

  • 构造函数未结束前,虚函数表未初始化完成,因此不能发生多态
  • 动态联编不等价于多态:
    动态联编:编译原理中的概念
    多 态:面向对象理论。 C++ 编译器在实现面向对象的多态时,使用动态联编的概念

发生了什么?

  • 在构造 A 部分时:

    • 在构造 A 部分时,回去构造 A 部分的虚函数表。在执行 A 的构造函数的函数体时,虚函数表是【完整】的
    • 在 A 的构造函数中调用 Func(), 发生动态联编,查找虚函数表得到具体函数地址
    • A 中的 Func 被调用

clipboard.png

  • 在构造 B 部分时:

    • 在构造 B 部分时,【虚函数表被更新】
    • 在 B 的构造函数中调用 Func(), 发生动态联编,查找虚函数表得到具体函数地址
    • B 中的 Func 被调用

clipboard.png

总结:

  • 在构造函数中,调用虚函数不会发生多态,但虚函数是动态联编的
  • 虚函数的调用,都是通过查找虚函数表完成的
问 三:上述的代码是否有其它问题?
  • 问题 1, 未判断内存是否申请成功
  • 问题 2

    • A* p = new B; delete p;
    • A 类指针 p 指向 B 类对象,在 delete 时,析构函数不是虚函数,因此,编译器就只会根据 p 的类型调用 A 类的析构函数, B 类的析构函数不会被调用!!!
要点
  • 面试官需要的不只是“正确答案”
  • 面试官会想要知道应试者的解题思路
  • 面试官会从题中间“随机抓取”细节进行追问
  • 面试官会把问题进行扩展(如:从语言扩展到编译器)

算法面试题

HR 问:编写代码将一个链表反转打印

面试者:需要思考的问题

  • 这具体是一个什么样的链表(单链表?双向链表?循环链表)
  • 链表中节点的数据是否允许改变
  • 时间复杂度有没有要求
  • 。。。
struct Node
{
    int v;
    struct Node* next;
};
void reversse_display_list(struct Node* list)
{
    if( list )
    {
        reversse_display_list(list->next);

        printf("%d ", list->v);
    }
}
void reversse_display_list_stack(struct Node* list)
{
    if( list )
    {
        LinkStack<int> stack;

        while( list )   // O(n)
        {
            stack.push(list->v);

            list = list->next;
        }

        while( stack.size() > 0 )   // O(n)
        {
            printf("%d ", stack.top());

            stack.pop();
        }

        printf("\n");
    }
}
struct Node* reverse_list(struct Node* list) // O(n)
{
    if( list )
    {
        struct Node* guard = NULL;
        struct Node* next = list->next;

        while( next )
        {
            list->next = guard;
            guard = list;
            list = next;
            next = list->next;
        }

        list->next = guard;
    }

    return list;
}
要点
  • 面试不是笔试,面试考察的不只是技术
  • 面试官需要全方位考察应试者(沟通,品质,态度)
  • 面试中尽量与面试官交流,获取解题的有用信息
  • 面试中尽量写出完整代码,而不是纸上谈兵的忽悠
  • 。。。

发散性思维问题

不用加减乘除运算符做整数加法

面试者: 需要思考的问题

  • 面试官想考察什么?
  • 加法的底层本质是什么?
  • 是否可以绕开 “+” 操作符的使用
  • 。。。
int add_1(int a, int b)
{
    int ret = 0;

    asm volatile
    (
         "movl %%eax, %%ecx\n"
         "addl %%ebx, %%ecx\n"
         : "=c"(ret)
         : "a"(a), "b"(b)
    );


    return ret;
}

int add_2(int a, int b)
{
    int part = 0;
    int carry = 0;

    do
    {
        part = a ^ b;
        carry = (a & b) << 1;

        a = part;
        b = carry;
    } while( b != 0 );

    return a;
}
要点
  • 不同公司对人才的要求是不同的
  • 一些公司会更看重应试者的可塑性
  • 学习,分析,思考能力是工作中必备的(面试需要考察)
  • 计算机系统底层原理的学习必不可少
  • 。。。

TianSong
734 声望138 粉丝

阿里山神木的种子在3000年前已经埋下,今天不过是看到当年注定的结果,为了未来的自己,今天就埋下一颗好种子吧