C语言篇 + 指针进阶练习 + qsort模拟实现(回调函数思想) + 指针和数组笔试题

Posted IT莫扎特

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C语言篇 + 指针进阶练习 + qsort模拟实现(回调函数思想) + 指针和数组笔试题相关的知识,希望对你有一定的参考价值。


前言

qsort(quicksort)根据你给的比较函数给一个数组快速排序,是通过指针移动实现排序功能,关于qsort的模拟实现,本次在底层的使用的排序算法是使用的冒泡排序,那么就先来了解一下冒泡排序吧,另外作为C/C++程序员我们更应该透彻地去理解指针和数组,这里我准备了几道大题,内容会很精彩,期待你的点赞

冒泡排序

1、比较相邻的元素。如果第一个比第二个大,就交换他们两个。
2、对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
3、针对所有的元素重复以上的步骤,除了最后一个。
4、持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

/* 打印 */
void print(int arr[],int sz) 
{
	for (int i = 0; i < sz; i++) 
	{
		printf("%d ",arr[i]);
	}
	printf("\\n");
}
/* 交换 */
void Swap(int *n1,int *n2) 
{
	int tmp = *n1;
	*n1 = *n2;
	*n2 = tmp;
}
/* 冒泡算法 */
void bubble_sort(int arr[], int sz) 
{
	int i = 0;
	for (i = 0; i < sz - 1; i++) 
	{
		int j = 0;
		for (j = 0; j < sz - 1 - i; j++)
		{
			if (arr[j] > arr[j + 1]) 
			{
				Swap(&arr[j],&arr[j + 1]);
			}
		}
	}
}

int main() 
{
	int arr[10] = {9,8,7,6,5,4,3,2,1,0};
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr,sz);
	print(arr,sz);
	return 0;
}

以上的讲解大家对冒泡排序应该有了一个清晰的认知了,接下来进入我们的主题

了解qsort


compare使用文档

void qsort(void *base, //待排序的对象,写成void*的形式便于排序任意类型的数据
		   size_t num, //待排序的元素个数
		   size_t width,//一个元素的大小,单位是字节
		   int(__cdecl *compare)
		   (const void *elem1, const void *elem2))
{

}
>compare指向排序时用来比较两个元素的函数
> void *无具体类型的指针,能够接受任意类型的地址
> 缺点:不能进行运算,不能解引用
> 举例:void *p3;
> 		p3++  //err

报错信息

c语言库qsort的使用

整形类型数据的排序

void print(int arr[],int sz) 
{
	for (int i = 0; i < sz; i++) 
	{
		printf("%d ",arr[i]);
	}
	printf("\\n");
}

int compare_int(const void *elem1, const void *elem2) 
{
/* 升序 */
	return *(int*)elem1 - *(int*)elem2;
/* 降序 */
	//return *(int*)elem2 - *(int*)elem1;
}

int main() 
{

	int arr[10] = {9,8,7,6,5,4,3,2,1,0};
	int n = sizeof(arr) / sizeof(arr[0]);
	/* 排序 */
	qsort(arr,n,sizeof(arr[0]), compare_int);
	/* 打印 */
	print(arr, n);
	return 0;
}

浮点型型数据的排序

这里要提一句,浮点型数据类型会有精度的丢失,不能直接通过两个形参值相减后直接返回结果,通过关系比较返回合适的整形

int cmp_double(const void* elem1, const void* elem2)
{
	return ((*(double*)elem1- *(double*)elem2) > 0)? 1:-1;
}
int main()
{
	double a[] = { 1.2,56.4,0.56,456.89,32.4 };
	int sz = sizeof(a) / sizeof(a[0]);
	qsort(a, sz, sizeof(a[0]), cmp_double);
	for (int i = 0; i < sz; i++)
	{
		printf("%.2f ", a[i]);
	}
	
	return 0;
}

结构体类型数据的排序

