指针进阶(3)--玩转指针

简介: 回调函数就是,把一个函数的地址,放在函数指针中,然后将该指针作为一个参数,传到另一个函数中,在这个函数内部使用了外部写好的一个函数.举一个例子,看完你一定明白了

回调函数就是,把一个函数的地址,放在函数指针中,然后将该指针作为一个参数,传到

另一个函数中,在这个函数内部使用了外部写好的一个函数.

举一个例子,看完你一定明白了

例子:

void menu(void)
{
  printf("*************************\n");
  printf("**1.Add   2.Sub**********\n");
  printf("**3.Mul   4.Div**********\n");
  printf("********0.exit***********\n");
}
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;
}
void cal(int (*pf)(int a, int b))//函数指针
{     //这里面存放的是你需要选用的函数的地址
  int a = 0;
  int b = 0;
  printf("请输入两个数:");
  scanf("%d%d", &a, &b);
  printf("结果为%d\n", pf(a, b));
}
int main()
{
  int input = 0;
  do
  {
    menu();
    printf("请选择:->\n");
    scanf("%d", &input);
    switch (input)
    {
    case 1:
      cal(Add);
      //在一个函数内部调用另一个已经写好的函数
      break;
    case 2:
      cal(Sub);
      break;
    case 3:
      cal(Mul);
      break;
    case 4:
      cal(Div);
      break;
    case 0:
      printf("退出\n");
      break;
    default:
      printf("选择错误\n");
      break;
    }
  } 
  while (input);
}

简单的说,就是在一个函数内部调用另一个已经写好的函数

dfc4ac3af0694955a515970a06810936.png

图解如上:

理解回调函数后,下面引出一个函数,叫做 qsort 函数

函数参数如下

void qsort (void* base, size_t num, size_t size,
            int (*compar)(const void*,const void*));
参数1 待排序数组首元素地址
参数2 元素个数
参数3 元素大小--单位是字节
参数4 函数指针,比较两个元素的方法和函数的地址,该函数自己实现

详解qsort函数参数:

1.为什么指针是void类型呢?

void指针,可以理解成一个万能桶,什么类型的地址都能放到void的指针里面

举一个简单的例子:

int a =10 ;
void*p = &a;
char ch ='w' ; 
p = & ch ;

43ab8b5a78014f1baa866785e626802a.png

编译该段代码可以发现,未发生错误

注意看,调试过程中,指针p存放的地址由a的地址变成了ch的地址,证明什么类型的地址都能放到void*的指针里面

image.gif

由于void是无类型指针,所以void类型指针不能进行加减操作,不能进行解引用操作,想要进行这些操作,需要把void类型强制类型转成所需要的类型才能操作


image.png

编译无法通过;

2.函数指针,比较两个元素的方法和函数的地址,该函数自己实现什么意思呢?

直接上代码:

下面排序一个 int 类型数组的元素

int cmp_int(const void* e1, const void* e2)
{
  return  *(int*)e1 - *(int*)e2; 
}
该函数实现的是 两个元素 比较的方法
int main()
{
  int arr[] = { 2,3,5,7,1,4,9,6,8 };
  int sz = sizeof(arr) / sizeof(arr[0]);
  qsort(arr, sz, sizeof(arr[0]), cmp_int);
  参数1:数组首元素地址,参数2:元素个数
  参数3:每个元素大小(单位字节)
  参数4:函数指针
  for (int i = 0; i < sz; i++)
  {
    printf("%d ", arr[i]);
  }
}

由于 void指针无类型,故在比较函数内部,需要把void指针强制类型转化为所需要的类型

可以想象 qsort 函数是拿到比较的方法后,通过这个方法比较完其他的所有元素

代码运行结果如下:

832416b8848f49d1920710207a209cb1.png

非常方便快捷得出结果

再来一个例子,假如要排序结构体呢?

int cmp_struct(const void* e1, const void* e2)
{
  return strcmp(((struct stu*)e1)->name, ((struct stu*)e2)->name);
  //以名字来对结构体进行排序
}
int main()
{
  struct stu student[3] = { {"张三",20},{"李四",19},{"王五",18}};
  int sz = sizeof(student) / sizeof(student[0]);
  qsort(student, sz, sizeof(student[0]), cmp_struct);
  for (int i = 0; i < sz; i++)
  {
    printf("%s %d\n", student[i].name, student[i].age);
  }
}

对结构体进行排序时,根据需求,是按名字字母顺序排序还是按年龄排序

对于结构体排序来说 , e1需要强制类型转化为结构体类型指针


127041abd8d04819bb3ab101cf8fe7ab.png

按名字排序的结果如上:

总结:
qsort函数参数的理解重点在于函数指针
就是在外部写好一个函数,将该函数的地址作为参数传给qsort函数,
外部写好的函数就是 回调函数

3.自己编写冒泡排序函数,要求能对任意类型的数据进行排序 (仿照qsort参数)

代码剖析:

要自己实现冒泡排序,基本的框架和思想是不变的,两层循环进行排序

下面开始深入剖析每段代码的含义:

1.主函数实现

int main()
{
  char arr[][15] = { "hello","world","i love china" };
  int sz = sizeof(arr) / sizeof(arr[0]);
  bubble_sort(arr, sz, sizeof(arr[0]), cmp_char);
  for (int i = 0; i < sz; i++)
  {
    printf("%s\n",arr[i]);
  }
}

2.冒泡排序函数实现,具体请看下面

