【数据结构】顺序表+回调函数
深入理解顺序表的回调函数设计与实现
在数据结构的学习中,顺序表是最基础的线性结构之一。而回调函数的引入,能让顺序表的操作更具灵活性、可扩展性。本文将以 C 语言为例,完整分析如何将顺序表与回调函数结合,并分享从需求到实现的全过程。
顺序表与回调函数
- 深入理解顺序表的回调函数设计与实现
- 一、为什么要在顺序表中使用回调函数?
- 二、回调函数的基础:函数指针
- 三、顺序表操作的 “回调化” 实现
- 1. 顺序表初始化与内存管理
- 2. 增操作的实现(头插、尾插、指定位置插)
- 3. 删操作的实现(头删、尾删、指定位置删)
- 4. 辅助操作(打印、销毁)
- 四、回调函数的调用逻辑(test.c)
- 五、回调函数的优势与扩展场景
- 1. 核心优势
- 2. 扩展场景
- 六、总结
- 七、总代码
- ceqList.h头文件
- SeqList.c文件
- test.c文件
一、为什么要在顺序表中使用回调函数?
在传统的顺序表代码中,我们通常直接调用SLPushFront、SLPopBack等函数来完成增删操作。这种写法虽然直接,但存在两个明显的不足:
- 耦合性高:调用逻辑和操作实现紧密绑定,若要修改操作(比如添加日志、统计性能),需改动所有调用处。
- 扩展性差:新增操作或替换操作逻辑时,需要大面积修改代码。
而 ** 回调函数(通过函数指针间接调用函数)** 可以解决这些问题 —— 它能让 “调用逻辑” 和 “操作实现” 解耦,让代码更灵活、易维护。
二、回调函数的基础:函数指针
在 C 语言中,函数指针是实现回调的核心。它本质是一个 “指向函数的指针变量”,可以像普通变量一样赋值、传递。
我们先定义几个与顺序表操作匹配的函数指针类型(以SeqList.h为例):
#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. 顺序表初始化与内存管理
#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. 增操作的实现(头插、尾插、指定位置插)
//头插
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. 删操作的实现(头删、尾删、指定位置删)
//头删
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. 辅助操作(打印、销毁)
//打印元素
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)
现在,我们在测试代码中通过函数指针来调用上述操作,实现 “回调” 逻辑。
#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
// 带日志的头插变种
void SLPushFront_WithLog(SL* psl, SLDataType x) {printf("【日志】执行头插操作,元素:%d\n", x);SLPushFront(psl, x); // 调用原头插逻辑
}// 在测试代码中替换函数指针
SLPushFrontFunc pushFront = SLPushFront_WithLog; // 后续调用将带有日志
- 可扩展性:新增操作时,只需定义新的函数指针类型和实现函数,调用逻辑几乎无需改动。
2. 扩展场景
回调函数在实际开发中还有更多应用场景:
- 自定义比较 / 排序:比如实现一个支持自定义比较函数的顺序表排序。
- 事件驱动:在顺序表操作前后触发自定义 “事件”(如统计操作次数、性能监控)。
- 多态模拟:在 C 语言中模拟面向对象的 “多态” 特性,不同操作对应不同的函数实现。
六、总结
通过将顺序表操作与回调函数结合,我们实现了代码解耦、灵活扩展的目标。核心步骤是:定义函数指针类型 → 实现基础操作 → 通过函数指针调用操作。
这种设计不仅让顺序表的代码结构更优雅,也为后续复杂功能的扩展(如日志、性能监控、自定义策略)打下了基础。掌握回调函数的思路,也能帮助你在其他数据结构(如链表、栈、队列)中写出更灵活的代码。
七、总代码
ceqList.h头文件
#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文件
#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文件
#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;
}
