文章目录
一、可变数组
1.1 可变数组
在C语言中,数组都是固定大小的。尽管C99可以使用变量作为数组大小,但是在运行过程中我们无法改变数组大小。因此,在实际应用中如果我们不知道有多少数据需要存放,传统方法就是尽量弄个大的数组,但是大的也有可能不够。因此,需要一个可以自己变大小的数组,这就是可变数组。
要实现可变数组需要满足以下特点:
• 可增长
• 可知道当前有多大
• 可以访问其中的单元
我们可以实现一个函数库:
Array array_create(int init_size); //创建一个数组
void array_free(Array *a); //释放这个数组的空间
int array_size(const Array *a); //数组现在有多少个单元可用
int* array_at(Array *a, int index); //访问数组某个位置的元素
void array_inflate(Array *a, int nore_size); //让数组增大
我们首先来实现创建数组和释放数组功能:
array.h
#pragma once
typedef struct {
int *array;
int size;
}Array;
Array array_create(int init_size);
void array_free(Array *a);
int array_size(const Array *a);
int* array_at(Array *a, int index);
void array_inflate(Array *a, int more_size);
array.c
#include "array.h"
#include <stdio.h>
Array array_create(int init_size)
{
Array a;
a.size = init_size;
a.array = (int*)malloc(sizeof(int)*a.size);
return a;
}
void array_free(Array *a)
{
free(a->array);
a->array = NULL;;
a->size = 0;
}
1.2 可变数组的数据访问
这里我们实现获取数组大小和数组单元的数据
array.c
#include "array.h"
#include <stdio.h>
#include <stdlib.h>
Array array_create(int init_size)
{
Array a;
a.size = init_size;
a.array = (int*)malloc(sizeof(int)*a.size);
return a;
}
void array_free(Array *a)
{
free(a->array);
a->array = NULL;;
a->size = 0;
}
//可能随着代码升级,不能直接通过a.size得到大小
//封装,将a的size保护起来
int array_size(const Array *a)
{
return a->size;
}
//返回指针比较好,我们可以对其赋值
int* array_at(Array *a, int index)
{
return &(a->array[index]);
}
void array_inflate(Array *a, int more_size);
1.3 可变数组的自动增长
要实现自动增长,需要考虑两个问题;
1、什么时候需要增长?
可以考虑在访问数组越界时进行增长
2、一次增长多大?
一次不要只增长1个int,可以设置一个Block,一个Block里面包含若干个int。
当访问越界时,根据越界多少来决定增长多少个Block。
我们这里设置一个Block包含20个int,根据访问数组下标越界的程度来决定增长多少个Block。
#include "array.h"
#include <stdio.h>
#include <stdlib.h>
const int BLOCK_SIZE = 20;
Array array_create(int init_size)
{
Array a;
a.size = init_size;
a.array = (int*)malloc(sizeof(int)*a.size);
return a;
}
void array_free(Array *a)
{
free(a->array);
a->array = NULL;;
a->size = 0;
}
//可能随着代码升级,不能直接通过a.size得到大小
//封装,将a的size保护起来
int array_size(const Array *a)
{
return a->size;
}
//返回指针比较好,我们可以对其赋值
int* array_at(Array *a, int index)
{
if (index >= a->size) {
array_inflate(a, (index/BLOCK_SIZE+1)*BLOCK_SIZE-a->size);
}
return &(a->array[index]);
}
void array_inflate(Array *a, int more_size)
{
int *p = (int*)malloc(sizeof(int)*(a->size + more_size));
int i;
for (i = 0; i < a->size; i++) {
p[i] = a->array[i];
}
free(a->array);
a->array = p;
a->size += more_size;
}
设置链表增长程度的代码详情如下图所示:
二、链表
2.1 可变数组的缺陷
可变数组有一些很大的缺陷:每次都需要申请足够大的空间以存放所有数据,同时需要进行拷贝。
• 拷贝很花时间
• 随着数组增长,比如增长到10000个,拷贝就很花时间
• 明明有空间,但是无法申请内存
• 数组前面和后面的内存都不够,但是加起来够
无法申请内存的情况如下图所示。虽然在计算机上有很大的内存,但是当我们使用一些资源受限的单片机时,可能只有16KB内存,这时候就很容易发生这种情况。
因此,如果我们可以不去申请N+Block那么大的内存,而是申请一块Block大小的内存,然后将它们链起来,那么我们即可以不用拷贝,又可以充分利用内存中的角角落落。
2.2 链表
链表的结构如下图所示。链表由多个节点组成,每个节点由数据和指针构成,指针指向下个结点的数据的地址。其中最后一个节点的指针需要表示后面没有东西了。
我们首先将结点和链表定义出来,同时写出功能函数的原型。
node.h
#pragma once
typedef struct _node {
int value;
//不要用Node *next,因为编译器这时候还不知道Node
struct _node *next;
} Node;
typedef struct _list {
Node* head; //链表的头结点
Node* tail; //链表的尾结点
}List;
void add(List* pList, int number); //添加一个结点
void print(List *pList); //打印链表中所有的值
int search(List *pList, int number); //搜索某个值是否在链表中
void del(List *pList, int number); //删除链表中的特定元素
void clear(List *pList); //清空整个链表
2.3 链表的函数
接着写出向链表中添加一个结点的代码。
node.c
void add(List* pList, int number)
{
Node *p = (Node*)malloc(sizeof(Node));
p->value = number;
p->next = NULL;
if (pList->head) {
pList->tail->next = p; //tail的下一个结点是p
pList->tail = p; //将tail移动到p
}
else
{
pList->head = p; //空链表,首次创建结点
pList->tail = p;
}
}
2.4 链表的搜索
为了验证是否成功添加了一个结点,我们写一个打印所有结点数据的函数。
void print(List *pList)
{
Node *p;
for (p = pList->head; p; p = p->next) {
printf("%d\t", p->value);
}
printf("\n");
}
现在我们要搜索特定的值是否在链表中,思路和上面打印所有节点数据的函数类似,只是多了一个判断。
int search(List *pList, int number)
{
Node *p;
int isFound = 0;
for (p = pList->head; p; p = p->next) {
if (p->value == number) {
isFound = 1;
break;
}
}
return isFound;
}
2.5 链表的删除
如果我想删除链表中的某个结,需要以下步骤:
1、定义两个结点p与q,分别代表当前结点与前面一个结点
2、令当前结点p为链表头结点,前面一个节点q为NULL
3、判断当前结点p是否为空。
3.1、如果不为空,则判断是否等于要找的那个number。
3.1.1、如果相等,则判断q是否存在。
3.1.1.1、如果q存在,则直接q->next=p->next,同时释放当前结点。
3.1.1.2、如果q不存在,说明头结点就是这个number,让链表头结点为p->next
3.1.2、如果不相等,q=p,p=p->next,转移到下一个结点进行检查
3.2、如果为空,则说明是个空链表
大概流程如下图所示:
代码如下所示:
void del(List *pList, int number)
{
Node *q;
Node *p;
for (q = NULL, p = pList->head; p; q = p, p = p->next) {
if (p->value == number) {
if (q) {
q->next = p->next;
}
else {
pList->head = p->next;
}
free(p);
break;
}
}
}
2.6 清除链表
清除整个链表需要两个结点,一个记录当前要清除的结点,一个记录下一个结点所在位置。
void clear(List *pList)
{
Node* p;
Node* q;
for (p = pList->head; p; p = q) {
q = p->next;
free(p);
}
}
2.7 总代码与测试
总体代码如下:
node.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include "node.h"
#include <stdio.h>
#include <stdlib.h>
typedef struct _node {
int value;
//不要用Node *next,因为编译器这时候还不知道Node
struct _node *next;
} Node;
typedef struct _list {
Node* head;
Node* tail;
}List;
void add(List* pList, int number);
void print(List *pList);
int search(List *pList, int number);
void del(List *pList, int number);
void clear(List *pList);
node.c
#define _CRT_SECURE_NO_WARNINGS
#include "node.h"
#include <stdio.h>
#include <stdlib.h>
void add(List* pList, int number)
{
Node *p = (Node*)malloc(sizeof(Node));
p->value = number;
p->next = NULL;
if (pList->head) {
pList->tail->next = p;
pList->tail = p;
}
else
{
pList->head = p;
pList->tail = p;
}
}
void print(List *pList)
{
Node *p;
for (p = pList->head; p; p = p->next) {
printf("%d\t", p->value);
}
printf("\n");
}
int search(List *pList, int number)
{
Node *p;
int isFound = 0;
for (p = pList->head; p; p = p->next) {
if (p->value == number) {
isFound = 1;
break;
}
}
return isFound;
}
void del(List *pList, int number)
{
Node *q;
Node *p;
for (q = NULL, p = pList->head; p; q = p, p = p->next) {
if (p->value == number) {
if (q) {
q->next = p->next;
}
else {
pList->head = p->next;
}
free(p);
break;
}
}
}
void clear(List *pList)
{
Node* p;
Node* q;
for (p = pList->head; p; p = q) {
q = p->next;
free(p);
}
}
主函数
#define _CRT_SECURE_NO_WARNINGS
#include "node.h"
int main(int argc, char const *argv[])
{
//1、定义一个链表
List *pList = (List*)malloc(sizeof(List));
pList->head = pList->tail = NULL;
//2、向链表中添加结点
int number;
printf("请输入需要存入链表的数字,输入-1结束\n");
do {
scanf("%d", &number);
if (number != -1) {
add(pList, number);
}
}while(number != -1);
//3、打印链表中所有数据
printf("你的链表中的数据有以下这些:\n");
print(pList);
//4、查询链表中是否有特定数据
printf("请输入你想查询的数字是否在链表中:");
scanf("%d", &number);
if (search(pList, number)) {
printf("%d在链表中\n", number);
}
else {
printf("%d不在链表中\n", number);
}
//5、删除特定数据,并打印删除后的结果
printf("请输入你想删除的数据:");
scanf("%d", &number);
del(pList, number);
printf("删除%d后链表数据如下:\n", number);
print(pList);
//6、清空整个链表
printf("最后,我们清空整个链表\n");
return 0;
}
我们运行,并测试每一个功能,可以看出结果正确。