动态内存分配
全局变量 global variable
在函数之外定义,内存空间在执行程序之前就进行了分配,并且保存直到程序结束
保存全局变量的时间是 compile time.
本地变量 local variable
在函数中定义,本地变量的存储空间是在运行时期 run time 期间定义的
动态内存分配
new 关键字
我们通过 new 进行新的的空间申请
1 | int * ip = new int; |
如果我们想同时初始化内存值,则
1 | int * ip = new int(5); |
对于类的 初始化:我们会调用类的构造函数进行返回;
对于数组的初始化,我们使用语法
1 | int *ip = new int[5]; // 这个 5 可以是一般变量 n |
申请两次内存
![[Pasted image 20240328150603.png]]
delete 关键字
通过 deletej’jjj 释放内存空间,也即是说这会消除空间中的内容
注:
- 不能同时 free 或者 delete 同一块内存, 也不能 delete 一般的非动态数据内容,但是不同指针可以delete不是其申请的空间头
- 编译器对于没有清空申请内存空间的行为不会 warning!
- 编译器对于内存删除的检查并不会进入完全的内存空间,真正的释放报错是因为运行时候的报错
删除数组
1 | delete[] ip; |
在申请数组空间的时候内存会自动记录数组的大小并且保存在前面,所以删除的时候不需要声明删除空间的大小
内存检查器 valgrind
1 | valgrind --leak-check-full ./program (<args>) |
这个可以检查内存是否存在泄露的行为
当然对于一般 debian 系的系统我们首先需要下载 valgrind:
1 | sudo apt-get update; |
使用逻辑
在系统中,申请的空间是在 global 内存区的上面按照 heap 的数据结构进行排列的,与函数的stack区域在相反方向,二者中间存在极大的void 未分配空间,我们称呼stack 的生长为 grow down.
重载问题 functional overloading
在 c++ 中是可以有多个函数具有同一个名称的,区分点在于输入的变量类型 signature 不同,运行时候会自动匹配适合的数据类型的函数进入编译
对于输入不同变量的构造函数,我们可以定义不同的重载构造函数进行运算
一种解决办法:默认值
1 | IntSet(int size = MAXELTS); |
这样我们在构造的时候是否输入 size 都可以
但是这样就存在问题:编译器(使用者)会分不清少输入变量什么时候是哪个变量, 所以我们规定:写默认值必须是最后一个变量,否则就会分不清.
二维数组的内存申请和清除问题
对于一个二维数组,我们并不能直接申请 int ** mat = new int[r][c]
道理上只能申请一个连续内存, 示例如下:
1 | int** mat = new int*[num_row]; |
这样写有个显著的好处, 每一行单独申请空间,矩阵的存储空间不连续,这个对于高维矩阵的运算有好处,因为我们不需要保存连续的空间,离散保存有助于空间有效利用。
当然我们直接申请二维矩阵获得的是连续的内存空间.和我们直接申请一个 int* mat = new int[r*c]; 效果相同,需要很大一块连续的内存空间
清除数组内存空间
1 | for(int i= 0;i < num_row; i++){ |
传递值为类
一般写法传入为类的时候会对传入类的数据表进行拷贝,也就是local variable 会获得一个和原定义的类同样的指针地址,那么如果我对类的该指针进行操作,全局都会受到影响。当然最严重的是我们在该函数结束的时候会调用类的析构函数,按照常理我们会删除掉类的动态内存指针,那么local 的指针就会被释放,执行完之后该内存仍然失效,再次运行析构函数或者其他用到指针的函数就会出现 runtime-error.
这时候我们需要用到 软拷贝
深拷贝(Shallow Copy)
诞生思路
在复制赋值的时候,我们事实上只是需要一个数组,也就是说我们要拷贝一个的并不是一个地址
复制的构造器
我们最常见的赋值构造器是 = 符号,在函数调用的时候我们往往会隐式调用赋值这些ADT的数据内容。
于是我们定义在传入一个 ADT 的时候,必须使用 const MyClass & 关键字进行限制,称之为 浅拷贝。
两种情况对比
1 | void foo(const IntSet& is); |
- 前者能通过赋值但是不会重新进入一个constructor
- 后者可能会进入constructor的递归
- 这里的
const限定符是用来扩大传入范围的,也就是是否为const的变量都可以传进来
深拷贝的思路实现
- 动态指针申请同样大小的空间
注:如果两者的指针尺寸不同,那么就必须先delete[]本地指针然后重新new - 将输入动态数组的每一位分别赋值给本地数组的对应位置
- 复制尺寸等其他参数
对于一般的c++程序运行的时候会默认执行浅拷贝,因此我们需要做的是手动编写硬拷贝脚本进行overload
赋值方法
c++ 存在一种称为链状的赋值方法如下:
1 | x = y = z; |
但是 c++ 的处理思路是从右到左
运算符重载思路 operator overload
重载运算符的写法如下:
1 | IntSet &operator= (const IntSet &is); |
使用 operator 关键字获得重载权限
注意整体可以看作是一个函数,需要有返回值为 IntSet& 或者说是 IntSet
调用的时候我们有两种方法使用:
1 | IntSet a = b; // 隐式调用函数 |
从赋值语句我们可以看到,赋值是存在一个返回 值的,一般而言是最左边变量的内容,如果是已有的数据类型的赋值算法,一般返回的是 引用(由于引用是一个 lvalue ,鲁棒性更强)所以如果我们重载运算符为 一般的 IntSet 而不是 IntSet& 我们执行赋值之后只会有一个临时变量的返回值,所以结果并不好
