第二十六篇,C++面经之问答(五)

一、new/delete和malloc/free的区别

  1. new/delete是C++的关键字、操作符,malloc/free是C的函数,需引入<stdlib.h>。
  2. new会调用构造函数会初始化并返回相应的数据类型,malloc无构造函数可调且返回void*需要强转,且需传入申请的字节大小,而new会根据数据类型自动确定大小;
  3. new失败抛出bad_alloc异常,malloc失败返回NULL;
  4. delete会调用析构函数,free不会也无析构可调;
  5. new/delete效率低,底层封装的是malloc/free,但应用大大扩展了、方便了。

二、频繁new/delete造成内存碎片的问题怎么解决

使用内存池化技术。
底层重载malloc()、free()函数,使用方重载new、delete操作符;
池化的方式一般和要解决的问题的数据类型有关联,即每个空间单元的字节大小;当然也可以无关联,做更通用化的。
一篇挺好的代码链接:
https://blog.csdn.net/K346K346/article/details/49538975

三、dynamic_cast和static_cast的区别

  1. dynamic_cast进行安全性检查,如转换失败将返回nullptr或引用时抛出异常,static_cast转换失败时仍会返回指针,但使用有风险;
  2. dynamic_cast一般用在多态中,将父类指针/引用转换成子类;
  3. dynamic_cast的对象必须有虚函数,因为运行时类型检查需要运行时类型信息,而这个信息存储在类的虚函数表中,static_cast无此限制;
  4. dynamic_cast工作在运行时,static_cast工作在编译时。

四、C++的类型安全手段

类比C语言:

  1. new/delete对比malloc/free,不再需要强制类型转换;
  2. 模板函数对void*,不再需要强制类型转换;
  3. const对宏,宏只是文本展开,可能造成错误;const是变量定义,意义明确,无此隐患;
  4. dynamic_cast,强化了安全检查机制。

五、哪里用大端哪里用小端

大端:数据高字节存放于内存低地址,Motorola处理器,芯片、网络数据传输;
小端:数据高字节存放于内存高地址,Intel x86处理器,编程;
至于大端更符合人的阅读习惯这个事,写一个多字节的16进制数比较一下大小端就看出来了,注意内存地址要从上到下竖着排,且内存低地址排在最上边:
https://blog.csdn.net/cuishumao/article/details/11777467

六、讲一下RAII

Resource acquisition is initialization.
资源获取即初始化,常见于智能指针、锁。
说到这个话题,也是我开悟C++ OOP编程的起点,突然就悟了,以前是多么的C with class,有之前写的文章为证:
https://blog.csdn.net/qq_42466012/article/details/124718492

七、git rebase的作用

一方面是合并几次连续的commit,精简commit历史;
另一方面是相对于git merge,merge会使得commit历史轨迹虽完整但不清晰,而rebase会呈现出单条开发线一路向前的表现,逻辑清晰,易于理解开发历史。

八、字节对齐的作用

字节对齐是CPU用空间换时间的做法,以提高内存读写的效率;因为CPU是按块读取的,如2字节、4字节、8字节,如果不对齐,会发生数据丢弃、拼接,影响效率。
在结构体或类中,按照变量声明的顺序,每个变量的起始地址相对于该结构体或类的0地址的偏移量,均是其字节大小的整数倍,因此如果它前边所有变量字节数的和不足此整数倍,需要补齐;所有变量的地址计算完成之后可能还需一次补齐,即找出所有变量中最大的字节数,总字节数需按此最大字节数的整数倍补齐。
https://blog.csdn.net/qq_41909314/article/details/89811606

九、为什么引入nullptr替代NULL

关键还是在于C++新增了函数重载机制,以及把NULL的宏定义直接改成了0(在C语言中对NULL的宏定义是((void*)0),即强转为了空指针),带来了困扰。
在C++加入重载并把NULL重新定义成0以后,问题就来了,假如重载两个函数:
void func(void* p);
void func(int a);
如此调用func(NULL),我们本意是让它执行第一个函数,但实际执行了第二个,也就是说直接把NULL当0传进去了,这就引发了二义性;这里还和编译器具体实现相关,有些编译器中会直接报ambiguous的error。
而对nullptr的宏定义是明确的空指针,不会引发歧义。

十、常用数据结构

数组、链表、树、图、哈希表、队列、堆、栈,共计8种。
推荐链接:八种数据结构大全

十一、生产消费模式中的cv

这个是我自创的一个问题,两个cv、一个mutex、notify_one()和notify_all()。
在学习生产消费模式的时候,发现用一个cv和用两个cv的困惑,并深入理解了wait()、notify_one()和notify_all()的效果。
无论是分析别人的代码还是修改代码,都没有看出用一个cv和用两个cv的区别,好像一个就够了,于是提了个问题,解答虽然是ChatGPT的,但总算引导我解了症结。

  1. 关键是多个生产者多个消费者在阻塞,如果一个cv,那它notify之后尤其notify_one()之后,由于生产消费线程共用同一个cv,则无法控制被唤醒的是生产者还是消费者,若此时数据队列为空但恰好唤醒了又一个消费者,则被唤醒者空跑一轮,虽不造成错误但造成CPU浪费;生产者消费者分别用各自的cv,则能做到精准控制,在生产者线程中唤醒消费者,在消费者线程中唤醒生产者,不仅精准而且逻辑清晰。
  2. 共用一个mutex是必须的,因为所有生产者消费者操作的是同一个数据队列,对它做互斥。
  3. 而wait()的逻辑是先释放锁以释放锁资源供其它线程竞争,然后等待唤醒,然后上锁,操作都是原子的,保证了互斥操作的正确性。
  4. notify_one()和notify_all(),notify_one()是唤醒线程队列中的第一个;notify_all()唤醒所有休眠线程,它们竞争临界资源,胜者执行处理流程,而此时CPU消耗巨大因为竞争败者不再进入休眠而是持续竞争,即不再进入被唤醒队列,直到都获取过一次临界资源。
    推荐这篇文章,有些东西想着想着突然就悟了。。。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值