void bubble_sort(void* base, int sz, int width, int (*cmp)(void* e1, void* e2))
{         //base是数组首元素地址      函数指针,存放的是cmp比较函数的地址
  int i = 0;
  int j = 0;
  for (i = 0; i < sz -1; i++)
  {
    for (j = 0; j < sz - i -1; j++)
    {
      if (cmp((char*)base + width * j, (char*)base + width * (j + 1)) > 0)
      {
        //把void*指针强制类型转化成char*指针,因为不知道未来要排序的元素是什么类型
          // 那就统一变成char*,是一个字节,加上宽度,就能知道每个元素是几个字节了
        //比较函数   base+每个元素的大小(宽度width) 意味着移动到该元素下
         //j =0时,比较第一个和第二个元素,j=1时,比较第二个和第三个元素
        swap( (char*)base + width * j, (char*)base + width * (j + 1), width);
          //交换  这里不仅要传两个要交换的元素的地址过去,还要知道这两个元素有多长,就需要传width
          //否则无法交换完成
      }
    }
  }
}

.交换函数实现

void swap(char* buf1, char* buf2 ,int width)
{    //这里用char*的指针来接受,每移动一次就是1个字节
  int i = 0;
  for (i = 0; i < width; i++)
  {
    char tmp = *buf1;
    *buf1 = *buf2;
    *buf2 = tmp;
    buf1++;
    buf2++;
    //每执行交换一次,指针往后移继续交换,直到把两个数组里面的元素都交换为止
  }
}

width 已经在主函数计算过,现在直接使用即可

4.cmp_char函数实现

int cmp_char(const void* e1, const void* e2)
{
  return strcmp( (char*)e1 , (char*)e2 );
}

cd894419728c4cd4a8b66b66707d9969.png

结果如上:

假如不想比较字符串了,想对结构体进行排序:

1.重新写一个函数,这个函数实现的是比较结构体的方法:

如下:

struct stu
{
  char name[20];
  int age;
};

先初始化结构体

自定义比较函数,通过年龄来比较,先把e1和e2强转为结构体类型指针,通过结构体指针直接访问年龄。

int main()
{
  struct stu student[3] = { {"张三",19},{"李四",20},{"王五",18}};
  int sz = sizeof(student) / sizeof(student[0]);
  bubble_sort(student, sz, sizeof(student[0]), cmp_struct_by_age);
  for (int i = 0; i < sz; i++)
  {
    printf("%s %d\n", student[i].name, student[i].age);
  }
}

设置好函数比较方法后,直接调用即可

下面是运行结果

678b78c70c424b6494c1616295723f08.png

说到这里,把全部代码贴出来,理解之后,你可以运行一下看看效果

struct stu
{
  char name[20];
  int age;
};
void swap(char* buf1, char* buf2, int width)
{    //这里用char*的指针来接受,每移动一次就是1个字节
  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))
{         //base是数组首元素地址
  int i = 0;
  int j = 0;
  for (i = 0; i < sz - 1; i++)
  {
    for (j = 0; j < sz - i - 1; j++)
    {
      if (cmp((char*)base + width * j, (char*)base + width * (j + 1)) > 0)
      {
        //把void*指针强制类型转化成char*指针,目的是不知道未来要排序的元素是什么类型
          // 那就统一变成char*,是一个字节,加上宽度,就能知道每个元素是几个字节了
        //比较函数   base+每个元素的大小(宽度width) 意味着移动到该元素下
         //j =0时,比较第一个和第二个元素,j=1时,比较第二个和第三个元素
        swap((char*)base + width * j, (char*)base + width * (j + 1), width);
        //交换  这里不仅要传两个要交换的元素的地址过去,还要知道这两个元素有多长,就需要传width
        //否则无法交换完成
      }
    }
  }
}
int cmp_struct_by_age(const void* e1, const void* e2)
{
  //return strcmp(((struct stu*)e1)->name, ((struct stu*)e2)->name);
  //以名字来对结构体进行排序
  return ((struct stu*)e1)->age - ((struct stu*)e2)->age; 
  //以年龄对结构体进行排序
}
int main()
{
  struct stu student[3] = { {"张三",19},{"李四",20},{"王五",18}};
  int sz = sizeof(student) / sizeof(student[0]);
  bubble_sort(student, sz, sizeof(student[0]), cmp_struct_by_age);
  for (int i = 0; i < sz; i++)
  {
    printf("%s %d\n", student[i].name, student[i].age);
  }
}


相关文章
|
1月前
|
C语言
【C语言】指针进阶之sizeof和strlen函数的对比
【C语言】指针进阶之sizeof和strlen函数的对比
|
1月前
|
C语言
C语言---指针进阶
C语言---指针进阶
19 0
|
1月前
C进阶:指针的练习(1)
C进阶:指针的练习(1)
|
1月前
C进阶:指针的进阶(4)
C进阶:指针的进阶(4)
|
1月前
|
存储 C语言
C进阶:指针的进阶(1)
C进阶:指针的进阶(1)
|
1月前
|
存储 C语言 C++
C语言指针进阶-1
C语言指针进阶-1
24 1
|
1月前
|
存储 C语言 C++
【指针的进阶】C语言
【指针的进阶】C语言
|
1月前
|
C语言
【C语言进阶】 指针进阶(二)
【C语言进阶】 指针进阶(二)
|
1月前
|
C语言 C++
【C语言进阶】 指针进阶(一)
【C语言进阶】 指针进阶(一)
|
1月前
|
C语言
【C语言】指针进阶之传值调用与传址调用
【C语言】指针进阶之传值调用与传址调用