一、new/delete和malloc/free的区别
- new/delete是C++的关键字、操作符,malloc/free是C的函数,需引入<stdlib.h>。
- new会调用构造函数会初始化并返回相应的数据类型,malloc无构造函数可调且返回void*需要强转,且需传入申请的字节大小,而new会根据数据类型自动确定大小;
- new失败抛出bad_alloc异常,malloc失败返回NULL;
- delete会调用析构函数,free不会也无析构可调;
- new/delete效率低,底层封装的是malloc/free,但应用大大扩展了、方便了。
二、频繁new/delete造成内存碎片的问题怎么解决
使用内存池化技术。
底层重载malloc()、free()函数,使用方重载new、delete操作符;
池化的方式一般和要解决的问题的数据类型有关联,即每个空间单元的字节大小;当然也可以无关联,做更通用化的。
一篇挺好的代码链接:
https://blog.csdn.net/K346K346/article/details/49538975
三、dynamic_cast和static_cast的区别
- dynamic_cast进行安全性检查,如转换失败将返回nullptr或引用时抛出异常,static_cast转换失败时仍会返回指针,但使用有风险;
- dynamic_cast一般用在多态中,将父类指针/引用转换成子类;
- dynamic_cast的对象必须有虚函数,因为运行时类型检查需要运行时类型信息,而这个信息存储在类的虚函数表中,static_cast无此限制;
- dynamic_cast工作在运行时,static_cast工作在编译时。
四、C++的类型安全手段
类比C语言:
- new/delete对比malloc/free,不再需要强制类型转换;
- 模板函数对void*,不再需要强制类型转换;
- const对宏,宏只是文本展开,可能造成错误;const是变量定义,意义明确,无此隐患;
- 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的,但总算引导我解了症结。
- 关键是多个生产者多个消费者在阻塞,如果一个cv,那它notify之后尤其notify_one()之后,由于生产消费线程共用同一个cv,则无法控制被唤醒的是生产者还是消费者,若此时数据队列为空但恰好唤醒了又一个消费者,则被唤醒者空跑一轮,虽不造成错误但造成CPU浪费;生产者消费者分别用各自的cv,则能做到精准控制,在生产者线程中唤醒消费者,在消费者线程中唤醒生产者,不仅精准而且逻辑清晰。
- 共用一个mutex是必须的,因为所有生产者消费者操作的是同一个数据队列,对它做互斥。
- 而wait()的逻辑是先释放锁以释放锁资源供其它线程竞争,然后等待唤醒,然后上锁,操作都是原子的,保证了互斥操作的正确性。
- notify_one()和notify_all(),notify_one()是唤醒线程队列中的第一个;notify_all()唤醒所有休眠线程,它们竞争临界资源,胜者执行处理流程,而此时CPU消耗巨大因为竞争败者不再进入休眠而是持续竞争,即不再进入被唤醒队列,直到都获取过一次临界资源。
推荐这篇文章,有些东西想着想着突然就悟了。。。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)