网络上收集的C++常见面试题

1. 进程与线程的关系,图解

进程简单理解就是我们平常使用的程序,进程拥有自己独立的内存空间地址,拥有一个以上的线程。

线程可以理解为轻量级的进程,是程序执行的最小单元。在某个进程启动后,会默认产生一个主线程,主线程可以创建多个子线程,因此线程是存在进程内的,位于一个进程内的线程可以共享部分资源,故线程间的切换比进程少得多。

多线程可以并行、并发执行(如互联网开发中高并发编程技术),可以共享数据和资源,线程间采用多种线程通信方式进行通信。

网络上收集的C++常见面试题

 

 

 相关链接:https://baijiahao.baidu.com/s?id=1630348661230501723&wfr=spider&for=pc

2. 线程的基本概念、线程的基本状态及状态之间的关系

基本概念:线程,即轻量级进程(LWP:Light Weight Process),是程序执行流的最小单元。一个线程是进程的一个顺序执行流。同类的多个线程共享一块内存空间和一组系统资源,线程本身有一个供程序执行时的堆栈。线程在切换时负荷小,因此,线程也被称为轻负荷进程。一个进程中可以包含多个线程。

在一个进程内部,要同时干多件事情,就需要同时运行多个子任务,我们把进程内的这些子任务叫做线程。

多线程就是为了同步完成多项任务(在单个程序中同时运行多个线程完成不同的任务和工作),不是为了提高运行效率,而是为了提高资源使用效率来提高系统的效率。

基本状态:就绪、阻塞和运行三种基本状态。

就绪状态,指线程具备运行的所有条件,逻辑上可以运行,在等待处理机;

运行状态,指线程占有处理机正在运行;

阻塞状态,指线程在等待一个事件(如信号量),逻辑上不可执行。

状态之间的关系参考:线程的基本概念、线程的基本状态及状态之间的关系

3. https://blog.csdn.net/morewindows/article/details/7392749

 

4. 多态性可以简单地概括为“一个接口,多种方法”,程序在运行时才决定调用的函数,它是面向对象编程领域的核心概念。多态(polymorphisn),字面意思多种形状。

    C++多态性是通过虚函数来实现的,虚函数允许子类重新定义成员函数,而子类重新定义父类的做法称为覆盖(override),或者称为重写。(这里我觉得要补充,重写的话可以有两种,直接重写成员函数和重写虚函数,只有重写了虚函数的才能算作是体现了C++多态性)而重载则是允许有多个同名的函数,而这些函数的参数列表不同,允许参数个数不同,参数类型不同,或者两者都不同。编译器会根据这些函数的不同列表,将同名的函数的名称做修饰,从而生成一些不同名称的预处理函数,来实现同名函数调用时的重载问题。但这并没有体现多态性

    那么多态的作用是什么呢,封装可以使得代码模块化,继承可以扩展已存在的代码,他们的目的都是为了代码重用。而多态的目的则是为了接口重用。也就是说,不论传递过来的究竟是那个类的对象,函数都能够通过同一个接口调用到适应各自对象的实现方法。

   之前学的Direct2D以及Direct3D都是通过接口调用来实现多种方法。

实现多态有两个条件:  一是虚函数重写

            二是对象调用虚函数时必须是指针或者引用

重写 :子类的函数覆盖父类的函数,子类重新定义父类的虚函数(针对虚函数而言)

           常用于继承关系中
特点:
(1)父类和子类函数名相同
(2)父类和子类的参数以及返回值值相同
(3)父类的函数中必须含有virtual关键字。 

重载:函数名相同,参数不同。与继承没有直接关联
特点:
(1)相同的作用域 (都在类中,或者类外)
(2)函数名相同
(3)参数不同
(4)virtual关键字可有可无
(5)返回值可以不同

隐藏:子类的函数屏蔽了父类的同名函数。只有是同名函数,没有virtual,不管参数列表是否形同。
           父类的函数都会被隐藏。

