复习文档1

本文详细探讨了C++中的数组和指针的区别,包括它们的赋值、存储方式、sizeof、初始化和传参方式的不同。此外,文章还讲解了指针和引用的区别。进一步,文章深入介绍了C++的STL内存优化,如二级配置器结构和内存池,以及STL的allocator。最后,文章提到了虚函数的概念和多态性,以及C++中的动态内存分配和内存泄漏问题,包括new、malloc、free和智能指针的使用及其可能导致的问题。
摘要由CSDN通过智能技术生成

数组和指针的区别
数组:数组是用于存储多个相同类型的集合。
指针:指向相当于一个变量,但是它和普通变量不一样,它存放的是其他变量的内存中的地址。
区别
1、赋值:同类型指针变量可以相互赋值,数组不行,只能一个一个元素的赋值或拷贝。
2、存储方式
数组:数组在内存中是连续存放的,开辟一块连续的内存空间。数组是根据数组的下标进行访问的,多维数组在内存中是按照一维数组存储的,只是在逻辑上是多维的。
指针:指针很灵活,它可以指向任意类型的数据。指针的类型说明了它所指向地址空间的内存。
4、sizeof:数组所占存储空间的内存。在32位平台下,无论指针的类型是什么,sizeof(指针名)都是4,64位平台下,sizeof(指针名)都是8。
5、初始化方式不同。
6、传参方式
数组传参时,会退化为指针,C语言将数组的传参进行退化。将整个数组拷贝一份传入函数时,将数组名看做常量指针,传数组首元素的地址。
一级指针传参可以接受的参数类型:(1)可以是一个整型指针(2)可以是整型变量地址(3)可以是一维整型数组名;当函数参数部分是二级指针,可以接受的参数类型:(1)二级指针变量(2)一级指针变量地址(3)一维指针数组的数组名

指针和引用的区别
1、指针是一个变量,只不过这个变量存储的是一个地址,而引用跟原来的变量实质上是一个东西,只不过是原变量的一个别名。
2、引用不可以为空,当被创建时,必须初始化,而指针可以为空。
3、指针可以有多级,但引用只能有一级。
4、指针的值初始化后可以改变,而引用进行初始化后就不会再改变了。
5、sizeof引用得到的是指向变量的大小,而指针得到的是本身的大小。
6、如果返回动态分配的对象或内存,必须使用指针,否则可能引起内存泄漏。

