【C进阶】* 指针(2)

简介: 目录1、函数指针 1.2、两段有趣代码2、函数指针数组 2.2、用途(转移表)->计算器3、指向函数指针数组的指针4、回调函数 4.1、回顾冒泡排序 4.2、使用qsort函数 4.3、使用回调函数,模拟实现qsort(采用冒泡的方式)

1、函数指针

  • 简要概念:既然数组指针是指向数组的指针,那么函数指针就是指向函数的指针,用来存放函数地址的一个指针。
  • 我们先写一个加法函数,并且打印出其地址看看。
#include<stdio.h>
int Add(int x, int y)
{
  return x + y;
}
int main()
{
  printf("%p\n", &Add);
  printf("%p\n", Add);
  //&函数名 和 函数名 都是函数的地址
  return 0;
}

image.png

不难发现,&函数名 和 函数名 都是函数的地址

  • 当我们拿到函数地址了,该怎么存起来呢?先回顾下数组地址的存法:
int arr[10] = { 0 };
int (*p)[10] = &arr;
  • 仿照数组指针的写法,写一下函数指针:
#include<stdio.h>
int Add(int x, int y)
{
  return x + y;
}
int main()
{
  int (*pa)(int, int) = Add;
  printf("%d\n", (*pa)(2, 3));  // 5
  return 0;
}
  • 不同函数的地址存起来定义的指针也不相同
#include<stdio.h>
void Print(char* str)
{
  printf("%s\n", str);
}
int main()
{
  void(*p)(char*) = Print;   
  (*p)("hello bit");
  p("hello bit");
  return 0;
}
  • 下面解释下为什么在存的时候*p要加括号。
void test()
{
printf("hehe\n");
}
//下面pfun1和pfun2哪个有能力存放test函数的地址?
void (*pfun1)();
void *pfun2();

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

答案是:

pfun1可以存放。这里涉及到优先级的问题,因为在不加括号的情况下,pfun1会先和右边的括号也就是函数相结合,后和*结合,那就不是指针了,继而存不了地址,所以要在*pfun1先整个用()括起来,确保pfun1先和*结合,说明pfun1是指针,指针指向的是一个函数,指向的函数无参数,返回值类型为void。


注意:

#include<stdio.h>
int Add(int x, int y)
{
  return x + y;
}
int main()
{
  int (*pa)(int, int) = Add;
  printf("%d\n", (*pa)(2, 3));  // 5
  printf("%d\n", (**pa)(2, 3));  // 5
  printf("%d\n", (***pa)(2, 3));  // 5
  printf("%d\n", (pa)(2, 3));  // 5
  printf("%d\n", Add(2, 3));  // 5
  return 0;
}

如果pa是个函数指针,我去调用这个函数的时候,我可以解引用,也可以不解引用,*号可以看成一个摆设

1.2、两段有趣代码

//代码1
(*(void (*)())0)();
//代码2
void (*signal(int , void(*)(int)))(int);
  • 解释代码1:
(*(void (*)())0)();

void (*)() -> 函数指针类型. 0前面有个括号,(void (*)())0,把0进行强制类型转换,此时0就是一个函数地址,此时再加个*进行解引用,再加一个括号去调用函数,不需要传参,因为指向的函数是无参的

总结:把0进行强制转换成函数指针类型,该指针指向的函数是无参,返回类型是void,当0变成一个函数地址时,对它进行解引用操作,去调用以0为地址的该函数。

解释代码2:

void (*signal(int, void(*)(int)))(int);
  • 可以将上述代码用typedef关键字进行简化:
typedef void(*pfun_t)(int);//就是说将void(*)(int)整个换为*pfun_t
  • 但是万万不可写成:
typedef void(*)(int) pfun_t;  // err
  • 此时上述代码就可优化为:
pfun_t signal(int, pfun_t);

总结:

signal是一个函数声明,

signal函数的参数有2个,第一个是int,第二个是函数指针,该函数指针指向的函数的参数是int,返回类型是void

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

2、函数指针数组

前面我们学习到了指针数组

#include<stdio.h>
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* arr[5];
  //数组的每个元素是int*
  int(*pa)(int, int) = Add;// 存了Add,也可以存Sub,Mul,Div  
  return 0;
}

这里我们创建了4个函数Add、Sub、Mul、Div,然后创建了函数指针来存放Add函数的地址,如果想要存其它的,只需要将Add换成其它的就可,但若都想存起来,则要创建4个不同的函数指针,继而有4个不同的变量,但duck不必这么麻烦,只需要用到函数指针数组即可:

//需要一个数组,这个数组可以存放4个函数的地址 - 函数指针的数组
int(*parr[4])(int, int) = { Add,Sub,Mul,Div };//函数指针数组
//想要输出内容只需要用for循环打印即可
int i = 0;
for (i = 0; i < 4; i++)
{
  int ret = parr[i](8, 4);
  printf("%d\n", ret);
}
  • 重新回顾下函数指针数组定义过程:
1. int *arr[10];
2. int (*parr1[10])();

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

  • 练习:
char* my_strcpy(char* dest, const char* src);
  • 1.写一个函数指针 pf ,能够存放my_strcpy
char* (*pf)(char*, const char*);
  • 2.写一个函数指针数组 pfArr ,能够存放4个my_strcpy函数的地址
char* (*pfArr[4])(char*, const char*);

2.2、用途(转移表)->计算器

  • 先写一个不用函数指针数组版本的简易计算器:
1.
#include<stdio.h>
void menu()
{
  printf("****************************************\n");
  printf("*****     1. add         2. sub    *****\n");
  printf("*****     3. mull        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 Mull(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;
  do
  {
    menu();
    printf("请选择\n");
    scanf("%d", &input);
    switch (input)
    {
    case 1:
      printf("请输入两个操作数:>\n");
      scanf("%d %d", &x, &y);
      printf("%d\n", Add(x, y));
      break;
    case 2:
      printf("请输入两个操作数:>\n");
      scanf("%d %d", &x, &y);
      printf("%d\n", Sub(x, y));
      break;
    case 3:
      printf("请输入两个操作数:>\n");
      scanf("%d %d", &x, &y);
      printf("%d\n", Mull(x, y));
      break;
    case 4:
      printf("请输入两个操作数:>\n");
      scanf("%d %d", &x, &y);
      printf("%d\n", Div(x, y));
      break;
    case 0:
      printf("退出\n");
      break;
    default:
      printf("选择错误\n");
      break;
    }
  } while (input);
  return 0;
}
  • 上述代码过于繁琐,可以使用函数指针数组进行简化
  • 函数指针数组的用途->转移表
int main()
{
  int input = 0;
  int x = 0;
  int y = 0;
  // pfArr是一个函数指针数组,用途->转移表
  int(*pfArr[])(int, int) = { 0 ,Add,Sub,Mull,Div };
  do
  {
    menu();
    printf("请选择\n");
    scanf("%d", &input);
    if (input >= 1 && input <= 4)
    {
      printf("请输入操作数\n");
      scanf("%d %d", &x, &y);
      int ret = pfArr[input](x, y);
      printf("%d\n", ret);
    }
    else if(input == 0)
    {
      printf("退出\n");
    }
    else
    {
      printf("选择错误\n");
    }
  } while (input);
  return 0;
}

主体定义函数的内容跟上份代码一样,这里就先省略,直接写出主要使用函数指针数组的内容。使用函数指针数组就可以不需要再使用这么多次的switch case语句了。避免了代码的过多重复性

  • 注意:
  • 在上上份的switch case法计算器中,有一段代码出现多次:
printf("请输入两个操作数:>\n");
scanf("%d %d", &x, &y);
  • 解决方法如下:需要用到回调函数,这里简单使用下,后续会详细介绍。
#include<stdio.h>
void menu()
{
  printf("****************************************\n");
  printf("*****     1. add         2. sub    *****\n");
  printf("*****     3. mull        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 Mull(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;
  printf("请输入两个操作数:>");
  scanf("%d %d", &x, &y);
  printf("%d\n", pf(x, y));
}
int main()
{
  int input = 0;
  do
  {
    menu();
    printf("请选择\n");
    scanf("%d", &input);
    switch (input)
    {
    case 1:
      Calc(Add);
      break;
    case 2:
      Calc(Sub);
      break;
    case 3:
      Calc(Mull);
      break;
    case 4:
      Calc(Div);
      break;
    case 0:
      printf("退出\n");
      break;
    default:
      printf("选择错误\n");
      break;
    }
  } while (input);
  return 0;
}
  • 这是完整代码,用到了函数指针,还有回调函数,不过后续会详细介绍回调函数。

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

  • 代码解释:
#include<stdio.h>
int Add(int x, int y)
{
  return x + y;
}
int main()
{
  int arr[10] = { 0 };
  //数组指针p
  int(*p)[10] = &arr;
  //函数指针pf
  int(*pf)(int, int); 
  //函数指针数组pfArr
  int(*pfArr[4])(int, int);
  //函数指针数组指针ppfArr
  int(*(*ppfArr)[4])(int, int) = &pfArr; 
  //ppfArr首先和*结合说明是指针,指针指向的是数组,数组有4个元素,每个元素的类型是函数指针
  /*
        正解:
    ppfArr是一个数组指针,指针指向的数组有4个元素
    指向的数组的每个元素的类型是一个函数指针 int(*)(int,int)
    */
  return 0;
}

4、回调函数

概念:

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


4.1、回顾冒泡排序

void bubble_sort(int arr[], int sz)
{
    //趟数
  int i = 0;
  for (i = 0; i < sz - 1; i++)
  {
        //每一趟排序中比较的对数
    int j = 0;
    for (j = 0; j < sz - 1 - i; j++)
    {
      if(arr[j]>arr[j+1])
      {
        int tmp = arr[j];
        arr[j] = arr[j + 1];
        arr[j + 1] = tmp;
      }
    }
  }
}
#include<stdio.h>
int main()
{
  int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
  int sz = sizeof(arr) / sizeof(arr[0]);
  bubble_sort(arr, sz);
    int i = 0;
    for (i = 0; i < sz; i++)
    {
      printf("%d ", arr[i]);  //0 1 2 3 4 5 6 7 8 9
    }
    return 0;
}
  • 我们都知道冒泡排序是专门用来排序整形数据的,那么浮点型数据和结构体该如何排序呢?
#include<stdio.h>
struct stu
{
  char name[20];
  int age;
    float score;
};
int main()
{
  struct stu s[3] = { {"zhangsan",20,87.5f},{"lisi",30,99.0f},{"wangwu",10,68.5f} };
  float f[] = { 9.0,8.0,7.0,6.0,5.0,4.0 };
  return 0;
}
  • 此时就引出万能函数qsort

4.2、使用qsort函数

  • qsort - 库函数 - 排序
  • 算法思想: 快速排序 quick sort
  • 格式:
void qsort(
          void* base,  ---> 目标数组 arr
          size_t num,  ---> 待排序元素的个数 n
          size_t width,---> 元素的字节大小  
          int(* cmp)(const void* elem1, const void* elem2)  ---> 函数比较 cmp_int
          );
  • 讲解下void*

void*类型的指针 可以接收任意类型的地址,且不报警告

  • 正文:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int cmp_int(const void* e1, const void* e2)
{
  //比较整形值
  return *(int*)e1 - *(int*)e2;
}
void test1()
{
  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]), cmp_int);
  int i = 0;
  for (i = 0; i < sz; i++)
  {
    printf("%d ", arr[i]);
  }
}
int cmp_float(const void* e1, const void* e2)
{
  // 比较浮点数值
  /*if (*(float*)e1 == *(float*)e2)
    return 0;
  else if (*(float*)e1 > *(float*)e2)
    return 1;
  else
    return -1;*/
    // 或者int强转
  return (int)(*(float*)e1 - *(float*)e2);
}
void test2()
{
  float f[] = { 9.0,8.0,7.0,6.0,5.0,4.0 };
  int sz = sizeof(f) / sizeof(f[0]);
  qsort(f, sz, sizeof(f[0]), cmp_float);
  int j = 0;
  for (j = 0; j < sz; j++)
  {
    printf("%.1f ", f[j]);
  }
}
struct stu
{
  char name[20];
  int age;
  float score;
};
// 打印后续结构体内容的函数
void print_stu(struct stu arr[], int sz)
{
  int i = 0;
  for (i = 0; i < sz; i++)
  {
    printf("%s %d %.1f\n", arr[i].name, arr[i].age, arr[i].score);
  }
  printf("\n");
}
int cmp_stu_by_age(const void* e1, const void* e2)
{
  // 比较年龄
  return ((struct stu*)e1)->age - ((struct stu*)e2)->age;
}
void test3() //年龄
{
  struct stu arr[3] = { {"zhangsan",20,87.5f},{"lisi",30,99.0f},{"wangwu", 10, 68.5f} };
  int sz = sizeof(arr) / sizeof(arr[0]);
  qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
  print_stu(arr, sz);
}
int cmp_stu_by_name(const void* e1, const void* e2)
{
  // 比较名字就是比较字符串
  // 字符串比较不能直接用><=来比较,应该用strcmp函数
  return strcmp(((struct stu*)e1)->name, ((struct stu*)e2)->name);
}
void test4() //姓名
{
  struct stu arr[3] = { {"zhangsan",20,87.5f},{"lisi",30,99.0f},{"wangwu", 10, 68.5f} };
  int sz = sizeof(arr) / sizeof(arr[0]);
  qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
  print_stu(arr, sz);
}
int cmp_stu_by_socre(const void* e1, const void* e2) //分数
{
  if (((struct stu*)e1)->score > ((struct stu*)e2)->score)
  {
    return 1;
  }
  else if (((struct stu*)e1)->score < ((struct stu*)e2)->score)
  {
    return -1;
  }
  else
  {
    return 0;
  }
}
void test5() //分数
{
  struct stu arr[3] = { {"zhangsan",20,87.5f},{"lisi",30,99.0f},{"wangwu", 10, 68.5f} };
  int sz = sizeof(arr) / sizeof(arr[0]);
  qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_socre);
  print_stu(arr, sz);
}
int main()
{
  //test1(); //排序整形数组
  //test2(); //排序浮点数
  //test3(); //排序结构体  通过年龄比较
  //test4(); //排序结构体  通过名字比较
  test5(); //排序结构体  通过分数比较
  return 0;
}

