C-指针的进阶(下)+qsort库函数

简介: C语言第十八弹

一、回顾上节所学知识


#include<stdio.h>
int my_strlen(const char* str)
{
  return 0;
}
int main()
{
  //指针数组
  char* arr[10];
  //数组指针
  int arr2[5] = { 0 };
  int(*p)[5] = &arr;//p是一个指向数组的指针变量
  //函数指针
  int (*pf)(const char*) = &my_strlen;//pf是一个指向函数的函数指针变量
  (*pf)("abcdef");
  pf("abcdef");
  //函数指针数组 - 存放函数指针的数组
  int (*pfArr[10])(const char*);
  return 0;
}

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


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

#include<stdio.h>
int Add(int x, int y)
{
  return x + y;
}
int Sub(int x, int y)
{
  return x - y;
}
int main()
{
  int (*pf)(int, int) = Add;
  int(*pfArr[10])(int, int) = { Add,Sub };//pfArr是数组名
  int(*(*ppfArr)[10])(int, int) =&pfArr;//取出数组的地址应该放在数组的指针中
  //ppfArr首先和*结合是一个指针,指向的一个数组(*ppfArr)[10];int(*)(int, int)剩下的这是一个函数指针类型这意味着指向的数组有10个元素每个元素都是函数指针类型
  //ppfArr是一个指向函数指针数组的指针变量 
  return 0;
}

三、回调函数


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

#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;
}
void menu()
{
  printf("************************\n");
  printf("**  1.add  **  2.sub  **\n");
  printf("**  3.mul  **  4.div  **\n");
  printf("****0.exit**************\n");
  printf("************************\n");
}
void Calc(int(*pf)(int, int))
{
  int x = 0;
  int y = 0;
  printf("请输入两个操作数:>");
  scanf("%d %d", &x, &y);
  int ret = pf(x, y);
  printf("%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时,把Add这个函数地址传给Calc,通过pf这个指针调用它所指向的指针,来响应选1的这个动作,所以当通过pf调用加法的时候,加法函数就是它的回调函数,同理当通过pf调用减法的时候,减法函数就是它的回调函数,当通过pf调用乘法的时候,乘法函数就是它的回调函数,当通过pf调用除法的时候,除法函数就是它的回调函数,我们并没有直接调用Add,Sub,Mul,Div而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应

四、qsort库函数


之前我们学过冒泡排序那你现在还会写吗?冒泡排序又有什么缺陷呢?我们有什么好办法去改善这个缺陷吗?

1.你还会写冒泡排序吗?


void bubble_sort(int* str, int sz)
{
  int i = 0;
  for (i = 0; i < sz - 1; i++)
  {
    int j = 0;
    for (j = 0; j < sz - 1 - i; j++)//两两相邻的元素进行比较
    {
      if (*(str+j) > *(str + 1+j))
      {
        int tmp = *(str+j);
        *(str+j) = *(str + j+1);
        *(str + 1+j) = tmp;
      }
    }
  }
}
#include<stdio.h>
int main()
{
  int arr[10] = { 3,2,5,7,8,1,9,4,6,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]);
  }
  return 0;
}

2.冒泡排序和qsort库函数的区别


qsort是可以排序任意类型的数据但是冒泡排序是不可以排序任意类型的数据的

3.qsort库函数介绍


