• C++面试常见问题


    1. stack 中有 pop() 和 top() 方法,为什么不直接用 pop() 实现弹出和取值的功能?

    如果 stack 中存放的是较大是内容时,比如 vector 类型,取值的时候就会发生拷贝,如果拷贝失败,这是,

    假设有一个stack<vector>,vector是一个动态容器,当你拷贝一个vector时,标准库会从堆上分配很多内存来完成这次拷贝。当这个系统处在重度负荷,或有严重的资源限制的情况下,这种内存分配就会失败,所以vector的拷贝构造函数可能会抛出一个std::bad_alloc异常。当vector中存有大量元素时,这种情况发生的可能性更大。当pop()函数返回“弹出值”时(也就是从栈中将这个值移除),会有一个潜在的问题:这个值被返回到调用函数的时候,栈才被改变;但当拷贝数据的时候,调用函数抛出一个异常会怎么样?如果事情真的发生了,要弹出的数据将会丢失;它的确从栈上移出了,但是拷贝失败了!std::stack的设计人员将这个操作分为两个部分:先获取顶部元素(top()),然后从栈中移除元素(pop())。这样,在不能安全的将元素拷贝出去的情况下,栈中的这个数据还依旧存在,没有丢失。当问题是堆空间不足时,应用可能会释放一些内存,然后再进行尝试。

    参考:为什么适配器stack中成员函数top()和pop()需要分离实现

    2、UDP首部长度,有哪些字段

    UDP数据报有两个字段:数据字段和首部字段。首部字段只有8个字节,由4个字段组成,每个字段长度都是2个字节。

    各字段意义:
    源端口号:
    需要对方回信时可选用,不需要可全为0

    目的端口号:
    在终点交付报文时必须使用

    长度:
    UDP数据报的长度,最小值是8字节(仅有首部)

    检验和:
    检验数据报在传送过程中是否出错

    3、TCP报文段的首部前面20个字节是固定的,而后面有4n个字节是根据需求而增加的选项(n为整数且最大为10),因此TCP报文报文段的首部最小长度是20个字节,最大长度是60个字节。

    首部固定部分个字段的意义:
    1、源端口和目的端口,各占两个字节。

    2、序号,占四个字节。
    TCP报文段的数据部分,每一个字节都有一个序号,TCP首部的序号就是记录数据部分第一个字节的序号是多少。

    3、确认号,占四个字节。
    指期望收到对方下一个报文段的第一个数据字节的序号。比如A收到B发来的TCP报文段,最后一个数据的序号是500,由于TCP协议规定,每收到一个报文段要向发送者返回一个确认信息(ACK),因此A收到B的TCP报文段后需要给B发送一个确认的TCP报文段,这个报文段的确认号就应该是501,意味着告诉B,你接下来要给我发序号为501开始的数据了。

    4、数据偏移,占四位
    指出TCP报文段首部的长度。由于TCP首部长度在20-60个字节之间,因此无法确认数据开始位置,因此需要数据偏移指出首部长度,由此得出数据部分开始位置在哪。

    5、保留,占六位,分别包含6个不同控制位
    (1)紧急URG
    放紧急URG被置1,说明此TCP报文段是紧急报文,需要尽快传送。
    (2)确认ACK
    当ACK=1,前面说的确认号的字段才会生效,TCP规定建立连接后,所有TCP报文段都必须把ACK置为1.
    (3)推送PSH
    较少使用,故不多说
    (4)复位RST
    当RST=1,说明TCP连接出现严重差错,必须释放连接,然后重新建立连接。
    (5)同步SYN
    在建立连接时的同步序号,当SYN=1,而ACK=0,说明这是一个连接请求的报文段,对方若是同意连接,则响应的报文段中SYN=1,ACK=1。因此SYN=1,说明这是一个连接请求或者连接接受的报文段。
    (6)终止FIN
    用来释放一个连接,当FIN=1,表面此报文段的发送方发送的数据已经发送完毕,并要求释放传输连接。

    6、窗口,占两个字节
    TCP报文段的窗口值告诉接收方,我这边最多一次能接受多少数据,所以你那边发送窗口的大小要设置好,别一次发太多我收不下。

    7、检验和,占两个字节
    检验和是检验TCP报文段的首部和数据部分在传输过程中是否有变化,如果接收方接收到的报文段的检验和有误,接收方就会丢弃此报文段。

    8、紧急指针
    只有URG=1时才有效,他指出本报文段中紧急数据的字节数,而紧急数据之后的数据那就是普通数据了。

    9、选项
    长度可变,为4n个字节(n为整数且最大为10)

    4、struct和class的区别

    默认的继承访问权限

    struct是public的,class是private的。

    5、C++继承:公有,私有,保护(转)

    6、面向对象的三大特征:

    1、封装:封装是将过程和数据包围起来,数据只能通过定义的接口访问。面向对象计算从一个基本概念开始,即现实世界可以表示为一系列完全自治的、封装的对象,这些对象通过受保护的接口访问其他对象。

    2、继承:继承是一种层次模型,它连接类,允许并鼓励类的重用,提供了一种明确表达共性的方法。对象的新类可以从现有类派生,这个过程称为类继承。新类继承原类的属性。新类被称为原类的派生类(子类),原类被称为新类的基类(父类)。

    3、多态:多态允许不同类的对象响应相同的消息。例如,同样的加法,两次相加和两个整数相加,一定是完全不同的。多态语言具有灵活性、抽象性、行为共享性和代码共享性等优点,较好地解决了应用程序功能的同名问题。

     

    7、全局变量、局部变量

      全局变量存放在静态存储区,初始值为0.全局变量默认作用域为整个工程。全局静态变量由static限制其作用范围为本文件。

      局部变量存放在栈区,初始值未知。局部静态变量初始值为0,存放在静态存储区。

    8、const

      定义const常量,const的函数不能对其数据成员进行修改操作。 const的对象,不能引用非const的成员函数。

      与define的区别:

        const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查,而对后者只进行字符替换,没有类型安全检查,并且在字符替换时可能会产生意料不到的错误;

        Const在堆栈分配了空间,而#define只是把具体数值直接传递到目标变量罢了。或者说,const的常量是一个Run-Time的概念,他在程序中确确实实的存在可以被调用、传递。而#define常量则是一个Compile-Time概念,它的生命周期止于编译期:在实际程序中他只是一个常数、一个命令中的参数,没有实际的存在。

        c++编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高

    9、排序

      

     10、二分查找

       时间复杂度logn。

      二叉查找树查找效率logn。最坏情况为退化成链表O(n),最好情况为均匀排布

    11、UDP丢包怎么办

    收包率低/丢包率高的原因分析

    • (1) 缓存太小,不能及时接收数据。

      连续多个UDP包超过了UDP接收缓冲区大小 ,比如:

      1. 如:UDP包过大
      2. 如:UDP发包速率过快,突发大数据流量超过了缓冲区上限
    • (2)recvfrom()接收到数据之后处理速度太慢

      如果数据接收和处理是连续进行的,那么可能由于数据处理过慢,两次recvfrom调用的时间间隔里发过来的包丢失

    对应的解决方法

    • UDP包过大

      解决方法:增加系统发送或接收缓冲区大小

      int nBuf=32*1024;//设置为32K  
      setsockopt(s,SOL_SOCKET,SO_RCVBUF,(const char*)&nBuf,sizeof(int));
      setsockopt(s,SOL_SOCKET,SO_SNDBUF,(const char*)&nBuf,sizeof(int));
      
      • 1
      • 2
      • 3
    • 发包速率过快

      解决方法:增加应答机制,处理完一个包后,在继续发包

    • recvfrom()接收到数据之后处理速度太慢

      服务器程序启动之出,开辟两个线程,一个线程专门用于接收数据包,并存放在应用层的缓存区;另外一个线程用于专门处理和响应数据包请求,避免因为处理数据造成数据丢包。其本质上还是增大了缓冲区大小,只是将系统缓冲区转移到了自己的缓冲区。

    • 最复杂的方式

      在应用层实现丢包重发机制和超时机制,确保数据包不丢失。

    12、指针常见错误

      引用未初始化的指针变量;往一个存放NULL地址的指针变量里面写入数据

    13、c++程序编译的过程

      linux 目标文件(*.o) bss,data,text,rodata,堆,栈 以及程序加载运行理解(转)

    14、gdb断点种类:

      行断点,条件断点,函数断点,数据断点(变量发生变化)

      https://blog.csdn.net/arv002/article/details/110133971

    15、linux命令行叫shell,linux下的shell是bash

    16、一个变量的值由IO决定,用volatile来定义

    17、线程池在linux下调用的是pthread,p代表POSIX

    18、哈希表底层实现

      C++ STL 标准库中,不仅是 unordered_map 容器,所有无序容器的底层实现都采用的是哈希表存储结构。更准确地说,是用“链地址法”(又称“开链法”)解决数据存储位置发生冲突的哈希表。

      当使用无序容器存储键值对时,会先申请一整块连续的存储空间,但此空间并不用来直接存储键值对,而是存储各个链表的头指针,各键值对真正的存储位置是各个链表的节点。STL 标准库通常选用 vector 容器存储各个链表的头指针。

      负载因子(load factor)。该属性同样适用于无序容器,用于衡量容器存储键值对的空/满程序,即负载因子越大,意味着容器越满,即各链表中挂载着越多的键值对,这无疑会降低容器查找目标键值对的效率;反之,负载因子越小,容器肯定越空,但并不一定各个链表中挂载的键值对就越少。

      负载因子 = 容器存储的总键值对 / 桶数。默认情况下,无序容器的最大负载因子为 1.0。如果操作无序容器过程中,使得最大复杂因子超过了默认值,则容器会自动增加桶数,并重新进行哈希,以此来减小负载因子的值。需要注意的是,此过程会导致容器迭代器失效,但指向单个键值对的引用或者指针仍然有效。

    19、手写malloc free

      https://blog.csdn.net/sgbfblog/article/details/7976634

    20、c++ 11、14、17

      c++11 新特性 (转)

    21、普通成员函数和构造函数不存在虚函数表中,只有虚函数存放在虚函数表中

    22、构造函数中可以使用虚函数,但是达不到效果。析构函数中同样。

    • a) 如果有继承,构造函数会先调用父类构造函数,而如果构造函数中有虚函数,此时子类还没有构造,所以此时的对象还是父类的,不会触发多态。更容易记的是基类构造期间,virtual函数不是virtual函数。
    • b) 析构函数也是一样,子类先进行析构,这时,如果有virtual函数的话,子类的内容已经被析构了,C++会视其父类,执行父类的virtual函数。
    • c) 总之,在构造和析构函数中,不要用虚函数。如果必须用,那么分离出一个Init函数和一个close函数,实现相关功能即可。

    23、在C++中,只有被声明为const的成员函数才能被一个const类对象调用

    24、this指针
    每个对象拥有一个this指针,通过this指针来访问自己的地址。

    this指针并不是对象的一部分,this指针所占的内存大小是不会反应在sizeof操作符上的。

    this指针只能在成员函数中使用,全局函数、静态函数都不能使用this指针

    在普通成员函数中,this是一个指向非const对象的const指针(如类类型为Student,那么this就是Student *const类型的指针);
    在const成员函数中,this指针是一个指向const对象的const指针(如类类型为Student,那么this就是const Student * const类型的指针)

    this指针在成员函数开始执行前构造,在成员函数执行结束后销毁。

    25、unique_ptr

    可以通过std::move转移内存对象的所有权。如果调用reset,会释放掉当前内存对象。

    可以通过release返回裸指针,并释放所有权。然后把裸指针赋值给另一个unique_ptr

    内部只有内存对象的指针

    26、shared_ptr

    内部保存了内存对象的指针,以及引用计数对象的指针。

    引用对象里边保存了shared_ptr的引用计数,也保存了weak_ptr的引用计数

    引用计数的修改是原子的,是线程安全的。但是对于shared_ptr的操作不是线程安全的。

    weak_ptr对象与shared_ptr对象共享一个计数器对象,计数器对象在构造第一个shared_ptr对象时生成;

    weak_ptr利用expired函数检查资源是否有效,若返回true(即引用计数为0),表示资源已经释放,否则,至少有一个shared_ptr对象持有资源。

    weak_ptr不能直接访问资源,而必须调用lock函数检查资源是否被释放,并返回一个shared_ptr对象。若资源已被释放(即expired为true),返回的是一个空shared_ptr对象;否则,返回一个shared_tr,引用计数+1。

    同shared_ptr一样,weak_ptr的生命期结束时,会自动调用析构函数,析构函数中会调用计数器的_Decwref()函数,资源的弱引用计数-1,若弱引用计数为0,就会调用_Delete_this() 函数删除计数器对象。

    综上所述,智能指针weak_ptr的原理为:weak_ptr对象依赖shared_ptr/weak_ptr对象生成,并自动调用计数器递增弱引用计数。当某个weak_ptr生命期结束时,会自动调用析构函数,析构函数中会通过计数器递减弱引用计数,若弱引用计数为0,析构函数就会调用计数器的_Delete_this()函数删除计数器对象。

    不知道大家有没有注意到一个问题,为什么在计数器初始化的时候就要将弱引用计数设置为1呢?
    若弱引用计数初始化为0,在强引用计数不为0的情况下,weak_ptr对象生命期都结束时,此时弱引用计数为0,就会删除计数器,但这时share_ptr对象尚在使用、资源也未释放,就会出现内存错误。

    27、c++ 内存模型

    子类会保存基类的private成员变量,但是编译器不允许访问。可以通过取地址移位的方式访问到。

    基类的私有虚函数也会放在子类的虚函数表中,但是编译时因为权限控制无法访问。

    28、

    常量左值引用,可以接收:非常量左值、常量左值、非常量右值、常量右值。

    如:const int & c = d;//接受一个非常量左值
    const int & v = e;//接受一个常量左值
    const int & z = 3 + 4;//接受一个右值

    可以用于拷贝构造函数

    常量右值引用,可以接受:非常量右值、常量右值

    目前没有用处

  • 相关阅读:
    【uoj3】 NOI2014—魔法森林
    【bzoj2002】 Hnoi2010—Bounce 弹飞绵羊
    【hdu4010】 Query on The Trees
    【uoj129】 NOI2015—寿司晚宴
    【bzoj2877】 Noi2012—魔幻棋盘
    【bzoj2876】 Noi2012—骑行川藏
    【bzoj2875】 Noi2012—随机数生成器
    【codeforces 235E】 Number Challenge
    【bzoj2154】 Crash的数字表格
    【bzoj3529】 Sdoi2014—数表
  • 原文地址:https://www.cnblogs.com/zl1991/p/16309671.html
Copyright © 2020-2023  润新知