在学习c语言的时候,我们接触到了冒泡排序,利用冒泡排序我们可以快速的对一个数组进行大小的比较,可是大家有没有想过一个问题,有没有一种排序方式可以排序任何类型的元素类型的呢?答案是有的,它是qsort函数,它c语言库里面的一个函数,同时也利用到了一个回调函数的思想在这里我们先一步步来了解这个函数,先从冒泡排序开始抛砖引玉的介绍一下qsort函数。
目录
- 冒泡排序
- 由冒泡排序引入qsort函数
- 浅谈回调函数
- 回调函数的实例多功能计算器
- 总结
1.冒泡排序
冒泡排序的是指在排序的时候,每次比较数组中相邻的两个数组的元素的值,将较小的数字从小到大排在较大数字的前面。 关于冒泡排序,我直说两个很重要的点,第一个就是每次排序,它必然会排一个大数的,就是最大数字会沉在下面即外部循环是总数减去一,第二是内层循环,在这层比较的时候要减去之前已排好的数字。 这两点不好理解,还是希望大家可以亲自上手试一试,写写代码。
代码如下
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
int i = 0;
int j = 0;
int t = 0;
int arr[5] = {12,5,23,6,2};//每次找到一个都需要把最大的放到最底下,不需要判断
for (i = 0; i < 4; i++)//外层的循环,代表要比较多少次10个数字只需要比较9次即可,最大的那个数字会默认排号
{
for (j = 0; j < 4- i; j++)//有5个数字,比4趟,每趟比较(4-i)次
{
if (arr[j] > arr[j + 1])
{
t = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = t;
}
}
}
for (i =0;i<5; i++)
{
printf("%d\t", arr[i]);
}
return 0;
}
冒泡排序的特点就是每次升序排序,每一轮都会把最大的数字下沉到最底端,如果在比较的中后一个元素的值小于前一个值则交换,当所有的循环都完成后,就将数组元素按照从小到达的顺序排列了。
2.由冒泡排序引入qsort函数
在学习了冒泡排序后,我们要引入c语言的一种排序方式qsort,它是一个库函数。我们在这里先给出它的一些简单的定义,然后逐一的给大家解释一下。
我们可以看到qsort的函数参数还是比较复杂的,我在这里做出一些说明
void qsort(void* base,//需要排序的数据的起始位置数组名字就是起始位置
size_t num,//待排序的数据元素的个数,
size_t width,//待排序的数据元素的大小字节
int(*cmp)(const void* e1, const void* e2)//函数指针-比较函数
);
接下来是一段代码演示
#include<stdlib.h>
#include<stdio.h>
int cmp_int(const void* e1, const void* e2);
int main()
{
int arr[] = { 1,-2,34,12,-8,-9,12 };
int sz = sizeof(arr) / sizeof(arr[0]);//计算数组的长度
//qsort(arr, sz, sizeof(arr[0]), cmp_int);
//void qsort(void* base,//需要排序的数据的起始位置数组名字就是起始位置
// size_t num,//待排序的数据元素的个数,
// size_t width,//待排序的数据元素的大小字节
// int(*cmp)(const void* e1, const void* e2)//函数指针-比较函数
//);
//)
qsort(arr, sz, sizeof(arr[0]), cmp_int);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d\t", arr[i]);
}
return 0;
}
int cmp_int(const void* e1, const void* e2)
{
return(*(int*)e2 - *(int*)e1);
}
我们来看一下这个代码,首先定义一个数组,然后我们用sizeof来得出数组的长度,存放在sz中,其中这里面最重要的就是比较函数cmp_int,这个是需要我们按照自己意愿去写的,我们这里面按照整型的数组来写,但是它还可以比较结构体,字符串等。在这里我来解释一些,为什么在int cmp_int(const void*e1,const void*e2);中是const 类型,因为我们不知道未来会是一个什么样子的函数类型去比较,void这里表示的是一个通用指针,之后我们要用到强制转换为int型,让编译器知道要比较一个int类型的数组元素。
在返回值上我们给出如下的一张图片
为了简便我们使用两个数字去相减,得到的数字会返回,这样不用写太多的代码,最后让我们运行一下看看结果是什么。
我们可以看到运行的结果是我们所期望的,同时给出回调函数的概念:回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
换句话说我们这个qsort函数中最后一个函数的参数,比较函数就是特定的事件,它是传入地址来找到这个比较函数的地址完成功能(其实这个qsort有点像Java的多态,在以后的深入学习中可能有更多的理解)
3.浅谈回调函数
函数指针是指向函数的指针变量,通过函数指针c语言可以实现强大功能和设计方法。函数的内存保存了函数开始执行的位置,使用指针来指向这个函数。函数的指针可以作为函数的参数来传递给函数,这样函数内部就可以根据函数指针所指向函数的不同而调用不同的函数,通过该函数的指针调用的函数称为回调函数。
在开发的场景中有开发者和使用者,开发者开发一个通用的函数,使用该函数时使用者需要传入函数指针,这个指针指向的函数是使用者开发的,在使用的过程中通过传入函数指针调用使用者开发的函数的过程就是回调过程,使用者开发的就是回调函数了。
最后用一个图片来演示一下
4.回调函数的实例多功能计算器
假如要用c语言来编写一个可以进行加减乘除的程序,要怎么做才能写出不冗杂简便的代码呢,用回调函数一个最好的选择,代码如下。
void menu();
int Add(int x, int y);
int Sub(int x, int y);
int Mul(int x, int y);
int Div(int x, int y);
void calc(int (*pf)(int, int));
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("退出选择");
break;
default:
break;
}
}
while (input);
return 0;
}
void menu()
{
{
printf("*********************************\n");
printf("***1.Add********2.Sub*************\n");
printf("***3.Mul*******4.Div**************\n");
printf("***0.exit*************************\n");
}
}
void calc(int (*pf)(int, int))
{
int x = 0;
int y = 0;
int ret = 0;
printf("请输入两个操作符:>");
scanf("%d %d", &x, &y);
ret = pf(x, y);//调用到Add加法的地址去了
printf("%d\n", ret);
}//pf指针是来接收Add函数的地址 加法的地址传进来指向了Add函数
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);
}
在这里我要解释一下这些代码,首先我们打印了一个简单的菜单,在这里我们选择对应的数字可以实现不同的功能,使用switch的语句可以实现这个功能。然后写了加减乘除的几个函数,这些函数的作用是把他们作为地址传入calc的这个函数中,实现调用功能,这点很重要。函数的名字就是地址,就可以传入calc中,实际上clac这个函数是封装起来了,它接收了函数的地址,就可以进行加减乘除的运算了,我给大家展示一下运行的结果。
5.总结
我们先学习了冒泡排序,然后引出qsort之后,了解回调函数,练习了一个简单的计算器实例,其实它的本质就是通过函数的地址来进行调用,这部分就涉及了指针的知识,今后还会再学习。