深度剖析指针(中)——“C”

简介: 深度剖析指针(中)——“C”

各位CSDN的uu们你们好呀,今天小雅兰的内容仍旧是深度剖析指针噢,在上一篇博客中,我已经写过了字符指针、数组指针、指针数组、数组传参和指针传参的知识点,那么这篇博客小雅兰会讲解一下函数指针、函数指针数组 、指向函数指针数组的指针的知识点,现在,就让我们进入指针的世界吧


函数指针


函数指针数组


指向函数指针的数组


回调函数


函数指针


仍然是采用我们的类比法!!!


整型指针——指向整型的指针  int *


字符指针——指向字符的指针  char *


数组指针——指向数组的指针  int arr[10];   int (*p)[10]=&arr;


函数指针——指向函数的指针


数组指针中存放的是数组的地址


函数指针中存放的应该是函数的地址


那么,函数有地址吗?

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int Add(int x, int y)
{
  return x + y;
}
//&Add和Add就是一样的,没有区别
int main()
{
  printf("%p\n", Add);
  printf("%p\n", &Add);
  return 0;
}

1f358c17113446bca3ac501230d0e44d.png

输出的是两个地址,这两个地址是 test 函数的地址。

那我们的函数的地址要想保存起来,怎么保存?

下面我们看代码:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int Add(int x, int y)
{
  return x + y;
}
//&Add和Add就是一样的,没有区别
int main()
{
  printf("%p\n", Add);
  printf("%p\n", &Add);
  //函数的地址要存起来,就得放在函数指针变量中
  //pf就是函数指针
  int (*pf)(int, int) = Add;
  int ret = (*pf)(3, 5);
  int ret = Add(3, 5);
  int ret = pf(3, 5);
  //这三种写法都是可以的
  //pf前面的这颗*就是一个摆设
  return 0;
}

下面,再来看看:

void test()
{
  printf("hehe\n");
}
//下面pfun1和pfun2哪个有能力存放test函数的地址?
void (*pfun1)();
void *pfun2();

首先,能给存储地址,就要求pfun1或者pfun2是指针,那哪个是指针?


答案是:pfun1可以存放。pfun1先和*结合,说明pfun1是指针,指针指向的是一个函数,指向的函数无参数,返回值类型为void。


阅读两段有趣的代码:


源于《C陷阱和缺陷》


(* ( void (*)() ) 0 )();


将0强制类型转化为void (*)()类型的函数指针

这就意味着0地址处放着一个函数,函数没参数,返回类型是void

调用0地址处的这个函数

其实这句代码的意思就是一次函数调用


void (   *signal (  int , void(*)(int) )   )(int);


signal括号里面只有类型,没有变量名,说明这是一个函数声明


void (*)(int) signal(int, void(*)(int));——可以这样理解


这句代码是一次函数声明

函数的名字是signal

signal函数的参数第一个是int类型,第二个是void(*)(int)类型的函数指针

该函数指针指向的函数参数是int,返回类型是void

signal函数的返回类型也是一个函数指针

该函数指针指向的函数参数是int,返回类型是void

可以把这句代码简化一下:


typedef void(*pfun_t)(int);


pfun_t signal(int, pfun_t);//将void(*)(int)重新起个别名叫pfun_t


注意:


typedef void (*pf_t2)(int);


//pf_t2是类型名


void (*pf)(int);


//pf是函数指针变量的名字


函数指针数组


数组是一个存放相同类型数据的存储空间,那我们已经学习了指针数组, 比如:

int *arr[10];
//数组的每个元素是int*

那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?

int (*parr1[10])();
int *parr2[10]();
int (*)() parr3[10];

答案是:parr1

parr1 先和 [] 结合,说明 parr1是数组,数组的内容是什么呢? 是 int (*)() 类型的函数指针。

函数指针数组的使用:

