看了咸鱼学长的数据结构关于顺序表的定义视频讲解。对于顺序表分配内存的原理有了一些自己的理解,故在此记下一些笔记,以便日后复习;
顺序表的定义:
顺序表是一组地址连续的存储单元格依次存储线性表中的数据元素,从而使得逻辑上相邻两个元素在物理位置上也相邻。表中元素的逻辑顺序与其存储的物理顺序相同。
顺序表的特点:
- 随机访问,即可以在O(1)时间内找到第i个元素(代码实现:data[i-1];)
- 存储密度高,每个节点只存储数据元素
- 扩展容量不方便(静态分布不能扩展容量,动态分布扩展长度所需的时间复制度也比较高)
顺序表的静态分布:
#include <stdio.h>
#define MaxSize 10
// 定义一个名为SqList的结构体,用于表示顺序表
typedef struct {
int data[MaxSize]; // 包含一个整数数组,用于存储顺序表的元素
int length; // 表示顺序表中当前元素的个数
} SqList;
// 定义一个函数InitList,用于初始化顺序表
// 参数L是一个引用,这样可以直接修改实参
void InitList(SqList &L) {
L.length = 0; // 将顺序表的长度设置为0,表示表为空
}
// 程序的主函数
int main() {
SqList L; // 声明一个SqList类型的变量L
InitList(L); // 调用InitList函数初始化L
return 0; // 程序正常退出,返回0
}
以上使用C#的形式实现了顺序表内存的静态分布,只需要定义一个结构体表示顺序表,然后再创建一个名为InitList的函数对顺序表进行初始化即可。
顺序表的动态分布:
当顺序表(数组)的空间不够时,我们就得需要使用动态分布来扩大内存空间。
这里我们需要先知道C语言中的malloc函数和free函数,
malloc函数是C 库函数 void *malloc(size_t size) 分配所需的内存空间,并返回一个指向它的指针。
free函数是C 库函数 void free(void *ptr) 释放之前调用 calloc、malloc 或 realloc 所分配的内存空间。
#include <stdio.h>
#include <stdlib.h> // 用于malloc和free
#define InitSize 10 //默认的最大长度
typedef struct {
int *data; //指示动态分配数组的指针
int MaxSize; //顺序表的最大容量
int length; //顺序表的当前长度
}SeqList;
void InitList(SeqList &L) {
//用malloc 函数申请一片连续的存储空间
L.data = (int*)malloc(InitSize *sizeof(int));
L.length = 0;
L.MaxSize = InitSize;
}
//增加动态数组的长度
void IncreaseSize(SeqList& L, int len) {
int *p = L.data;
L.data = (int*)malloc((L.MaxSize + len) *sizeof(int));
for (int i = 0;i < L.length;i++) {
L.data[i] = p[i]; //将数据复制到新区域
}
L.MaxSize = L.MaxSize + len;//顺序表最大长度增加len
free(p);//释放原来的内存空间
}
int main() {
SeqList L; //声明一个顺序表
InitList(L); //初始化顺序表
IncreaseSize(L, 5); //往顺序表中随便插入几个元素
return 0;
}
在上面的代码中,我们依旧是定义了一个结构体作为顺序表,它的最大长度为10,如果我们想要再增加一些长度的话,则需要使用malloc函数,我们先在初始函数InitList中使用malloc函数申请了一片内存空间,再在IncreaseSize的参数变量中定义一个参数len 来表示我们需要增加的长度,定义一个指针p指向第一个顺序表,再使用malloc函数申请了另一片内存空间(在原来的最大长度上加一个len),然后再通过for循环将第一顺序表中的数据元素对应复制到新的顺序表中,最后再使用free函数消除原来的顺序表。
图解:
第一次创建顺序表
第二次创建了一个再原来长度基础上+"len"的顺序表:
通过指针将第一个顺序表中的数据元素复制到第二个顺序表中的对应元素位置:
最后,调用free函数释放第一个顺序表的内存空间:
代码中的混淆点:
在IncreaseSize
函数中的int *p = L.data;
和L.data = (int*)malloc(InitSize * sizeof(int));
这两行代码执行了不同的操作,它们在动态数组扩容的过程中扮演了不同的角色。
-
int *p = L.data;
: 这行代码声明了一个指向整型的指针p
,并将L.data
的值赋给p
。这里,L.data
是指向顺序表L
中数据数组的指针。通过将L.data
赋值给p
,p
现在存储了原始数据数组的首地址。这样做的目的是为了在后续的代码中能够访问和操作原始数据数组,因为在扩容后,L.data
将指向新分配的内存地址。 -
L.data = (int*)malloc(InitSize * sizeof(int));
: 这行代码使用malloc
函数为新数组分配内存。malloc
函数返回一个指向void类型的指针,它指向一块大小为InitSize * sizeof(int)
字节的新分配的连续内存空间。这个内存空间足够大,可以存储InitSize
个整数。然后,这个void指针被强制转换为指向整型的指针,并将其赋值给L.data
。现在,L.data
指向了新分配的内存空间,这个空间可以用来存储扩容后的顺序表数据。
总结一下,int *p = L.data;
是为了保存原始数据数组的地址,而L.data = (int*)malloc(InitSize * sizeof(int));
是为了分配一个新的、更大的内存空间,以便顺序表扩容。在为新数组分配内存后,原始数据被复制到新数组中,然后原始数组所占用的内存被释放。