当前位置: 首页 > news >正文

C++面试3

一、常用设计模式

https://blog.csdn.net/m0_71530237/article/details/141140118?spm=1001.2014.3001.5501

二、死锁以及解决方式?

死锁:一种常见的并发问题,发生在多个进程或线程因为竞争资源而陷入相互等待的状态,导致这些进程或线程无法继续执行,要理解死锁,通常会涉及以下四个必要条件。

1、互斥条件:资源是不可共享的,即一个资源在同一时间只能被一个进程占用。

2、占有且等待:一个进程已经持有一个资源,同时又在请求其他资源,而此时这些资源正被其他进程占有。

3、不可剥夺:资源不能被强制剥夺,进程只能资源释放资源。

4、循环等待:存在一个进程集,这些进程之间形成一个循环的资源等待链。

死锁的解决方式

1、死锁预防:确保上诉四个条件中的至少一个不能成立,即可阻止死锁发生

2、死锁避免:使用某些算法来动态的判断资源分配状态,避免进入死锁状态,常见的方法是银行家算法

        银行家算法在进程请求资源时,判断系统是否会进入不安全状态。如果不会,则分配资源;否则拒绝该请求,防止死锁发生。

3、死锁检测与恢复:允许系统进入状态,然后检测和处理它。

4、资源有序分配:通过给资源定义一个全局顺序,进程只能按照顺序请求资源,避免循环等待的产生。

实践中的死锁解决方式

超时机制:当一个线程请求资源超过一定时间时,认为它可能处于死锁状态,释放已持有资源并重新开始请求。

锁分层:为不同资源定义不同的优先级和层级,线程只能按照优先级从低到高的顺序去请求资源,避免循环等待。

三、堆栈空间?堆和栈的区别?堆栈溢出问题?

1、栈

栈是操作系统为每个线程分配的一块连续内存空间,用于存储局部变量、函数参数、返回地址等。栈是一种后进先出(LIFO)的数据结构,具有自动管理内存的特点。

存储内容:局部变量、函数参数、返回地址、函数调用过程中的临时数据。

分配方式:栈的内存分配由操作系统自动完成,通常由编译器在函数调用时生成指令管理。

内存大小:站的大小是固定的,一般由操作系统在程序启动时确定。

优点:速度快,自动分配内存和释放,不容易产生内存泄漏。

缺点:大小优先,不能存储大数据;递归调用或过深的函数调用栈容易造成栈溢出。

栈的调用流程:

1、参数返回地址等数据会压入栈中。

2、在函数内部的局部变量也会存储在栈上。

3、函数返回时,栈顶数据(函数返回值和局部变量等)会被弹出,释放栈空间

2、堆

堆是操作系统提供的一块较大的动态空间,专门用于存储动态分配的数据,程序运行时可以通过动态内存分配函数(如malloc()、new)来手动管理堆内存。

存储内容:动态分配的内存快(如malloc()、new分配的内存)

分配的方式:由程序员手动分配和释放,灵活且可动态扩展。

内存大小:堆的大小不固定,通常由操作系统的物理内存决定,能够存储较大的数据。

优点:可以动态分配大内存快,灵活方便,数据生命周期可以跨越多个函数调用。

缺点:分配和释放的速度较慢,如果程序员忘记释放内存,容易造成内存泄漏;过多的动态分配也可能导致堆碎片问题,影响内存效率。

堆的调用流程:

1、使用malloc()或new函数分配内存。

2、返回一个指向这块内存的指针。

3、当不在需要时通过free()或delete释放内存,否则会发生内存泄漏。

栈与堆的比较:

特性
管理方式由操作系统自动管理需要程序员手动管理
存储内容局部变量、函数参数、返回地址等动态分配的对象和数组
分配速度快(内存连续,自动分配和释放)慢(需要寻找空闲内存、手动管理)
内存大小较小(通常几MB)较大(取决于系统可用物理内存)
生命周期随函数调用结束自动释放需程序员手动释放、否则会内存泄漏
使用场景局部变量、函数调用动态分配大数据或对象
常见问题栈溢出(如递归过深)内存泄漏、堆碎片

四、内存泄漏?如何避免?

内存泄漏是指程序在动态分配内存后,未能正确释放已不再使用的内存,导致该内存无法被再次分配和使用。随着程序的运行,未释放的内存逐渐增多,最终可能导致系统内存耗尽,引发系统崩溃或系统性能下降和。避免内存泄漏是编写高效、健壮系统的重要环节。

如何避免:

1、手动释放内存

2、避免重复分配内存

3、使用智能指针

五、智能指针的本质是什么?

