一、可变数组
编写可变数组函数库
- 创建数组
- 回收数组
- 获取数组中单元个数
- 访问数组中的某个单元
- 增长数组长度
定义Array结构体:
定义为指针类型的结构体,似乎可以简化某些函数的调用传递参数,节省内存空间?
1 |
|
但是,当我们在函数内部创建变量的时候,这个时候a指向的是已经存在的内存空间。
1 | void test() |
所以,一般情况下,定义结构体变量不要加*,不要定义指针类型
1 |
|
函数实现:
创建可变数组
思考:为什么不返回指针?
- 本地变量在函数使用后会销毁
1 | Array array_create(int init_size) |
释放数组空间
1 | void array_free(Array *a) |
获取数组大小
思考:为什么不直接a.size获取大小?
- 目的:封装,保护了a的size,防止被修改,也利于后期维护
1 | int array_size(const Array *a) |
获取数组中对应元素的位置
思考:为什么要返回int类型指针而不直接返回int
- 使用
array_at(&a,0)
取地址 - 使用
*array_at(&a,0)=10;
可以直接进行赋值操作
当然,也可以重新编写array_set,array_get两个函数,分别用于赋值和取值
1 | int* array_at(Array *a,int index) |
增长数组空间
1 | void array_inflate(Array *a,int more_size) |
不断写入数组内容
一直循环下去,总会出现数组越界的情况。
于是,继续修改array_at
引入block概念
二、 可变数组的缺陷
- 拷贝时间开销大
- 在内存受限的情况下,可能难以申请到更大的内存空间
解决方案:
不去修改原来的内存空间,链接1个block即可拓展数组长度
三、链表
链表中的元素簇称为“节点”
每个节点仅包含两个部分:
- 数据
- 指向下一个节点的指针
1 | typedef struct _node{ |
思考:为什么不用:
1 | struct Node next; |
因为在这一行还没有执行Node的类型重定义,编译器无法识别。
使用struct _node{
}定义了struct _node类型的结构体
使用typedef … Node重定义了struct _node类型
程序:循环输入数字,如果为-1则退出,
1. 定义head
1 | Node *head = Null; |
2.定义第一个节点
1 | Node *p = (Node*)malloc(sizeof(Node)); |
3. 定义last
1 | Node *last = head; |
4. 链表函数——添加节点
实现一个创建节点的函数:
缺点:函数内部对head进行改变,但是并不影响外部的head(PS:这里的head为指针)
tips:尽量少用全局变量
1 | void add(Node* head,int number) |
| |
| |
/
修改程序
1 | Node* add(Node* head,int number) |
在主程序中,即可实现改变head的操作,但是这也存在一个缺点,如果忘记调用head = …,程序会报错,封装还不够完美
1 | head = add(...); |
| |
| |
/
第三种方案,传head改为传递head的指针(实际上是指针的指针)
tips:数据结构
实际上,现在这个函数,return返回值,以及主程序中的赋值都可以不进行,提高了程序的易用性,根本原因是因为pHead是head的指针,即指针的指针
1 | Node* add(Node** pHead,int number) |
| |
| |
/
第四种方案 ,添加一个List类型结构体
1 | typedef struct _list{ |
这样,函数可以改写为
1 | void add(List *plist,int number) |
思考:调用这个函数,last每次都要从head开始指向,执行了没有必要的操作,如何让函数执行完之后,last指向保持上一次的指向?
| |
| |
/
1 | typedef struct _list{ |
实现:
在 List
结构体中添加一个指向链表尾节点的指针 tail
,然后每次添加节点时,直接更新 tail
的指向。这样可以避免每次都从头开始遍历链表,优化了插入操作的效率。
修改后的代码示例
- 修改
List
结构体:添加一个tail
指针,指向链表的最后一个节点。 - 修改
add
函数:在每次添加新节点时,直接更新tail
指针。
1 | void add(List *plist,int number) |
这种方式通过维护一个指向尾节点的指针,可以避免每次添加节点都从头遍历,优化了链表操作的效率。
5. 链表函数——打印
1 | void print(List *list){ |
6. 链表函数——删除
7. 链表函数——清除
1 | void clear(List *plist) { |