面试问题总结

c++常见面试题总结

1、c++基础

  • 虚析构和多态

    多态就是利用继承基类重写虚函数virtual和动态绑定来实现调用基类函数,在运行期根据绑定的不同类型的子类,去调用子类的接口。这里就衍生出一个虚析构问题,假如基类的析构函数没有声明virtual,那么释放基类指针的时候也不会调用派生类的析构函数。对于需要在派生类中释放资源的类就会出现内存泄漏的问题,所以如果基类中存在虚函数,那么一般是会去继承重写虚函数的,这时候把基类的析构函数也顺便定义成virtual。

  • 深拷贝和浅拷贝

    简单理解就是深拷贝重新分配内存然后将原始内容拷贝过来,而浅拷贝没有重新分配内存,只是从引用了已经存在的资源。简单内置类型比如int型一般不用考虑这些问题,对于复杂类或结构体,如果存在需要释放的资源并且没有提供自定义的拷贝构造函数,编译器会提供默认的拷贝构造函数,那么一旦发生拷贝,默认拷贝提供的是浅拷贝。所以这两个对象都将共用一份资源,一旦释放其中一个的资源,而另外一个对象再去访问共用的的资源就会触发异常。

  • std容器时间复杂度

    访问 插入 删除 是否连续内存
    vector O(1) 尾部O(1),其他位置O(n) 尾部O(1),其他位置O(n)
    deque O(1) 头尾部O(1),中间O(n) 头尾部O(1),中间O(n)
    list O(n) 任意位置O(1) 任意位置O(1)
    map O(logn)红黑树查询删除插入时间都一样 O(logn) O(logn)
    set O(logn) O(logn) O(logn)
    unordered_map 哈希表实现O(1),最坏情况O(n) O(1),最坏情况O(n) O(1),最坏情况O(n)
    unordered_set 哈希表实现O(1),最坏情况O(n) O(1),最坏情况O(n) O(1),最坏情况O(n)
  • 优缺点对比

    优点 缺点 适用场景
    vector 支持随机访问,不需要维护额外的数据所以内存占用小,尾部插入或删除速度快。 插入和删除如果在中间,需要往后或往前整体移动插入或删除的位置后面的内存,数据量大的话消耗就大。所以在已知数据大小的情况下可以调用reserver预先分配内存 需要经常随机访问,插入删除等操作少
    deque 双向队列,兼有vector随机访问,同时又可以像list一样可以快速在两端pop,push 中间位置插入删除比较耗时,需要移动元素 频繁随机访问,在两端插入删除数据
    list 任意位置插入删除效率高,头尾访问快 内存不连续所以无法随机访问 频繁任意位置插入删除数据
    forward_list 单向链表所以比list内存占用小,随机插入效率高 不支持随机访问 不需要双向迭代数据
    set 红黑树(平衡二叉树)实现,元素自动排序并唯一 插入元素需要重新排序,降低效率 需要自动排序并保持数据唯一
    map 红黑树(平衡二叉树)实现,元素自动排序并且键值对映射关系唯一 插入元素需要重新排序,降低效率 需要排序且数据为字典形式
    unordered_set 哈希表实现无序排序,查找速度快,大量数据下插入删除均比set快,因为很难遇到最坏情况 哈希表额外内存占用 不需要自动排序并保持数据唯一
    unordered_map 哈希表实现不会排序,查找速度快,大量数据下插入删除均比map快 哈希表额外内存占用 不需要排序且数据为字典形式
  • priority_queue(优先级队列)与队列(queue)

    优先级队列是在队列先进先出的基础上,增加优先级的规则,可以分为最大优先级和最小优先级。本质是在插入数据的时候按照优先级排序,可以有相同优先级。实现原理是堆。

  • 静态局部对象、静态全局对象、全局对象区别

    内存位置 作用域
    静态局部对象 静态区,程序结束释放 作用于特定类或者函数
    静态全局对象 静态区,程序结束释放 作用于整个编译单元(cpp),对其他编译单元无效。
    全局对象 静态区,程序结束释放 作用于所有编译单元,只需要包含声明的头文件。在.h中使用extern声明,在.cpp初始化。
  • 进程和线程的区别

    1. 进程是操作系统分配资源的基本单位,线程是处理器调度和执行任务的基本单位。

    2. 同一进程的线程共享进程的地址空间和资源,不同进程之间是独立的地址空间和资源。

    3. 进程一般包含多个线程,当一个线程崩溃会导致整个进程崩溃。