C++ STL的内存优化
1)二级配置器结构
STL内存管理使用二级内存配置器
1、第一级配置器
第一级配置器以malloc(),free(),realloc()等C函数执行实际的内存配置、释放、重新配置等操作,并且能在内存需求不被满足的时候,调用一个指定的函数。
一级空间配置器分配的是大于128字节的空间
如果分配不成功,调用句柄释放一部分内存
如果还不能成功,抛出异常
2、第二级配置器
在STL的第二级配置器中多了一些机制,避免太多小区块造成的内存碎片,小额区块带来的不仅是内存碎片,配置时还有额外的负担。区块越小,额外负担所占比例就越大。
3、分配原则
如果要分配的区块大于128字节,则移交给第一级配置器处理。
如果要分派的区块小于128字节,则以内存池管理(memory pool),又称之次配置(suballocation):每次配置一大块内存,并维护对应的16个空闲链表(free-list)。下次若有相同大小的内存需求,则直接从free-list中取。如果有小额区块被释放,则由配置器回收到free-list中。
当用户申请的空间小于128字节时,将字节数扩展到8的倍数,然后在自由链表中查找对应大小的子链表。如果在自由链表查找不到或者块数不够,则向内存池进行申请,一般一次20块。
如果内存池空间足够,则取出内存,如果不够分配20块,则分配最多的块数给自由链表,并且更新每次申请的块数。
如果一块都无法提供,则把剩余的内存挂到自由链表,然后向系统heap申请空间,如果申请失败,则看看自由链表还有没有可用的块,如果也没有,则最后调用一级空间配置器。
2)二级内存池
二级内存池采用了16个空闲链表,这里的16个空闲链表分别管理大小为8、16、24、…120、128的数据块。这里的空闲链表结点的设计十分巧妙,这里用了一个联合体既可以表示下一个空闲数据块(存在于空闲链表中)的地址,也可以表示已经被用户使用的数据块的地址(不存在空闲链表中)。
1、空间配置函数allocate
首先要检查申请空间的大小,如果大于128字节就调用第一级配置器,小于128字节就检查对应的空闲链表,如果空闲链表中有可用的数据块,则直接拿来用(拿取空闲链表中的第一个可用数据块,然后把该空闲链表的地址设置为该数据块指向的下一个地址),如果没有可用的数据块,则调用refill重新填充空间。
2、空间释放函数deallocate
首先要检查释放数据块的大小,如果大于128字节就调用第一级空间配置器,小于128字节则根据数据块的大小来判断回收后的空间会被插入到哪个空闲链表。
3、重新填充空闲链表refill
在用allocate配置空间时,如果空闲链表中没有可用数据块,就会调用refill来填充空间,新的空间取自内存池。缺省取20个数据块,如果内存池空间不足,那么能取多少个节点就取多少个。
从内存池取空间给空闲链表用是chunk_alloc的工作,首先根据end_free-start_free来判断内存池中的剩余空间是否足以调出nobjs个大小为size的数据块出去,如果内存连一个数据块的空间都无法供应,需要用malloc取堆中申请内存。
假如山穷水尽,整个系统的堆空间都不够用了,malloc失败,那么chunk_alloc会从空闲链表中找是否有大的数据块,然后将该数据块的空间分给内存池(这个数据块会从链表中去除)。
3)总结
1、使用allocate向内存池请求size大小的内存空间,如果需要请求的内存大小大于128字节,直接使用malloc。
2、如果需要的内存大小小于128字节,allocate根据size找到最合适的自由链表。
A.如果链表不为空,返回第一个node,链表头改为第二个node。
B.如果链表为空,使用blockAlloc请求分配node。
C.如果内存池中有大于一个node的空间,分配尽可能多的node(但是最多为20个),将一个node返回,其他的node添加到链表中。
D.如果内存池只有一个node的空间,直接返回给用户。
E.如果连一个node都没有,再次向操作系统请求分配内存。
1、分配成功,在此进B过程。
2、分配失败,循环各个自由链表,寻找空间。
1)、找到空间,在此进行过程B。
2)找不到空间,抛出异常。
3、用户调用deallocate释放内存空间,如果要求释放的内存空间大于128字节,直接调用free。
4、否则按照其大小找到合适的自由链表,并将其插入。

STL的allocator
STL的分配器用于封装STL容器在内存管理上的底层细节。C++中,其内存配置和释放如下:
new运算分两个阶段:1)调用::operator new配置内存;2)调用对象构造函数构造对象内容
delete运算分为两个阶段:1)调用对象析构函数;2)调用::operator delete释放内存
为了精密分工,STL allocator将两个阶段分开来:内存配置有alloc::allcate()负责,内存释放由alloc::deallocate()负责;对象构造由::contruct()负责,对象析构由::destroy()负责。
同时为了提升内存管理的效率,减少申请小内存造成的内存碎片问题,SGI STL采用了两级配置器,当分配的空间大于128字节时,采用第一空间配置器;当分配空间小于128字节时,采用第二空间配置器。第一级空间配置器直接使用malloc()、realloc()、free()函数进行内存空间的分配和释放,而第二级空间配置器采用了内存池技术,通过空闲链表来管理内存。