void qsort(void* base,指向了待排序数组的第一个元素

size_t num,待排序的元素个数

size_t size,每个元素的大小,单位是字节

int ( * cmp)(const void * ,const void * )指向一个函数,这个函数可以比较2个元素的大小

指向一个函数,这个函数可以比较2个元素的大小时,当p1==p2时返回0,当p1<p2返回<0;当p1>p2返回>0;

注意:

int ( * cmp)(const void * ,const void * )这个函数指针参数类型是void * ;

#include<stdio.h>
int main()
{
  int a = 10;
  int* p = &a;
  //char* p = &a;
  void* p = &a;//*void - 无具体类型的指针,所以它可以接受任何类型的地址
  *p;//void*的指针不能解引用操作符
  p++;//也不能进行++ --操作
  *(int*)p;//强制类型转换
  return 0;
}

a是一个int类型的放在int * 的指针中,所以放在char * 指针中会发生报错。但是放在void * 类型中却不会报错,但是void*的指针不能解引用操作符;也不能进行++ --操作,应该进行强制类型转换

qsort是可以排序任意类型的数据:

1.比较2个整数的大小,> < ==

2.比较2个字符串,strcmp进行比较

3.比较2个结构体数据(学生:张三、李四)指定比较的标准,拿什么比较?

4.qsort库函数完成冒泡排序


qsort默认是排成升序的

#include<stdio.h>
#include<stdlib.h>
int cmp_int(const void* p1, const void* p2)
{
  return *(int*)p1 - *(int*)p2;
}
test1()
{
  int arr[] = { 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 main()
{
  test1();
  return 0;
}

5.qsort库函数完成结构体排序


比较2个结构体数据(张三、李四)指定比较的标准,拿什么比较?

我们可以根据年龄姓名等来比较:

(1).根据年龄来排序


#include<stdio.h>
#include<stdlib.h>
struct stu
{
  char name[20];
  int age;
};
int cmp_stu_by_age(const void* p1, const void* p2)
{
  return ((struct stu*)p1)->age  - ((struct stu*)p2)->age ;
}
test2()
{
  struct stu s[] = { {"zhangsan",30},{"lisi",25},{"wangwu",50} };
  int sz = sizeof(s) / sizeof(s[0]);
  qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);
}
int main()
{
  test2();
  return 0;
}

通过调试可以发现根据年龄改变了顺序


(2).根据名字来排序


名字是字符串,比较2个字符串,strcmp进行比较

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct stu
{
  char name[20];
  int age;
};
int cmp_stu_by_name(const void* p1, const void* p2)
{
  return strcmp(((struct stu*)p1)->name, ((struct stu*)p2)->name);
}
test2()
{
  struct stu s[] = { {"zhangsan",30},{"lisi",25},{"wangwu",50} };
  int sz = sizeof(s) / sizeof(s[0]);
  qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
}
int main()
{
  test2();
  return 0;
}

strcmp比较时同样是当p1==p2时返回0,当p1<p2返回<0;当p1>p2返回>0;

通过调试可以发现根据名字改变了顺序是根据所在字母的ASCII码值如果第一对首字母都相同就比较下一对依次往后


五、使用冒泡排序的思想来实现一个类似于qsort这个功能的冒泡排序函数bubble_sort()


(1).冒泡排序


#include<stdio.h>
#include<stdlib.h>
int cmp_int(const void* p1, const void* p2)
{
  return *(int*)p1 - *(int*)p2;
}
//希望这个bubble_sort函数可以排序任意类型的函数
//交换buf1和buf2这两个元素,每个元素假设6个字节,我们不能用两个变量进行交换因为我们不知道这两个元素是什么类型的所以不知道创建什么类型的临时变量所以我们一个字节一个字节的交换
//也就是buf1的第一个字节和buf2的第一个字节交换依次开始直到每个字节都交换了这样我们这两个元素就交换了
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, size_t num, size_t width,int (*cmp)(const void* p1,const void* p2))
{
  //排序的是整形把数组名传过去是int* base,如果把void*改成char*会报警告,写成void*不论什么数据都能很好的接收
  //因为排序的数字不可能是负数个所以size_int更贴合一点它是无符号整形
  //确定冒泡排序的趟数
  size_t i = 0;
  for (i = 0; i < num - 1; i++)
  {
    //一趟冒泡排序的过程
    size_t j = 0;
    for (j = 0; j < num - 1 - i; j++)
    {
      //两个相邻元素的比较
      //arr[j] arr[j+1]
      //把两个需要比较的函数放在cmp中
      //通过代码我们可以知道首元素地址是base,那我们写成base+j 与base+j+1对吗?//base是一个void*的指针不能直接加一个值的
      //我们不知道base所指向的类型是什么类型,如果我们强制类型转换成int*指针,加j就跳过j个整形,那如果我们排序的是字符串或者结构体呢,整形指针就不合适了
      //width是宽度,一个元素是width加j就跳过j个元素,相当于跳过j*width这么多字节,char*的指针跳过一个一个字节,让它加j*width相当于跳过了j个元素字节的个数
      //如果升序那就写成cmp()>0,也就是p1>p2然后进行交换
      if (cmp((char*)base + j * width, (char*)base + (j + 1) * width)>0)
      {
        //当返回值>0时我们就交换
        //Swap函数已经强制类型转换成char*
        //把两个数的地址传过去和两个字节的宽度这样我们就知道从buf1和buf2开始交换width这么多的字节
        Swap((char*)base + j * width, (char*)base + (j + 1) * width,width);
      }
    }
  }
}
void test3()
{
  int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
  int sz = sizeof(arr) / sizeof(arr[0]);
  //qsort默认是排成升序的
  bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
  int i = 0;
  for (i = 0; i < sz; i++)
  {
    printf("%d ", arr[i]);
  }
}
int main()
{
  test3();
  return 0;
}

步骤:


(1).

首先准备一个数组,然后求一下它的个数是10,然后我们把首元素地址,个数,一个元素的大小,cmp_int的函数的地址传进去

然后调用冒泡函数,base指向了数组首元素地址也就是9的地址,num是个数也就是10,宽度是4个字节,把cmp_int(也就是函数名)传给了cmp这个指针,然后指针指向了int cmp_int(const void p1, const void* p2)

(2).

假设第一对也就是比较9和8用cmp来比较,把9和8的地址传过去然后base强制类型转换成字符指针

第一次进去时j=0;0width=0,所以base+0还是指向首元素9的地址;j=0;j+1=1;1width(宽度是4)=4,强制类型转换成char*跳过4个字节指向8的地址

(3).

然后int cmp_int(const void* p1, const void* p2)中p1指向的是9;p2指向的是8,p1强制类型转换成整形指针解引用拿到的是9;同理p2拿到的是8;9-8=1

调用返回后1>0,所以9>8不满足升序,我们进行交换,把两个数的地址传过去,buf1指向的就是9,buf2指向的就是8;宽度是4

我们知道9和8在内存中存储时(小端存储);09 00 00 00 ;08 00 00 00

(4).

我们的buf1指向的就是09,buf2指向的就是08,我们循环4次,09和08交换,同理后面的00和00依次交换,直到全部交换完成后就变成了8,9

(5).

同理我们交换第一对后j++就变成了1,1width=4,所以base+1强制类型转换成char跳过4个字节指向元素9(交换后的)的地址;j=1;j+1=2;2width(宽度是4)=8,强制类型转换成char跳过8个字节指向7的地址,所以比较的是9和7

(6).

cmp这个函数是在外面提供的,所以通过函数指针去调用这个函数时,它就被成为回调函数

(7).

排成升序,假设在int cmp_int(const void* p1, const void* p2)中p1是1,p2是3,1-3<0;所以返回时就不需要交换了

如果降序就if (cmp((char*)base + j * width, (char*)base + (j + 1) * width)<0)改成<0;

(2).根据名字来排序


#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct stu
{
  char name[20];
  int age;
};
int cmp_stu_by_name(const void* p1, const void* p2)
{
  return strcmp(((struct stu*)p1)->name, ((struct stu*)p2)->name);
}
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, size_t num, size_t width, int (*cmp)(const void* p1, const void* p2))
{
  size_t i = 0;
  for (i = 0; i < num - 1; i++)
  {
    size_t j = 0;
    for (j = 0; j < num - 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);
      }
    }
  }
}
void test3()
{
  struct stu s[] = { {"zhangsan",30},{"lisi",25},{"wangwu",50} };
  int sz = sizeof(s) / sizeof(s[0]);
  bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_name);
}
int main()
{
  test3();
  return 0;
}

