C++基础知识点整理

在这里插入图片描述

基本语法

1、static关键字的作用

1、全局静态变量
加了static关键字的全局变量只能在本文件中使用。
存储在静态存储区,整个程序运行期间都存在。
2、局部静态变量
作用域仍为局部作用域。
不过离开作用域之后,并没有销毁,而是贮存程序中,不能进行访问,除非函数再次被调用。
3、静态函数
只能在声明它的文件中使用,不能被其他文件使用,也不会和其他cpp中的同名函数起冲突。
4、类的静态成员
使用静态成员可以实现多个对象之间的数据共享,也不会破坏隐藏原则。静态数据成员只存储于一处,供所有对象共用。
5、类的静态函数
属于类的静态成员,对静态成员引用不需要对象名。注意,静态成员函数的实现中不能直接引用类中非静态成员,可以直接引用类中的静态成员。

2、C++四种cast转换

const_cast : 用于将const变量转为非const.
static_cast : 用于各种隐式转换,用于多态向上转换,向下转换不安全
dynamic_cast : 用于动态类型转换,用于类层次间的向上向下转化。只能转指针或者引用。
对于指针,转换失败返回nullptr,对于引用,转换失败会抛出异常。
向上:子类向基类转化
向下:基类向子类转化
reinterpret_cast:几乎都可以转化,可能会出问题
C语言的强制转换不能进行错误检查。

3、指针与引用区别

1、指针有自己空间,引用是一个别名
2、sizeof指针为4,引用是被引用对象的大小
3、指针可以初始化为NULL,引用被初始化必须是一个已有对象的引用
4、指针可以指向其他对象,引用只能是一个对象的引用,不能被改变
5、指针可以多级,引用只有1级
6、返回动态内存分配的对象,使用指针。

4、智能指针

作用:
申请的空间在函数结束时忘记释放,造成内存泄漏。
智能指针是一个类,当超出了类的作用域,类会自动调用析构函数,析构函数自动释放资源,这样就不需要手动释放内存了。
auto_ptr : 存在隐患
unique_ptr : 保证了同时只有一个智能指针指向该对象。
shared_ptr : 多个智能指针可以指向相同对象,该对象和相关的资源会在最后一个引用被销毁的时候释放。
其成员函数use_count() 用来查看资源所有者个数。
调用release,当前指针会释放资源所有权,计数减一。
weak_ptr : 不控制对象生命周期,它指向一个shared_ptr管理的对象。
进行该对象的内存管理的是shared_ptr,weak_ptr 只提供了对管理对象的一个访问手段。
weak_ptr 的构造和析构不会引起计数的增加或减少。它是用来解决shared_ptr相互引用时的死锁问题:如果两个shared_ptr相互引用,这两个指针的引用计数永远不可能降为0,资源永远不会释放。
举例:
在Man类内部会引用一个Woman,Woman类内部也引用一个Man。当一个man和一个woman是夫妻的时候,他们直接就存在了相互引用问题。man内部有个用于管理wife生命期的shared_ptr变量,也就是说wife必定是在husband去世之后才能去世。同样的,woman内部也有一个管理husband生命期的shared_ptr变量,也就是说husband必须在wife去世之后才能去世。这就是循环引用存在的问题:husband的生命期由wife的生命期决定,wife的生命期由husband的生命期决定,最后两人都死不掉,违反了自然规律,导致了内存泄漏。
https://blog.csdn.net/shanno/article/details/7363480

5、数组与指针

在这里插入图片描述

6、野指针

野指针就是指向一个已删除的对象或者未申请访问受限内存区域的指针

7、智能指针内存泄漏如何解决

智能指针的内存泄漏主要是由于循环引用造成的。可以引入weak_ptr指针。weak_ptr的构造函数不会修改引用计数的值,从而不会对对象的内存进行管理,类似一个普通指针,但不指向引用计数的共享内存。weak_ptr可以检测到所管理的对象是否被释放,从而避免非法访问。