智能指针本质上是一个类,用来存储指向动态分配对象的指针,负责动态释放动态分配的对象,防止内存泄漏。动态分配资源,交给一个类对象去管理,当类对象声明周期结束时,自动调用析构函数释放资源。借助RAII和类的特点,防止内存泄漏的问题。

六、野指针?

①、定义一个指针变量,没有给他指向有效的空间或置为空时,系统会默认初始化一个随机地址,变成野指针。②、释放一个指针变量指向的空间时,没有置为空,也会变成野指针。

避免野指针:

1、定义指针变量时,要么设为空,要么指向合法的内存

2、向指针变量指向的空间赋值时,一定要开辟内存空间,

3、开辟空间后需要检查是否分配成功

4、分配成功后要进行初始化

5、注意使用时不要越界访问,否则会产生内存泄漏

6、结束时要释放内存空间

7、释放完之后要将指针变量置为空

检测工具:

gcc自带的ASAN或valgrind工具

七、指针和引用的区别?

指针是一个变量,引用是变量的别名;指针可用为空,引用定义时必须初始化;指针在初始化之后可以改变指向,引用在初始化之后不可以改变指向;指针可以有多级,引用最多两级;指针需要动态分配内存空间,引用不需要动态分配;指针引用占8个字节;指针间接访问,效率低、,引用直接访问,效率高;做函数形参时,指针需要考虑传值还是传址,引用不需要考虑,它既可以访问,也可以修改,函数返回值是引用可以做左值;

八、C++三大特性?

封装:

封装是面向对象的核心思想,因为封装解决了代码独立性的问题,从而提高了代码的复用性,为复用性和扩展性提供了基础,封装将成员属性和成员方法绑定在一起,通过选择访问权限实现对外提供接口,对内开放数据从而达到对数据的保护,类内有构造函数和析构函数,不用去考虑释放,提供了一种更安全的机制,提高开发效率。

类封装的本质是在于将数据和行为绑定在一起,然后通过对象来完成操作,封装对私有函数添加get/set方法,全局变量会破坏数据的封装,可以采用静态成员来解决。

继承:

派生类可以使用基类的属性和方法,实现不同类之间的代码复用性,继承使得我们可以基于已有的类创建新的类,无需重新编写实现代码,派生类可以添加新的属性和方法,或重写(override)基类的方法来实现新的功能(通过重写虚函数virtual来实现)

基类提供属性和方法,派生类继承自基类并扩展或修改其行为

单继承 :一个派生类只能继承自一个基类

多继承  :一个派生类可以同时继承自多个基类

多态:

①多态是使用同一接口,传递不同实例对象,执行不同操作(C语言中的共用体就是一种多态)

②当功能不断变化,考虑用多态实现代码的扩展性

③静多态(编译时绑定)的函数重载和模板和动多态(运行时绑定)的虚函数(少用)

④基类中要有虚函数;派生类要重写基类中的虚函数(override);通过基类的指针或引用绑定派生类的对象

⑤优点,多态解决代码扩展性的问题,增加程序的灵活性,减轻系统升级、维护、调试的工作量和复杂度

缺点:多态影响效率,造成空间浪费(引入虚函数表指针)

九、重载、重写和隐藏?

重写:派生类中重新定义基类的虚函数,具有相同的函数签名,实现多态

重载:同一作用域内定义多个同名函数,但是他们的参数不同

隐藏:子类中定义了与父类具有相同名字但不同参数列表的函数,或子类中定义了与父类中非虚函数同名的函数,此时,父类中的同名函数在子类中被隐藏。子类的函数会覆盖父类的同名函数

十、this指针?

this指针是隐藏在对象成员函数内的一种指针,当一个对象被创建后,他的没有个成员函数都含有一个系统自动生成的隐含指针this,用于保存这个对象的地址,也就是说我们没有写上this指针,编译器在编译的时候也会加上。this永远指向当前对象,this指针并不是对象的一部分,不会影响sizeof(对象的大小)的结果。

this指针是C++实现封装的一种机制,他将对象和该对象调用的成员函数连接在一起,在外部看来,每一个对象都拥有自己的成员函数。一般情况下,不写this,而是让系统进行默认设置。

十一、局部变量和全局变量?

特性局部变量全局变量
作用范围尽在定义的函数或代码块内可见整个程序范围内可见
生命周期进入作用域时创建,离开作用域时销毁程序启动时创建,程序结束时销毁
存储位置栈内存全局数据区或静态存储区
初始化无默认初始值,使用前需手动初始化自动初始化为0或null
访问方式只能子啊定义他的函数或块内访问可被程序的任何地方访问(除非有限定)
冲突问题不会和其他函数中的局部变量冲突可能会与其他模块中的变量或函数名冲突

