【C语言】指针进阶(qsort的用法和模拟实现)

本文详细介绍了C语言中的指针概念,包括字符指针、指针数组、数组指针、一级指针和二级指针的使用。同时,讨论了函数指针和函数指针数组的应用,特别提到了回调函数的概念,并通过qsort函数的例子展示了回调函数的使用。此外,还模拟实现了qsort函数,使用冒泡排序算法对数组进行排序。
摘要由CSDN通过智能技术生成

字符指针

在指针的类型中我们知道有一种指针类型为字符指针 char* ;

#include<stdio.h>
int main()
{
	char s = 'a';
	char* p = &s;
	*p = 'b';
	return 0;
}
//我们可以将单个字符的地址放在字符指针中
char* t = "hello world!";
printf("%s", t);
//我们还可以将字符串储存在字符指针中 用%s打印就可以打印出字符串

第二种使用场景实际上不是将整个字符串储存在字符指针中,而是将字符串首元素的地址存放在字符指针中
在这里插入图片描述
以%s打印就是找到字符串中的\0才终止打印;

观察这段代码

#include <stdio.h>
int main()
{
	char str1[] = "hello world.";
	char str2[] = "hello world.";
	const char *str3 = "hello world.";
	const char *str4 = "hello world.";
	//分别用数组的形式和字符指针的方式储存字符串
	if(str1 ==str2)//数组的方式给创建的str1和str2是否相同
	printf("str1 and str2 are same\n");
	else
	printf("str1 and str2 are not same\n");
	if(str3 ==str4)//字符指针的方式给创建的str1和str2是否相同
	printf("str3 and str4 are same\n");
	else
	printf("str3 and str4 are not same\n");
	return 0;
}

结果如下
在这里插入图片描述
可以看到数组方式创建的两个是不同,字符指针创建的相同
在这里插入图片描述
原因如图所示

1.以数组的方式创建是开辟了两块不同的空间来存放数据,且数组内数据是可变的,这就要求必须开辟不同空间;

2.以字符指针的方式是开辟了一块空间放置“hello world.”,两个字符指针都指向第一个元素的位置,且字符串本质上是常量字符串,不可与被更改,所以只需要开辟一块空间

指针数组

指针数组顾名思义就是存放指针变量的数组

int* arr[10];
/arr先与[]结合,说明是数组,然后去掉 arr[], int*就是每个元素的类型
char* arr2[10];

数组指针

数组指针顾名思义是指向数组的指针

int *p
//指向整型的指针
char*p
//指向字符的指针
int (*p)[10]
//指向数组的指针  数组类型为int[10]

例如

#include <stdio.h>
void print_arr1(int arr[3][5], int row, int col)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}
void print_arr2(int(*arr)[5], int row, int col)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10 };
	print_arr1(arr, 3, 5);
	//数组名arr,表示首元素的地址
	//但是二维数组的首元素是二维数组的第一行
	//所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
	//可以数组指针来接收
	print_arr2(arr, 3, 5);
	return 0;

学习了数组指针和指针数组,来看下面的代码

int arr[5];
//arr是数组 可存放5个元素 每个元素类型为int
int* parr1[10];
//parr1是指针数组 可存放十个元素 每个元素类型为int*
int(*parr2)[10];
//parr2是数组指针 指向的数组有10个元素 每个元素类型为int
int(*parr3[10])[5];
//parr3是一个数组 可存放10个元素 每个元素类型为int(*)[5]

数组参数 指针参数

一维数组传参

#include <stdio.h>
void test(int arr[])
{}
void test(int arr[10])
{}
void test(int* arr)
{}
void test2(int* arr[20])
{}
void test2(int** arr)
{}
//一维数组传参 参数可以写成一维数组,元素个数可以省略
//也可以写成一级指针的方式,因为数组名表示数组首元素的地址
int main()
{
	int arr[10] = { 0 };
	int* arr2[20] = { 0 };
	test(arr);
	test2(arr2);
}

二维数组传参

void test(int arr[3][5])//ok?
{}
void test(int arr[][])//ok?
{}
void test(int arr[][5])//ok?
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。

void test(int (*arr)[5])//ok?
{}
//二维数组首元素地址是第一行的地址 是一个一维数组的地址,所以可以用数组指针来接受

int main()
{
	int arr[3][5] = {0};
	test(arr);
	return 0;
}

一级指针传参

#include <stdio.h>
void print(int* p, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d\n", *(p + i));
	}
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9 };
	int* p = arr;
	int sz = sizeof(arr) / sizeof(arr[0]);
	//一级指针p,传给函数
	print(p, sz);
	return 0;
}

