C进阶:指针(2),qsort函数,模拟实现冒泡算法

C/C++
127
0
0
2024-04-30

一.回调函数

要想理解回调函数,就要先知道什么是函数指针,函数指针详见:http://t.csdn.cn/oYiuC

1.回调函数的定义
函数指针作为某个函数的参数
函数指针变量可以作为某个函数的参数来使用的,回调函数就是一个通过函数指针调用的函数。 简单讲:回调函数是由别人的函数执行时调用你实现的函数。
2.来自知乎作者常溪玲的解说
你到一个商店买东西,刚好你要的东西没有货,于是你在店员那里留下了你的电话,过了几天店里有货了,店员就打了你的电话,然后你接到电话后就到店里去取了货。在这个例子里,你的电话号码就叫回调函数,你把电话留给店员就叫登记回调函数,店里后来有货了叫做触发了回调关联的事件,店员给你打电话叫做调用回调函数,你到店里去取货叫做响应回调事件。

我们来看一个实例:

代码语言:javascript

复制

#include <stdlib.h>  
#include <stdio.h>
 
void populate_array(int *array, size_t arraySize, int (*getNextValue)(void))
{
    for (size_t i=0; i<arraySize; i++)
        array[i] = getNextValue();
}
 
// 获取随机值
int getNextRandomValue(void)
{
    return rand();
}
 
int main(void)
{
    int myarray[10];
    /* getNextRandomValue 不能加括号,否则无法编译,因为加上括号之后相当于传入此参数时传入了 int , 而不是函数指针*/
    populate_array(myarray, 10, getNextRandomValue);
    for(int i = 0; i < 10; i++) 
    {
        printf("%d ", myarray[i]);
    }
    printf("\n");
    return 0;
}

实例中 populate_array() 函数定义了三个参数,其中第三个参数是函数的指针,通过该函数来设置数组的值。

实例中我们定义了回调函数 getNextRandomValue(),它返回一个随机值,它作为一个函数指针传递给 populate_array() 函数。

populate_array() 将调用 10 次回调函数,并将回调函数的返回值赋值给数组。

二.qsort函数

顾名思义, quick sort 快排,就是快速排序的意思,它是一个库函数,包含在头文件 <stdlib.h> 中,我们来看看它在库函数里是怎么定义的:

由定义可知,qsort 函数一共有 4 个参数:

1. 第一个参数是 类型为 void * ,名为 base 的变量; 2.第二个参数和第三个参数都是 一个无符号整型; 3.第四个参数是一个两个参数类型为 const void * ,返回值类型为 int 的函数指针。

那这些都代表的是什么意思呢?

我们来看官方的解释:

翻译版本:

由此可知:

1.第一个参数是指向要排序的数组的第一个元素的指针,所以实参应该传一个数组过来; 2.第二个参数是数组中元素的个数; 3.第三个参数是数组中每个元素的大小; 4.第四个参数是一个函数指针,且需要我们自己写。

所以我们需要传一个数组,数组中元素的个数,每个元素的大小,和一个函数;

因为 qsort 函数在设计的时候,作者并不知道你要比较什么,且也不知道你想要怎么比较,所以这个函数就需要我们自己来完成,我们写这个函数时,应把函数的两个参数的两个参数类型设计成 void *,返回值类型设计成 int 。

补充: void * 就像一个垃圾桶,你往里边放什么类型的指针都行,但不能对他进行解引用操作,因为不知道是什么类型的指针,如果解引用就不知道要访问几个字节

再来看看 qsort 函数的返回值:

有了以上这些,我们就可以尝试使用这个函数

下面来看一个实例:

代码语言:javascript

复制

#include <stdio.h>
#include <stdlib.h>

int cmp_int (const void * e1, const void * e2)   //这个函数的解释请参看下面的图解
{
   return ( *(int*)e1 - *(int*)e2 );
}

int main()
{
   int i;
   int arr[] = { 88, 56, 100, 2, 25 };
   printf("排序之前的列表:\n");
   for( i = 0 ; i < 5; i++ ) {
      printf("%d ", arr[i]);
   }

   qsort(arr, 5, sizeof(int), cmp_int);  //调用 qsort 函数

   printf("\n排序之后的列表:\n");
   for( i = 0 ; i < 5; i++ )
   {
      printf("%d ", arr[i]);
   }
 
  return 0;
}