2、Qt基础

  • connect第五个参数的用法

    默认参数Qt::AutoConnection,当receiver和sender在同一个线程时,使用Qt::DirectConnection,当不在一个线程时,会切换成Qt::QueuedConnection。从字面意思就能发现这两者的区别,Qt::DirectConnection表示直接连接,当信号发送的时候会立即触发槽函数,所以是同步。而Qt::QueuedConnection由于两个对象不在同一个线程,槽函数不会立即触发,会进入事件循环,所以是异步执行。当没有receiver的时候,槽函数会在sender线程执行。

    Qt::BlockingQueuedConnection是Qt::QueuedConnection的同步版,除了会在receiver线程执行槽函数以外,sender线程会一直卡住等待槽函数执行完成。所以使用这个连接类型,一定要确认sender和receiver在不同线程,如果在同一个线程会导致死锁。

    Qt::UniqueConnection是一个辅助标志位,和其他连接类型一起使用,用|(逻辑OR)拼接,用来确保同一组对象和槽函数不会多次连接。

  • QObject对象和线程关系

    继承QObject的对象都是不能跨线程调用的,QObject在哪个线程创建它就属于哪个线程。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    class Thread: public QThread
    {
    Q_OBJECTMeta-Object Compiler
    public:
    Thread()
    {
    m_pObject = new QObject();
    qDebug() << QThread::currentThread();
    qDebug() << m_pObject->thread();
    }
    ~Thread()
    {
    if (m_pObject) {
    m_pObject->deleteLater();
    }
    if (m_pObject1) {
    m_pObject1->deleteLater();
    }
    }
    protected:
    void run() override
    {
    m_pObject1 = new QObject();
    qDebug() << QThread::currentThread();
    qDebug() << m_pObject1->thread();
    }
    private:
    QObject *m_pObject;
    QObject *m_pObject1;
    };

    这个例子就能发现QObject对象的所属线程和当前创建时线程一致。在Thread的构造函数里面创建时还没有启动线程,这时m_pObject的所属线程和Thread类的对象是同一个。run函数由于是在新线程中执行,所以m_pObject1也属于新线程。当出现跨线程警告时,不妨输出一下当前线程和对象所属线程比较一下,就能排查出问题。

  • 信号槽原理

    信号槽的实现基础在于moc,全称是Meta-Object Compiler(元对象编译器),在使用信号槽时,需要在类的定义中加一个Q_OBJECT宏定义。这个宏定义展开如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    #define Q_OBJECT \
    public: \
    QT_WARNING_PUSH \
    Q_OBJECT_NO_OVERRIDE_WARNING \
    static const QMetaObject staticMetaObject; \
    virtual const QMetaObject *metaObject() const; \
    virtual void *qt_metacast(const char *); \
    virtual int qt_metacall(QMetaObject::Call, int, void **); \
    QT_TR_FUNCTIONS \
    private: \
    Q_OBJECT_NO_ATTRIBUTES_WARNING \
    Q_DECL_HIDDEN_STATIC_METACALL static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **); \
    QT_WARNING_POP \
    struct QPrivateSignal {}; \
    QT_ANNOTATE_CLASS(qt_qobject, "")

    moc工具就是自动帮我们实现宏定义展开的函数,所以在编译目录下会存在一个moc_xxx.cpp。我们自定义的信号其实也是个函数,实现也在这个moc生成的cpp中。

    这是关于信号在moc的cpp中的实现:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // SIGNAL 0
    void MainWindow::test()
    {
    QMetaObject::activate(this, &staticMetaObject, 0, nullptr);
    }

    // SIGNAL 1
    void MainWindow::test1(int _t1)
    {
    void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
    QMetaObject::activate(this, &staticMetaObject, 1, _a);
    }

    信号槽利用观察者模式,在connect的时候将接收者添加到通知列表中,发送信号就是执行QMetaObject::activate,然后通过回调执行接收者的槽函数。

3、Win32原生绘制界面流程

  1. RegisterClassEx首先是注册窗口类型,这里注册的是真正的窗口。里面lpfnWndProc参数用于传入自定义的事件循环处理函数,lpszClassName用于声明窗口的类型名称。

  2. CreateWindowEx创建窗口,会用到上一步中的lpszClassName。

  3. WM_PAINT是回调函数中消息的一种,在这里使用GDI接口在窗口的DC(设备上下文,我一般理解为画板)画图。比如常见的LineTo等函数。GDI基于c语言,GDI+基于cpp开发,是GDI的扩展,简化了GDI的操作并升级了它的功能。

4、计算机基础

4.1、七层网络模型和四层网络模型

  • OSI七层模型

    1. 应用层

    2. 表示层

    3. 会话层

    4. 传输层

    5. 网络层

    6. 数据链路层

    7. 物理层

  • TCP/IP协议族

    1. 应用层(http、https、ftp、telnet等上层协议)

    2. 传输层(tcp、udp两种传输协议)

    3. 网络层(处理ip包和icmp包等基础最小传输单位)

    4. 网络接口层(和硬件的驱动和操作系统底层接口交互)

    TCP/IP是一个协议族包含很多协议,这个四层模型和OSI模型范围不同,没有包含物理层的,但是具有一定的对应关系。


面试问题总结
http://yoursite.com/2021/01/30/面试问题总结/
作者
还在输入
发布于
2021年1月30日
许可协议