8、析构函数必须是虚函数

如果父类的析构函数不是虚函数,那么当我们new一个子类对象,然后使用基类指针指向该子类对象,释放基类指针时就只会释放基类对象,子类对象的空间不会被释放,从而造成内存泄漏。

C++默认的析构函数不是虚函数,是因为虚函数需要额外的虚函数表和虚表指针,占用额外内存。如果这个类不会被继承,析构函数设为虚函数反而会造成内存浪费。

9、函数指针

函数指针是指向函数的指针变量。
C在编译的时候,每个函数都有一个入口地址,该入口地址就是函数指针所指向的地址。有了指向函数的指针变量后,可以用该指针调用函数。
用途:
调用函数和做函数的参数,如回调函数

10、析构函数作用

在这里插入图片描述

11、静态函数与虚函数的区别

静态函数在编译的时候就已经确定了运行机制,虚函数在运行的时候动态绑定。虚函数因为有虚函数表机制,调用的时候会增加一次内存开销。

12、重载与覆写

重载:两个函数名字相同,但是参数列不同
覆写:子类继承父类,父类中的函数是虚函数,在子类中重新定义这个虚函数。

13、虚函数与多态理解

多态分为静态与动态。静态多态通过重载,在编译时就确定了。
动态多态是用虚函数机制实现的,在运行期间动态绑定。
举例:一个父类类型的指针指向一个子类对象的时候,使用父类指针调用子类中的虚函数时,调用的就是子类覆写后的函数。

14、const修饰成员函数

表明函数调用不会对对象做出任何更改。
如果确认不会对对象做更改,更应该为函数加上const限定。

15、隐式类型转换

对于内置类型,低精度的变量给高精度的变量赋值会发生隐式类型转换。

16、虚函数的实现

在有虚函数的类中,类的最开始部分时一个虚函数表的指针,这个指针指向一个虚函数表,表中放了虚函数的地址,实际的虚函数在代码段(.text)中。当子类继承了父类的时候也会继承其虚函数表,当子类重写父类中虚函数时候,会将其继承到的虚函数表地址替换为重新写的函数地址。使用了虚函数,回增加访问内存开销,降低效率。

17、C++中怎么定义常量?常量存放在内存的哪个位置

常量定义必须初始化。对于局部对象,常量存放在栈区,对于全局对象,常量存放在全局/静态存储区。杜宇字面值常量,常量存放在常量存储区。

18、auto、nullptr关键字

auto关键字:编译器可以根据初始值自动推导出类型。但是不能用于函数传参以及数组类型的推导。
nullptr关键字: nullptr是一种特殊类型的字面值,它可以被转换成任意其它的指针类型;而NULL一般被宏定义为0,在遇到重载时可能会出现问题。
智能指针:C++11新增了std::shared_ptr、std::weak_ptr等类型的智能指针,用于解决内存管理的问题。
初始化列表:使用初始化列表来对类进行初始化。
右值引用:基于右值引用可以实现移动语义和完美转发,消除两个对象交互时不必要的对象拷贝,节省运算存储资源,提高效率。

20、右值

C++中,左值通常指可以取地址,有名字的值就是左值,而不能取地址,没有名字的就是右值。而在指C++11中,右值是由两个概念构成,将亡值和纯右值。纯右值是用于识别临时变量。
右值引用就是对一个右值进行引用的类型。
更加详细的区别可以看这篇笔记:
https://blog.csdn.net/qq_42604176/article/details/110941759

21、lambda表达式

Lambda 是一个匿名函数,可以把 Lambda表达式 理解为是一段可以传递的代码 (将代码像数据一样进行传递)。可以写出更简洁、更灵活的代码。
比如你代码里有一些小函数,而这些函数一般只被调用一次(比如函数指针),这时你就可以用lambda表达式替代他们,这样代码看起来更简洁些,用起来也方便。
具体用途和理解看这儿:
Lambda 表达式有何用处