结果:

cmp_int 函数解释:

三.模拟实现冒泡算法

1.什么是冒泡排序
冒泡排序

冒泡排序(英语:Bubble Sort)是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序(如从大到小、首字母从A到Z)错误就把他们交换过来。

过程演示:

图解:

下面来看一个实例:

代码语言:javascript

复制

void bubble_sort(int arr[], int len)
 {
    int i, j, temp;
    for (i = 0; i < len - 1; i++)    //确定趟数
    {
        for (j = 0; j < len - 1 - i; j++)  //对一趟进行冒泡排序
        {
            if (arr[j] > arr[j + 1]) 
            {
                temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
}
int main() 
{
    int arr[] = { 22, 34, 3, 32, 82, 55, 89, 50, 37, 5, 64, 35, 9, 70 };
    int len = (int) sizeof(arr) / sizeof(*arr);
    bubble_sort(arr, len);
    int i;
    for (i = 0; i < len; i++)
        printf("%d ", arr[i]);
    return 0;
}

其实我们会发现,上面这种方法有一定的局限性,它只能排整型数据,那我们可不可以写一个类似于冒泡排序算法的函数来实现可以排序任何数据呢?

这就需要利用到回调函数了

2.模拟实现冒泡算法

通过上文我们知道,qsort 是一个可以快速排序的库函数,它使用起来很方便,那么我们就可以模仿 qsort 函数的定义来实现 一个可以排任何数据的冒泡函数 bubble_sort;

函数名有了,接下来就是函数参数和返回值的设计了,仿照 qsort ,我们可以这么设计:

void bubble ( void*base , size_t sz ,size_t width, void (*cmp) (const void * ,const void * )

1.第一个参数 是指向要排序的数组的第一个元素的指针; 2.第二个参数 是数组元素的个数; 3.第三个参数 是数组每个元素的大小,也可以理解成宽度,单位是字节; 4.第四个参数 是一个函数指针,且需要我们自己设计

那么这个函数要怎么设计呢?

我们知道冒泡排序是两个相邻元素之间的比较,所以说在设计函数参数时,参数应该指向的是数组中两个相邻的元素,可是我们在设计函数时并不知道参数的具体类型,又该怎么向函数传数组中的两个相邻元素呢?

请看下图:

两个相邻的元素找到了,下面就是交换了

交换就需要创建一个中间变量,可是我们并不知道要交换的两个数据的类型,那自然就不知道该怎么定义中间变量的类型,但是众所周知,所有的数据类型中 char 是最小的,只占1个字节,那么其他的数据类型我们都可以看成是由若干个 char 组成的,所以我们可以定义一个 char 变量来作为中间变量,然后对数据进行一个一个字节的交换,交换终止的条件由 width 决定,所以我们定义的 width 也要传到交换函数里面去。

具体代码:

代码语言:javascript

复制

void Swap(char* buf1, char* buf2, int width)  
{
	int i = 0;
	for (i = 0; i < width; i++)  //循环由 width 控制
	{
		char tmp = *buf1;   //一个一个字节进行交换
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;      //一个字节交换完后 ,buf1++ buf2++,再进行下一轮的字节交换
		buf2++;
	}
}

实例:

代码语言:javascript

复制

int cmp_int(const void* e1, const void* e2)
{
	return *((int*)e2) - *((int*)e1);
} 
//交换函数
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)(const void* e1, const void* e2))
{
	int i = 0, j = 0;
	for (i = 0; i < sz - 1; i++)  //确定趟数
	{
		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); //交换
			}
		}
	}
}
//打印数组
void print(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}
void test4()
{
	int arr[10] = { 1,3,5,7,9,2,4,6,8,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
	print(arr, sz);
}
int main()
{
	test4();
	return 0;
}

运行结果:

😸😼本篇文章到此就结束啦,如有错误或是建议,欢迎小伙伴们指出。🐆🐅

😻😽谢谢你的阅读。🦖🦄