Select,epoll的区别,原理,性能,限制
1)IO多路复用
IO复用模型在阻塞IO模型上多了一个select函数,select函数有一个参数是文件描述符集合,意思就是对这些的文件描述符进行循环监听,当某个文件描述符就绪的时候,就对这个文件描述符进行处理。
这种IO模型是属于阻塞的IO。但是由于它可以对多个文件描述符进行阻塞监听,所以它的效率比阻塞IO模型高效。
IO多路复用就是我们说的select,poll,epoll。
Select/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。它的基本原理就是select,poll,epoll,这个function会不断的轮询所负责的socket,当某个socket有数据到达了,就通知用户进程。
当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。
所以IO多路复用的特点是通过一种机制一个进程能同时等待多个文件描述符,而这些文件描述符其中的任意一个进入读就绪状态,select()函数就可以返回。
IO多路复用和阻塞IO其实并没有太大的不同,事实上,还更差一点。因为这里需要使用两个system call(select和recvfrom),而blocking IO只调用了一个system call(recvfrom)。但是,用select的优势在于它可以同时处理多个connection。
所以,如果处理的连接数不是很高的话,使用select/epoll的web server不一定比使用multi-threading+blocking IO的web server性能更好,可能延迟还更大。Select/epoll的优势并不是对于单个连接能处理的更快,而是在于能处理更多的连接。
在IO multiplexing Model中,实际中,对于每一个socket,一般都设置为non-blocking,但是,整个用户的process其实是一直被block的,不过process是被select这个函数block,而不是被socketIO给block。
2)Select
Select是最初解决IO阻塞问题的方法。用结构体fd_set来告诉内核监听多个文件描述符,该结构体被称为描述符集。由数组来维持哪些描述符被置位了。对结构体的操作封装在三个宏定义中。通过轮询来查找是否有描述符要被处理。
存在的问题:
1、内置数组的形式使得select的最大文件数受限于FD_SIZE;
2、每次调用select前都要重新初始化描述符集,将fd从用户态拷贝到内核态,每次调用select后,都需要将fd从内核态拷贝到用户态;
3、轮询排查当文件描述符个数很多时,效率很低。
3)Poll
Poll:通过一个可变长度的数组解决了select文件描述符受限的问题。数组中元素是结构体,该结构体保存描述符的信息,每增加一个文件描述符就向数组中加入了一个结构体,结构体只需要拷贝一次到内核态。Poll解决了select重复初始化的问题。轮询排查的问题未解决。
4)epoll
epoll:轮询排查所有文件描述符的效率不高,使服务器并发能力受限。因此,epoll采用只返回状态发生变化文件描述符,便解决了轮询的瓶颈。

虚函数
拥有Virtual关键字的函数称之为虚函数,虚函数的作用是实现动态绑定的,也就是说程序在运行的时候动态的选择合适成员函数。要成为虚函数必须满足两点:
一就是这个函数依赖于对象调用,因为虚函数就是依赖于对象调用,虚函数是存在于虚函数表中,有一个虚函数指针指向这个虚表,所以要调用虚函数,必须通过虚函数指针,而虚函数指针是存在于对象中的。
二就是这个函数必须可以取地址,因为我们虚函数表中存放的是虚函数入口地址,如果函数不能寻址,就不能成为虚函数。

动态分配
在程序执行的过程中动态地分配或者回收存储空间的分配内存的方法。动态内存分配不像数组等静态内存分配方法那样需要预先分配存储空间,而是由系统根据程序需要即时分配,且分配的大小就是程序要求的大小。

深copy和浅copy
浅拷贝是增加了一个指针,指向原来已经存在的内存。而深拷贝是增加了一个指针,并新开辟了一块空间让指针指向这块新开辟的空间。浅拷贝在多个对象指向一块空间的时候,释放一个空间会导致其他对象所使用的的空间也被释放了,再次释放便会出现错误。

C语言中如何申请和释放内存
C++中使用:new delete
C中用的最多的是:malloc,calloc,realloc(三者都是在堆上申请获得内存空间)free
malloc函数分配得到的内存空间是未初始化的。
free只是释放指针指向的内容,该指针仍然指向原来的地方,此时,指针为野指针。安全做法是:在使用free函数释放指针指向的空间之后,将指针的值置为NULL。

