c语言指针详解7

我们来回顾一下qsort函数

qsort(s,sz,sizeof(arr[0]),cmp_stu_by_name)

qsort函数一共有四个参数

第一个参数:待排列数组的首元素地址

第二个参数:待排列数组的元素个数

第三个参数:待排列数组一个元素所占的字节数。

第四个参数:是函数指针,比较两个元素所用函数的地址,函数使用者自己实现,函数的两个参数是带比较的两个参数的地址

struct Stu
{
	char name[20];
	int age[10];
};
void test4()
{
	int arr[10] = { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 };
	bubble_sort();

}
void test5()
{
	struct Stu s[3] = { { "zhangsan", 20 }, { "lisi", 30 }, { "wangwu", 10 } };
}

test4函数是我们使用冒泡排序函数对整形数组进行排列

test5函数是我们使用冒泡排序函数对结构体数组进行排列,我们进行类比操作

我们构建我们的冒泡排序函数

void bubble_sort(void*base, int sz, int width,比较函数)
{

}

我们对这个函数的参数进行分析:

第一个参数:void*base 第一个参数是一个无类型的指针 ,因为我们对待排序数组一无所知,所以我们首先要把待排序数组的首元素的地址传入函数内部,方便函数使用参数,为什么用void* 因为待排序数组的类型有很多种,用void*表示可以接收各种类型的地址,比如我们要对整型数组和结构体数组都排序

 分别使用两个函数分别对整型数组和结构体数组排序,我们传递第一个参数时,只需传递待排序数组首元素的地址即可,无需对其进行其他处理,因为void*指针能够接收不同种类型的指针

第二个参数:int sz,这里的sz是sizeof(arr)/sizeof(arr[0]),表示待排列数组元素的个数,注意!我们需要在test函数内部对sz参数进行定义过后才能将元素个数通过传参传递过去

第三个参数:int width,width表示宽度,这里表示待排列指针一个元素所占的字节数,我们需要在test函数内部对width参数进行定义过后才能将元素的宽度通过传参传递过去

第四个参数:第四个参数是我们的比较函数的地址,在冒泡函数中不可避免要使用两次for循环

void bubble_sort(void*base, int sz, int width,int(*cmp)(void*e1 ,void*e2))
{
	int i = 0;
	for (i = 0; i < sz; i++)//趟数
	{
		int j = 0;
		for (j = 0; j < sz - 1; j++)//每一趟的对数
		{
			//排序的具体逻辑
		}
	}
}

如图所示,这里的排序的基本逻辑就是我们需要构建的比较函数。

我们的比较函数的两个参数必须是void*型,两个void*的指针表示我们能函数能接收任意类型的指针

在排序的基本逻辑中,我们首先要明确,冒泡函数是把两个元素进行比较,再通过for循环实现每个元素的比较,所以我们要写出相邻两个元素的表达式,我们可以思考一下

cmp(base,base+1);

相邻两个元素用这样表达可以吗?

答:不可以,我们的base的类型是void*,void*是不能进行加减运算的,原因是void类型接收的指针类型是不同的,不同的类型+1跳过的字节数是不同的,int型的+1跳过四个字节,char型的+1跳过1个字节

cmp(base, (int*)base+1)

这种可以吗?

答:不可以,这种方法的意思是将base的类型强制转化为int*,在+1表示下一个元素,但这种方法有缺陷,当我们base接收的类型是结构体类型的指针时,元素之间的间隔不仅仅是四个字节,使用这种方法也会错误

cmp(base, (char*)base+width)

这种可以吗?

答:也是不可以的,但这种方法已经接近正确答案,我们对这种方法进行说明:第二个元素

(char*)base+width,我们首先将base强制类型转化为char*,这样base+1 -1只会跳过一个字节,我们再加上width,width在这里表示元素之间的间距,比如int型的width=4,char型的width=1,实现了任意类型都可以访问第二个元素,但是这种写法只能访问第一个元素和第二个元素,其他的元素无法比较

最正确的写法是这样

cmp((char*)base+j*width, (char*)base+(j+1)*width))

说明:(char*)base+j*width表示前一个元素,(char*)base+(j+1)*width)表示后一个元素,为什么呢?(char*)base表示我们首先将其强制类型转化为char型的指针,j表示的是第j对,当j=0时,参数就变成((char*)base,(char*)base+1*width)  满足第一对,当j=1时,参数就变成(char*)base+1*width, (char*)base+2*width) ,满足第二对,完整的冒泡函数是这样的

void bubble_sort(void*base, int sz, int width, int(*cmp)(void*e1 ,void*e2))
{
	int i = 0;
	for (i = 0; i < sz; i++)//趟数
	{
		int j = 0;
		for (j = 0; j < sz - 1; j++)//每一趟的对数
		{
			if (cmp((char*)base + j*width, (char*)base + (j + 1)*width)>0)
			{
				//交换
			}
		}
	}
}

接下来,我们来创建函数cmp

void cmp_int(void*e1, void*e2)
{
	return *(int*)e1 - *(int*)e2;
}

我们需要再写一个交换函数

swap((char*)base + j*width, (char*)base + (j + 1)*width,width);

swap函数的参数类型和比较函数的相同,我们需要对swap函数进行定义

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++;
	}
}

为什么这样写呢?

答:假设数组元素的宽度是8个字节,第一个元素为(1111 1111) 第二个元素为(0000 0000),如果我们直接将*buf1和*buf2进行交换,因为我们buf的类型是char型,char型只占一个字节,我们进行交换后的结果为(0111 1111) 第二个元素为(1000 0000)并没有实现真正意义的交换,所以我们要使用for循环,这时候的width=8,我们执行8次交换,得出的结果才是真正意义上的交换,注意这里的buf是指针,要访问的话需要解引用操作, 还要注意这里需要参数width,

我们将完整的int型的数组排列代码写出来

void cmp_int(void*e1, void*e2)
{
	return *(int*)e1 - *(int*)e2;
}
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; i++)//趟数
	{
		int j = 0;
		for (j = 0; j < sz - 1; 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 main()
{
	int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
		
	}
	return 0;
}

对这个函数进行简要说明

第一部分 :main函数(内部引用了冒泡函数(内部引用了int型的比较函数))

第二部分:创建冒泡函数(内部接收了int型的比较函数)(内部引用了swap函数)

第三部分:创建swap函数,实现交换

第四部分:创建int型的比较函数

接下来,我们写一个结构体类型的数组

	struct Stu 
{
	char name[20];
	int age[10];
};
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; i++)//趟数
	{
		int j = 0;
		for (j = 0; j < sz - 1; 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_by_age(const void*e1, const void*e2)
{
	return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}

int main()
{
	 struct Stu s[3] = { { "zhangsan", 20 }, { "lisi", 30 }, { "wangwu", 10 } };
	 int sz = sizeof(s) / sizeof(s[0]);
	 bubble_sort(s, sz, sizeof(s[0]), cmp_by_age);
	 return 0;
}

可以发现,使用冒泡函数调用结构体类型和整数数组类型差距很小

第一个:我们要对结构体类型进行声明    struct Stu 
{
    char name[20];
    int age[10];
};

第二:比较函数的定义不同int cmp_by_age(const void*e1, const void*e2)
{
    return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}

这里要注意:(struct Stu*)e1外围必须加上括号,因为->的优先级大于强制类型转化,所以要保证强制类型转化,所以必须加上括号。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值