dxgzg-商汤科技一面面经——解答

从今天起,豆芽有空也尽己所能,帮助一下大家。

面经来源:https://www.nowcoder.com/discuss/694328?channel=1009&source_id=discuss_center_discuss_history_nctrack&ncTraceId=828c6977acfc42d680c9803e2a376f4f.286.16276989747911046


1. C++14有啥新特性?

  1. 泛型Lambda

  2. constexpr:C++11的constexpr函数只能包含一个表达式,C++14放松了这些限制,支持诸如if 和switch等条件语句,支持循环,其中包括基于区间(range)的for 循环。

  3. 类型推导

    • 支持所有函数的返回类型推导

    • 改进decltype(auto)语法,它支持使用与auto同样的机制计算给定表达式的类型。


2. C++17有啥新特性?

  1. 构造函数模板推导

  2. 新增Attribute

  3. namespace嵌套

  4. std::apply函数

等等


3. C++20有啥新特性?

  1. 概念(concept)

  2. 协程(Coroutines)

  3. Modules


4. lambda如何实现的?

原理:编译器会把一个lambda表达式生成一个匿名类的匿名对象,并在类中重载函数调用运算符()

我们举个简单的例子:

   auto print = []{cout << "Douya" << endl; };

那么编译器生成一个匿名类的匿名对象,形式可能如下:

   //用给定的lambda表达式生成相应的类
   class print_class{
   public:
       void operator()(void) const{
           cout << "Douya" << endl;
       }
   };
   //用构造的类创建对象,print此时就是一个函数对象
   auto print = print_class();

可以看到匿名类里重载了函数调用运算符()。还生成了一个函数对象,那么我们就直接可以使用这个函数对象了。


5. shared_ptr介绍一下,来手撕一下

shared_ptr实现共享式拥有概念。多个智能指针可以指向相同对象,该对象和其相关资源会在“最后一个引用被销毁”时候释放。

实现原理:有一个引用计数的指针类型变量,专门用于引用计数,使用拷贝构造函数和赋值拷贝构造函数时,引用计数加1,当引用计数为0时,释放资源

    #include <iostream>  
    #include <stdlib.h>  
    using namespace std;  

    template <typename T>  
    class mysharedPtr {  
    public:  
        mysharedPtr(T* p = NULL);  
        ~mysharedPtr();  
        mysharedPtr(const mysharedPtr<T>& other);  
        mysharedPtr<T>& operator=(const mysharedPtr<T>& other);  
    private:  
        T* m_ptr;  
        unsigned int* m_count;  
    };  

    template <typename T>  
    mysharedPtr<T>::mysharedPtr(T* p) {  
        m_ptr = p;  
        m_count = new unsigned int(0);  
        ++(*m_count);  
        cout << "Constructor is succeed!" << endl;  
    }  

    template <typename T>  
    mysharedPtr<T>::~mysharedPtr() {  
        --(*m_count);  
        if ((*m_count) == 0) {  
            delete[] m_ptr;  
            m_ptr = NULL;  
            delete[] m_count;  
            m_count = NULL;  
            cout << "Destructor is succeed!" << endl;  
        }  
    }  

    template <typename T>  
    mysharedPtr<T>::mysharedPtr(const mysharedPtr<T>& other) {  
        m_ptr = other.m_ptr;  
        m_count = other.m_count;  
        ++(*m_count);  
        cout << "Copy constructor is succeed!" << endl;  
    }  

    template <typename T>  
    mysharedPtr<T>& mysharedPtr<T>::operator=(const mysharedPtr<T>& other) {  
        // 《C++ primer》:“这个赋值操作符在减少左操作数的使用计数之前使other的使用计数加1,  
        // 从而防止自身赋值”而导致的提早释放内存  
        ++(*other.m_count);  
        --(*m_count);  
        // 将左操作数对象的使用计数减1,若该对象的使用计数减至0,则删除该对象  
        if ((*m_count) == 0) {  
            delete[] m_ptr;  
            m_ptr = NULL;  
            delete[] m_count;  
            m_count = NULL;  
            cout << "Left side object is deleted!" << endl;  
        }  
        m_ptr = other.m_ptr;  
        m_count = other.m_count;  
        cout << "Assignment operator overloaded is succeed!" << endl;  
        return *this;  
    }  