二级指针传参

#include <stdio.h>
void test(int** ptr)
{
	printf("num = %d\n", **ptr);
}
int main()
{
	int n = 10;
	int* p = &n;
	int** pp = &p;
	test(pp);
	//pp是二级指针 可用二级指针接受
	test(&p);
	//p是一级指针 &p取出一级指针的地址 可用二级指针接受
	return 0;
}

函数指针

函数指针顾名思义就是存放函数地址的指针
例如

#include<stdio.h>
void test(int a, int b)
{
	printf("a+b=%d\n", a + b);
}
int main()
{
	int a = 2, b = 3;
	void (*pfun1)(int, int) = &test;
	//pfun1先与*结合说明是指针变量
	//剩下的void (int,int)就是函数的参数和返回类型
	(*pfun1)(a, b);
	
	return 0;
}

在这里插入图片描述
看两个有趣的代码

//代码1
(*(void (*)())0)();
//这个代码从0开始分析,先将0强制类型转换为
//(void (*)())的函数指针类型然后在解引用调用这个
//函数指针指向的函数
//代码2
void (*signal(int , void(*)(int)))(int);
//这个代码从signal分析首先和()结合说明是
//一个函数 函数的参数一个是int 一个是void(*)(int)
//返回类型是void(*)(int)

//把第二个代码简化一下就是将void(*)(int)重命名为pfun_t
typedef void(*pfun_t)(int);
pfun_t signal(int, pfun_t);
//这样看起来更加明了

函数指针数组

顾名思义就是存放函数指针的数组
例如

int (*parr1[10])();
//parr1先和[]结合说明是个数组
//parr1[]就剩int(*)()说明数组中每个元素都是函数指针类型
//这样就定义了一个函数指针数组

用法

计算器的实现
如果不使用函数指针类型的话,计算器的实现如下

#include <stdio.h>
int add(int a, int b)
{
	return a + b;
}
int sub(int a, int b)
{
	return a - b;
}
int mul(int a, int b)
{
	return a * b;
}
int div(int a, int b)
{
	return a / b;
}
int main()
{
	int x, y;
	int input = 1;
	int ret = 0;
	do
	{
		printf("*************************\n");
		printf(" 1:add 2:sub \n");
		printf(" 3:mul 4:div \n");
		printf("*************************\n");
		printf("请选择:");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("输入操作数:");
			scanf("%d %d", &x, &y);
			ret = add(x, y);
			printf("ret = %d\n", ret);
			break;
		case 2:
			printf("输入操作数:");
			scanf("%d %d", &x, &y);
			ret = sub(x, y);
			printf("ret = %d\n", ret);
			break;
		case 3:
			printf("输入操作数:");
			scanf("%d %d", &x, &y);
			ret = mul(x, y);
			printf("ret = %d\n", ret);
			break;
		case 4:
			printf("输入操作数:");
			scanf("%d %d", &x, &y);
			ret = div(x, y);
			printf("ret = %d\n", ret);
			break;
		case 0:
			printf("退出程序\n");
			break;
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);
	return 0;
}

这样我们就实现了这个计算器的基本逻辑
但是这样写有几个问题

printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = add(x, y);
printf("ret = %d\n", ret);
	break;
//1.switch语句中类似这样的语句多次重复出现
//2.如果还需要加其他运算方法时需要修改的地方很多

这时我们就可以使用函数指针数组的方式,将每个运算逻辑的函数放在一个函数指针数组中

#include <stdio.h>
int add(int a, int b)
{
	return a + b;
}
int sub(int a, int b)
{
	return a - b;
}
int mul(int a, int b)
{
	return a * b;
}
int div(int a, int b)
{
	return a / b;
}
int main()
{
	int x, y;
	int input = 1;
	int ret = 0;
	int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表
	while (input)
	{
		printf("*************************\n");
		printf(" 1:add 2:sub \n");
		printf(" 3:mul 4:div \n");
		printf("*************************\n");
		printf("请选择:");
		scanf("%d", &input);
		if ((input <= 4 && input >= 1))
		{
			printf("输入操作数:");
			scanf("%d %d", &x, &y);
			ret = (*p[input])(x, y);
		}
		else
			printf("输入有误\n");
		printf("ret = %d\n", ret);
	}
	return 0;
}

这样重复的代码就只需要写一次,且我要想往内容中加其他运算逻辑只需要写好函数,然后加入数组中,非常方便

