选用教材为“数据结构(C语言版 第三版)李冬梅 严蔚敏 吴伟民编著”
实验工具使用手册
工具:CLion【A cross-platform IDE for C and C++】--对非商业用途免费(薅它)

安装:详细步骤略,注意在初始化选项中,勾选所有选项。
新建项目:以新建c语言项目为例-->
插件:安装通义灵码插件,使用情况如下,帮助回答代码问题,辅助编程。


实验一线性表的顺序表示和实现:
- 顺序表初始化;
- 在顺序表上插入元素;
- 删除顺序表上的元素;
- 在顺序表上定位;
- 输出顺序表;
- 销毁顺序表。
线性表的顺序结构定义
#define LIST_INIT_SIZE 100 // 线性表存储空间的初始分配量
#define LISTINCREMENT 10 // 线性表存储空间的分配增量
typedef struct {
ElemType *elem; // 存储空间基址
int length; // 当前长度
int listsize; // 当前分配的存储容量
// (以sizeof(ElemType)为单位)
}SqList; // 俗称 顺序表
相关算法
Status InitList_Sq( SqList& L ) {
// 构造一个空的线性表
L.elem = (ElemType*) malloc (LIST_
INIT_SIZE*sizeof (ElemType));
if (!L.elem) exit(OVERFLOW);
L.length = 0;
L.listsize = LIST_INIT_SIZE
return OK;
} // InitList_Sq
int LocateElem_Sq(SqList L, ElemType e, Status (*compare)(ElemType, ElemType)) {
// 在顺序表中查询第一个满足判定条件的数据元素,
// 若存在,则返回它的位序,否则返回 0
i = 1; // i 的初值为第 1 元素的位序
p = L.elem; // p 的初值为第 1 元素的存储位置
while (i <= L.length &&
!(*compare)(*p++, e)) ++i;
if (i <= L.length) return i;
else return 0;
} // LocateElem_Sq
status ListInsert_Sq(sqlist &L,int i,ElemType e){
If (i<1||i>L.length+1) return ERROR;
If (L.length>=L.listsize){
Newbase =(elemtype*)realloc(L.elem,
(L.listsize+listincrement)*sizeof(ElemType));
if (!newbase) exit (overflow);
l.elem =newbase;
l.listsize+=listincrement;
}
q=&(L.elem[i-1]); // q 指示插入位置
for (p=&(L.elem[L.length-1]);p>=q;--p) *(p+1)=*p;
// 插入位置及之后的元素右移
*q=e; // 插入e
++L.length; // 表长增1
return OK;
}
status listdelete_sq(sqlist &L,int i,elemtype &e){
if ((i<1)||(i>L.length)) return error;
p=&(L.elem[i-1]); // p 为被删除元素的位置
e=*p; // 被删除元素的值赋给 e
q=L.elem+L.length-1; // 表尾元素的位置
for (++p;p<=q;++p) *(p-1)=*p; // 被删除元素之后的元素左移
--L.length; // 表长减1
return ok;
}
实验要求
(1) 程序要添加适当的注释,程序的书写要采用缩进格式。
(2) 程序要具有一定的健壮性,即当输入数据非法时,程序也能适当地做出反应,如插入删除时指定的位置不对等等。
(3) 程序要做到界面友好,在程序运行时用户可以根据相应的提示信息进行操作。
程序代码
// 顺序表 (C语言版)
#include <stdio.h>//包含标准输入输出库头文件,提供基本的输入输出函数,如printf()和scanf()
#include <stdlib.h>//提供内存管理、程序控制等常用函数,在代码中用于 malloc()、realloc()、free() 等动态内存操作,也用于 exit() 函数来终止程序
#include <stdbool.h>//提供 bool、true、false 等布尔类型支持,使代码可以使用布尔逻辑进行条件判断
// 初始化存储的元素个数
#define INIT_SIZE 10
// 增长因子:每次增长为原来的两倍
#define ADD_TWICE 2
// 存储的元素类型,这里使用 int 类型,可根据需要修改为对应的数据类型
typedef int ElemType;
// 顺序表的存储结构
typedef struct {
ElemType* data;
int max_size; // 表中最多存储元素个数
int now_size; // 当前表中存储元素个数
} Sqlist;
// 显示菜单
//\t表示一个制表符,制表符作用是把输出位置移到下一个Tab位,Tab位是8个字符的倍数,Tab位是4个字符的倍数,Tab位是2个字符的倍数,Tab位是1个字符的倍数
// \n表示一个回车符,回车符作用是把输出位置移到下一行开头
void show() {
printf("\n\n\t顺序表基础操作\n");
printf("\t1--新建顺序表\n");
printf("\t2--表尾插入数据\n");
printf("\t3--遍历显示顺序表\n");
printf("\t4--当前表长\n");
printf("\t5--输出特定位置上的元素\n");
printf("\t6--输出特定元素的位置\n");
printf("\t7--在特定的位置上插入数据\n");
printf("\t8--删除特定位置上的元素\n");
printf("\t9--销毁顺序表\n");
}
bool judge_fulled(Sqlist* sq) {
return sq->max_size == sq->now_size;
}
bool judge_empty(Sqlist* sq) {
return sq->now_size == 0;
}
// 初始化顺序表
bool init_sqlist(Sqlist* sq) {
sq->data = (ElemType*)malloc(sizeof(ElemType) * INIT_SIZE);
if(sq->data == NULL) {
printf("开辟顺序表数据存储空间失败\n");
exit(-1);
}
sq->max_size = INIT_SIZE;
sq->now_size = 0;
return true;
}
// 销毁顺序表
bool delete_sqlist(Sqlist* sq) {
if(sq == NULL) {
printf("当前表不存在\n");
return false;
}
if(judge_empty(sq)) {
printf("当前表内为空\n");
return false;
}
free(sq->data);
sq->data = NULL;
printf("已销毁\n");
return true;
}
// 顺序表扩容
bool sq_growth(Sqlist* sq) {
ElemType* new_data = (ElemType*)realloc(sq->data, sizeof(ElemType) * sq->max_size * ADD_TWICE);
if(new_data == NULL) {
printf("扩容失败\n");
exit(-1);
}
sq->data = new_data;
sq->max_size *= ADD_TWICE;
return true;
}
// 插入数据函数
bool add_data(Sqlist* sq, ElemType data) {
if(judge_fulled(sq)) sq_growth(sq);
sq->data[sq->now_size++] = data;
return true;
}
// 批量插入数据
bool add_datas(Sqlist* sq) {
int count, i;
ElemType temp;
printf("请输入要插入元素的个数:");
scanf("%d", &count);
for(i = 0; i < count; i++) {
printf("请输入第%d个元素:", i+1);
scanf("%d", &temp);
add_data(sq, temp);
}
printf("插入完成\n");
return true;
}
// 显示顺序表
bool sq_show(Sqlist* sq) {
if(sq == NULL) {
printf("当前表不存在\n");
return false;
}
if(judge_empty(sq)) {
printf("当前表内为空\n");
return false;
}
printf("表内元素为:");
for(int i = 0; i < sq->now_size; i++)
printf("%d ", sq->data[i]);
printf("\n");
return true;
}
// 获取表长
int sq_length(Sqlist* sq) {
return sq ? sq->now_size : 0;
}
// 输出指定位置的元素
bool get_data(Sqlist* sq, int addr) {
addr -= 1;
if(addr < 0 || addr >= sq->now_size) {
printf("访问位置不合法\n");
return false;
}
printf("此位置上的元素为:%d\n", sq->data[addr]);
return true;
}
// 输出指定元素位置
bool get_addr(Sqlist* sq, ElemType data) {
for(int i = 0; i < sq->now_size; i++) {
if(sq->data[i] == data) {
printf("此元素在表中处于第%d个位置\n", i+1);
return true;
}
}
printf("此元素不存在于表中\n");
return false;
}
// 在指定位置插入数据
bool insert_data(Sqlist* sq, ElemType data, int addr) {
addr -= 1;
if(addr < 0 || addr > sq->now_size) {
printf("此位置不合法\n");
return false;
}
if(judge_fulled(sq)) sq_growth(sq);
for(int i = sq->now_size; i > addr; i--)
sq->data[i] = sq->data[i-1];
sq->data[addr] = data;
sq->now_size++;
return true;
}
//在指定位置删除数据
bool delete_data(Sqlist* sq, int addr) {
addr -= 1;
if(addr < 0 || addr >= sq->now_size) {
printf("此位置不合法\n");
return false;
}
for(int i = addr; i < sq->now_size-1; i++)
sq->data[i] = sq->data[i+1];
sq->now_size--;
return true;
}
// 菜单功能选择
void switch_channel(int channel, Sqlist* sq) {
switch(channel) {
case 1: init_sqlist(sq); break;
case 2: add_datas(sq); break;
case 3: sq_show(sq); break;
case 4: printf("当前表长:%d\n", sq_length(sq)); break;
case 5: {
int addr;
printf("请输入查询的元素位置:");
scanf("%d", &addr);
get_data(sq, addr);
} break;
case 6: {
int data;
printf("请输入查询的元素:");
scanf("%d", &data);
get_addr(sq, data);
} break;
case 7: {
int addr, data;
printf("请输入插入的元素:");
scanf("%d", &data);
printf("请输入插入元素的位置:");
scanf("%d", &addr);
insert_data(sq, data, addr);
} break;
case 8: {
int addr;
printf("请输入删除的元素位置:");
scanf("%d", &addr);
delete_data(sq, addr);
} break;
case 9:
delete_sqlist(sq);
break;
}
}
int main() {
Sqlist s;
Sqlist* sq = &s;
int channel;
while (1) {
show();
scanf("%d", &channel);
//system("cls"); // Windows 清屏
switch_channel(channel, sq);
// system("pause");
}
return 0;
}
各部分函数的讲解:
- 菜单功能选择
switch_channel 函数是菜单功能选择的核心实现,它通过以下方式工作:
1. switch-case 语句
- 使用 switch 语句根据 channel 值选择执行不同的代码块
- 每个 case 对应一个菜单选项
2.需要用户输入的复杂操作
对于需要额外参数的操作(如查询、插入、删除),函数内嵌套了用户输入代码:
case 5: { // 查询特定位置元素
int addr;
printf("请输入查询的元素位置:");
scanf("%d", &addr); // 获取用户输入的位置
get_data(sq, addr); // 调用查询函数
} break;
3.执行流程:
main函数调用:
show(); // 显示菜单
scanf("%d", &channel); // 获取用户选择
switch_channel(channel, sq); // 执行对应功能
功能匹配:
- 用户输入数字1-9
- switch 语句匹配对应的 case
- 执行相应的函数调用
参数传递:
- 所有操作都需要 Sqlist* sq 指针作为参数
- 复杂操作额外获取用户输入参数
这种设计将用户界面与功能实现分离,使程序结构清晰,易于维护和扩展。
- 初始化顺序表
sq->data = (ElemType*)malloc(sizeof(ElemType) * INIT_SIZE);
这行代码是顺序表初始化的核心语句,用于动态分配内存空间:
1. malloc:
- C标准库函数,用于动态分配内存
- 在堆(heap)上分配指定大小的连续内存空间
2. sizeof(ElemType) * INIT_SIZE:
- 计算需要分配的总字节数
- sizeof(ElemType):单个元素占用的字节数(这里是int,通常为4字节)
- INIT_SIZE:初始容量大小(定义为10)
- 总共分配 4字节 × 10 = 40字节的内存空间
3. (ElemType*):
- 强制类型转换,将malloc返回的void*转换为ElemType*指针
- 使指针类型与数据类型匹配
4. sq->data:
- 将分配的内存地址赋值给顺序表结构体的data指针成员
- 使data指针指向这块新分配的内存空间
- 销毁顺序表
delete_sqlist 函数用于彻底删除顺序表并释放其占用的所有动态内存资源,防止内存泄漏。
详细实现过程
1. 安全检查阶段
// 检查顺序表指针是否存在
if(sq == NULL) {
printf("当前表不存在\n");
return false;
}
- 首先验证传入的顺序表指针是否有效
- 防止对空指针进行解引用操作导致程序崩溃
// 检查顺序表是否为空
if(judge_empty(sq)) {
printf("当前表内为空\n");
return false;
}
- 调用 judge_empty 函数检查表中是否有数据
- 如果表为空,提示用户并返回(避免销毁一个空表)
2. 资源释放阶段
free(sq->data); // 释放数据存储空间
sq->data = NULL; // 避免野指针
- 使用 free()函数释放通过malloc()分配的内存
- 将指针置为 NULL,防止后续误用造成内存访问错误
- 顺序表扩容
这个 sq_growth 函数用于顺序表的动态扩容,当表满时自动增加存储容量。
1. 核心扩容操作
ElemType* new_data = (ElemType*)realloc(sq->data, sizeof(ElemType) * sq->max_size * ADD_TWICE);
- 使用 realloc 函数重新分配内存:
- 第一个参数:原内存指针 sq->data
- 第二个参数:新的内存大小 = 原大小 × 扩容因子
- sizeof(ElemType) * sq->max_size * ADD_TWICE 计算新内存大小
- ADD_TWICE 定义为2,表示容量翻倍
2. 错误处理
if(new_data == NULL) {
printf("扩容失败\n");
exit(-1);
}
- 检查 realloc 是否成功分配内存
- 如果分配失败(返回NULL),输出错误信息并终止程序
3. 更新顺序表信息
sq->data = new_data; // 更新数据指针
sq->max_size *= ADD_TWICE; // 更新最大容量(翻倍)
- 将新分配的内存地址赋给 sq->data
- 将最大容量更新为原来的2倍
扩容时机:
当 judge_fulled 判断表已满时,add_data 和 insert_data 函数会自动调用此函数进行扩容。
内存管理特点:
1. 保持数据不变:realloc 会尽量在原地址扩展内存,如果不行则分配新内存并复制原数据
2. 容量翻倍:每次扩容都将容量增加一倍,减少频繁扩容的开销
3. 自动处理:对用户透明,无需手动调用
示例场景:
初始状态:容量10个元素,已存10个 → 表满
执行插入:触发扩容
扩容后:容量20个元素,已存10个 → 可继续插入10个元素
-->这个函数是实现动态顺序表的关键,保证了顺序表可以根据需要自动增长容量。
- 获取表长
int sq_length(Sqlist* sq) {
return sq ? sq->now_size : 0;
}
这行代码是 sq_length 函数的返回语句,使用了条件运算符(三元运算符)来简洁地处理边界情况:
语法结构:
这是C语言中的条件运算符,格式为:条件 ? 值1 : 值2
执行逻辑:
1. 条件判断:sq
- 检查 sq 指针是否为非空(真值)
2. 分支处理:
- 如果 sq 不为空(true):返回 sq->now_size(当前元素个数)
- 如果 sq 为空(false):返回 0
等价写法:
这段代码等价于以下if-else写法:
if(sq != NULL) {
return sq->now_size;
} else {
return 0;
}
设计意义:
1. 安全性考虑
- 防止对空指针解引用导致程序崩溃
- 当顺序表未初始化或已被销毁时,sq 可能为 NULL
2. 逻辑合理性
- 如果顺序表不存在,长度自然为0
- 避免返回未定义的值
3. 代码简洁性
- 用一行代码完成安全检查和值返回
- 比传统的if-else写法更简洁
使用场景:
当用户选择菜单选项4"当前表长"时,会调用此函数:
case 4: printf("当前表长:%d\n", sq_length(sq)); break;
1万+

被折叠的 条评论
为什么被折叠?