6. 引用计数器的值是堆上的还是栈上的?

堆上。涉及到new申请的都是堆上。


7. 拷贝构造函数参数必须要const?

必须要有。

我们给出例子:不加const

#include <iostream>
using namespace std;

class Test{
public:
    Test(int x):m_val(x){};
    Test(Test&){};
    ~Test(){};
    void GetVal() const {cout << m_val << endl;};
    //Test GetObj(void){return Test(20);}
private:
    int m_val;
};

int main()
{
    Test obj1(20);
    obj1.GetVal();

    //Test obj2 = obj1.GetObj();

    cout << "Hello World";
    return 0;
}

20
Hello World

不加const对于普通构造没有问题。我们接下来考虑一种场景,加入:Test GetObj(void){return Test(20);}

#include <iostream>
using namespace std;

class Test{
public:
    Test(int x):m_val(x){};
    Test(Test&){};
    ~Test(){};
    void GetVal() const {cout << m_val << endl;};
    Test GetObj(void){return Test(20);}
private:
    int m_val;
};

int main()
{
    Test obj1(20);
    obj1.GetVal();

    Test obj2 = obj1.GetObj();

    cout << "Hello World";
    return 0;
}

报错:
error: cannot bind non-const lvalue reference of type ‘Test&’ to an rvalue of type ‘Test’

原因是:返回一个局部变量是通过一个临时的变量返回,对象也不例外,这里也会产生一个临时的对象,而这个临时对象,具有常性,也就是const,不可被修改。

就是告诉我们非const,所以我们为拷贝构造函数加入const后编译器通过。

返回临时对象是C++中非常常见的一种操作,由此看来,为拷贝构造函数加入const是很有必要的


8. 为什么不能返回局部的引用?

返回值是栈上的值,函数结束,栈被系统回收,内存的值就不存在了。


9. 为什么要operator=重载等于号,C++为啥要这么设计?

对于C++提供的所有操作符,通常只支持对于基本数据类型(如int、float)和标准库中提供的类(如string)的操作,而对于用户自己定义的类,如果想要通过该操作符实现一些基本操作(比如比较大小,判断是否相等),就需要用户自己来定义关于这个操作符的具体实现了。

比如:

class myselfclass{  
public:  
    myselfclass();  
    ~myselfclass();  
private:  
    int m_val;  
    bool m_flag;  
};  

myselfclass a,b;  
if (a == b){  
    ......  
}  

这里我们定义了一个类,然后定义了两个对象a、b,然后判断a == b,那问题来了,这个语句是表达什么意思呢?是a整个对象空间都等于b的对象空间呢,还是仅仅a的某个成员变量等于b的某个成员变量呢?

关键看程序员在这里想实现什么样的逻辑,就重定义怎样的行为。所以需要对 == 操作符进行个人定义。


10. push_back和emplace_back的区别

  1. 原版的push_back需要调用拷贝构造函数,2倍扩容机制。而C++11后, push_back直接调用emplace_back,利用了右值引用的移动语义特性,避免了拷贝构造函数的调用,提升了效率

  2. push_back没有利用可变参数模板,emplace_back可以传入多个参数,到函数内调用placement new构造。


11. weak_ptr了解吗?

shared_ptr存在共享指针的循环引用计数问题。weak_ptr是弱引用,weak_ptr的构造和析构不会引起引用计数的增加或减少。我们可以将其中一个改为weak_ptr指针就可以了。比如我们将class B里shared_ptr换成weak_ptr


12. push_back插入均摊复杂度

采用均摊分析的方法,公式如下:

img

公式里面,假定有 n 个元素,倍增因子为 m(我们设定为2),那么完成这 n 个元素往一个 vector 中的push_back操作,需要重新分配内存的次数大约为 log2(n),第 i 次重新分配将会导致复制 2^i (也就是当前的vector.size() 大小)个旧空间中元素,因此 npush_back 操作所花费的总时间:

img

然后 n 次push_back操作,每次分摊O(1),即为常数时间复杂度了。


13. epoll的原理?在对比一下select的效率

epoll提供了三个函数,epoll_create、epoll_ctl和epoll_wait。

