如何降低代码的冗余度(指针的妙用)——探索指针数组,数组指针,函数指针,函数指针数组,回调函数的奥妙

简介: 如何降低代码的冗余度(指针的妙用)——探索指针数组,数组指针,函数指针,函数指针数组,回调函数的奥妙

一.指针数组

首先我们得明确指针数组究竟是指针还是数组


整形数组     ——  装有整形数据的数组

浮点型数组  ——  装有浮点型数据的数组

字符型数组 ——  装有字符型数据的数组

指针数组     ——  ?


按照上面的逻辑,我们就可以判断出指针数组是数组,只不过里面装的是指针类型的数组

  int* arr[10] = { NULL };//整形指针数组,里面放的是空指针
  int* arr1[10]; //整形指针的数组
  char* arr2[4]; //一级字符指针的数组
  char** arr3[5];//二级字符指针的数组

指针数组的用处

       指针数组最大的一个用处就是可以用来构造二维数组,试曾设想,我们创造了一列数组,这个数组中每一个元素都是一个地址,而数组也是有地址的,换言之,我们可以将这一列中的的每一个元素放入不同数组的地址,每个元素又代表了一行数组,这样就构造成了一个二维数组。大概图示如下:

我们创建了一个名为 Arr 的指针数组,其中放了6个地址,分别是6个数组的地址,那我们就相当于完成了一个二维数组的构建

二.数组指针

同样,我们也得明确数组指针到底是数组还是指针


整形指针     —— 是一个指针,指向一个整形变量

浮点型指针  —— 是一个指针,指向一个浮点型变量

结构体指针 —— 是一个指针,指向结构体变量

数组指针     —— ?


我们推断出数组指针是一个指针,指向一个数组

一般形式

返回类型  ( * 指针名 )[数组大小]


    数组指针可以指向一整个数组,而不在局限与访问数组中一个元素的地址,这样说起来可能有点不明确,那我们写个程序如下,观看一个输出的结果

#include <stdio.h>
int main()
{
  int arr[10] = { 0 };
  printf("arr = %p\n", arr);
  printf("&arr= %p\n", &arr);
  printf("arr+1 = %p\n", arr + 1);
  printf("&arr+1= %p\n", &arr + 1);
  return 0;
}

在研究这个问题之前,我们必须知道一下的基础知识

在上述代码中,直接使用数组的名字,是在访问数组的首元素的地址

  printf("arr = %p\n", arr);
  printf("arr+1 = %p\n", arr + 1);

而使用 &数组名,则是在访问整个数组的地址,整个值与首元素的地址是一样的,但是意义完全不一样

  printf("&arr= %p\n", &arr);
  printf("&arr+1= %p\n", &arr + 1);

上述代码输出结果如下:

我们可以观察出,使用取值符的时候,访问地址加一,地址值加了40,也证明了上述结论

   而指针数组的使用就可以让我们更好的实现上述对整个数组操作的需求,方便了后续诸如函数指针数组的更高级的使用技巧(后文会介绍到,这里就不再继续赘述)

#include <stdio.h>
int main()
{
  int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
  int(*p)[10] = &arr;
    //把数组arr的地址赋值给数组指针变量p
  //但是我们一般很少这样写代码
  return 0;
}

三.函数指针

       诸如上述数组指针的推理判断,函数指针也是指针,只不过指向的是函数,常量,变量,数组我们都已经充分了解到这些数据都是有地址的,那函数有地址吗?我们做以下测试

#include <stdio.h>
void test()
{
  printf("this is a test\n");
}
int main()
{
  printf("%p\n", test);
  printf("%p\n", &test);
  return 0;
}

     输出结果如下,我们可以看见函数是有地址的,我们也可以取到这个地址,同时函数名也代表了地址

一般形式

返回类型  ( * 指针名) (函数参数类型)

既然函数有地址,那我们就可以使用指针来保存,我们具体实现如下:

void test()
{
  printf("this is a test\n");
}
int main()
{
  printf("%p\n", test);
  printf("%p\n", &test);
  void (*p)() = &test;
  printf("%p\n", p);
  return 0;
}

们试运行一下看看,可以看见第三行的输出确实是使用了指针p对函数进行了访问