4.3、使用回调函数,模拟实现qsort(采用冒泡的方式)

#include<stdio.h>
struct stu
{
  char name[20];
  int age;
  float score;
};
// 打印后续结构体内容的函数
void print_stu(struct stu arr[], int sz)
{
  int i = 0;
  for (i = 0; i < sz; i++)
  {
    printf("%s %d %.1f\n", arr[i].name, arr[i].age, arr[i].score);
  }
  printf("\n");
}
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++;
  }
}
void bubble_sort(void* base, int sz, int width, int(*cmp)(void* e1, void* e2))
{
  int i = 0;
  for (i = 0; i < sz - 1; i++)
  {
    int j = 0;
    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);
      }
    }
  }
}
int cmp_int(const void* e1, const void* e2)
{
  //比较整形值
  return *(int*)e1 - *(int*)e2;
}
int cmp_stu_by_age(const void* e1, const void* e2)
{
  // 比较年龄
  return ((struct stu*)e1)->age - ((struct stu*)e2)->age;
}
int cmp_stu_by_name(const void* e1, const void* e2)
{
  // 比较名字就是比较字符串
  // 字符串比较不能直接用><=来比较,应该用strcmp函数
  return strcmp(((struct stu*)e1)->name, ((struct stu*)e2)->name);
}
int cmp_stu_by_socre(const void* e1, const void* e2) //分数
{
  if (((struct stu*)e1)->score > ((struct stu*)e2)->score)
  {
    return 1;
  }
  else if (((struct stu*)e1)->score < ((struct stu*)e2)->score)
  {
    return -1;
  }
  else
  {
    return 0;
  }
}
void test6() //整型数组
{
  int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
  int sz = sizeof(arr) / sizeof(arr[0]);
  bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
  int i = 0;
  for (i = 0; i < sz; i++)
  {
    printf("%d ", arr[i]);
  }
}
void test7()  // 年龄
{
  struct stu arr[3] = { {"zhangsan",20,87.5f},{"lisi",30,99.0f},{"wangwu", 10, 68.5f} };
  int sz = sizeof(arr) / sizeof(arr[0]);
  bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
  print_stu(arr, sz);
}
void test8() // 姓名
{
  struct stu arr[3] = { {"zhangsan",20,87.5f},{"lisi",30,99.0f},{"wangwu", 10, 68.5f} };
  int sz = sizeof(arr) / sizeof(arr[0]);
  bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
  print_stu(arr, sz);
}
void test9() // 分数
{
  struct stu arr[3] = { {"zhangsan",20,87.5f},{"lisi",30,99.0f},{"wangwu", 10, 68.5f} };
  int sz = sizeof(arr) / sizeof(arr[0]);
  bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_socre);
  print_stu(arr, sz);
}
int main()
{
  //test6(); //排序整形数组
  //test7(); //排序结构体  通过年龄比较
  //test8(); //排序结构体  通过名字比较
  test9();   //排序结构体  通过分数比较
  return 0;
}
相关文章
|
2月前
|
存储
指针进阶详解(下)
指针进阶详解(下)
27 0
|
2月前
|
存储 编译器 C语言
指针进阶详解(上)
指针进阶详解(上)
89 0
指针进阶详解(上)
|
8月前
|
存储
指针进阶详解!!!,干货满满(下)
指针进阶详解!!!,干货满满
|
11月前
|
存储 C++
|
11月前
|
存储 C++
指针进阶(纯干货!!!)
指针进阶(纯干货!!!)
27 0
|
8月前
|
安全 编译器 C++
【C进阶】指针(一)
【C进阶】指针(一)
18 0
|
11月前
|
存储 C++
指针的进阶(1)下
指针的进阶(1)下
|
11月前
|
存储 C++
指针进阶(一)上
指针进阶(一)
|
11月前
|
存储 编译器