首先创建一个epoll对象,然后使用epoll_ctl对这个对象进行操作(添加、删除、修改),把需要监控的描述符加进去,这些描述符将会以epoll_event结构体的形式组成一颗红黑树,接着阻塞在epoll_wait,进入大循环,当某个fd上有事件发生时,内核将会把其对应的结构体放入一个链表中,返回有事件发生的链表。

(1)select,poll实现需要自己不断轮询所有fd集合,直到设备就绪,期间可能要睡眠和唤醒多次交替。而epoll只要判断一下就绪链表是否为空就行了,这节省了大量的CPU时间。

(2)select,poll每次调用都要把fd集合从用户态往内核态拷贝一次,并且要把当前进程往设备等待队列中挂一次,而epoll只要一次拷贝,而且把当前进程往等待队列上挂也只挂一次,这也能节省不少的开销。


14. epoll为什么是红黑树?

查询更稳定(对比哈希表,哈希表还存在扩容问题),内存占用更小(对比哈希表,哈希表一般占得空间更大)



以上所有题的答案其实都来源于我的博客面经,欢迎大家围观:https://blog.nowcoder.net/jiangwenbo



#软件工程师##面经##商汤科技##C++工程师##校招#
全部评论
受教了,引用计数器是原子的,保证了线程安全
1
送花
回复
分享
发布于 2021-07-31 13:37
你做的这个太好了,牛客里好多面经不知道答案是啥
1
送花
回复
分享
发布于 2021-07-31 23:59
滴滴
校招火热招聘中
官网直投
是个好豆芽
1
送花
回复
分享
发布于 2021-08-03 22:16
老哥,你的编译器是什么版本?在公司我的是gcc4.8.5我的。你的例子我写了,确实不加const会报错。但是加了后面的hello world也没有输出
点赞
送花
回复
分享
发布于 2021-07-31 13:54
我分别在VS2019和gcc11的版本跑了一下,不加const是不会报错的,我依然觉得const不是关键
点赞
送花
回复
分享
发布于 2021-07-31 16:39

相关推荐

面试官人特别特别好,真的学到很多东西,他还会引导我回答问题没有自我介绍和项目问题,直接开始问技术问题html5,4.0,说说这些版本之间的区别?语义化有什么好处吗?script一般放在哪个位置?为什么放在body最后?什么时候用defer,什么时候不用defer?要是有一个script标签里面的内容不是必须的,但是这个script标签发生错误,页面会白屏吗?有哪几种可能性呢?没有加async/defer情况下一定会白屏吗?常见的js错误有哪些呢?(看错误有没有影响主页面渲染进程)position有哪几种取值?哪些脱离文档流,哪些没有脱离文档流?现在有一个元素(fixed定位)在页面的右下方,这时给它套一个父元素(relative定位),会有什么影响呢?要是换成absolute呢?es6有哪些新特性?这些知识大都是背下来还是理解下来的?const&nbsp;a&nbsp;=&nbsp;&quot;a&quot;const&nbsp;b&nbsp;=&nbsp;&quot;b&quot;const&nbsp;c&nbsp;=&nbsp;&quot;a&quot;&nbsp;+&nbsp;&quot;b&quot;const&nbsp;d&nbsp;=&nbsp;a&nbsp;+&nbsp;bc&nbsp;==&nbsp;d&nbsp;?c&nbsp;===&nbsp;d?在js中基本类型有哪些?基本类型引用类型存在哪里?在栈中存的是值还是地址?string是什么类型?上述代码有几份“ab”?使用vue中v-for渲染十个div([0....9])(div里面没有东西),现在变成[0.....8]是第一个被销毁还是第十个被销毁?哪些元素被销毁了,哪些元素会重新渲染?变成[1...9],那现在被销毁的是哪个?现在变成{{&nbsp;index&nbsp;}},现在变成[0....8],销毁的是哪一个?变成[1....9]呢?大量元素的时候,除了虚拟滚动的方式还有什么方法?(时间分片、管道)前端组件化、模块化怎么做的?a文件中引入c文件,b文件中引入c文件,打包完成后,源码中有几份c文件?npm里面安装了几个依赖,一个组件包安装的是vue2,另外一个安装的是vue3,会不会安装成功?三到五天出结果,面试官夸我了诶他居然说我很不错我真的哭死
点赞 评论 收藏
转发
9 42 评论
分享
牛客网
牛客企业服务