【计算机基础理论知识】C++篇(一)
【计算机基础理论知识】C++篇(一)
🔥个人主页:大白的编程日记
🔥专栏:计算机基础理论知识
文章目录
- 【计算机基础理论知识】C++篇(一)
- 前言
- 面向对象的三大特性
- 封装
- 继承
- 多态
- Volatile
- 主要用途
- 1. 编译器优化
- 2. 多线程优化
- 3. 硬件映射
- 内存排布
- 栈区
- 堆区
- 数据区
- 代码区
- 常量区
- Static
- 局部静态变量
- 全局静态变量和函数
- 类静态成员变量
- 类静态成员函数
- 四种智能指针 && 循环引用
- auto_ptr(已废弃)
- unique_ptr
- shared_ptr
- 原理
- shared_ptr 的线程安全问题
- weak_ptr
- 循环引用
- RAII
- 构造函数和析构函数的虚函数问题
- 构造函数不能是虚函数
- 析构函数通常是虚函数,否则会导致内存泄漏
- 后言
前言
哈喽,各位小伙伴大家好!今天我们来讲一下【计算机基础理论知识】C++篇(一)。话不多说,我们进入正题!向大厂冲锋
面向对象的三大特性
封装
定义:
封装是指将数据和方法绑定在一起,通过访问权限符对外部隐藏对象内存的实现细节,以接口的形式与外界交互。
作用:
- 防止外部直接访问和修改对象的数据,提高代码的安全性和可靠性
- 对象的数据和方法绑定在一起,模块直接以接口的方式交互,减低代码模块之间的耦合度
- 提高代码的可读性和可维护性
继承
定义:
继承是指子类可以通过继承的方式继承父类的属性和方法,并且子类继承后可以复用父类的代码。子类也可以新增属性和方法或覆盖父类的方法。
作用:
- 继承可以让子类直接复用父类的代码,减少冗余代码,代码的可读性和可维护性更好
- 通过子类可以在父类的继承上扩展属性和方法,增强代码的可扩展性
多态
定义:
多态是指同一接口对不同对象做出不同响应,即同一个函数可以根据对象的不同具有不同的行为。
作用:
- 多态可以使用同一的接口来处理不同的对象,无需关心具体对象的类型,提高代码的灵活性
- 要增加新的功能时只需要定义新的类并实现接口即可,无需修改现有的代码,可扩展性强
Volatile
volatile
是一个用于修饰变量的关键字,表示该变量在程序运行过程中可能会被外部因素改变,相当于告诉编译器不要对该变量进行优化,不要把变量缓存在寄存器。每次访问变量都去内存中取出该变量的最新值,而不是去访问优化后缓存在寄存器中的值。
主要用途
1. 编译器优化
对于编译器的优化,编译器可能会把变量的值缓存在速度更快的寄存器中来减少对内存的访问,提高效率。但寄存器中的值是内存中值的一个副本。如果变量的值在内存中被其他因素(如硬件设备、其他线程等)修改了,而寄存器中的值没有更新,就会导致程序读取到过时的值。volatile
就是告诉寄存器每次访问都是读取内存中最新的值。
2. 多线程优化
在多线程程序中,一个变量被多线程访问时可能会被优化,volatile
可以用于确保变量的访问不会被线程优化掉。
3. 硬件映射
在嵌入式系统中,硬件设备的状态通常通过内存映射的寄存器来表示。这些寄存器的值可能会被硬件设备随时更新。程序需要能够及时检测到这些变化,并正确地与硬件设备进行交互。volatile
可以防止变量的值缓存在寄存器中,确保每次都能读取到内存中最新的值。
内存排布
栈区
- 存储局部变量,如函数参数、函数地址等
- 特点:后进先出,由操作系统自动管理分配,大小较小,通常为几M,高向低地址方向增长
- 在函数调用后数据会自动销毁,操作系统提供了专门的寄存器存放栈的地址,压栈和出栈操作都有专门的指令,分配效率高
堆区
- 存储动态分配的内存
- 特点:由程序员通过
new
或malloc
手动申请、管理和释放(delete
、free
),大小较大 - 低向高地址方向增长,需要遍历空闲链表,记录分配大小,内存不连续
- 频繁申请释放会导致内存碎片化,无法申请出更大空间,分配效率低
数据区
- 分为初始化数据区(显示初始化)和未初始化数据区(BSS,默认初始化为0)
- 存储全局变量和静态变量
- 特点:程序的整个周期有效
代码区
- 存储程序编译好后的机器指令(如
mov
、call
) - 特点:只读,大小固定,具有共享性
- 例如动态库的代码加载一次就可以被调用的地方跳转执行
常量区
- 存储字符串字面量、全局常量
- 特点:只读属性
Static
static
是一个关键字,通常用来修饰变量或函数。
局部静态变量
- 生命周期随程序,程序运行期间一直存在
- 函数调用结束后不会销毁
全局静态变量和函数
- 具有内部链接属性
- 链接时其他文件不可见,只能在当前文件中使用
- 可以避免命名冲突
类静态成员变量
- 属于整个类,而不是某个类的实例
- 所有的类对象共享一份静态成员变量
类静态成员函数
- 属于整个类,而不是某个类的实例
- 没有
this
指针,因此无法调用普通成员函数 - 只能调用静态成员变量和静态成员函数
四种智能指针 && 循环引用
智能指针就是在 RAII(资源获取立即初始化) 的思想上设计的类。
通过封装对象指针,自动管理内存的分配和释放,同时提供运算符重载访问对象资源。
可以解决异常的栈展开资源没有析构的安全问题等,避免内存泄露。
auto_ptr(已废弃)
- C++98 引入的智能指针
- 独占资源所有权,不支持移动语义
- 只支持拷贝和赋值转移资源所有权
- 转移后原来的
auto_ptr
指针会悬空,再次访问会有野指针问题 - 语义不明确,通过拷贝隐式转移,容易误用
- 目前已被弃用,不建议使用
unique_ptr
- 独占资源所有权
- 明确只有一个智能指针可以管理一个对象
- 不支持拷贝,赋值重载和拷贝构造都被
=delete
删除了 - 支持移动语义,通过
move()
来转移资源所有权 move()
之后原来的unique_ptr
也会悬空- 语义明确,比
auto_ptr
更安全
shared_ptr
- 可以共享资源,允许多个智能指针共享同一个对象资源
- 通过引用计数记录指向对象的
shared_ptr
的数目 - 只有当引用计数减为 0 才释放资源
原理
shared_ptr
核心在于维护一个控制块,包含:
创建一个 shared_ptr
对象时,初始化强引用计数为 1。
如果是通过 make_shared
创建的话,还会同时开辟控制块和对象的空间,避免内存碎片,提高性能。
复制一个 shared_ptr
,强引用计数 ++
;
销毁一个 shared_ptr
,强引用计数 --
。
如果强引用计数变为 0,销毁对象;如果弱引用计数不为 0,不释放控制块。
当强引用计数和弱引用计数都为 0 时,才会释放控制块。
因为 weak_ptr
需要通过控制块的强引用计数判断资源是否过期,否则访问资源会出现野指针。
shared_ptr 的线程安全问题
shared_ptr
本身并不是完全线程安全的- 它的引用计数是线程安全的,因为是通过标准库的
atomic
原子操作保证的 - 保证多线程对引用增加删除时操作是原子的,所以析构也是线程安全的
- 当引用计数减为 0 时,调用析构,只会调用一次
但是:
- 多线程对同一个
shared_ptr
对象进行赋值操作时,涉及指针的修改 - 会导致数据竞争,导致数据不一致
- 此时就会出现线程安全问题,可以通过加上互斥锁解决
weak_ptr
- 弱引用智能指针,指向一个由
std::shared_ptr
管理的对象 - 不增加引用计数,不参与资源对象的管理
- 通过
lock()
获取shared_ptr
使用访问对象 - 检查控制块的强引用计数判断资源是否过期
- 如果对象还存在,创建一个新的
shared_ptr
并返回 - 如果对象不存在,返回一个空的
shared_ptr
表示资源过期 - 使用
expired()
方法访问控制块的强引用计数,检查对象是否已经被销毁
循环引用
- 多个
shared_ptr
对象互相指向,持有对方的引用,形成闭环 - 此时当
shared_ptr
指针释放后,因为互相指向,引用计数依然 > 0 - 每个对象的释放都依赖于对方,就会陷入循环引用的闭环
- 导致每个对象都不会释放,引发内存泄露
解决方法:
将其中一个对象的 shared_ptr
替换为 weak_ptr
即可,因为 weak_ptr
不增加引用计数,不参与对象的管理。
RAII
RAII(Resource Acquisition Is Initialization,资源获取即初始化)是一种编程范式,主要用于管理资源的生命周期,确保资源在使用期间始终保持有效,并在不再需要时正确释放。主要用于资源管理、异常安全。
RAII 的核心思想是:
将资源的获取与对象的构造绑定在一起,将资源的释放与对象的析构绑定在一起。
构造函数和析构函数的虚函数问题
构造函数不能是虚函数
虚函数表的构造是在对象构造中完成的,对象构造完成后虚函数表才构造完成。而在调用构造函数时,对象还没构造完成,即虚函数表也没有构造完成,此时就无法在虚函数表中找到构造函数的实现。
析构函数通常是虚函数,否则会导致内存泄漏
基类的指针可以指向派生类对象,此时如果基类的析构函数不是虚函数,指向派生类的基类指针在通过指针删除时,就只会调用基类的析构函数,导致派生类中的分配资源没有得到释放,从而导致内存泄露。
因此,基类的析构函数通常声明为虚函数,并且编译器会把析构函数的名字统一处理为 destructor
。派生类完成对基类析构函数的重写后,就可以实现多态析构:
- 基类调用基类的析构函数
- 派生类调用派生类的析构函数
这样可以保证资源的正确释放,防止内存泄漏。
后言
这就是【计算机基础理论知识】C++篇(一)。大家自己好好消化!今天就分享到这!感谢各位的耐心垂阅!咱们下期见!拜拜~