(1)不在同一个作用域(分别位于子类和父类)
(2)函数名相同
(3)返回值可以不同
(4)参数可以不同,此时,不论有无 virtual 关键字,基类的函数将被隐藏
     (与重载有区别)
(5)参数相同,但是基类函数没有 virtual关键字。此时,

     基类的函数被隐藏(注意与重写的区别)

 相关链接:从一个面试题来谈C++的多态性

                   C++ 多态详解及常见面试题

 5. TCP三次握手,四次挥手

     TCP/IP协议详解

   三次握手:(SYN:同步标志    ACK:确认标志    FIN:结束标志)

(1)第一次握手:建立连接时,客户端发送SYN包(SYN=i)到服务器,并进入到SYN-SEND状态,等待服务器确认

(2)第二次握手:服务器收到SYN包,必须确认客户的SYN(ack=i+1),同时自己也发送一个SYN包(SYN=k),即SYN+ACK包,此时服务器进入SYN-RECV状态

(3)第三次握手:客户端收到服务器的SYN+ACK包向服务器发送确认报ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手,客户端与服务器开始传送数据

   四次挥手:

(1)第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态

(2)第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。

(3)第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。

(4)第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,Server收到并确认后,Server进入CLOSED状态,完成四次挥手。

 6. GDI vs Direct2D

  • 硬件加速 (硬件越新,加速越快,Direct2D全部都是硬件加速,而GDI和GDI+基本不靠硬件加速)

          GDI将其资源(尤其是位图)保留在系统内存中窗口内容的存储在视频内存表面。GDI使用CPU将大部分渲染执行到光圈存储器,当GDI需要更新窗口内容,也就是需要更新      视频内存时,除非资源已经在光圈存储器段中或者可以直接表示该操作,否则必须通过(bus)总线来完成,最后将结果发送回窗口表面。

          而Direct2D就直接在显示适配器上的视频内存中维护其资源,然后将结果呈现在GPU上,然后发送到窗口表面。GDI也有部分api是在GPU上加速的,比如BitBlts,AlphaBlend,TransparentBlt和StretchBlt等。

  • Direct2D完全在用户模式下运行。这有助于防止由于内核中的代码缺陷而导致系统崩溃。但是,GDI在内核模式下的会话空间中具有大部分功能,而在用户模式下则具有API界面。
  • Direct2D的渲染调用都是到GPU的独立命令流。每个Direct2D工厂代表一个不同的Direct3D设备。GDI对系统上的所有应用程序使用一个命令流。GDI的方法可能导致GPU和CPU渲染上下文开销的累积。

 7. TCP协议-如何保证传输可靠性    

  • 校验和
  • 序列号
  • 确认应答
  • 超时重传
  • 连接管理
  • 流量控制
  • 拥塞控制

 8. 虚拟内存(为什么要有虚拟内存)(好文章)

     物理内存是有限的,多个进程要运行的时候,很显然内存不够分配。并且操作指令都是直接访问物理内存的,那么我这个进程就可以修改其他进程的数据,这是不合理的。

所以我们需要虚拟内存,进程运行时都会得到4G(32位)的虚拟内存,进程得到的这4G虚拟内存是一个连续的地址空间(这也只是进程认为),而实际上,它通常是被分隔成多个物理内存碎片,还有一部分存储在外部磁盘存储器上,在需要时进行数据交换。

    进程开始要访问一个地址,它可能会经历下面的过程:(如果面试官问的特别仔细)

  •      进程访问地址空间上的某一个地址,都需要把地址翻译为实际物理内存地址
  •      所有进程共享这整一块物理内存,每个进程只把自己目前需要的虚拟地址空间映射到物理内存上
  •      进程需要知道哪些地址空间上的数据在物理内存上,哪些不在(可能这部分存储在磁盘上),还有在物理内存上的哪里,这就需要通过页表来记录
  •      页表的每一个表项分两部分,第一部分记录此页是否在物理内存上,第二部分记录物理内存页的地址(如果在的话)
  •      当进程访问某个虚拟地址的时候,就会先去看页表,如果发现对应的数据不在物理内存上,就会发生缺页异常
  •      缺页异常的处理过程,操作系统立即阻塞该进程,并将硬盘里对应的页换入内存,然后使该进程就绪,如果内存已经满了,没有空地方了,那就找一个页覆盖,至于具体覆盖的哪个页,就需要看操作系统的页面置换算法是怎么设计的了。