C++智能指针
智能指针主要用于管理在堆上分配的内存,它将普通的指针封装为一个栈对象,当栈对象的生存周期结束后,会在析构函数中释放掉申请的内存,从而防止内存泄漏。C++11中最常用的智能指针类型为share_ptr,它采用引用计数的方法,记录当前内存资源被多少个智能指针引用。该引用计数的内存在堆上分配。当新增一个时引用计数加1,当过期时引用计数减一。只有引用计数为0时,智能指针才会自动释放引用的内存资源。对share_ptr进行初始化时不能将一个普通指针直接赋值给智能指针,因为一个指针,一个是类。可以通过make_shared函数或者通过构造函数传入普通指针。并通过get函数获得普通指针。
智能指针是否存在内存泄漏的情况
当两个对象相互使用一个shared_ptr成员变量指向对方,会造成循环引用,使引用计数失效,从而导致内存泄漏。
如何解决智能指针的内存泄漏问题
为了解决循环引用导致的内存泄漏,引入了weak_ptr弱指针,weak_ptr的构造函数不会修改引用计数的值,从而不会对对象的内存进行管理,其类似一个普通指针,但不会指向引用计数的共享内存,但是其可以检测到所管理的对象是否已经被释放,从而避免非法访问。

C++如何处理内存泄漏
使用valgrind,mtrace检测

如何判断内存泄漏
内存泄漏通常是由于调用了malloc/new等内存申请的操作,但是缺少了对应的free/delete。为了判断内存是否泄漏,我们一方面可以使用linux环境下的内存泄漏检查工具Valgrind,另一方面我们在写代码时可以添加内存申请和释放的统计功能,统计当前申请和释放的内存是否一致,以此来判断内存是否泄漏。

什么情况会发生段错误
段错误通常发生在访问非法内存地址时,具体如下:
1)使用野指针
2)试图修改字符串常量的内容
3)memory leak-内存泄漏
内存泄漏是指由于疏忽或错误造成了程序未能释放掉不再使用内存的情况。指应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
内存泄漏的分类:
1、堆内存泄漏(Heap Leak):指调用malloc,realloc,new操作后,却没有调用free(),delete()释放掉,如果程序设计的错误导致这部分内存没有被释放掉,那么此后这段内存将不会被使用,就会产生Heap Leak。
2、系统资源泄漏(Resource Leak):主要指程序使用系统分配的资源Bitmap,handle,SOCKET等没有使用相应的函数释放掉,导致系统资源的浪费,严重可导致系统效能降低,系统运行不稳定。
3、没有将基类的析构函数定义为虚函数。当基类指针指向子类对象时,如果基类的析构函数不是virtual,那么子类的析构函数将不会被调用,子类的资源没有正确被释放,因此造成内存泄漏。

C++类内可以定义引用数据成员么
可以,必须通过成员函数初始化列表初始化

C++和C的区别
简要版:
设计思想上:C++是面向对象的语言,而C是面向过程的结果化编程。
语法上:
C++具有重载、继承和多态三种特性。
C++相比C,增加了许多类型安全的功能,比如强制类型转换
C++支持范式编程,比如模板类、函数模板等

1、C是面向过程的语言,C++是面向对象的语言。
2、C和C++动态管理内存的方法不一样,C是使用malloc/free。而C++除此之外还有new/delete关键字。
3、C中struct和C++的类,C++的类是C所没有的,但是C中的struct是可以在C++中正常使用的,并且C++对struct进行了进一步的发展,使struct在C++中可以和class一样当做类使用,而唯一和class不同的地方在于struct的成员默认访问修饰符是public,而4、class默认的是private;
5、C++支持函数重载,而C不支持函数重载,而C++支持重载的依仗就在于C++的名字修饰与C不同,C++:int fun(int,int)经过名字修饰变为_fun_int_int,而C是_fun,一般是这样,所以C++才会支持不同的参数调用不同的函数;
6、C++中有引用,而C没有;
7、C++全部变量的默认链接属性是外链接,而C是内连接;
8、C中用const修饰的变量不可以用在定义数组时的大小,但是C++用const修饰的变量可以;

