【数据结构】顺序表+回调函数

深入理解顺序表的回调函数设计与实现

在数据结构的学习中,顺序表是最基础的线性结构之一。而回调函数 的引入,能让顺序表的操作更具灵活性、可扩展性。本文将以 C 语言为例,完整分析如何将顺序表与回调函数结合,并分享从需求到实现的全过程。

顺序表与回调函数

  • 深入理解顺序表的回调函数设计与实现
    • 一、为什么要在顺序表中使用回调函数?
    • 二、回调函数的基础:函数指针
    • 三、顺序表操作的 "回调化" 实现
        1. 顺序表初始化与内存管理
        1. 增操作的实现(头插、尾插、指定位置插)
        1. 删操作的实现(头删、尾删、指定位置删)
        1. 辅助操作(打印、销毁)
    • 四、回调函数的调用逻辑(test.c)
    • 五、回调函数的优势与扩展场景
        1. 核心优势
        1. 扩展场景
    • 六、总结
    • 七、总代码
      • ceqList.h头文件
      • SeqList.c文件
      • test.c文件

一、为什么要在顺序表中使用回调函数?

在传统的顺序表代码中,我们通常直接调用SLPushFrontSLPopBack等函数来完成增删操作。这种写法虽然直接,但存在两个明显的不足:

  • 耦合性高:调用逻辑和操作实现紧密绑定,若要修改操作(比如添加日志、统计性能),需改动所有调用处。
  • 扩展性差:新增操作或替换操作逻辑时,需要大面积修改代码。

而 ** 回调函数(通过函数指针间接调用函数)** 可以解决这些问题 ------ 它能让 "调用逻辑 " 和 "操作实现" 解耦,让代码更灵活、易维护。

二、回调函数的基础:函数指针

在 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;
}
相关推荐
大数据张老师3 小时前
数据结构——堆排序
数据结构·算法·排序算法
画个逗号给明天"3 小时前
C++十大排序算法
数据结构·c++·排序算法
xiaobobo33303 小时前
c语言中const关键字和枚举enum的新认识
c语言·开发语言·const·enum
仰泳的熊猫3 小时前
LeetCode:268. 丢失的数字
数据结构·c++·算法·leetcode
刘一说3 小时前
Spring Boot 主程序入口与启动流程深度解析:从 `@SpringBootApplication` 到应用就绪
java·spring boot·后端
合作小小程序员小小店3 小时前
web开发,在线%蛋糕销售%管理系统,基于asp.net,webform,c#,sql server
开发语言·后端·asp.net·html5·教育电商
小龙报3 小时前
《算法通关指南数据结构和算法篇(3)--- 栈和stack》
开发语言·数据结构·c++·算法·创业创新·学习方法·visual studio
怪力乌龟3 小时前
Go语言数组和切片
开发语言·后端·golang
Yeats_Liao3 小时前
Go Web 编程快速入门 08 - JSON API:编码、解码与内容协商
后端·golang·json