9. 纯虚函数只能被继承,无法被实例化。纯虚函数用来定义没有意义的实现,用于抽象类中需要交给派生类具体实现的方法。

    因为虚函数都是采用的虚函数列表来进行的,如果是纯虚函数的话,该表指向一个不存在的函数,所以实例化被禁止。简单来说,就是 如果基类中含有纯虚函数,都不能实例化,在继承了该基类中的派生类中,如果不对该函数进行改写,也不能实例化。

小知识:纯虚函数还有利于检查基类的虚函数是否定义,比如有些时候我们使用虚函数光声明,但是没有定义的话,编译器就会报错。 使用了纯虚函数就不会报错了。

virtual void show() = 0; //纯虚函数

10. 虚函数列表的一些知识

    一个类存在虚函数,那么编译器就会为这个类生成一个虚表,在虚表里存放的是这个类所有虚函数的地址。当生成类对象的时候,编译器会自动的将类对象的前四个字节设置为虚表的地址,而这四个字节就可以看作是一个指向虚表的指针。虚表里依次存放的是虚函数的地址,每个虚函数的地址占4个字节。

11. 广度优先搜索和深度优先搜索

    这类概念经常在遍历文件或者遍历文件夹上用到。 

 网络上收集的C++常见面试题

12. 为什么需要内存对齐?

  • 某些处理器只能存取对齐的数据,存取非对齐的数据可能会引发异常;
  • 某些处理不能保证在存取非对齐数据的时候的操作是原子操作;
  • 相比于存取对齐的数据,存取非对齐数据需要额外花费更多的时钟周期;
  • 有些处理器虽然支持非对齐数据访问,但是会引发对齐陷阱;
  • 某些处理只支持简单数据指令非对齐存取,不支持复杂数据指令非对齐存取。

     总结起来就是两个大的原因:提升效率和避免出错。

13. C++中的static关键字

      静态全局变量有以下特点:

  • 该变量在全局数据区分配内存;
  • 未经初始化的静态全局变量会被程序自动初始化为0(自动变量的值是随机的,除非它被显式初始化);
  • 静态全局变量在声明它的整个文件都是可见的,而在文件之外是不可见的;  

      静态局部变量有以下特点:
    (1)该变量在全局数据区分配内存;
    (2)静态局部变量在程序执行到该对象的声明处时被首次初始化,即以后的函数调用不再进行初始化;
    (3)静态局部变量一般在声明处初始化,如果没有显式初始化,会被程序自动初始化为0;
    (4)它始终驻留在全局数据区,直到程序运行结束。但其作用域为局部作用域,当定义它的函数或语句块结束时,其作用域随之结束;

       关于静态成员函数,可以总结为以下几点:

  • 出现在类体外的函数定义不能指定关键字static;
  • 静态成员之间可以相互访问,包括静态成员函数访问静态数据成员和访问静态成员函数;
  • 非静态成员函数可以任意地访问静态成员函数和静态数据成员;
  • 静态成员函数不能访问非静态成员函数和非静态数据成员;
  • 由于没有this指针的额外开销,因此静态成员函数与类的全局函数相比速度上会有少许的增长;
  • 调用静态成员函数,可以用成员访问操作符(.)和(->)为一个类的对象或指向类对象的指针调用静态成员函数,也可以直接使用如下格式:

          <类名>::<静态成员函数名>(<参数表>)
           调用类的静态成员函数。

原文链接: https://www.cnblogs.com/strive-sun/p/14386681.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍;

也有高质量的技术群,里面有嵌入式、搜广推等BAT大佬

    网络上收集的C++常见面试题

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/404698

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年4月25日 下午4:41
下一篇 2023年4月25日 下午4:41

相关推荐