深入理解顺序表的回调函数设计与实现
在数据结构的学习中,顺序表是最基础的线性结构之一。而回调函数 的引入,能让顺序表的操作更具灵活性、可扩展性。本文将以 C 语言为例,完整分析如何将顺序表与回调函数结合,并分享从需求到实现的全过程。
顺序表与回调函数
- 深入理解顺序表的回调函数设计与实现
-
- 一、为什么要在顺序表中使用回调函数?
- 二、回调函数的基础:函数指针
- 三、顺序表操作的 "回调化" 实现
-
-
- 顺序表初始化与内存管理
-
- 增操作的实现(头插、尾插、指定位置插)
-
- 删操作的实现(头删、尾删、指定位置删)
-
- 辅助操作(打印、销毁)
-
- 四、回调函数的调用逻辑(test.c)
- 五、回调函数的优势与扩展场景
-
-
- 核心优势
-
- 扩展场景
-
- 六、总结
- 七、总代码
-
- ceqList.h头文件
- SeqList.c文件
- test.c文件
一、为什么要在顺序表中使用回调函数?
在传统的顺序表代码中,我们通常直接调用SLPushFront、SLPopBack等函数来完成增删操作。这种写法虽然直接,但存在两个明显的不足:
- 耦合性高:调用逻辑和操作实现紧密绑定,若要修改操作(比如添加日志、统计性能),需改动所有调用处。
- 扩展性差:新增操作或替换操作逻辑时,需要大面积修改代码。
而 ** 回调函数(通过函数指针间接调用函数)** 可以解决这些问题 ------ 它能让 "调用逻辑 " 和 "操作实现" 解耦,让代码更灵活、易维护。
二、回调函数的基础:函数指针
在 C 语言中,函数指针是实现回调的核心。它本质是一个 "指向函数的指针变量",可以像普通变量一样赋值、传递。
我们先定义几个与顺序表操作匹配的函数指针类型(以SeqList.h为例):
C
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<assert.h>
#include<string.h>
#include<stdlib.h>
typedef int ListDateType;
typedef struct CeqList
{
ListDateType* arr;
int size;
int capecity;
}SL;
// 函数指针类型:头插操作
typedef void (*SLPushFrontFunc)(SL*, ListDateType);
// 函数指针类型:尾插操作
typedef void (*SLPushBackFunc)(SL*, ListDateType);
// 函数指针类型:头删操作
typedef void (*SLPopFrontFunc)(SL*);
// 函数指针类型:尾删操作
typedef void (*SLPopBackFunc)(SL*);
// 函数指针类型:指定位置插入操作(假设SLPush的签名是 void SLPush(SL*, int 位置, SLDataType 元素))
typedef void (*SLPushPosFunc)(SL*, int, ListDateType);
// 函数指针类型:指定位置删除操作(假设SLPop的签名是 void SLPop(SL*, int 位置))
typedef void (*SLPopPosFunc)(SL*, int);
//顺序表基本操作类型
//定义顺序表
void CaqListInit(SL* ps);
//尾插
void SLPushBack(SL* ps, ListDateType x);
//头插
void SLPushFront(SL* ps, ListDateType x);
//随机加入元素
void SLPush(SL* ps, int pos, ListDateType x);
//随机删除
void SLPop(SL* ps, int pos);
//尾删元素
void SLPopBack(SL* ps);
//头删
void SLPopFront(SL* ps);
//顺序表销毁
void SLDestory(SL* ps);
//打印元素
void SLPrint(SL ps);
三、顺序表操作的 "回调化" 实现
接下来,我们需要实现顺序表的基础操作(这些操作是回调的 "具体执行逻辑")。
1. 顺序表初始化与内存管理
C
#define _CRT_SECURE_NO_WARNINGS
#include"SeqList.h"
//定义顺序表
void CaqListInit(SL* ps)
{
ps->arr = NULL;
ps->size = ps->capecity = 0;
}
//判断内存够不够
void SLCheckCapecity(SL* ps)
{
if (ps->capecity == ps->size)
{
//第一种情况,都为0
//用三目运算符
int NewCapecity = ps->capecity == 0 ? 4 : ps->capecity * 2;
//第二种情况,空间不够
//开辟新空间,tmp来接受
ListDateType* tmp = (ListDateType*)realloc(ps->arr, NewCapecity * sizeof(ListDateType));
if (tmp == NULL)
{
perror("realloc");
exit(1);
}
//空间申请成功
ps->arr = tmp;
ps->capecity = NewCapecity;
}
}
2. 增操作的实现(头插、尾插、指定位置插)
C
//头插
void SLPushFront(SL* ps, ListDateType x)
{
assert(ps);
SLCheckCapecity(ps);
for (int i = ps->size; i > 0; i--)
{
ps->arr[i] = ps->arr[i - 1];
}
ps->arr[0] = x;
ps->size++;
}
//尾插
void SLPushBack(SL* ps, ListDateType x)
{
assert(ps);
SLCheckCapecity(ps);
ps->arr[ps->size] = x;
ps->size++;
}
3. 删操作的实现(头删、尾删、指定位置删)
C
//头删
void SLPopFront(SL* ps)
{
assert(ps);
assert(ps->size > 0);
for (int i = 0; i < ps->size - 1; i++)
{
ps->arr[i] = ps->arr[i + 1];
}
ps->size--;
}
//尾删
void SLPopBack(SL* ps)
{
assert(ps);
assert(ps->size > 0);
ps->size--;
}
//随机删除
void SLPop(SL* ps, int pos)
{
assert(ps);
assert(pos >= 0 && pos < ps->size);
for (int i = pos; i < ps->size - 1; i++)
{
ps->arr[i - 1] = ps->arr[i];//arr[pos] = arr[pos + 1]
}
ps->size--;
}
4. 辅助操作(打印、销毁)
C
//打印元素
void SLPrint(SL ps)
{
for (int i = 0; i < ps.size; i++)
{
printf("%d ", ps.arr[i]);
}
printf("\n");
}
//顺序表的销毁
void SLDestory(SL* ps)
{
if (ps->arr)
{
free(ps->arr);
ps->arr = NULL;
}
ps->capecity = ps->size = 0;
}
四、回调函数的调用逻辑(test.c)
现在,我们在测试代码中通过函数指针来调用上述操作,实现 "回调" 逻辑。
C
#define _CRT_SECURE_NO_WARNINGS
#include"SeqList.h"
void menu()
{
printf("***********************************\n");
printf("**** 1.头插 2.尾插 3.随机插入 ****\n");
printf("**** 4.头删 5.尾删 6.随机删除 ****\n");
printf("**** 0.退出 ***\n");
printf("**********************************\n");
}
void SeqListTest()
{
SL sl;
CaqListInit(&sl);
// 定义函数指针,指向具体的操作函数
SLPushFrontFunc pushFront = SLPushFront;
SLPushBackFunc pushBack = SLPushBack;
SLPushPosFunc pushPos = SLPush;
SLPopFrontFunc popFront = SLPopFront;
SLPopBackFunc popBack = SLPopBack;
SLPopPosFunc popPos = SLPop;
int input = 0;
do
{
menu();
scanf("%d", &input);
ListDateType val = 0;
int pos = 0;
switch (input)
{
case 0:
printf("退出顺序表\n");
break;
case 1:
printf("请输入要添加的元素:");
scanf("%d", &val);
pushFront(&sl, val); // 回调头插函数
break;
case 2:
printf("请输入要添加的元素:");
scanf("%d", &val);
pushBack(&sl, val); // 回调尾插函数
break;
case 3:
printf("请输入位置和元素(位置 元素):");
scanf("%d %d", &pos, &val);
pushPos(&sl, pos, val); // 回调指定位置插入函数
break;
case 4:
popFront(&sl); // 回调头删函数
break;
case 5:
popBack(&sl); // 回调尾删函数
break;
case 6:
printf("请输入要删除的位置:");
scanf("%d", &pos);
popPos(&sl, pos); // 回调指定位置删除函数
break;
default:
printf("输入错误,请重新输入\n");
break;
}
if (input != 0)
{
SLPrint(sl); // 每次操作后打印顺序表
}
} while (input);
SLDestory(&sl);
}
int main()
{
SeqListTest();
return 0;
}
五、回调函数的优势与扩展场景
1. 核心优势
- 解耦性 :调用逻辑(
test.c)和操作实现(SeqList.c)完全解耦。修改操作逻辑时,无需改动调用处。 - 灵活性 :可在运行时动态替换函数指针指向的函数。例如,若要给 "头插" 添加日志,只需实现一个带日志的
SLPushFront变种,再将函数指针重新绑定即可。
C
```c
// 带日志的头插变种
void SLPushFront_WithLog(SL* psl, SLDataType x) {
printf("【日志】执行头插操作,元素:%d\n", x);
SLPushFront(psl, x); // 调用原头插逻辑
}
// 在测试代码中替换函数指针
SLPushFrontFunc pushFront = SLPushFront_WithLog; // 后续调用将带有日志
- 可扩展性:新增操作时,只需定义新的函数指针类型和实现函数,调用逻辑几乎无需改动。
2. 扩展场景
回调函数在实际开发中还有更多应用场景:
- 自定义比较 / 排序:比如实现一个支持自定义比较函数的顺序表排序。
- 事件驱动:在顺序表操作前后触发自定义 "事件"(如统计操作次数、性能监控)。
- 多态模拟:在 C 语言中模拟面向对象的 "多态" 特性,不同操作对应不同的函数实现。
六、总结
通过将顺序表操作与回调函数结合,我们实现了代码解耦、灵活扩展的目标。核心步骤是:定义函数指针类型 → 实现基础操作 → 通过函数指针调用操作。
这种设计不仅让顺序表的代码结构更优雅,也为后续复杂功能的扩展(如日志、性能监控、自定义策略)打下了基础。掌握回调函数的思路,也能帮助你在其他数据结构(如链表、栈、队列)中写出更灵活的代码。
七、总代码
ceqList.h头文件
C
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<assert.h>
#include<string.h>
#include<stdlib.h>
typedef int ListDateType;
typedef struct CeqList
{
ListDateType* arr;
int size;
int capecity;
}SL;
// 函数指针类型:头插操作
typedef void (*SLPushFrontFunc)(SL*, ListDateType);
// 函数指针类型:尾插操作
typedef void (*SLPushBackFunc)(SL*, ListDateType);
// 函数指针类型:头删操作
typedef void (*SLPopFrontFunc)(SL*);
// 函数指针类型:尾删操作
typedef void (*SLPopBackFunc)(SL*);
// 函数指针类型:指定位置插入操作(假设SLPush的签名是 void SLPush(SL*, int 位置, SLDataType 元素))
typedef void (*SLPushPosFunc)(SL*, int, ListDateType);
// 函数指针类型:指定位置删除操作(假设SLPop的签名是 void SLPop(SL*, int 位置))
typedef void (*SLPopPosFunc)(SL*, int);
//定义顺序表
void CaqListInit(SL* ps);
//尾插
void SLPushBack(SL* ps, ListDateType x);
//头插
void SLPushFront(SL* ps, ListDateType x);
//随机加入元素
void SLPush(SL* ps, int pos, ListDateType x);
//随机删除
void SLPop(SL* ps, int pos);
//尾删元素
void SLPopBack(SL* ps);
//头删
void SLPopFront(SL* ps);
//顺序表销毁
void SLDestory(SL* ps);
//打印元素
void SLPrint(SL ps);
SeqList.c文件
C
#define _CRT_SECURE_NO_WARNINGS
#include"SeqList.h"
//定义顺序表
void CaqListInit(SL* ps)
{
ps->arr = NULL;
ps->size = ps->capecity = 0;
}
//判断内存够不够
void SLCheckCapecity(SL* ps)
{
if (ps->capecity == ps->size)
{
//第一种情况,都为0
//用三目运算符
int NewCapecity = ps->capecity == 0 ? 4 : ps->capecity * 2;
//第二种情况,空间不够
//开辟新空间,tmp来接受
ListDateType* tmp = (ListDateType*)realloc(ps->arr, NewCapecity * sizeof(ListDateType));
if (tmp == NULL)
{
perror("realloc");
exit(1);
}
//空间申请成功
ps->arr = tmp;
ps->capecity = NewCapecity;
}
}
//尾插
void SLPushBack(SL* ps, ListDateType x)
{
assert(ps);
SLCheckCapecity(ps);
ps->arr[ps->size] = x;
ps->size++;
}
//头插
void SLPushFront(SL* ps, ListDateType x)
{
assert(ps);
SLCheckCapecity(ps);
for (int i = ps->size; i > 0; i--)
{
ps->arr[i] = ps->arr[i - 1];
}
ps->arr[0] = x;
ps->size++;
}
//尾删
void SLPopBack(SL* ps)
{
assert(ps);
assert(ps->size > 0);
ps->size--;
}
//头删
void SLPopFront(SL* ps)
{
assert(ps);
assert(ps->size > 0);
for (int i = 0; i < ps->size - 1; i++)
{
ps->arr[i] = ps->arr[i + 1];
}
ps->size--;
}
//随机加入
void SLPush(SL* ps, int pos, ListDateType x)
{
assert(ps);
assert(pos >= 0 && pos <= ps->size);
SLCheckCapecity(ps);
for (int i = ps->size; i > pos; i--)
{
ps->arr[i] = ps->arr[i - 1]; //arr[pos + 1] = arr[pos]
}
ps->arr[pos] = x;
ps->size++;
}
//随机删除
void SLPop(SL* ps, int pos)
{
assert(ps);
assert(pos >= 0 && pos < ps->size);
for (int i = pos; i < ps->size - 1; i++)
{
ps->arr[i - 1] = ps->arr[i];//arr[pos] = arr[pos + 1]
}
ps->size--;
}
//打印元素
void SLPrint(SL ps)
{
for (int i = 0; i < ps.size; i++)
{
printf("%d ", ps.arr[i]);
}
printf("\n");
}
//顺序表的销毁
void SLDestory(SL* ps)
{
if (ps->arr)
{
free(ps->arr);
ps->arr = NULL;
}
ps->capecity = ps->size = 0;
}
test.c文件
C
#define _CRT_SECURE_NO_WARNINGS
#include"SeqList.h"
void menu()
{
printf("***********************************\n");
printf("**** 1.头插 2.尾插 3.随机插入 ****\n");
printf("**** 4.头删 5.尾删 6.随机删除 ****\n");
printf("**** 0.退出 ***\n");
printf("**********************************\n");
}
void SeqListTest()
{
SL sl;
CaqListInit(&sl);
// 定义函数指针,指向具体的操作函数
SLPushFrontFunc pushFront = SLPushFront;
SLPushBackFunc pushBack = SLPushBack;
SLPushPosFunc pushPos = SLPush;
SLPopFrontFunc popFront = SLPopFront;
SLPopBackFunc popBack = SLPopBack;
SLPopPosFunc popPos = SLPop;
int input = 0;
do
{
menu();
scanf("%d", &input);
ListDateType val = 0;
int pos = 0;
switch (input)
{
case 0:
printf("退出顺序表\n");
break;
case 1:
printf("请输入要添加的元素:");
scanf("%d", &val);
pushFront(&sl, val); // 回调头插函数
break;
case 2:
printf("请输入要添加的元素:");
scanf("%d", &val);
pushBack(&sl, val); // 回调尾插函数
break;
case 3:
printf("请输入位置和元素(位置 元素):");
scanf("%d %d", &pos, &val);
pushPos(&sl, pos, val); // 回调指定位置插入函数
break;
case 4:
popFront(&sl); // 回调头删函数
break;
case 5:
popBack(&sl); // 回调尾删函数
break;
case 6:
printf("请输入要删除的位置:");
scanf("%d", &pos);
popPos(&sl, pos); // 回调指定位置删除函数
break;
default:
printf("输入错误,请重新输入\n");
break;
}
if (input != 0)
{
SLPrint(sl); // 每次操作后打印顺序表
}
} while (input);
SLDestory(&sl);
}
int main()
{
SeqListTest();
return 0;
}