函数指针数组:更高效的代码实现方式——指针进阶(二)

简介: 函数指针数组:更高效的代码实现方式——指针进阶(二)

前言

当谈到C语言的高级特性时,函数指针和函数指针数组通常是最常见的话题之一。虽然这些概念可能会让初学者感到困惑,但它们对于编写高效、可读性强且易于维护的代码来说是至关重要的。在本篇博客中,我们将深入探讨函数指针及函数指针数组的概念,并介绍它们如何在C语言中发挥作用。


一、函数指针

什么是函数指针

我们已经知道指针可以指向变量和数组,那指针能不能指向函数呢?

我们先看下面的代码:

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

函数指针是指向函数的指针变量。它存储着函数的地址,可以用来调用该函数。

前边我们知道&数组名和数组名的地址是一样的,那函数呢?

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

通过程序我们可以发现两种形式输出的结果是一样的。那既然我们可以知道函数的地址,那我们就可以通过指针变量来存储函数的地址。

那函数指针的类型应该怎么定义呢?格式又是什么呢?

int (*pf)(int,int)=&Add;

首先我们要取函数的地址存放到指针变量当中。

pf=&Add,那pf就要是一个指针变量,所以(*pf)=&Add,加*说明pf是一个指针,指针指向的是什么呢?

函数,那么也就有(*pf) (int , int)=&Add , pf 后边加()说明它是一个指向函数的指针,括号里为函数的参数类型,最后前边的int就是函数的返回类型。

它与数组指针很相似:int (*parr)[10]=&arr,int (*)[10]是数组指针类型。类比一下

int (*) (int,int)就是函数指针类型。

练一练

void test(char* ch,int arr[5])

{}

函数test的函数指针类型怎么写?、

void (*pf) (char*,int*)=&test;或者void (*pf) (char*,int[5])=&test;

函数指针的使用

既然我们知道了函数指针,那函数指针有什么用呢?又怎么用呢?

int Add(int x, int y)
{
  return (x + y);
}
int main()
{
  int (*pf)(int,int) = &Add;
  int ret = Add(3, 5);
  int t = (*pf)(3, 5);
  printf("%d\n%d", ret, t);//输出结果都为8
  return 0;
}

通过程序可以看出,通过指针确实可以调用函数,那我们前边提到&Add和Add地址相同,那可不可以用Add替代&Add?当然可以

int Add(int x, int y)
{
  return (x + y);
}
int main()
{
  int (*pf)(int,int) = Add;
  int ret = Add(3, 5);
  int t = pf(3, 5);
  printf("%d\n%d", ret, t);//输出结果都为8
  return 0;
}

这样写也是可以的。

了解过函数指针后,我们来看看这两个代码:

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

看到这两个代码是不是感觉头皮发麻,这两个代码出于《C陷阱和缺陷》这本书。

我们先来看第一个代码

突破口就在于0,从0开始看,0前边的void(*)(),是一个函数指针类型,

那(void(*)())不就是将0强制类型转换为函数指针类型

那剩下部分就很清晰了,再对函数指针进行解引用调用该函数。

这段代码的作用是什么呢?

调用0地址处的函数

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

2.调用0地址处的函数。

我们再来看第二个

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

signal是一个函数声明,它有两个参数,一个int类型,一个void(*)(int)类型,

而signal函数返回类型也是一个void(*)(int)类型。

这样嵌套的方式写代码可读性很差,不建议大家在写代码时这样写,我们可以对代码简化一下。

我们先来学习一个新的知识点typedef,它可以对数据类型进行重命名

例如typedef  unsigned int   uint;将unsigned int重命名为uint,那在声明无符号整形时我们就可以这样用:

typedef unsigned int uint;
int main()
{
    uint n;
}

uint n就等价于unsigned int n;typedef不仅可以对基本的数据类型进行重命名,对函数指针类型,结构体类型都可以重命名。

那我们就对void(*)(int)进行重命名,

我们想对函数指针类型进行重命名怎么写呢?

我们可以这样写typedef void(*pf)(int);它与一般的类型命名不同,不可以像这样typedef void(*)(int) pf;C语言不允许这样写。pf需要靠近*写,此时pf就等价于void(*)(int)类型,注意:数组指针类型的重命名也类似。

那我们就可以对原代码进行简化

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

简化后:

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

使用函数指针数组,结构就简洁清晰了许多。

三、指向函数指针数组的指针

看到这个标题大家是不是想吐槽还套娃起来了,这里只是为了让大家能够更好的理解数组,指针,函数之间嵌套的规律,便于大家更好的掌握。

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

那如何定义呢?

我们来看一下以下代码:

void test(const char* str)
{
printf("%s\n", str);
}
int main()
{
//函数指针pfun
void (*pfun)(const char*) = test;
//函数指针的数组pfunArr
void (*pfunArr[5])(const char* str);
pfunArr[0] = test;
//指向函数指针数组pfunArr的指针ppfunArr
void (*(*ppfunArr)[5])(const char*) = &pfunArr;
return 0;
}

我们先从函数指针开始进行定义,每次的嵌套都是在原先定义的基础上稍做了一些修改。通过函数指针到函数指针数组,再到指向函数指针数组的指针,我们发现了什么规律?每次嵌套都是在对指针变量进行修改(注意:[]的优先级高于*)。


总结

好了本期内容到此结束,希望大家能够掌握函数指针及函数指针数组的概念,掌握了这些概念,可以帮助你编写出更加高效、灵活、可读性强且易于维护的代码。虽然这些概念可能对初学者来说会有些困难,但是通过不断地学习和实践,你将能够更加熟练地掌握它们。希望这篇博客能够帮助你更好地理解函数指针及函数指针数组的概念,从而在日后的编程中更加得心应手,最后感谢阅读!

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