详解指针进阶2

简介: 详解指针进阶2

前言

书接上回,我们继续详解指针,接下来我们从函数指针说起。

一 函数指针

首先我们来看到下面这代码:

从中我们可以看出函数名就是函数的地址,但我们要将函数的地址存放起来又改如何呢?

我们都知道指针是用来存放地址的,那对于函数的地址,指针又将如何书写。

函数指针的书写

函数的反回类型  (*函数名)(函数的参数类型)

#include<stdio.h>
int Add(int x, int y)
{
  return x + y;
}
int main()
{
  int (*Add)(int, int) = &Add;
  return 0;
}

对于函数Add我们要将它的地址存起来,就要用到函数指针,上面的函数指针,函数的返回类型是int函数的参数类型是int 。

我们知道什么是函数指针下面阅读两段有趣的代码:

//代码1

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

//代码2

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

我相信大家看到这段代码不免有点后怕,一脸懵逼。

下面我就为大家细细剖析这段代码。

代码1

拿到这段代码,我们首先从自己的熟悉的地方开始,( )0,这是什么呢?这不就是就0强制转换为什么类型,好我们继续看((void (*)())里面的void (*)()对于这个我们又是比较熟悉的,这不就是函数指针,所以是我们将0强制转换为函数指针类型;(*)()这不就是一次函数调用,那么这段代码可以理解为:

代码1为一次函数调用,调用的是0作为地址处的函数。

把0强制类型转换为:无参,返回类型是void的函数的地址;

调用0地址处的函数

代码2

我们继续从熟悉的地方分析,signal函数有二个参数intvoid(*)(int)这是函数指针,那么就还剩下void(*)(int),这样我们可以很容易看出这是个函数指针

所以代码2可以理解为:

以上是一次函数声明,

声明函数的signal函数的第一个参数的类型是int,第二个参数的类型是函数指针;

返回类型是void,signal函数的返回类型也是函数指针。

其实对于代码2我们还可以经行简化。

我们可以给void(*)(int)取个名字为puf:

typedef void(*puf)(int);

puf signal(int ,puf)l

二 函数指针数组

什么我们知道了函数指针是一个函数,是用来存放函数地址的,那么函数指针数组又是什么呢?

我们可以知道函数指针数组,的本质是数组,这个数组的作用是存放,函数指针的

函数指针数组的定义:

数据类型(*函数名[元素个数 ])()

下面我们写一个计算器来理解函数指针数组的用法。

普通方法:

//转移表
#include<stdio.h>
 
void menu()
{
  printf("*************************\n");
  printf("*****  1.Add  2.sub *****\n");
  printf("*****  3.mul  4.div *****\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;
  printf("请选择:\n");
  menu();
  scanf("%d", &input);
  switch (input)
  {
  case 1:
    printf("请输入操作:\n");
    scanf("%d%d", &x, &y);
    int ret = Add(x, y);//调用加法函数
    printf("sum = %d\n", ret);
    break;
  case 2:
    printf("请输入操作:\n");
    scanf("%d%d", &x, &y);
    ret = sub(x, y);//调用减法函数
    printf("sum = %d\n", ret);
    break;
  case 3:
    printf("请输入操作:\n");
    scanf("%d%d", &x, &y);
    ret = mul(x, y);//调用乘法函数
    printf("sum = %d\n", ret);
    break;
  case 4:
    printf("请输入操作:\n");
    scanf("%d%d", &x, &y);
    ret = div(x, y);//调用减法函数
    printf("sum = %d\n", ret);
    break;
  }
  return 0;
}

虽然我们用常规方法写出了转移表,但是在switch语句中出现了大量冗余的代码。

所以我们可以经行改进:

#include<stdio.h>
 
void menu()
{
  printf("*************************\n");
  printf("*****  1.Add  2.sub *****\n");
  printf("*****  3.mul  4.div *****\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 = 1;
  int x = 0;
  int y = 0;
  int(*p[5])(int x, int y) = { 0,Add,sub,mul,div };//转移表
  int ret = 0;
  while (input)
  {
    printf("请选择:\n");
    menu();
    scanf("%d", &input);
    if (input >= 1 && input <= 4)
    {
      printf("请输入二个操作数\n");
      scanf("%d%d", &x, &y);
      ret = (*p[input])(x, y);//函数调用
      printf("ret = %d\n", ret);
    }
  }
    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;
}

其中还有指向函数指针数组的指针数组,指向函数指针数组的指针数组指针,,,下面就没必要讨论下去了,理解思路都和上面的差不多。

四 回调函数

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

为了加深对回调函数的理解,我们将模拟实现qorst函数。

qsort函数是C语言提供的一个库函数,是用来快速排序的。

qsort有4个参数:

base: 要排序元素的起始地址

mum: 要排序的元素个数

witdth:每个元素的大小(字节)

cmp: 比较函数

qsort 函数实现了一种快速排序算法,用于对 num 元素数组进行排序,每个元素的宽度为字节。参数基是指向要排序的数组的基的指针。qsort 用排序的元素覆盖此数组。参数 compare 是指向用户提供的例程的指针,该例程比较两个数组元素并返回指定其关系的值。qsort 在排序过程中调用 compare 例程一次或多次,每次调用时将指针传递给两个数组元素

细心的小伙伴可能看出来void *是我们没有见的。

void*

是无具体类型的指针,这种指针可以接类型的地址。

注:void*不能进行*(解引用操作),也不能+-整数。

qsort用法举例:

#include<stdio.h>
#include<stdlib.h>
int int_cmp(const void*e1,const void*e2)
{
  return (*(int*)e1 - *(int*)e2);
}
 
int main()
{
  int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
  int sz = sizeof(arr) / sizeof(arr[0]);
  qsort(arr, sz, sizeof(arr[0]), int_cmp);
  int i = 0;
  for (i = 0;i < sz;i++)
  {
    printf("%d ", arr[i]);
  }
  return 0;
}

这里我们注意比较函数是要自己写的,比较函数的返回值:

数组按递增的顺序排序,如比较函数所定义。要按降序对数组进行排序,请反转比较函数中的“大于”和“小于”的含义。

模仿qsort的功能实现一个通用的冒泡排序

void swap(char* buf1, char* buf2, int width)
{
  int i = 0;
  //将字符进行一对一对的交换
  for (i = 0;i < width;i++)
  {
    char tmp = *buf1;
    *buf1 = *buf2;
    *buf2 = tmp;
    *buf1++;
    *buf2++;
  }
}
//模拟实现qsort
void doubble_sort(void* base, int sz, int width, int (*cmp)(const void* e1, const void* e2))
{
  int i = 0;
  for (i = 0;i < sz - 1;i++)
  {
    int j = 0;
    int flag = 1;//假设数组已经是排好顺序
    for (j = 0;j < sz - 1 - i;j++)
    {
      if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
      {
        swap((char*)base + j * width, (char*)base + (j + 1) * width, width);//交换
        flag = 0;
      }
    }
    if (flag == 1)
    {
      break;
    }
  }
}

我们通过函数指针cmp调用了函数swap,所以函数swap是回调函数。

总结

看完这篇博客,大家应该知道函数指针指针,用来存放函数的地址,对于函数指针数组数组,数组中的每个元素是函数指针指向函数指针数组的指针指针指向数组,数组中的每个元素是函数指针回调函数指的是通过函数指针调用的函数


相关文章
|
6月前
|
C语言
指针进阶(C语言终)
指针进阶(C语言终)
|
6月前
|
机器学习/深度学习 搜索推荐 算法
【再识C进阶2(下)】详细介绍指针的进阶——利用冒泡排序算法模拟实现qsort函数,以及一下习题和指针笔试题
【再识C进阶2(下)】详细介绍指针的进阶——利用冒泡排序算法模拟实现qsort函数,以及一下习题和指针笔试题
|
6月前
|
C语言
指针进阶(回调函数)(C语言)
指针进阶(回调函数)(C语言)
|
6月前
|
存储 C语言 C++
指针进阶(函数指针)(C语言)
指针进阶(函数指针)(C语言)
|
6月前
|
编译器 C语言
指针进阶(数组指针 )(C语言)
指针进阶(数组指针 )(C语言)
|
6月前
|
搜索推荐
指针进阶(2)
指针进阶(2)
52 4
|
6月前
指针进阶(3)
指针进阶(3)
44 1
|
6月前
|
C++
指针进阶(1)
指针进阶(1)
46 1
|
6月前
|
存储 安全 编译器
C++进阶之路:何为引用、内联函数、auto与指针空值nullptr关键字
C++进阶之路:何为引用、内联函数、auto与指针空值nullptr关键字
50 2
|
6月前
|
Java 程序员 Linux
探索C语言宝库:从基础到进阶的干货知识(类型变量+条件循环+函数模块+指针+内存+文件)
探索C语言宝库:从基础到进阶的干货知识(类型变量+条件循环+函数模块+指针+内存+文件)
51 0