typedef struct Student 
{
	int age;
	char name[20];
	float score;

}Stu;

void print(Stu arr[], int sz)
{
	for (int i = 0; i < sz; i++)
	{
		printf("%.1f ", arr[i].score);
	}
	printf("\\n");
}

int compare_name(const void *elem1, const void *elem2) 
{
/* 升序,这里比较的是字符串的长度*/
	return strcmp(((Stu*)elem1)->name, ((Stu*)elem2)->name);
}

int compare_age(const void *elem1, const void *elem2)
{
/* 升序 */
	return ((Stu*)elem1)->age - ((Stu*)elem2)->age;
}

float compare_score(const void *elem1, const void *elem2)
{
/* 升序 */
	return ((Stu*)elem1)->score - ((Stu*)elem2)->score;
}

int main() 
{
	Stu s[3] = { {18,"zhangsan",99.5},{20,"lisi",66.5},{21,"wangwu",76} };
	int n = sizeof(s) / sizeof(s[0]);
	qsort(s,n,sizeof(s[0]), compare_score);
	print(s, n);
	return 0;
}

qsort模拟实现

typedef struct Student
{
	int age;
	char name[20];
	float score;

}Stu;

int compare_name(const void *elem1, const void *elem2)
{
	/* 升序,这里比较的是字符串的长度*/
	return strcmp(((Stu*)elem1)->name, ((Stu*)elem2)->name);
}

/* 输出整形数据 */
void print1(int arr[], int sz)
{
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\\n");
}
/* 输出结构体成员数据 */
void print2(Stu arr[], int sz)
{
	for (int i = 0; i < sz; i++)
	{
		printf("%s ", arr[i].name);
	}
	printf("\\n");
}

void Swap(char *buf1,char *buf2, size_t width)
{
	size_t i = 0;
	/* 交换宽度次 */
	for (i = 0; i < width; i++) 
	{
		int tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;

		/* 指针往后迭代 */
		buf1++;
		buf2++;
	}
}

