金山云一面(C/C++研发):2017-09-04

2017-09-08 08:50 阅读 782 次 评论 1 条

说一下你理解的深、浅拷贝,写时拷贝的适用场景是什么?

所谓的[深拷贝]与[浅拷贝]各自代表不同的含义,各有所需,关键是要区分值语意(value semantice)和引用语意(reference semantice)。

对于值语意的对象:在x=y完成复制之后,y的状态改变不能影响到x,则称其具有独立性。使用深拷贝的方式可以完全复制一个独立于原来的对象。C++提供的(模板)类大部分都是值语意的,比如:std::vector、std::basic_string等。

对于引用语意:在C++中除了可以使用原始指针,还可以使用std::shared_ptr、std::weak_ptr等智能指针来表示引用,比如:

假设p数是引用语意,X b = a浅拷贝之后,a.p和b.p引用的是同一个int变量。

编译器之所以默认行为为浅拷贝,在于有些行为深拷贝并不能实现。例如:指向对象的可能是多态,也可能是数组,也可能有循环引用,所以只能留待成员变量的类来决定怎样复制。

写时拷贝的适用场景:当内存比较吃紧,又想避免简单浅拷贝带来的问题时。

谈一下C++的异常机制,了解seh吗,简单说说吧?

C/C++异常处理机制,此篇博客已经对C++的异常有了深刻的认识,下述为扩展:

整个 C++ exception 的行为在常见语言中是最奇葩的, 因为这个语言特性与 C++ 其他 feature(特别是确定性析构) 格格不入。在 C++ 中全面铺开使用异常会遇到其他语言中不存在的问题。

C++ 引入异常的原因之一是为了能让构造函数报错(析构函数不能抛异常这是大家都知道的常识),毕竟构造函数没有返回值,没有异常的话调用方如何得知对象构造是否成功呢?但是编译器/标准库为了让构造函数能抛异常却是麻烦重重:

  1. 数组元素构造时抛异常,前面已经构造好的元素要析构,还没有构造的元素不能析构。
  2. 构造函数的初始化列表里抛异常,前面已经构造好的成员和基类子对象要析构,还没有构造的成员则不能析构。而且这个异常捕获之后必须重新抛出(编译器强制),因为C++不允许“半吊子”构造的对象存在。
  3. 多继承中某个基类的构造函数抛异常,那么已经构造好的基类子对象要析构,还没有构造的基类子对象则不能析构。虚拟继承,虚基类只能析构一次,你慢慢想吧。
  4. 函数实参对象构造时抛异常,那么多个实参中已经构造好的实参对象要析构,尚未构造的实参对象不能析构。
  5. std::vector 在 resizing 的时候某个元素的拷贝发生异常,那么前面已经拷贝的元素要析构,尚未拷贝的元素则不必也不能析构。

C++ 编译器要随时提防调用某个函数 foo 会抛异常,这会阻止一些优化,也会产生很多累赘的代码(随时准备析构那些调用 foo 函数前已经构造好的栈上对象),因此 C++11 的 noexcept 应该大力推广。

RAII 的优势在于将对象的生命期管理与其他资源(锁、文件、网络连接等等)的管理整合,然后通过 smart pointers 一并解决了,这是 C++ 独一无二的优势。

至于seh,这里我不想多做介绍,你可以看看这篇文章 seh结构化异常处理机制 。

你如何理解boost库中的智能指针?

我不想花大量的篇幅去介绍全部的智能指针,如果你感兴趣,可以看auto_ptrscoped_ptr 、shared_ptr。下面我主要谈谈对weak_ptr的认识。

weak_ptr和shared_ptr最大的区别在于weak_ptr在指向一个对象的时候不会增加其引用计数,因此你可以用weak_ptr去指向一个对象并且在weak_ptr仍然指向这个对象的时候析构它。此时你再访问weak_ptr的时候,它返回的会是一个空的shared_ptr。