十二、进程间通信和线程间通信?

进程间通信:

传统通信:管道、信号

ipc通信:消息队列、共享内存、信号量

网络通信:socket(套接字)

线程间通信:

全局变量、条件变量、信号量、消息队列、事件、管道和文件

十三、多线程访问同步方式?

互斥锁:用于保护共享资源,确保同一时间只有一个线程能够访问该资源

读写锁:允许多个线程同时读取共享资源,但写操作需要独占资源

条件变量:用于在线程之间传递信号,允许一个线程等待条件满足后继续执行

信号量:一种计数器,用于控制对资源的访问

十四、线程池?

线程池如何实现?

线程池通过安全队列存储执行的任务,并使用多个线程从队列中取出任务执行。线程池通过 submit 方法提交任务,任务被封装成 std::packaged_task 并加入队列中。当任务到来时,通过条件变量唤醒等待的线程,线程执行任务。线程池在析构时会确保所有任务执行完毕,关闭线程并释放资源。

如何确保线程池中的任务能够按照顺序执行?

线程池不保证任务按照提交顺序执行,因为多个线程可能并发执行任务,如果需要严格的顺序控制,可以通过使用额外的同步机制(队列或条件变量)来管理任务的执行顺序

线程池中为什么不直接存储线程对象,而是存储指向线程的指针?

存储指向线程的指针可以避免对象的拷贝和移动操作,保证每个线程对象在创建后都能被正确管理和释放。如果存储线程对象本身,可能会引发不必要的拷贝构造和移动构造操作

线程池的作用是什么?他是如何提高多线程应用的性能?

线程池的主要作用是通过复用以创建的线程来执行任务,避免频繁创建和销毁线程开销,从而提高性能。线程池提前创建·一定数量的线程,当有任务时,线程池中的线程可以直接处理任务,避免了创建新线程的延迟

十五、connect连接方式?

网络编程中用于建立客户端和服务器端之间的连接的系统调用。它主要用于基于 TCP/IP 协议的套接字编程中,用于客户端连接到服务器

十六、TCP与UDP的区别?

十七、简单的数据结构?

https://blog.csdn.net/m0_71530237/article/details/141996354?spm=1001.2014.3001.5501

十八、槽函数的使用以及注意事项?

槽函数本质上是一个普通的成员函数,它可以被信号触发,槽函数的定义与普通函数类似,但是要放在slots:访问控制符之下,或通过Q_OBJECT宏使其成为槽函数。使用connect()函数将信号和槽连接起来。第一个参数:发送信号的对象;第二个参数:信号的名称;第三个参数:接收信号的对象;第四个参数:槽函数的名称,从QT5开始,可以使用lambda表示式,不需要定义一个单独的槽函数

注意事项:信号和槽默认线程安全;使用Q_OBJECT宏

十九、C++新特性

自动类型推导 (auto): 自动推导变量的类型,减少显式类型声明的繁琐

范围 for 循环: 用于遍历容器或数组,简化代码。

nullptrnullptr 取代 NULL,表示空指针,类型更明确。

lambda 表达式: 允许定义匿名函数,常用于 STL 算法或事件处理。

智能指针 (std::shared_ptrstd::unique_ptr): 引入了自动管理内存的智能指针,减少手动管理内存泄漏的风险

右值引用 (&&) 和移动语义: 移动语义允许资源的转移,而不是复制,提高了性能,特别是在容器操作中

线程支持 (<thread> 头文件): 引入了标准线程库,简化了多线程编程


http://www.mrgr.cn/news/29729.html

相关文章:

  • 根据 IP 地址进行 VPN 分流(详细,亲测,通用)
  • 深度学习 之 常见损失函数简介:名称、作用及用法
  • vue 2表格滚动加载
  • 【VUE】快速上手
  • 心觉:不能成事的根本原因
  • 龙海家园的免费停车点探寻
  • 【C语言】带你手把手拿捏指针(3)(含转移表)
  • 中、美、德、日制造业理念差异
  • Linux常见查看文件命令
  • 数学学习记录
  • vue获取最近7天时间;获取任意时间段时间
  • Flutter 优化技巧分享
  • 基于python+django+vue的医院预约挂号系统
  • Mobile net V系列详解 理论+实战(1)
  • python中网络爬虫框架
  • 拦截器Interceptor
  • 一些学习three的小记录
  • A股上市公司企业创新能力、质量、效率-原始数据+dofile+结果(2006-2023年)
  • 策略模式结合反射在电商支付系统中的深入应用与实战解析
  • SIP Servlets学习