用顺序表实现学生信息管理。这里放一下有哪些文件。
下面是具体实现的代码。
SeqList.h
#pragma once防止库函数的重复引用,因为库函数会在预编译的时候在程序中展开,会增大程序的体积
#pragma once #include <stdio.h> #include <stdlib.h> #include <assert.h> // 顺序表中存储的数据类型,可以根据自己的需要修改 typedef struct student { char name[20]; char sex[5]; int sno; int age; }SLDataType; typedef struct SeqList { SLDataType* a; //指向动态开辟的数组 int size; //记录存储数据个数 int capacity; //空间容量大小 }SL; // 打印 void SLPrint(SL* ps); // 初始化与销毁 void SLInit(SL* ps); void SLDestroy(SL* ps); // 尾插尾删 void SLPushBack(SL* ps, SLDataType* x); void SLPopBack(SL* ps); // 头插头删 void SLPushFront(SL* ps, SLDataType* x); void SLPopFront(SL* ps); // 顺序表查找 int SLFind(SL* ps, int x); int Find(SL* ps, int sno); // 顺序表在pos位置插入x void SLInsert(SL* ps, int pos, SLDataType* x); // 顺序表删除pos位置的值 void SLErase(SL* ps, int pos); // 扩容函数 void SLCheckCapacity(SL* ps);
main.c
因为重点在于数据结构顺序表的使用,所以直接给定一些数据,就不进行重复繁琐的数据输入工作了。
#include "SeqList.h" void TestSeqList() { SL sl; SLInit(&sl); SLDataType stu1 = { "张三", "男", 110701, 22 }; SLDataType stu2 = { "李四", "男", 110702, 21 }; SLDataType stu3 = { "王五", "女", 110703, 23 }; SLDataType stu4 = { "赵六", "女", 110704, 22 }; SLDataType stu5 = { "周七", "男", 110705, 23 }; SLPushBack(&sl, &stu1); SLPushBack(&sl, &stu2); SLPushBack(&sl, &stu3); SLPushBack(&sl, &stu4); SLPrint(&sl); SLInsert(&sl, 2, &stu5); SLPrint(&sl); // 查找可以有多种方式,按照不同的信息查找,这里只是以学号为例 int n = SLFind(&sl, 110703); printf("%d\n", n); SLErase(&sl, 2); SLPrint(&sl); SLDestroy(&sl); } int main() { TestSeqList(); return 0; }
SeqList
打印函数的实现,如果顺序表中的数据类型发生了改变,其他功能函数基本上不需要有什么变化, 打印函数对应修改一下就行了,毕竟打印需要涉及到具体的数据问题了。
void SLPrint(SL* ps) { assert(ps); int i; for (i = 0; i < ps->size; i++) { printf("%d %s %s %d\n", ps->a[i].sno, ps->a[i].name, ps->a[i].sex, ps->a[i].age); } printf("\n"); }
顺序表的初始化,相当于int a = 0;实际上这个功能并不是很必要,但是为了代码的规范,以及防止一些错误的出现,建议要进行初始化,一个合格的程序员应该具备初始化意识。
void SLInit(SL* ps) { assert(ps); ps->a = NULL; ps->size = 0; ps->capacity = 0; }
顺序表的销毁,因为采用的是动态开辟空间的方式,所以需要释放空间,如果不释放空间会造成内存泄露。这里我们需要先释放内存空间,然后再把指向这块内存空间的指针置为NULL,否则可能会出现非法访问的问题。之后的size和capacity也应该跟着置为0,一方面,空间已经销毁了,他具备的数据个数和容量本身就应该没有了。另一方面,防止让人误以为有数据或者有空间而去进行一些非法操作。
void SLDestroy(SL* ps) { assert(ps); if (ps->a) { free(ps->a); ps->a = NULL; ps->size = ps->capacity = 0; } }
检查是否需要扩容,只要在进行数据插入时都需要检查是否需要扩容,然后我们这里有头插尾插和在pos位置插入数据,所以单独把这一功能作为一个函数写出来。提高代码的复用,这样就不用每次都写这么一串代码出来。
注意:在动态开辟空间之后一定要检查空间是否开辟成功了,如果不检查的话可能会出现一些不可预料的错误。malloc和realloc开辟空间都会是返回一个指针,只是在开辟失败后返回的是一个空指针,所以我们只需要检查返回值是否是NULL就行了。
void SLCheckCapacity(SL* ps) { assert(ps); if (ps->size == ps->capacity) { // 判断是没有空间还是空间不够,如果是没有空间就给个初始空间,空间不够,就把空间扩大一倍 int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2; // 按照数据类型来进行空间扩容 SLDataType* tmp = (SLDataType*)realloc(ps->a, newCapacity * sizeof(SLDataType)); // 检查空间是否开辟成功 if (NULL == tmp) { perror("realloc fail"); exit(-1);//终止程序 } ps->a = tmp; ps->capacity = newCapacity; } }
顺序表的尾插和尾删,因为空间是动态开辟的,所以应该在插入数据之前使用SLCheckCapacity()来确保空间是能够允许插入一个数据,在数据插入之后元素个数要加一,不然加了和没加没有区别,因为虽然你空间上存在这个数据了,但是你的函数访问不到这个数据。
还有就是在删除数据的时候,我们并不需要真正的删除掉这个数据,而且要删除掉一个数据实际上并不好删。我们要做的只是让我们的程序访问不到已经被删除的数据就行了,也就是只要size减一就可以了。当我们在插入新数据时,如果插入位置是有数据的,这个数据就会被覆盖掉,所以我们删没删这个数据,实际上是没有影响的。就相当于他有一个初始值而已,但是初始值并不是不能被改变的。
void SLPushBack(SL* ps, SLDataType* x) { assert(ps); SLCheckCapacity(ps); ps->a[ps->size] = *x; ps->size++; } void SLPopBack(SL* ps) { assert(ps); assert(ps->size > 0); ps->size--; }
顺序表的头插和头删,这里和尾插尾删的区别不是很多,不过有一个点需要注意的是,头插和头删需要移动数据。头插时需要将数据挨着向后移动一位,然后在头部插入数据。头删时,也是采用的覆盖,数据向前移动覆盖掉前一个数据就行了。
void SLPushFront(SL* ps, SLDataType* x) { assert(ps); // 在数据插入前检查是否需要扩容 SLCheckCapacity(ps); int end = ps->size - 1; while (end >= 0) { ps->a[end + 1] = ps->a[end]; end--; } ps->a[0] = *x; ps->size++; } void SLPopFront(SL* ps) { assert(ps); assert(ps->size > 0); int begin = 0; while (begin < ps->size - 1) { ps->a[begin] = ps->a[begin + 1]; begin++; } ps->size--; }
在pos插入或者删除数据,这组函数的功能实际上是包括头插头删、尾插尾删的,可以用这组函数分别头插头删、尾插尾删。
void SLInsert(SL* ps, int pos, SLDataType* x) { assert(ps); assert(pos >= 0); assert(pos <= ps->size); // 在数据插入前检查是否需要扩容 SLCheckCapacity(ps); int end = ps->size - 1; while (end >= pos) { ps->a[end + 1] = ps->a[end]; end--; } ps->a[pos] = *x; ps->size++; } void SLErase(SL* ps, int pos) { assert(ps); assert(ps->size >= pos); int ret = pos; while (ret < ps->size - 1) { ps->a[ret] = ps->a[ret + 1]; ret++; } ps->size--; }
查找有多种方式,如顺序查找、二分查找这些,也可以按学号、姓名、年龄这些的来查找,我这里只是写了一个按学号进行的顺序查找。
// 查找数据 int SLFind(SL* ps, int x) { return Find(ps, x); } int Find(SL* ps, int sno) { int i = 0; while (i < ps->size) { if (ps->a[i].sno == sno) return i; i++; } return -1; }