可以看出经过调试根据名字排序是可以实现的

根据上面的代码那你知道怎么根据结构体年龄来排序吗

总结


以上是对C-指针进阶的简单了解和qsort库函数的简单了解。

相关文章
|
26天前
|
存储 人工智能 算法
数据结构实验之C 语言的函数数组指针结构体知识
本实验旨在复习C语言中的函数、数组、指针、结构体与共用体等核心概念,并通过具体编程任务加深理解。任务包括输出100以内所有素数、逆序排列一维数组、查找二维数组中的鞍点、利用指针输出二维数组元素,以及使用结构体和共用体处理教师与学生信息。每个任务不仅强化了基本语法的应用,还涉及到了算法逻辑的设计与优化。实验结果显示,学生能够有效掌握并运用这些知识完成指定任务。
49 4
|
2月前
|
存储 C语言 C++
如何通过指针作为函数参数来实现函数的返回多个值
在C语言中,可以通过将指针作为函数参数来实现函数返回多个值。调用函数时,传递变量的地址,函数内部通过修改指针所指向的内存来改变原变量的值,从而实现多值返回。
|
2月前
|
存储 搜索推荐 C语言
如何理解指针作为函数参数的输入和输出特性
指针作为函数参数时,可以实现输入和输出的双重功能。通过指针传递变量的地址,函数可以修改外部变量的值,实现输出;同时,指针本身也可以作为输入,传递初始值或状态。这种方式提高了函数的灵活性和效率。
|
2月前
|
C++
指针中的回调函数与qsort的深度理解与模拟
本文详细介绍了回调函数的概念及其在计算器简化中的应用,以及C++标准库函数qsort的原理和使用示例,包括冒泡排序的模拟实现。
22 1
|
2月前
利用指针函数
【10月更文挑战第2天】利用指针函数。
19 1
|
2月前
|
算法 搜索推荐 C语言
【C语言篇】深入理解指针4(模拟实现qsort函数)
【C语言篇】深入理解指针4(模拟实现qsort函数)
25 2
|
3月前
|
Linux
在Linux内核中根据函数指针输出函数名称
在Linux内核中根据函数指针输出函数名称
|
26天前
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
92 13
|
2月前
|
C语言
无头链表二级指针方式实现(C语言描述)
本文介绍了如何在C语言中使用二级指针实现无头链表,并提供了创建节点、插入、删除、查找、销毁链表等操作的函数实现,以及一个示例程序来演示这些操作。
35 0
|
3月前
|
存储 人工智能 C语言
C语言程序设计核心详解 第八章 指针超详细讲解_指针变量_二维数组指针_指向字符串指针
本文详细讲解了C语言中的指针,包括指针变量的定义与引用、指向数组及字符串的指针变量等。首先介绍了指针变量的基本概念和定义格式,随后通过多个示例展示了如何使用指针变量来操作普通变量、数组和字符串。文章还深入探讨了指向函数的指针变量以及指针数组的概念,并解释了空指针的意义和使用场景。通过丰富的代码示例和图形化展示,帮助读者更好地理解和掌握C语言中的指针知识。
122 4