四.函数指针数组

       对于函数指针数组,主语是数组,所以他的本质就是一个数组,只不过在这个数据结构中,我们需要用到上述铺垫的知识,我们将不同函数的地址放进同一个数组中,可以帮助我们更高效的完成程序。

一般形式


返回类型  ( * 指针名[数组大小] )(函数参数类型)

       在使用函数指针数组的情况下,我们就可以用数组来调用函数了,在部分程序中,可以有非常高的效率,以下笔者仅做简单示例

void F1()
{
  printf("F1\n");
}
void F2()
{
  printf("F2\n");
}
void F3()
{
  printf("F3\n");
}
int main()
{
  void (*pf[3])() = { F1,F2,F3 };
  for (int i = 0; i < 3; i++)
  {
    pf[i]();
  }
  return 0;
}

输出结果中显示我们确实成功的使用数组,通过下标的方式对函数进行了调用

五.回调函数

什么是回调函数

       回调函数就是一个通过函数指针调用的函数 。如果你把函数的指针(地址)作为参数传递给另一个 函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数 的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应



  如下图所示,在程序 x 中放有函数 f,并且函数 f 中又有函数指针,而函数 f 又可以将函数 1,函数 2,函数 3,函数 4的地址作为参数,当我们通过函数 f 中的函数指针进行调用函数 1~4 的时候,我们就叫做回调函数

进阶技巧使用(降低代码冗余度)

