C语言——指针进阶(2)

简介: C语言——指针进阶(2)

image.png

继续上次的指针,想起来还有指针的内容还没有更新完,今天来补上之前的内容,上次我们讲了函数指针,并且使用它来实现一些功能,今天我们就讲一讲函数指针数组等内容,废话不多说,我们开始今天的学习吧。

函数指针数组

在引出函数指针的时候,我们会先引出字符指针还有整型指针,那同样的道理,函数指针数组是什么,我们可能还是比较陌生,但是如果是字符指针数组,还有整型指针数组我们可能听起来不是特别陌生。

那再讲之前,我给大家先讲一下函数指针,函数指针是啥呢,函数指针竟然是指针,那就是来存放函数的地址的,那我们先写一个简单的函数,然后来存放它的地址。

int Add(int x, int y)
{
  return x + y;
}
int main()
{
  printf("%p\n", &Add);
  printf("%p\n", Add);
  return 0;
}

我们可以发现打印出来的两个地址都是同一个地址,所以表示两个方式都可以取出函数的地址

在这里我们可以看到我们的地址是一样的,那我们要存放他们的地址改怎么写呢

我们来看看

#include<stdio.h>
int Add(int x, int y)
{
  return x + y;
}
int main()
{
  printf("%p\n", &Add);
  printf("%p\n", Add);
  int (*pf)(int, int) = &Add;
  return 0;
}

这样我们就可以把它函数的地址写出来,这和那个数组指针也特别相似,取出的数组地址也是这样的。

因为时间太长,我帮大家在回忆一下我们的数组指针,比如我们现在要取出的是数组的地址,我们改怎么表示,让我们来看一看吧。

int main()
{
  int arr[] = { 1,2,3,4,5,6 };
  int(*pa)[6] = &arr;
  return 0;
}

在这里我们可以看到的是数组指针,是指向数组地址的指针,可以看到它的格式其实和函数指针的差不多。

那我们的函数指针有什么用呢,答案是可以调用这个函数,比如就我们上面的Add函数,我们来看代码。

int Add(int x, int y)
{
  return x + y;
}
int main()
{
  printf("%p\n", &Add);
  printf("%p\n", Add);
  int (*pf)(int, int) = &Add;
  return 0;
}

我们可以这样写

int Add(int x, int y)
{
  return x + y;
}
int main()
{
  printf("%p\n", &Add);
  printf("%p\n", Add);
  int (*pf)(int, int) = &Add;
  int ret = (*pf)(3, 5);
  printf("%d\n", ret);
  return 0;
}

在这里其实可以不写,意思就是int ret = (*pf)(3, 5);可以写成int ret = pf(3, 5);,其实这两个的结果是一样的,或者说就算我们加很多也不影响我们的结果,来看看吧

int Add(int x, int y)
{
  return x + y;
}
int main()
{
  printf("%p\n", &Add);
  printf("%p\n", Add);
  int (*pf)(int, int) = &Add;
  int ret = (*pf)(3, 5);
  printf("%d\n", ret);
  ret = printf("%d\n", (*********pf)(4, 6));
  return 0;
}

写了这么多主要是为了后面做铺垫,那我们现在才来讲我们的函数指针数组

数组是什么,我们之前见过字符指针数组,他是来存放字符指针,我们也讲过整型指针数组,它是来存放整型指针的数组,那同样函数指针数组就是来存放函数指针的,我们来举个列子

int Add(int x, int y)
{
  return x + y;
}
int Sub(int x, int y)
{
  return x - y;
}
int Mul(int x, int y)
{
  return x * y;
}
int Div(int x, int y)
{
  return x / y;
}
int main()
{
  int (*pf1)(int, int) = &Add;
  int (*pf2)(int, int) = &Sub;
  int (*pf3)(int, int) = &Mul;
  int (*pf4)(int, int) = &Div;
  return 0;
}

我们首先看到这么多的函数,他们的功能类似我们的计算器,这里就只写了加减乘除的功能,通过我们的代码我们可以看到的是四个一样的函数指针,那我们是不能就可以用数组的形似来存放它们。因为数组是不是能存放多个类型相同的元素。

那我们就可以来写一写看

int main()
{
  int (*pf1)(int, int) = &Add;
  int (*pf2)(int, int) = &Sub;
  int (*pf3)(int, int) = &Mul;
  int (*pf4)(int, int) = &Div;
  int(*pfarr[4])(int, int) = { &Add,&Sub,&Mul,&Div };
  //pfarr就是存放函数指针的数组
  return 0;
}

那函数指针数组难道就是一个摆设吗,不!!!它还是有一点作用的,那它的作用是什么呢,让我们来一起看看吧。