容器

1、STL迭代器删除元素迭代器失效

1、对于序列容器来说,使用erase后,后面的每个元素的迭代器都会失效,但是后面每个元素都会向前移动一个位置,然后返回下一个有效的迭代器。

2、对于关联容器来说,使用erase后,当前元素迭代器失效,但由于其内部结构,删除当前元素不会影响到下一个元素的迭代器。只需要在调用erase之前,记录下一个元素的迭代器即可。

3、对于list来说,它使用了不连续分配的内存,而且erase方法也会返回下一个有效的iterator,所以两个方法都可以使用

2、STL由什么组成

容器、迭代器、仿函数、算法、分配器、容器适配器

分配器给容器分配存储空间,算法通过迭代器获取容器中的内容,仿函数协助算法完成操作,适配器用来适配仿函数。

3、vector 与 list区别

vector
连续存储的容器,动态数组,在对上分配空间。

底层实现:数组

两倍容量增长:

vector增加插入元素的时候,如果未超过当时的容量,则还有剩余空间,那么直接添加到最后位置,然后调整迭代器。

如果没有剩余空间了,则会重新配置原有元素个数的两倍空间,然后将原空间元素通过复制的方式初始化新空间,再向新空间增加元素,最后析构释放掉原来空间,之前的迭代器会失效。
list
list
底层:双向链表

4、STL中迭代器的作用,有指针为何还要迭代器

底层

1、C/C++内存分布

虚拟内存被分为代码段、数据段、bss段、堆区、文件映射区、栈区。
代码段:只读存储区、文本区。只读存储区存储字符串常量、文本区存储程序的代码
数据段:存储程序中已经初始化的全局变量和静态变量
bss段:存储未初始化的全局变量和静态变量
堆区:程序员动态分配内存,并由程序员手动释放
映射区:存储动态链接库以及调用mmap函数进行文件映射
栈:存储函数的返回地址、参数、局部变量、返回值

2、malloc底层原理

malloc采用内存池的方式,先申请大块内存作为堆区,然后将堆区分割为很多个内存块,以块作为内存管理的基本单元。当用户申请内存时,直接从堆区分配一块合适的空闲块。malloc使用隐式链表来将堆区分成连续的、大小不一的内存块。同时使用显示链表管理所有内存块,即使用一个双向链表将空闲链表连接起来。
当分配内存时,malloc会通过隐式链表遍历所有空闲块,选择满足要求的块进行分配;
当内存合并时,malloc采用边界标记法,根据每个块的前后块是否已经分配来决定是否将块合并。

当申请内存小于128k,会使用brk在堆区分配。当申请内存大于128k时,使用mmap在映射区分配。

3、内存泄漏分类

1、堆内存泄漏:new和delete没有对应上
2、系统资源泄漏:程序使用系统分配的资源,然后没有相应的函数释放,导致系统资源浪费。
3、没有将基类的析构函数定义为虚函数。当基类指针指向子类对象,如果基类析构函数不是虚函数,那么子类的析构函数就不会被调用,子类的资源就没有被释放。

4、STL内存优化

使用二级配置器结构。
1、第一级配置器
如果申请内存大于128字节的空间,如果分配不成功,就调用句柄释放内存,如果还不能分配成功就会抛异常
2、第二级配置器,每次配置一大块内存,并维护16个自由链表。
处理小于128字节的申请。
首先将申请空间扩展到8倍数,然后从freelist查找对应大小的子链表。
如果该自由链表下没有挂内存,或者挂的内存块太少了,就向内存池申请,一般来说申请20块内存。
如果内存池空间足够,取出内存。如果不够,就分配出最多的块数给自由链表。如果一块都无法提供,则把剩余的内存挂到最符合的自由链表上。然后调用malloc向堆区申请空间,如果申请失败就看自由链表上有没有可用的块,如果没有,就调用一级空间配置。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

拾牙慧者

欢迎请作者喝奶茶

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值