回调函数

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

qsort函数

库函数中的qsort函数就使用了回调函数

且qsort函数不止可以对整型数组排序,根据我们写的比较函数,可以给结构体元素等等一系列数组排序
在这里插入图片描述
具体用法如下

#include <stdio.h>
#include <stdlib.h>
//qosrt函数的使用者得实现一个比较函数
int int_cmp(const void* p1, const void* p2)
{
	return (*(int*)p1 - *(int*)p2);
}
int main()
{
	int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
	int i = 0;
	qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), int_cmp);
	//第一个参数是数组的第一个元素的地址
	//第二个参数是需要排序的元素的个数
	//第三个参数是数组中元素的大小
	//第四个参数是一个函数指针 需要自己实现一个比较函数 这里就是回调函数的用法 
	//用指针的方式调用函数,而不是直接调用
	for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
	return 0;
}

模拟实现qsort函数

库函数中的qsort函数内部是使用快速排序的算法来实现的

我们在这里使用冒泡排序的方式模拟实现qsort函数

首先根据资料确定函数的参数和返回类型

void bubble_sort(void* base, int count, int size, int(*cmp)(void*, void*))
//这里参数中出现的void*可以接受任何类型的指针
//因为我们无法确定需要排序的数组元素类型是什么所以采用void*
//void*指针在使用时需要强制类型转换为需要的类型再使用
{
	
}

函数内部先实现冒泡排序的基本思想

void bubble_sort(void* base, int count, int size, int(*cmp)(void*, void*))
{
	int i = 0;
	int j = 0;
	for (i = 0; i < count - 1; i++)
	{
		for (j = 0; j < count - i - 1; j++)
		{
			if (//是否交换的判断条件)
			{
				//交换-
			}
		}
	}
}
//这里因为是实现任意类型数组的排序 所以要自己设计一个判断交换的条件和一个交换函数

下面就是判断条件和交换的实现

判断条件

//因排序的内容不同,所以判断条件也不同
struct stuent
{
	int age;
	char name[20];
};//例如对结构体进行排序
//如果按年龄排序
int cmp_age(const void* e1, const void* e2)
{
	return ((struct stuent*)e1)->age - ((struct stuent*)e2)->age;
}
//将p1和p2强制类型转换为struct stuent*再指向age相减
//如果按名字排序
int cmp_name(const void* e1, const void* e2)
{
	return strcmp(((struct stuent*)e1)->name, ((struct stuent*)e2)->name);
	//字符串比较函数比较大小
}
//将p1和p2强制类型转换为struct stuent*再指向name相减

//p1>p2	返回大于0
//p1=p2 返回等于0
//p1<p2 返回小于0

交换

void swap(char* e1, char* e2,int width)
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char temp = *e1;
		*e1 = *e2;
		*e2 = temp;
		e1++;
		e2++;
	}
	//因为不知道具体类型 所以必须使用char类型一个字节一个字节交换
}

所有加起来就是整个qsort的模拟实现


void swap(char* e1, char* e2,int width)
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char temp = *e1;
		*e1 = *e2;
		*e2 = temp;
		e1++;
		e2++;
	}
}

void bubble_sort(void* base, size_t num, size_t width, int(*cmp)(const void* e1, const void* e2))
{
	size_t i = 0;
	for (i = 0; i < num - 1; i++)
	{
		size_t j = 0;
		for (j = 0; j < num - 1 - i; j++)
		{
			//判断函数 j j+1
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
			{
				//交换函数
				swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
			}
		}
	}
}



struct stuent
{
	int age;
	char name[20];
};
int cmp(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
	//e1 > e2  return>0
}
int cmp_age(const void* e1, const void* e2)
{
	return ((struct stuent*)e1)->age - ((struct stuent*)e2)->age;
}
int cmp_name(const void* e1, const void* e2)
{
	return strcmp(((struct stuent*)e1)->name, ((struct stuent*)e2)->name);
}

int main()
{
	//整型数组排序
	int arr[10] = { 1,5,9,3,7,8,4,2,6 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz, sizeof(arr[0]), cmp);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	//***********************************
	//结构体排序(age)
	struct stuent s[3] = { {20,"zhangsan"},{40,"lisi"},{30,"wangwu"} };
	sz = sizeof(s) / sizeof(s[0]);
	qsort(s, sz, sizeof(s[0]), cmp_age);
	//***********************************
	//结构体排序(name)
	qsort(s, sz, sizeof(s[0]), cmp_name);
	return 0;
}```

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

沉着的码农

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值