C++中多态是怎么实现的
C++的多态性是用虚函数和延迟绑定来实现的,在积累的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数。如果对象类型是派生类,就调用派生类的函数;如果对象类型是基类,就调用基类的函数。
1、用virtual关键字声明的函数叫做虚函数,虚函数肯定是类的成员函数。
2、存在虚函数的类都有一个一维的虚函数表叫做虚表。类的对象有一个指向虚表开始的虚指针。虚表是和类对应的,虚表指针是和对象相应的。
3、多态性是一个接口多种实现,是面向对象的核心。分为类的多态性和函数的多态性。
4、多态用虚函数来实现,结合动态绑定。
5、纯虚函数是虚函数再加上=0;
6、抽象类是指包括至少一个虚函数的类。

C语言的内存分配
在C语言中,对象可以使用静态或者动的方式分配内存空间。
静态分配:编译器在处理程序源代码时分配,由于是在程序执行之前进行所以效率比较高。
动态分配:程序在执行时调用malloc库函数申请分配,可以灵活的处理未知数目的对象。
静态和动态内存分配的只要区别:
静态对象是有名字的变量,可以直接对其进行操作;动态对象是没有名字的变量,需要通过指针间接地对它进行操作。
静态对象的分配与释放由编译器自动处理;动态对象的分配与释放必须由程序员显式地管理,通过malloc和free实现。

!> 算术运算法 >关系运算符>&&>||>赋值运算符

new 和 malloc的区别
1、属性:new是C++关键字,需要编译器支持,malloc是库函数,需要头文件支持。
2、参数:使用new操作符申请内存分配时无需指定内存块的大小,编译器会根据类型信息自行计算,malloc则需要显式的指出所需内存的尺寸。
3、返回类型:new返回的是对象类型的指针,malloc返回void*,需要通过强制类型转换将void*指针转换成我们需要的类型。
4、分配失败:new内存分配失败时,会跑出bac_alloc异常,malloc分配内存失败放回null。
5、自定义类型:new会先调用operate new函数,申请足够的内存(底层使用malloc实现)。然后调用类型的构造函数,初始化成员变量,最后返回自定义类型指针。delete先调用析构函数,然后调用operator delete函数释放内存(通常底层使用free实现)。
malloc/free是库函数,只能动态地申请和释放内存,无法强制要求其做自定义类型对象构造和析构函数。
6、重载:C++允许重载new/delete操作符,特别的,布局new的就不需要为对象分配内存,而是指定了一个地址作为内存起始区域,new在这段内存上为对象调用构造函数完成初始化工作,并返回此地址。而malloc不允许。
7、内存区域:new操作符从自由存储区(不位于堆中)上为对象动态分配内存空间,而malloc函数从堆上动态分配内存。

C++三大特性:封装、继承、多态

虚函数和纯虚函数的区别
纯虚函数没有函数体,有纯虚函数的类为抽象类,不可以实例化对象,需要派生类来实现。虚函数只在派生类中有同名函数时才会被隐藏。
虚函数是为了继承接口和默认行为。
纯虚函数只是继承接口,行为必须重新定义。

Static作用
1、隐藏,当同时编译多个文件时,所有未加static的全局变量和函数都具有全局可见性。2、保持变量内容的持久,存储在静态数据区的变量会在程序放开运行时就完成初始化,也是唯一一次初始化,共有两种变量存储在静态存储区,全局变量和static变量。
3、默认初始化为0。
1、static在修饰局部变量时,其使得局部变量的生命周期发生改变,使得其放在data段,直到程序运行结束才结束。
2、static在修饰全局变量时,作用是改变其作用域,使得全局变量只能在定义的文件中使用。
3、static在修饰函数时,同样也是使函数只能在当前文件中使用。

多态有什么好处
所谓多态,就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行时确定,即一个引用变量到底会指向哪个类的实例对象,调用哪个类的实现方法,由程序运行期间才确定,这样不用修改程序源代码就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值