1、回调函数
1.1、回调函数概念
- 回调函数就是通过函数指针调用的函数,如果把函数的指针作为参数传递给另外一个函数,当这个指针被用来调用所指向的函数时,我们就说这个函数为回调函数,是在A函数里面调用B函数,A函数称为回调函数。
1.2、函数指针
- 函数指针是指向函数的指针变量,函数指针本质实际就是一个变量,是一个指针变量(指针变量可以指向整型、字符型、自定义的结构体、数组等),这里的指针变量是指向函数;每一个函数都有一个入口地址,该入口地址就是函数指针所指向的地址,拿到了函数的入口地址,我们就可以根据自己的需要,在任何我们想调用函数的时刻去通过指针变量调用这个函数。
- 函数指针有两个用途:调用函数和做函数的参数。
示例:
/*函数指针用作调用函数使用*/
typedef void(*fun)(int data);
void fun1(int data1)
{
printf("data is:%d\n",data1);
}
fun Fun= &fun1; //fun为函数指针变量,可以定义一个Fun变量值为fun1函数的入口地址,,指向函数的指针变量没有++和--运算
Fun(20);//输出结果为20
/*函数指针用作函数的参数使用*/
typedef int(*fun)(int data);
int fun1(int data1)
{
return data1;
}
void fun2(int data2,fun para1 ) //定义两个形参,一个是int类型,另外一个是int (*)(int)类型的函数指针
{
printf("result is:%d\n",para1(data2));
}
fun fUn = fun1; //用fun自定义的类型定义一个fUn变量,值为fun1的函数入口地址
fun2(100,fUn);//输出结果为100
1.3、回调函数的真正面目
- 函数指针变量可以作为某个函数的参数使用时,回调函数就是通过一个函数指针调用的函数,简单说,回调函数就是由别人的函数执行时调用你实现的函数。
示例:
// 回调函数
void populate_array(int *array, size_t arraySize, int (*getNextValue)(void))
{
for (size_t i=0; i<arraySize; i++)
array[i] = getNextValue();
}
// 获取随机值
int getNextRandomValue(void)
{
return rand();
}
// 获取同样值
int getValue(void)
{
return 8;
}
int main(void)
{
int myarray[10];
int ayyay[10];
populate_array(myarray, 10, getNextRandomValue);/调用回调函数
for(int i = 0; i < 10; i++) {
printf("%d ", myarray[i]); //输出10个随机值
}
populate_array(array, 10, getValue);//调用回调函数
for(int i = 0; i < 10; i++) {
printf("%d ", myarray[i]);//输出10次8
}
printf("\n");
return 0;
}
- 回调函数的意义:可以把调用者和被调用者分开,所以调用者不关心谁是被调用者,它只需要知道存在一个具有特定原型 和限制条件的被调用函数即可。
2、链表
2.1、结构体中包含指向自己的指针变量
示例:
struct link
{
int data; //定义数据域
struct link *next; //定义指针域,存储直接后继的节点信息
}
- 定义一个结构体类型为link类型,结构体link类型中包含一个整型变量data,和一个指针变量next
- 有些同学可能会比较有疑惑,为什么link类型还没有被定义,就在结构体中struct link *next这样使用了,因为C语言允许不完全类型的声明,这里虽然是struct link *next,也可以用struct A *next等等不完全类型的声明,这里struct A是完全不存在的类型,但是如果写成struct link next;这种形式是不允许的,因为struct link还没有完全定义成一种类型,会报不存在这种类型的错误。
- 那么结构体中既然可以定义指向自己的指针,在定义结构体变量A时的时候,可以让A.next = &A;这样A结构体变量中的next指针就指向了A自己的地址,也可以指向struct link同类型结构体变量的地址,这样就可以把和A一样的结构体类型的变量串起来,这样就形成了链表。
2.2、链表的基本知识
- 链表的定义:链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列节点构成,每个节点包含两个部分:一个是用于存储数据元素的数据域,这个数据域可以是整型、字符串、结构体等变量,另外一个是存储下一个结点地址的指针变量。
- 链表相对于数组优缺点:使用链表结构可以克服数组链表需要预先知道数据大小的缺点,链表结构可以充分利用计算机的内存空间,实现灵活的内存动态管理,链表可以灵活的创建和删除、插入结点,但是链表失去了数组随机读取的有点,同时链表由于增加了结点的指针域,空间开销比数组大一点。
2.3、链表的结构
结点示例:
typedef struct NODE
{
int data;//数据域
struct NODE* next;//指针域
}Link_Node;
链表结点包括两个部分:
1、存储数据元素的数据域;
2、存储下一个结点地址的指针域;
如图为单链表结构图:
2.4、链表的分类
- 链表可以分为:单链表、双链表、循环链表
- 单链表结构体在2.3中已经画出
- 双链表结构体如下:
typedef struct NODE
{
struct Node *pPre;指向上一个节点的指针域
int data;//数据域
struct Node *pNext;//指向下一个节点的指针域
}Link_Node;
如图为单链表结构图:
2.5、链表的基本操作(基于单项链表)
2.5.1、创建链表前的基本概念
- 首节点是存放第一个有效数据的节点,尾结点是存放最后一个有效数据的节点。
- 头结点的数据类型与首节点的数据类型相同,并且头结点是首节点的前面一个节点,指向首节点,头结点一般不存放数据,头结点的存在只是为了方便链表的操作。
- 头指针是指向单链表的第一个结点,如果单链表有头结点,则头指针指向头结点,如果单链表没有头结点,则头指针指向第一个首元结点。
如图为带有头指针和头结点的单链表:
如图为带有头指针和无头结点的单链表:
2.5.2、创建单链表结点域
typedef struct _NODE
{
int data;//数据域
struct NODE* next;//指针域
}Node;
2.5.3、创建单链表
Node * Headp = (Node*)malloc(sizeof(Node)); //创建头结点
Node *Temp = Headp; //创建指向头结点的指点
for(int i = 10;i<10;i++)
{
Node *User = (Node*)malloc(sizeof(Node)); //创建链表结点
User->data = i;
User->next = NULL;
Temp->next = User;
Temp = Temp->next;
}
2.5.4、查询链表中的数据
int SelectNode(Node *Headp,int NunData)
{
Node *FindData = Headp;
int i = 1;
while(FindData->next)
{
FindData = FindData->next;
if(FindData->data = NunData)
{
printf("您查找的节点为:%d\n",i);
return i;
}
i++
}
printf("没有你要查找的节点!\n");
return -1;
}
2.5.5、删除链表中的指定结点
int DelNode(Node *Headp,int delData)
{
Node * Temp = Headp; //指向头结点
//指向被删除结点的前一个结点
for(int i = 1;i<delData;i++)
{
Temp = Temp->next;
}
Node *Del = Temp->next;
Temp->next = Temp->next->next;
free(Del); //释放被删除的结点,防止内存泄漏
}
2.5.6、向链表指定位置插入新结点
int InsNode(Node *Headp,int insData,int insNode)
{
Node * Temp = Headp;
for(int i = 1;i<insNode;i++)
{
Temp = Temp->next;
}
Node *newNode = (Node*)malloc(sizeof(Node));
newNode->data = insData;
newNode->next = Temp->next;
Temp->next = newNode;
}
3、队列
3.1、队列的定义
队列是一种操作受限的线性表,其限制条件为允许在表的一端进行插入,而在表的另一端进行删除。插入的一端叫做对尾,删除的一端叫做队头。向队列中插入新元素的行为成为进队,从队列中删除元素的行为成为出队。
3.2、队列的特点
- 数据从队列的一端进,从队列的另外一端出;
- 数据的入队和出队遵循“先进先出”的原则;
因此,只要使用哦顺序表按以上两个要求操作数据,即可实现顺序队列