通俗理解回调函数、链表、队列

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、队列的特点

  • 数据从队列的一端进,从队列的另外一端出;
  • 数据的入队和出队遵循“先进先出”的原则;

因此,只要使用哦顺序表按以上两个要求操作数据,即可实现顺序队列

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值