摘要:本文将详细介绍一段用 C 语言实现线性表基本操作的代码,涵盖了线性表的初始化、扩容、插入、删除、查找等常见功能。通过对代码结构、函数实现以及整体系统流程的深入剖析,帮助读者更好地理解线性表在 C 语言中的应用,并给出了具体的使用示例和测试情况,方便大家学习和参考。
一、引言
线性表作为数据结构中最基础、最常用的一种结构,在程序开发中有着广泛的应用。无论是存储简单的数据序列,还是作为更复杂数据结构和算法的基础组成部分,掌握线性表的操作实现都是至关重要的。在 C 语言中,我们可以通过结构体和指针等语法特性来构建和操作线性表。今天,就和大家一起深入分析一段实现线性表操作的 C 语言代码,看看它是如何完成各项功能的,以及我们该如何使用它。
代码
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <conio.h>
#define InitSize 10
#define SATUS int
#define OK 1
#define ERROR 0
typedef int ElemType;
typedef struct {
ElemType * data;
int MaxSize;
int length;
} SqList;
SqList sl;
void InitList(SqList * pl);
void IncreaseSize(SqList * pl, int len);
void ShowList(SqList * pl);
SATUS ListInsert(SqList * pl, int i, ElemType e);
SATUS ListDelete(SqList * pl, int i, ElemType * pe);
ElemType GetElem(SqList * pl, int i);
int LocateElem(SqList * pl, ElemType e);
void test_insert(SqList * pl);
void test_del(SqList * pl);
void test_get(SqList * pl);
void test_locate(SqList * pl);
void _line();
void _show_menu();
void _full();
void _inc();
void _ins();
void _get();
void _loc();
void _del();
SATUS _execution_opt(char opt);
void sys();
int main() {
sys();
return 0;
}
void InitList(SqList * pl) {
pl->data = (ElemType *) malloc(sizeof(ElemType) * InitSize);
pl->length = 0;
pl->MaxSize = InitSize;
}
void IncreaseSize(SqList * pl, int len) {
int * p = pl->data;
pl->data = (ElemType *) malloc((pl->MaxSize + len) * sizeof(ElemType));
for (int i = 0; i < pl->length; i++) {
pl->data[i] = p[i];
}
pl->MaxSize += len;
free(p);
}
void ShowList(SqList * pl) {
printf("\n展示列表中\n");
_line();
printf("数据:\n");
for (int i = 0; i < pl->length; i++) {
printf("%d : %d\n", i + 1, pl->data[i]);
}
printf("长度 : %d\n", pl->length);
printf("容量 : %d\n", pl->MaxSize);
}
SATUS ListInsert(SqList * pl, int i, ElemType e) {
if (i < 1 || i > pl->length + 1) return ERROR;
if (i > pl->MaxSize) return ERROR;
if (pl->length+1 > pl->MaxSize) return ERROR;
for (int j = pl->length; j >= i; i++)
pl->data[j] = pl->data[j - 1];
pl->data[i - 1] = e;
pl->length++;
return OK;
}
ElemType GetElem(SqList * pl, int i) {
if (i < 0 || i >= pl->length+1)
return ERROR;
return pl->data[i-1];
}
SATUS ListDelete(SqList * pl, int i, ElemType * pe) {
if (i < 1 || i >= pl->length + 1)
return ERROR;
(*pe) = pl->data[i - 1];
for (int j = i; j < pl->length; j++)
pl->data[j - 1] = pl->data[j];
pl->length--;
}
int LocateElem(SqList * pl, ElemType e) {
for (int i = 0; i < pl->length; i++)
if (pl->data[i] == e)
return i+1;
return 0;
}
void test_insert(SqList * pl) {
srand(time(NULL));
InitList(pl);
IncreaseSize(pl, 10);
printf("\n插入元素中:\n");
for (int i = 1; i <= 10; i++) {
ElemType e = rand() % 20 + 10;
if (ListInsert(pl, i, e))
printf("pl->data[%d] <- %d 插入成功\n", i-1, e);
else
printf("插入失败\n");
}
ShowList(pl);
}
void test_del(SqList * pl) {
printf("\n删除元素:\n");
int idx = 0, e;
scanf("%d", &idx);
if (ListDelete(pl, idx, &e)) {
printf("已删除,删除 pl->data[%d] : [%d]\n", idx - 1, e);
} else
printf("idx 有误\n");
ShowList(pl);
}
void test_get(SqList * pl) {
printf("\n按位查找:\n");
for (int i = 0; i < pl->length; i++) {
ElemType e = GetElem(pl, i+1);
printf("pl->data[%d], 位序%d = %d\n", i, i+1, e);
}
}
void test_locate(SqList * pl) {
printf("\n测试 LocateElem 函数:\n");
ElemType e;
scanf("%d", &e);
int idx = LocateElem(pl, e);
if (idx)
printf("%d 存在于 List[%d]\n", e, idx);
else
printf("未找到元素\n");
}
void _line() {
printf("----------------------------\n");
}
void _show_menu() {
system("cls");
printf("\n------ 线性表操作系统 ------\n");
_line();
printf("0. 查看列表\n");
printf("f. 填充列表\n");
printf("1. 扩容列表\n");
printf("2. 插入元素\n");
printf("3. 访问元素\n");
printf("4. 查询位序\n");
printf("5. 删除元素\n");
printf("q. 退出\n");
_line();
printf("你的选择:");
}
void _full() {
srand(time(NULL));
ElemType e = -1;
for (int i = 0; i < sl.MaxSize; i++) {
e = rand() % 30 + 10;
if (ListInsert(&sl, i+1, e))
printf("List[%d] 填充 %d 成功\n", i+1, e);
else
printf("填充失败\n");
}
printf("填充完毕,共填充 %d 个元素\n", sl.MaxSize);
}
void _inc() {
int len = 0;
printf("输入扩容数量(0 退出):\n");
scanf("%d", &len);
if (len == 0) return ;
IncreaseSize(&sl, len);
printf("扩容成功,扩容 %d 位\n", len);
}
void _ins() {
ElemType e;
int idx;
printf("输入插入位序,元素值(用空格分开, 位序 = -1 退出):\n");
scanf("%d%d", &idx, &e);
if (idx == -1) return ;
if (ListInsert(&sl, idx, e))
printf("插入成功\n");
else
printf("插入失败\n");
}
void _get() {
int idx;
printf("输入访问位序,0 退出\n");
scanf("%d", &idx);
if (idx == 0) return ;
ElemType e = GetElem(&sl, idx);
if (e)
printf("List[%d] = %d\n", idx, e);
else
printf("未找到元素");
}
void _loc() {
ElemType e;
int idx = -1;
printf("输入你需要查询的值,(-1 退出)\n");
scanf("%d", &e);
if (e == -1) return ;
idx = LocateElem(&sl, e);
if (idx)
printf("%d 位于 List[%d]\n", e, idx);
else
printf("未找到指定值\n");
return ;
}
void _del() {
int idx = 0;
ElemType e;
printf("输入要删除元素的位序(-1 退出)\n");
scanf("%d", &idx);
if (idx == -1) return ;
if (ListDelete(&sl, idx, &e))
printf("已将 %d 在 List[%d] 中删除\n", e, idx);
else
printf("删除失败");
}
SATUS _execution_opt(char opt) {
fflush(stdin);
printf("\n");
switch (opt) {
case '0': ShowList(&sl); break;
case 'f': _full(); break;
case '1': _inc(); break;
case '2': _ins(); break;
case '3': _get(); break;
case '4': _loc(); break;
case '5': _del(); break;
case 'q': return ERROR;
default:
}
printf("\n【按任意键继续】");
_getch();
return OK;
}
void sys() {
SqList l;
char opt;
InitList(&sl);
do {
_show_menu();
opt = _getch();
if (!_execution_opt(opt))
break;
} while (1);
printf("\nByeBye~\n");
}
二、代码整体结构概述
这段 C 语言代码实现了一个简单的线性表操作的“操作系统”,用户可以通过命令行界面选择不同的操作来对线性表进行处理。下面我们先来看一下代码中涉及的主要部分:
(一)头文件引入
代码开头引入了几个必要的头文件:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <conio.h>
<stdio.h>
:提供了标准输入输出函数,比如printf
和scanf
,用于在控制台进行数据的显示和读取。<stdlib.h>
:包含了一些常用的函数,例如malloc
和free
,这对于动态内存分配和释放至关重要,在线性表的实现中,我们需要动态分配内存来存储表中的元素。<time.h>
:主要用于获取当前时间相关信息,在这里代码中用于初始化随机数生成器的种子,使得生成的随机数具有一定的随机性。<conio.h>
:这个头文件提供了一些控制台输入输出相关的非标准函数,像_getch
函数,它可以获取用户输入的一个字符且不需要用户按下回车键,增强了交互的即时性。
(二)宏定义与类型重定义
#define InitSize 10
#define SATUS int
#define OK 1
#define ERROR 0
typedef int ElemType;
typedef struct {
ElemType * data;
int MaxSize;
int length;
} SqList;
- 通过
#define
定义了一些常量,比如InitSize
表示线性表初始的容量大小为 10,OK
和ERROR
分别作为表示操作成功和失败的返回值代码(值为 1 和 0),方便函数返回结果的统一表示。 typedef
关键字用于给已有的数据类型定义新的别名,这里将int
类型重定义为ElemType
,使得代码在表示线性表中元素类型时更加清晰和易于修改(如果之后想更换元素类型,只需要修改这一处typedef
即可)。同时定义了结构体SqList
,它代表线性表,其中包含一个指向存储元素的内存空间的指针data
、线性表的最大容量MaxSize
和当前元素个数(即长度)length
。
(三)函数声明部分
void InitList(SqList * pl);
void IncreaseSize(SqList * pl, int len);
void ShowList(SqList * pl);
SATUS ListInsert(SqList * pl, int i, ElemType e);
SATUS ListDelete(SqList * pl, int i, ElemType * pe);
ElemType GetElem(SqList * pl, int i);
int LocateElem(SqList * pl, ElemType e);
void test_insert(SqList * pl);
void test_del(SqList * pl);
void test_get(SqList * pl);
void test_locate(SqList * pl);
void _line();
void _show_menu();
void _full();
void _inc();
void _ins();
void _get();
void _loc();
void _del();
SATUS _execution_opt(char opt);
void sys();
声明了一系列函数,这些函数大致可以分为两类:一类是实现线性表基本操作的函数,如初始化、插入、删除、查找等;另一类是用于测试这些基本操作以及构建整个命令行交互系统的辅助函数,例如显示菜单、根据用户输入执行相应操作等。
(四)全局变量定义
SqList sl;
定义了一个全局的线性表变量 sl
,它将在整个程序中被多个函数操作和访问,用于存储数据以及记录线性表的状态(长度和容量等)。
(五)主函数
int main() {
sys();
return 0;
}
主函数非常简洁,直接调用 sys
函数来启动整个线性表操作系统,最后返回 0 表示程序正常结束。
三、线性表基本操作函数详解
(一)初始化函数 InitList
void InitList(SqList * pl) {
pl->data = (ElemType *) malloc(sizeof(ElemType) * InitSize);
pl->length = 0;
pl->MaxSize = InitSize;
}
这个函数的作用是初始化一个线性表。它通过 malloc
函数动态分配了一段连续的内存空间,大小为 InitSize
(初始定义为 10 个 ElemType
类型元素所占空间)来存储线性表的元素,将线性表当前长度设置为 0,表示初始时表中没有元素,同时将最大容量设置为初始容量 InitSize
。
(二)扩容函数 IncreaseSize
void IncreaseSize(SqList * pl, int len) {
int * p = pl->data;
pl->data = (ElemType *) malloc((pl->MaxSize + len) * sizeof(ElemType));
for (int i = 0; i < pl->length; i++) {
pl->data[i] = p[i];
}
pl->MaxSize += len;
free(p);
}
当线性表中的元素个数快要达到当前最大容量时,就需要对其进行扩容操作。该函数首先保存原来数据区的指针 p
,然后重新分配一块更大的内存空间(新空间大小为原最大容量加上传入的扩容长度 len
),接着将原来存储的元素依次复制到新的内存空间中,更新最大容量,并释放原来的内存空间,从而实现了线性表的扩容功能。
(三)插入元素函数 ListInsert
SATUS ListInsert(SqList * pl, int i, ElemType e) {
if (i < 1 || i > pl->length + 1) return ERROR;
if (i > pl->MaxSize) return ERROR;
if (pl->length + 1 > pl->MaxSize) return ERROR;
for (int j = pl->length; j >= i; j--)
pl->data[j] = pl->data[j - 1];
pl->data[i - 1] = e;
pl->length++;
return OK;
}
此函数用于在线性表的指定位置 i
插入元素 e
。首先进行一系列合法性检查,比如插入位置是否超出范围(小于 1 或者大于当前长度加 1),是否超出最大容量等。如果检查通过,就将插入位置及之后的元素依次向后移动一位,然后将新元素插入到指定位置,最后更新线性表的长度,并返回操作成功的标志 OK
。
(四)删除元素函数 ListDelete
SATUS ListDelete(SqList * pl, int i, ElemType * pe) {
if (i < 1 || i >= pl->length + 1)
return ERROR;
(*pe) = pl->data[i - 1];
for (int j = i; j < pl->length; j++)
pl->data[j - 1] = pl->data[j];
pl->length--;
}
用于删除线性表中指定位置 i
的元素。同样先进行位置合法性判断,若合法,则将要删除的元素通过指针 pe
返回给调用者,然后将该位置之后的元素依次向前移动一位,覆盖要删除的元素,最后减少线性表的长度,表示成功删除了一个元素。
(五)按位查找函数 GetElem
ElemType GetElem(SqList * pl, int i) {
if (i < 0 || i >= pl->length + 1)
return ERROR;
return pl->data[i - 1];
}
根据给定的位序 i
查找线性表中对应的元素。先判断位序是否合法,若合法则返回该位置对应的元素值,若不合法则返回错误标志 ERROR
。
(六)按值查找函数 LocateElem
int LocateElem(SqList * pl, ElemType e) {
for (int i = 0; i < pl->length; i++)
if (pl->data[i] == e)
return i + 1;
return 0;
}
在线性表中查找值为 e
的元素,通过遍历整个线性表,一旦找到相等的元素,就返回其位序(注意这里返回的是从 1 开始的位序,所以是 i + 1
),如果遍历完整个表都没有找到,则返回 0,表示未找到该元素。
四、测试与辅助函数介绍
(一)测试插入函数 test_insert
void test_insert(SqList * pl) {
srand(time(NULL));
InitList(pl);
IncreaseSize(pl, 10);
printf("\n插入元素中:\n");
for (int i = 1; i <= 10; i++) {
ElemType e = rand() % 20 + 10;
if (ListInsert(pl, i, e))
printf("pl->data[%d] <- %d 插入成功\n", i - 1, e);
else
printf("插入失败\n");
}
ShowList(pl);
}
这个函数主要用于测试插入元素的功能。首先初始化线性表并进行扩容,然后通过循环生成随机数作为要插入的元素,尝试将它们插入到线性表的不同位置,并根据插入结果输出相应提示信息,最后调用 ShowList
函数展示线性表的当前状态。
(二)其他测试函数
类似地,还有 test_del
、test_get
、test_locate
等函数,分别用于测试删除元素、按位查找元素以及按值查找元素的功能,它们的实现思路都是先进行一些必要的初始化操作或者接收用户输入,然后调用对应的基本操作函数,并根据返回结果输出相应提示信息,最后展示线性表的状态(部分函数有展示操作),方便查看操作是否正确执行。
(三)辅助显示函数
_line
函数:
void _line() {
printf("----------------------------\n");
}
用于输出一条分割线,起到美化控制台输出界面,区分不同功能模块显示内容的作用。
_show_menu
函数:
void _show_menu() {
system("cls");
printf("\n------ 线性表操作系统 ------\n");
_line();
printf("0. 查看列表\n");
printf("f. 填充列表\n");
printf("1. 扩容列表\n");
printf("2. 插入元素\n");
printf("3. 访问元素\n");
printf("4. 查询位序\n");
printf("5. 删除元素\n");
printf("q. 退出\n");
_line();
printf("你的选择:");
}
它的作用是清屏(通过 system("cls")
)后显示整个线性表操作系统的命令行菜单,列出了用户可以选择的各项操作,提示用户进行输入,构建了一个简单直观的交互界面。
(四)具体操作执行函数
例如 _full
、_inc
、_ins
、_get
、_loc
、_del
等函数,它们分别对应着菜单中各项操作的具体实现逻辑。比如 _full
函数用于填充线性表,通过循环生成随机数并插入到线性表中;_inc
函数实现了扩容操作,接收用户输入的扩容数量并调用 IncreaseSize
函数进行扩容;其他函数也都是类似地根据用户输入,调用对应的基本操作函数来完成相应功能。
(五)操作选择执行函数 _execution_opt
SATUS _execution_opt(char opt) {
fflush(stdin);
printf("\n");
switch (opt) {
case '0': ShowList(&sl); break;
case 'f': _full(); break;
case '1': _inc(); break;
case '2': _ins(); break;
case '3': _get(); break;
case '4': _loc(); break;
case '5': _del(); break;
case 'q': return ERROR;
default:
}
printf("\n【按任意键继续】");
_getch();
return OK;
}
这个函数根据用户输入的字符(代表不同的操作选项),通过 switch
语句调用相应的具体操作函数来执行对应的线性表操作,并且在操作完成后提示用户按任意键继续,同时返回操作执行的结果(成功或失败),用于控制整个系统的流程。
(六)系统主函数 sys
void sys() {
SqList l;
char opt;
InitList(&sl);
do {
_show_menu();
opt = _getch();
if (!_execution_opt(opt))
break;
} while (1);
printf("\nByeBye~\n");
}
sys
函数作为整个线性表操作系统的核心控制函数,首先初始化全局线性表 sl
,然后进入一个循环,不断显示菜单、获取用户输入,并根据输入执行相应操作,直到用户选择退出(输入 q
)为止,最后输出告别信息结束程序运行。
五、使用示例与测试情况
(一)启动程序
当编译并运行这段代码后,会首先看到控制台清屏并显示出线性表操作系统的菜单界面,如下所示:
------ 线性表操作系统 ------
----------------------------
0. 查看列表
f. 填充列表
1. 扩容列表
2. 插入元素
3. 访问元素
4. 查询位序
5. 删除元素
q. 退出
----------------------------
你的选择:
(二)查看列表(选项 0)
输入 0
并按下回车键,会调用 ShowList
函数展示当前线性表的状态,由于刚初始化,此时线性表长度为 0,容量为初始的 10
,数据部分没有实际元素显示,输出类似如下内容:
展示列表中
----------------------------
数据:
长度 : 0
容量 : 10
(三)填充列表(选项 f)
输入 f
并回车,会执行 _full
函数,通过随机数生成的方式向线性表中插入元素,填充完毕后会显示填充的元素个数以及相应提示信息,例如:
List[1] 填充 15 成功
List[2] 填充 22 成功
...
填充完毕,共填充 10 个元素
(四)扩容列表(选项 1)
选择 1
后,程序会提示输入扩容数量,输入一个正数(比如 5
)并回车,就会调用 IncreaseSize
函数对线性表进行扩容,扩容成功后会显示相应提示信息:
输入扩容数量(0 退出):
5
扩容成功,扩容 5 位
(五)插入元素(选项 2)
输入 2
进入插入元素操作,按照提示输入插入位序和元素值(用空格分开),例如输入 3 25
表示要在位置 3 插入元素 25,如果插入成功会显示插入成功的提示,否则显示插入失败提示,并且可以多次进行插入操作,输入 -1
可退出插入操作界面。
(六)访问元素(选项 3)、查询位序(选项 4)、删除元素(选项 5)
类似地,选择相应选项后,按照提示输入相应参数,即可执行按位查找元素、按值查找元素以及删除元素等操作,程序会根据操作结果输出对应的提示信息,方便我们了解操作是否成功以及线性表的当前状态。
六、总结与拓展
通过对这段 C 语言线性表操作代码的详细分析,我们可以看到它完整地实现了线性表从初始化、各种基本操作到命令行交互的一整套功能,对于学习 C 语言数据结构以及理解线性表的操作原理有着很好的参考价值。
在实际应用中,我们可以基于这个代码框架进一步拓展功能,比如可以将线性表的数据存储方式从顺序存储