#include <stdio.h>
int Add(int a, int b)
{
  return a + b;
}
int Sub(int a, int b)
{
  return a - b;
}
int Mul(int a, int b)
{
  return a * b;
}
int Div(int a, int b)
{
  return a / b;
}
int main()
{
  //存放函数指针的数组——函数指针数组
  int (*pf[4])(int, int) = { Add,Sub,Mul,Div };
  //0 1 2 3
  int i = 0;
  for (i = 0; i < 4; i++)
  {
    int ret = pf[i](8, 4);
    printf("%d\n", ret);
  }
  return 0;
}

b1574cbb54fc4efc9babfb68c54969a8.png下面,我们来写一个计算器,来完成整数的+ - * /

#include <stdio.h>
int Add(int a, int b)
{
  return a + b;
}
int Sub(int a, int b)
{
  return a - b;
}
int Mul(int a, int b)
{
  return a * b;
}
int Div(int a, int b)
{
  return a / b;
}
void menu()
{
  printf("#######################################\n");
  printf("#######1.Add      2.Sub################\n");
  printf("#######3.Mul      4.Div################\n");
  printf("#######0.exit          ################\n");
  printf("#######################################\n");
}
int main()
{
  int input = 0;
  int x = 0;
  int y = 0;
  int ret = 0;
  do
  {
    menu();
    printf("请选择:>\n");
    scanf("%d", &input);
    printf("请输入两个操作数:>\n");
    scanf("%d %d", &x, &y);
    switch (input)
    {
    case 1:
      ret=Add(x, y);
      break;
    case 2:
      ret=Sub(x, y);
      break;
    case 3:
      ret=Mul(x, y);
      break;
    case 4:
      ret=Div(x, y);
      break;
    case 0:
      printf("退出计算器\n");
      break;
    default:
      printf("选择错误,请重新选择\n");
      break;
    }
    printf("%d\n", ret);
  } while (input);
  return 0;
}

很轻松的,一个简易计算器的功能就实现了,我们来运行一下这个程序

c4ed158c4be7444b93ce9f99ea40aa0b.png

6a7e422a554c4270869c523a09845dd1.png

但是这有问题啊!!!我选择了一个8,理应打印选择错误,而不应该打印请输入两个操作数呀!!!也不应该再打印这个ret!!!所以,我们要把程序修改一下!!!

#include <stdio.h>
int Add(int a, int b)
{
  return a + b;
}
int Sub(int a, int b)
{
  return a - b;
}
int Mul(int a, int b)
{
  return a * b;
}
int Div(int a, int b)
{
  return a / b;
}
void menu()
{
  printf("#######################################\n");
  printf("#######1.Add      2.Sub################\n");
  printf("#######3.Mul      4.Div################\n");
  printf("#######0.exit          ################\n");
  printf("#######################################\n");
}
int main()
{
  int input = 0;
  int x = 0;
  int y = 0;
  int ret = 0;
  do
  {
    menu();
    printf("请选择:>\n");
    scanf("%d", &input);
    switch (input)
    {
    case 1:
      printf("请输入两个操作数:>\n");
      scanf("%d %d", &x, &y);
      ret = Add(x, y);
      printf("%d\n", ret);
      break;
    case 2:
      printf("请输入两个操作数:>\n");
      scanf("%d %d", &x, &y);
      ret = Sub(x, y);
      printf("%d\n", ret);
      break;
    case 3:
      printf("请输入两个操作数:>\n");
      scanf("%d %d", &x, &y);
      ret = Mul(x, y);
      printf("%d\n", ret);
      break;
    case 4:
      printf("请输入两个操作数:>\n");
      scanf("%d %d", &x, &y);
      ret = Div(x, y);
      printf("%d\n", ret);
      break;
    case 0:
      printf("退出计算器\n");
      break;
    default:
      printf("选择错误,请重新选择\n");
      break;
    }
  } while (input);
  return 0;
}

这样一修改,功能实现才完全正确,但是,还是有问题,这个代码代码冗余的问题非常严重!!!如果未来要增加一些其他的功能,例如:<<  >>  &   |    &&    ||  那么我们的代码会越写越长,case会·越写越多,这样显然是不太好的!!!

使用函数指针数组的实现:

#include <stdio.h>
int Add(int a, int b)
{
  return a + b;
}
int Sub(int a, int b)
{
  return a - b;
}
int Mul(int a, int b)
{
  return a * b;
}
int Div(int a, int b)
{
  return a / b;
}
void menu()
{
  printf("#######################################\n");
  printf("#######1.Add      2.Sub################\n");
  printf("#######3.Mul      4.Div################\n");
  printf("#######0.exit          ################\n");
  printf("#######################################\n");
}
int main()
{
  int input = 0;
  int x = 0;
  int y = 0;
  int ret = 0;
  int (*pfArr[])(int, int) = { 0,Add,Sub,Mul,Div };
                         //    0  1   2   3   4
  do
  {
    menu();
    printf("请选择:>\n");
    scanf("%d", &input);
    if (input == 0)
    {
      printf("退出计算器\n");
      break;
    }
    else if (input >= 1 && input <= 4)
    {
      printf("请输入两个操作数:>\n");
      scanf("%d %d", &x, &y);
      ret = pfArr[input](x, y);
      printf("%d\n", ret);
    }
    else
    {
      printf("选择错误\n");
    }
  } while (input);
  return 0;
}

函数指针数组的用途:转移表


指向函数指针数组的指针

指向函数指针数组的指针是一个指针,指针指向一个数组 ,数组的元素都是函数指针

#include <stdio.h>
int Add(int a, int b)
{
  return a + b;
}
int Sub(int a, int b)
{
  return a - b;
}
int main()
{
  int (*pf)(int, int) = Add;
  //函数指针数组
  int (*pfArr[4])(int, int) = { Add,Sub };
  //ppfArr是一个指向函数指针数组的指针变量
  int (*(*ppfArr)[4])(int, int) = &pfArr;
  return 0;
}

回调函数


回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个 函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。


回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。


之前我们实现了一个简易版计算器功能,但是那个代码的冗余问题非常严重,后面我们用函数指针数组的方法改造了一下这段代码,那现在,我们换一种新的方式来改造,就是我们的回调函数啦!!!


改造前:

#include <stdio.h>
int Add(int a, int b)
{
  return a + b;
}
int Sub(int a, int b)
{
  return a - b;
}
int Mul(int a, int b)
{
  return a * b;
}
int Div(int a, int b)
{
  return a / b;
}
void menu()
{
  printf("#######################################\n");
  printf("#######1.Add      2.Sub################\n");
  printf("#######3.Mul      4.Div################\n");
  printf("#######0.exit          ################\n");
  printf("#######################################\n");
}
int main()
{
  int input = 0;
  int x = 0;
  int y = 0;
  int ret = 0;
  do
  {
    menu();
    printf("请选择:>\n");
    scanf("%d", &input);
    switch (input)
    {
    case 1:
      printf("请输入两个操作数:>\n");
      scanf("%d %d", &x, &y);
      ret = Add(x, y);
      printf("%d\n", ret);
      break;
    case 2:
      printf("请输入两个操作数:>\n");
      scanf("%d %d", &x, &y);
      ret = Sub(x, y);
      printf("%d\n", ret);
      break;
    case 3:
      printf("请输入两个操作数:>\n");
      scanf("%d %d", &x, &y);
      ret = Mul(x, y);
      printf("%d\n", ret);
      break;
    case 4:
      printf("请输入两个操作数:>\n");
      scanf("%d %d", &x, &y);
      ret = Div(x, y);
      printf("%d\n", ret);
      break;
    case 0:
      printf("退出计算器\n");
      break;
    default:
      printf("选择错误,请重新选择\n");
      break;
    }
  } while (input);
  return 0;
}

改造后:

#include <stdio.h>
int Add(int a, int b)
{
  return a + b;
}
int Sub(int a, int b)
{
  return a - b;
}
int Mul(int a, int b)
{
  return a * b;
}
int Div(int a, int b)
{
  return a / b;
}
void menu()
{
  printf("#######################################\n");
  printf("#######1.Add      2.Sub################\n");
  printf("#######3.Mul      4.Div################\n");
  printf("#######0.exit          ################\n");
  printf("#######################################\n");
}
void Calc(int (*pf)(int,int))//函数指针
{
  int x = 0;
  int y = 0;
  int ret = 0;
  printf("请输入两个操作数:>");
  scanf("%d %d", &x, &y);
  ret = pf(x, y);
  printf("%d\n", ret);
}
int main()
{
  int input = 0;
  int x = 0;
  int y = 0;
  int ret = 0;
  do
  {
    menu();
    printf("请选择:>\n");
    scanf("%d", &input);
    switch (input)
    {
    case 1:
      Calc(Add);
      break;
    case 2:
      Calc(Sub);
      break;
    case 3:
      Calc(Mul);
      break;
    case 4:
      Calc(Div);
      break;
    case 0:
      printf("退出计算器\n");
      break;
    default:
      printf("选择错误,请重新选择\n");
      break;
    }
  } while (input);
  return 0;
}

00303c90bcfd412bbf5b4bdf1dd38589.png

也就是说:上述代码中的Add、Sub、Mul、Div都是回调函数!!!


之前用函数指针数组改造的那个代码实质上解决的是case语句过多的问题,而用回调函数改造的此代码实质上解决的是代码冗余的问题。


改造的两份代码解决的是完全不一样的问题,所以谈不上哪个方法更好!!!


其实回调函数还有更多内容,下一篇博客小雅兰带你玩转qsort,今天的内容就先到这里啦


好啦,告辞!!!

4836d8db7b2240389741bf2cd5fa21c7.jpg

相关文章
|
7月前
|
存储 算法 C语言
C语言深度剖析指针
C语言深度剖析指针
50 0
|
9月前
|
存储 C语言 C++
深度剖析C语言指针笔试题 Ⅱ
&lt;1&gt;主页:C语言的前男友 &lt;2&gt;知识讲解:C语言指针 &lt;3&gt;创作者:C语言的前男友 &lt;4&gt;开发环境:VS2022 &lt;5&gt;前言:继续练习指针的笔试题,今天的笔试题更有难度哦。欢迎大家前来指正,如果觉得作者写的还不错的话,请麻烦动动发财的小手,关注,点赞,收藏,评论。
|
10月前
|
存储
学C的第二十三天【继续深度剖析数据在内存中的存储:3. 浮点型在内存中的存储(重点);练习:1. 有序序列判断;2. 获得月份天数(多组输入);3. 使用指针打印数组内容;4. 使用指针使字符串逆序】-2
(4). 取出内存中的 指数E(三种情况):E全为1 指数E 是通过 真实值+中间值 算出来的,如果E全是1,(32位系统)说明E的真实值是 128,指数是128说明这个值是非常大的。 这时,如果 有效数字M 全为0,表示 ±无穷大(正负取决于符号位s)
深度剖析指针(下)——“C”
深度剖析指针(下)——“C”
|
存储 C语言 C++
深度剖析指针(上)——“C”
深度剖析指针(上)——“C”
|
C语言 索引
【数据结构】链表OJ特别篇 —— 面试情景带你深度剖析 环形链表系列问题 && 复制带随机指针的链表2
【数据结构】链表OJ特别篇 —— 面试情景带你深度剖析 环形链表系列问题 && 复制带随机指针的链表
82 0
【数据结构】链表OJ特别篇 —— 面试情景带你深度剖析 环形链表系列问题 && 复制带随机指针的链表2
|
机器学习/深度学习 存储 索引
【数据结构】链表OJ特别篇 —— 面试情景带你深度剖析 环形链表系列问题 && 复制带随机指针的链表
【数据结构】链表OJ特别篇 —— 面试情景带你深度剖析 环形链表系列问题 && 复制带随机指针的链表
97 0
【数据结构】链表OJ特别篇 —— 面试情景带你深度剖析 环形链表系列问题 && 复制带随机指针的链表
|
存储 C语言
⭐️ 关键字深度剖析 ⭐️第五章(深入C语言三种类型(float/bool/指针)与“零值“的比较)(续)
浮点数在内存中存储,并不想我们想的是完整存储的 在十进制转化成为二进制,是有可能有精度损失的
⭐️ 关键字深度剖析 ⭐️第五章(深入C语言三种类型(float/bool/指针)与“零值“的比较)(续)

热门文章

最新文章