int BubbleSrot_cmp(const void *elem1, const void *elem2)
{
	return *(char*)elem1 - *(char*)elem2;
}
/* 使用回调函数实现一个通用的冒泡排序函数 */
void BubbleSrot(void *base, size_t num, size_t width, int (*BubbleSrot_cmp)(const void *elem1, const void *elem2))
{
	//趟数
	size_t i = 0;
	for (i = 0; i < num - 1; i++) 
	{
		//比较的对数
		size_t j = 0;
		for (j = 0; j < num - 1 - i; j++)
		{
			//判断
			if (BubbleSrot_cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
			{
				//交换
				Swap((char*)base + j * width, (char*)base + (j + 1) * width,width);
			}
		}
	}
}
/* 整形数据排序*/
void test3() 
{
	int arr[10] = { 9,8,66,47,65,77,2,1,0 ,4};
	int n = sizeof(arr) / sizeof(arr[0]);
	BubbleSrot(arr, n, sizeof(arr[0]), BubbleSrot_cmp);
	print1(arr, n);
}
/* 结构体类型数据排序 */
void test4() 
{
	Stu s[3] = { {18,"zhangsan",99.5},{20,"lisi",66.5},{21,"wangwu",76} };
	int n = sizeof(s) / sizeof(s[0]);
	qsort(s, n, sizeof(s[0]), compare_name);
	print2(s, n);
}
int main() 
{
	test3();
	test4();
	return 0;
}

int BubbleSrot_cmp(const void elem1, const void elem2)
{
return * (char * )elem1 - * (char * )elem2;
}
BubbleSrot_cmp是比较函数,这次的模拟实现是基于对compar函数的理解和运用,所以功能在本质上是一样的

指向比较两个元素的函数的指针。
qsort会反复调用这个函数来比较两个元素。它将遵循以下原型:
接受两个指针作为实参(都转换为const void
)。该函数通过返回(以稳定和可传递的方式)来定义元素的顺序:
(char * )base写成这种形式主要是因为最合适,粒度更细,指针的步长跟类型是有关的,char
的指针步长是1,p + 1跳过的是一个字节长度根据,这样一来(char * )base参与加减运算的时候步长就是1了,再通过(j * width)和(j + 1 ) * width 拿到相邻的 两个元素的地址,传递过去的实参大小取决于width的宽度,传递过去的指针比较指针所指向地址标识的那块空间存储数据的大小,确定是否执行交换,这里的排序是一个升序

void Swap(char *buf1,char *buf2, size_t width)
{
	size_t i = 0;
	/* 交换宽度次 */
	for (i = 0; i < width; i++) 
	{
		int tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;

		/* 指针往后迭代 */
		buf1++;
		buf2++;
	}
}

Swap函数交换的是指针指向的地址的宽度个字节

此时我们可以看到e1指向了数组的首地址,而这3个元素的值都是9、8、66,

执行第一次循环8和9换了,执行次数的条件限制是 i < width,也就是会循环 width - 1 次,而恰好int类型的数据占4字节,所以循环结束了这两个值也交换完了

指针和数组笔试题解析

一维数组

了解sizeof和strlen

sizeof是一个操作符,求的是变量所占空间的大小,求类型创建的变量所占空间的大小,单位是字节
strlen是一个库函数,功能是求字符串长度,从起始位置开始向后计数直到遇见‘\\0’结束标记,中间统计的字符个数就是字符串的长度

//一维数组
1int a[4] = {1,2,3,4};
//这是一个一维数组,数组的长度是4、数组的每个元素类型是int,数组的大小是16
2printf("%d\\n",sizeof(a));
//sizeof操作符与数组名单独放在一起,计算的是数组的大小,16
3printf("%d\\n",sizeof(a+0));
//a是数组首元素的地址,a + 0依旧是首元素的地址,计算地址的大小具体跟操作系统相关,4/8
4printf("%d\\n",sizeof(*a));
//a是首元素的地址,对地址解引用找到的是数组下标为0的整形元素,int占4个字节,4
5printf("%d\\n",sizeof(a+1));
//a是首元素的地址,a + 1跳过一个整形,但他还是一个地址,计算地址的大小具体跟操作系统相关,4/8
6printf("%d\\n",sizeof(a[1]));
//下标为1的整形元素,类型是int占4个字节
7printf("%d\\n",sizeof(&a));
//&a取出整个数组的地址,地址 + 1跳过一个数组的大小,但是a还是一个地址,计算地址的大小具体跟操作系统相关,4/8
8printf("%d\\n",sizeof(*&a));
//&a取出整个数组的地址(int(*)[4]),整个数组的大小是16,对数组的地址解引用,计算的是数组的大小,16
9printf("%d\\n",sizeof(&a+1));
//&a是数组的地址,&a + 1跳过整个数组,但是此时的&a + 1还是一个地址,计算地址的大小具体跟操作系统相关,4/8
10printf("%d\\n",sizeof(&a[0]));
//首元素的地址 int*, 4/8
11printf("%d\\n",sizeof(&a[0]+1));
//首元素的地址 int*, 首元素的地址+ 1 得到的是下标为1的元素地址,地址的大小跟操作系统相关,4/8

字符数组

//字符数组
char arr[] = 以上是关于C语言篇 + 指针进阶练习 + qsort模拟实现(回调函数思想) + 指针和数组笔试题的主要内容,如果未能解决你的问题,请参考以下文章

C*指针进阶

C语言进阶之旅 (11.5)指针下 提升篇

指针的这些知识你知道吗?C语言超硬核指针进阶版3w+字详解+指针笔试题画图+文字详细讲解

C语言进阶学习笔记二指针的进阶(重点必看+代码演示+练习)

C语言进阶笔记深入了解进阶指针

C语言进阶笔记深入了解进阶指针