我们是不是就可以用它来实现一个计算器,我们来设计一个吧

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

虽然我们实现一个简单的计算器,但是其实我们的代码看起来并不是特别好看,反而有点冗余,重复的内容有点多,而且如果我们加一些其他的运算的时候,函数会变得越来越长,那我们该怎么改改让我们的代码看起来又好看又实用呢,这个时候我们就可以用函数指针数组进行改正,现在我们来对它进行整改。

我们用这个的时候之前一定要注意的问题是计算器这个函数的类型是一样的,否则其他的话就不行了。

int Add(int x, int y)
{
  return x + y;
}
int Sub(int x, int y)
{
  return x - y;
}
int Mul(int x, int y)
{
  return x * y;
}
int Div(int x, int y)
{
  return x / y;
}
void menu()
{
  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("请选择>");
    scanf("%d", &input);
    int (*pf[5])(int, int) = { NULL,Add,Sub,Mul,Div };
    if (input == 0)
    {
      printf("退出计算器\n");
    }
    else if (input >= 1 && input <= 4)
    {
      printf("请输入两个操作数 :");
      scanf("%d %d", &x, &y);
      ret = pf[input](x, y);
      printf("%d\n", ret);
    }
  } while (input);
  return 0;
}

这样也能实现我们计算器的效果,而且好处也是特别大,首先解决了我们的代码冗余的现象,其次是我们如果想加入其他的运算,也可以直接加入,只要改一下数组的大小,并把函数的地址放到里面就可以解决问题,其实很简单。我们也将这个东西叫做转移表,这就是我们函数数组指针应用,但是这只是一个例子,其他还有很多的作用,这里就不扩展了。

指向函数指针数组的指针

一看这个标题不知道大家头是不是大了,反正小编一开始看这个有点云里雾里的感觉,但是经过一段时间的学习,发现它也就这啊,那现在我们开始举列子。

int a = 10;
int b = 20;
int c = 30;
int* arr[] = { &a,&b,&c };//整型指针数组

我们先来看简单的,然后慢慢的引入,先是整型指针数组,那如果是这样的一个问题,要指向整型指针数组的指针,我们该怎么写

int* (*parr)[3] = &arr;

这就是指向整型指针数组的指针,同样的道理我们来看指向函数指针数组的指针。

int (*pf[5])(int, int) = { NULL,Add,Sub,Mul,Div };
int(*(*ppf)[5])(int, int) = &pf;

我们就拿计算器的来写,就是这个样子的。

但是这个其实不用理解太深,稍微了解一下就知道了,不是特别重要。

回调函数

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个

函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数

的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进

行响应。

什么意思呢,就是假设我们有两个函数一个函数叫A,一个函数叫B,我们B函数的参数是A函数的地址,我们在B函数里使用这个函数A。

今天就讲一个例子,还是我们的计算器,下次给大家讲一个qsort,这也叫快速排序,让大家更好的理解我们的内容。

先把我们的计算器在拿出来给大家看一下

int Add(int x, int y)
{
  return x + y;
}
int Sub(int x, int y)
{
  return x - y;
}
int Mul(int x, int y)
{
  return x * y;
}
int Div(int x, int y)
{
  return x / y;
}
void menu()
{
  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("请选择>");
    scanf("%d" ,&input);
    switch (input)
    {
    case 0:
      printf("退出计算器\n");
      break;
    case 1:
      printf("请输入两个操作数 :");
      scanf("%d %d", &x, &y);
      ret = Add(x, y);
      printf("ret = %d\n", ret);
      break;
    case 2:
      printf("请输入两个操作数 :");
      scanf("%d %d", &x, &y);
      ret = Sub(x, y);
      printf("ret = %d\n", ret);
      break;
    case 3:
      printf("请输入两个操作数 :");
      scanf("%d %d", &x, &y);
      ret = Mul(x, y);
      printf("ret = %d\n", ret);
      break;
    case 4:
      printf("请输入两个操作数 :");
      scanf("%d %d", &x, &y);
      ret = Div(x, y);
      printf("ret = %d\n", ret);
      break;
    default:
      break;
    }
  } while (input);
  return 0;
}

我们可以把相同的部分也就是case语句分装成一个函数的思路去实现

我们可以看到我们的代码哪里是不太一样的地方,其实就是我们调用函数的地方,如果我们能写一个函数calc,来放这些函数的地址,问题是不是解决了,其实就是写成这个样子。