接下里,笔者就通过计算器小程序,来对以上进阶技巧进行演示

       首先,我们按照一般的算法思路进行编程,设计个计算器小程序,那么最基本的加减乘除是肯定要包含的,那我们就分别写出加减乘除的函数,然后再分别调用他们


       诸如下面代码,我们可以发现,这个程序是非常的臃肿的,代码冗余度非常高,有很多重复且效率不高的代码,目前还只是4个功能,要是以后需求变大变多,我们就又要加入 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;
}
int main()
{
  int x, y;
  int input = 1;
  int ret = 0;
  do
  {
    printf("*************************\n");
    printf(" 1:add           2:sub \n");
    printf(" 3:mul           4:div \n");
    printf("*************************\n");
    printf("请选择:");
    scanf("%d", &input);
    switch (input)
    {
    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;
    case 0:
      printf("退出程序\n");
      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;
}

  然后我们将这4个函数封装在数组里面,数组中之所以第一个元素放空指针,是为了和菜单中的数字对应起来

int (*pfArr[])(int, int) = {NULL, Add, Sub, Mul, Div};
//                           0     1    2    3    4
void menu()
{
  printf("****************************\n");
  printf("***  1. add      2. sub  ***\n");
  printf("***  3. mul      4. div  ***\n");
  printf("***  0. exit             ***\n");
  printf("****************************\n");
}

        在运算部分,我们使用函数指针数组,将输入的数字作为数组的下标来访问对应的函数,并且将值赋给 ret

printf("请输入2个操作数:");
scanf("%d %d", &x, &y);
ret = pfArr[input](x, y);
printf("ret = %d\n", ret);

完整代码

void menu()
{
  printf("****************************\n");
  printf("***  1. add      2. sub  ***\n");
  printf("***  3. mul      4. div  ***\n");
  printf("***  0. exit             ***\n");
  printf("****************************\n");
}
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 input = 0;
  int x = 0;
  int y = 0;
  int ret = 0;
  do
  {
    menu();
    printf("请选择:>");
    scanf("%d", &input);
    //函数指针数组 - 转移表
    int (*pfArr[])(int, int) = {NULL, Add, Sub, Mul, Div};
    //                          0     1     2   3    4
    if (0 == input)
    {
      printf("退出计算器\n");
    }
    else if (input >= 1 && input <= 4)
    {
      printf("请输入2个操作数:");
      scanf("%d %d", &x, &y);
      ret = pfArr[input](x, y);
      printf("ret = %d\n", ret);
    }
    else
    {
      printf("选择错误,重新选择!\n");
    }
  } while (input);
  return 0;
}

在完整代码中,我们明显会发现,尤其是在主函数中,省去了很多冗余的代码,看起来精简了很多

方法二:回调函数法

我们可以使用下面这种结构来完成设计

我们建立一个函数,将加减乘除的函数地址作为参数传进来调用

void calc(int (*pf)(int,int))
{
  int x = 0;
  int y = 0;
  int ret = 0;
  printf("请输入2个操作数:");
  scanf("%d %d", &x, &y);
  ret = pf(x, y);
  printf("ret = %d\n", ret);
}

完整代码

void menu()
{
  printf("****************************\n");
  printf("***  1. add      2. sub  ***\n");
  printf("***  3. mul      4. div  ***\n");
  printf("***  0. exit             ***\n");
  printf("****************************\n");
}
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 calc(int (*pf)(int,int))
{
  int x = 0;
  int y = 0;
  int ret = 0;
  printf("请输入2个操作数:");
  scanf("%d %d", &x, &y);
  ret = pf(x, y);
  printf("ret = %d\n", ret);
}
int main()
{
  int input = 0;
  do
  {
    menu();
    printf("请选择:>");
    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;
}

我们可以看出来,函数冗余度还是降低了不少的

 

本次分享就到此为止了,如有错误,欢迎积极指出,感谢您的支持

目录
相关文章
|
1月前
使用指针访问数组元素
【10月更文挑战第30天】使用指针访问数组元素。
38 3
|
27天前
|
存储 程序员 编译器
C 语言数组与指针的深度剖析与应用
在C语言中,数组与指针是核心概念,二者既独立又紧密相连。数组是在连续内存中存储相同类型数据的结构,而指针则存储内存地址,二者结合可在数据处理、函数传参等方面发挥巨大作用。掌握它们的特性和关系,对于优化程序性能、灵活处理数据结构至关重要。
|
1月前
|
存储 C语言 计算机视觉
在C语言中指针数组和数组指针在动态内存分配中的应用
在C语言中,指针数组和数组指针均可用于动态内存分配。指针数组是数组的每个元素都是指针,可用于指向多个动态分配的内存块;数组指针则指向一个数组,可动态分配和管理大型数据结构。两者结合使用,灵活高效地管理内存。
|
1月前
|
容器
在使用指针数组进行动态内存分配时,如何避免内存泄漏
在使用指针数组进行动态内存分配时,避免内存泄漏的关键在于确保每个分配的内存块都能被正确释放。具体做法包括:1. 分配后立即检查是否成功;2. 使用完成后及时释放内存;3. 避免重复释放同一内存地址;4. 尽量使用智能指针或容器类管理内存。
|
1月前
|
存储 NoSQL 编译器
C 语言中指针数组与数组指针的辨析与应用
在C语言中,指针数组和数组指针是两个容易混淆但用途不同的概念。指针数组是一个数组,其元素是指针类型;而数组指针是指向数组的指针。两者在声明、使用及内存布局上各有特点,正确理解它们有助于更高效地编程。
|
1月前
|
存储 人工智能 算法
数据结构实验之C 语言的函数数组指针结构体知识
本实验旨在复习C语言中的函数、数组、指针、结构体与共用体等核心概念,并通过具体编程任务加深理解。任务包括输出100以内所有素数、逆序排列一维数组、查找二维数组中的鞍点、利用指针输出二维数组元素,以及使用结构体和共用体处理教师与学生信息。每个任务不仅强化了基本语法的应用,还涉及到了算法逻辑的设计与优化。实验结果显示,学生能够有效掌握并运用这些知识完成指定任务。
57 4
|
1月前
使用指针访问数组元素
【10月更文挑战第31天】使用指针访问数组元素。
48 2
|
1月前
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
121 13
|
2月前
|
C语言
无头链表二级指针方式实现(C语言描述)
本文介绍了如何在C语言中使用二级指针实现无头链表,并提供了创建节点、插入、删除、查找、销毁链表等操作的函数实现,以及一个示例程序来演示这些操作。
38 0
|
3月前
|
存储 人工智能 C语言
C语言程序设计核心详解 第八章 指针超详细讲解_指针变量_二维数组指针_指向字符串指针
本文详细讲解了C语言中的指针,包括指针变量的定义与引用、指向数组及字符串的指针变量等。首先介绍了指针变量的基本概念和定义格式,随后通过多个示例展示了如何使用指针变量来操作普通变量、数组和字符串。文章还深入探讨了指向函数的指针变量以及指针数组的概念,并解释了空指针的意义和使用场景。通过丰富的代码示例和图形化展示,帮助读者更好地理解和掌握C语言中的指针知识。
143 4

热门文章

最新文章