实际上,通常shared_ptr内部实现的时候维护的就不是一个引用计数,而是两个引用计数,一个表示strong reference,也就是shared_ptr进行复制的时候进行的计数;一个时weak_reference,也就是weak_ptr进行复制的时候的计数。weak_ptr本身并不会增加strong reference的值,而strong reference降低到0,对象被自动析构。这也就是打破shared_ptr循环引用的秘诀所在。

STL中list与vector的区别是什么?

① vector是一个变长的数组,其物理空间是连续的;对于List中同一个节点,值域指针是一块分配的,是连续的,不同节点的内存地址可能连续也可能跳跃。

② vector删除节点后不会释放内存空间,而是预留,以便再次插入,如果非要释放,则需要开辟一块同样的内存空间,将原数据拷贝过去,再释放旧空间。而List可以直接删除。

③ vector支持随机访问,时间复杂度为O(1);而List不支持,时间复杂度为O(n)。

④ vector的尾插和尾删的时间复杂度为0(1),其他位置为O(n);链表任意位置的插入与删除若给了迭代器,则为O(1),其他情况为O(n)。

⑤ vector在删除时会引起迭代器失效,因为删除一个元素相当于让后面的元素向前挪动一个位置,会覆盖掉要删除的位置,导致后面的迭代器全部失效。插入时,若未扩容,则不会失效。List删除使得当前迭代器失效,不会引起其他迭代器失效,插入不会引起迭代器失效。

进程间通信方式有哪些,谈谈管道和共享内存,了解windows下的进程间通信方式吗?

进程间通信方式前面博客已经介绍完了,下面主要介绍一下windows下的进程间通信方式:文件映射、共享内存、管道、邮件槽、剪贴板、动态数据交换、对象连接与嵌入、动态链接库、远程过程调用、NetBios函数、Sockets、WM_COPYDATA消息。

死锁产生的原因及场景,你是如何解决的?

死锁的产生原因及场景

谈一下C++的对象模型?

虚函数引发的对象模型

C++虚函数的理解,你认为它有什么缺陷?

一、通过父类型的指针访问子类自己的虚函数

我们知道,子类没有重载父类的虚函数是一件毫无意义的事情。因为多态也是要基于函数重载的。虽然在上面的图中我们可以看到Base1的虚表中有Derive的虚函数,但我们根本不可能使用下面的语句来调用子类的自有虚函数:

任何妄图使用父类指针想调用子类中的未覆盖父类的成员函数的行为都会被编译器视为非法,所以,这样的程序根本无法编译通过。但在运行时,我们可以通过指针的方式访问虚函数表来达到违反C++语义的行为。(关于这方面的尝试,通过阅读后面附录的代码,相信你可以做到这一点)

二、访问non-public的虚函数

另外,如果父类的虚函数是private或是protected的,但这些非public的虚函数同样会存在于虚函数表中,所以,我们同样可以使用访问虚函数表的方式来访问这些non-public的虚函数,这是很容易做到的。如:

三、虚函数影响执行效率

1)Cache命中率不够好。一般函数可能编译后的指令就在当前函数地址附近,而虚函数不再cache中的概率高。并且一调函数就可能在cache中载入虚函数表,如果这个虚函数又调用其他虚函数,可能又得载入到cache中导致cache被占用,指令和数据的cache命中率下降。

2)编译器不好优化。因为编译器只知道你要调用的是一个不确定的地址处的函数,没法知道更多的细节,也就无法替你做更多的优化。

C++动态内存管理,谈谈new、new[]等的底层实现,new[]申请的空间,直接delete会有什么问题?

线程同步机制,在Linux下你如何进行线程间互斥与同步?

你如何将Linux的知识迁移至windows?

版权声明:本文著作权归原作者所有,欢迎分享本文,谢谢支持!
转载请注明:金山云一面(C/C++研发):2017-09-04 | 术与道的分享
分类:笔经面经 标签:, ,
1024do.com导航_术与道导航平台

发表评论


表情

  1. 北漂青年
    北漂青年 【农民】 @回复

    你的价值对得起你付出的努力!