int Add(int x, int y)
{
  return x + y;
}
int Sub(int x, int y)
{
  return x - y;
}
int Mul(int x, int y)
{
  return x * y;
}
int Div(int x, int y)
{
  return x / y;
}
void menu()
{
  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("请选择>");
    scanf("%d" ,&input);
    switch (input)
    {
    case 0:
      printf("退出计算器\n");
      break;
    case 1:
      calc(Add);
      break;
    case 2:
      calc(Sub);
      break;
    case 3:
      calc(Mul);
      break;
    case 4:
      calc(Div);
      break;
    default:
      break;
    }
  } while (input);
  return 0;
}

那现在要做的是什么,就是完成我们的calc函数这样就可以实现计算器的功能

calc我们的返回类型就是void 函数他们的地址,那我们是不是可以写成int (*pf)(int,int)

int Add(int x, int y)
{
  return x + y;
}
int Sub(int x, int y)
{
  return x - y;
}
int Mul(int x, int y)
{
  return x * y;
}
int Div(int x, int y)
{
  return x / y;
}
void menu()
{
  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("ret = %d\n", ret);
}
int main()
{
  int input = 0;
  int x = 0;
  int y = 0;
  int ret = 0;
  do
  {
    menu();
    printf("请选择>");
    scanf("%d", &input);
    switch (input)
    {
    case 0:
      printf("退出计算器\n");
      break;
    case 1:
      calc(Add);
      break;
    case 2:
      calc(Sub);
      break;
    case 3:
      calc(Mul);
      break;
    case 4:
      calc(Div);
      break;
    default:
      break;
    }
  } while (input);
  return 0;
}

这样就把之前的功能也实现了。我们后面再讲一个qsort,今天懒得讲了,摆烂ing

我们下次再见,拜拜



相关文章
|
1月前
|
存储 C语言
【C语言篇】深入理解指针3(附转移表源码)
【C语言篇】深入理解指针3(附转移表源码)
38 1
|
30天前
|
C语言
【c语言】指针就该这么学(1)
本文详细介绍了C语言中的指针概念及其基本操作。首先通过生活中的例子解释了指针的概念,即内存地址。接着,文章逐步讲解了指针变量的定义、取地址操作符`&`、解引用操作符`*`、指针变量的大小以及不同类型的指针变量的意义。此外,还介绍了`const`修饰符在指针中的应用,指针的运算(包括指针加减整数、指针相减和指针的大小比较),以及野指针的概念和如何规避野指针。最后,通过具体的代码示例帮助读者更好地理解和掌握指针的使用方法。
49 0
|
29天前
|
C语言
【c语言】指针就该这么学(3)
本文介绍了C语言中的函数指针、typedef关键字及函数指针数组的概念与应用。首先讲解了函数指针的创建与使用,接着通过typedef简化复杂类型定义,最后探讨了函数指针数组及其在转移表中的应用,通过实例展示了如何利用这些特性实现更简洁高效的代码。
17 2
|
30天前
|
C语言
如何避免 C 语言中的野指针问题?
在C语言中,野指针是指向未知内存地址的指针,可能引发程序崩溃或数据损坏。避免野指针的方法包括:初始化指针为NULL、使用完毕后将指针置为NULL、检查指针是否为空以及合理管理动态分配的内存。
|
30天前
|
C语言
C语言:哪些情况下会出现野指针
C语言中,野指针是指指向未知地址的指针,通常由以下情况产生:1) 指针被声明但未初始化;2) 指针指向的内存已被释放或重新分配;3) 指针指向局部变量,而该变量已超出作用域。使用野指针可能导致程序崩溃或不可预测的行为。
|
1月前
|
存储 C语言
C语言32位或64位平台下指针的大小
在32位平台上,C语言中指针的大小通常为4字节;而在64位平台上,指针的大小通常为8字节。这反映了不同平台对内存地址空间的不同处理方式。
|
1月前
|
存储 算法 C语言
C语言:什么是指针数组,它有什么用
指针数组是C语言中一种特殊的数据结构,每个元素都是一个指针。它用于存储多个内存地址,方便对多个变量或数组进行操作,常用于字符串处理、动态内存分配等场景。
|
1月前
|
存储 C语言
C语言指针与指针变量的区别指针
指针是C语言中的重要概念,用于存储内存地址。指针变量是一种特殊的变量,用于存放其他变量的内存地址,通过指针可以间接访问和修改该变量的值。指针与指针变量的主要区别在于:指针是一个泛指的概念,而指针变量是具体的实现形式。
|
1月前
|
C语言
C语言指针(3)
C语言指针(3)
14 1
|
1月前
|
C语言
C语言指针(2)
C语言指针(2)
15 1
下一篇
无影云桌面