指针大魔王(下)

 

         ✨✨欢迎大家来到贝蒂大讲堂✨✨

        ​​​​养成好习惯,先赞后看哦~

                 所属专栏:C语言学习        

                 贝蒂的主页:Betty‘s blog


目录

引言

1. 函数指针 

  1.1 函数的地址

  1.2 函数指针变量

   1.3  函数指针的使用

2. 两段有趣的代码 

 2.1 typedef的使用

2.2 代码解析 

3. 计算器

3.1 函数指针数组

3.2 回调函数 

4. qsort()函数

4.1 qsort()的使用

4.2 冒泡排序

(1)算法步骤

(2)动图演示

(3)代码实现 

4.3 模拟实现qsort() 

结言 


引言

   经过前面的刻苦学习,今天我们终于来到了指针的最后一节,这一节将是对前面内容的总结与深化,相信学完之后,大家能对指针有一个更深的理解~

1. 函数指针 

  1.1 函数的地址

   函数也有地址吗?相信大家看到这里一定会有这个疑问吧,我们其实可以做一个小的实验来证明一下。

   代码如下:

#include
int Add(int a, int b)
{
	return a + b;
}
int main()
{
	int x = 1;
	int y = 2;
	int ret = Add(x, y);
	printf("%p \n", Add);//打印函数名
	printf("%p \n", &Add);//对函数名取地址
	return 0;
}

00FE10B9
00FE10B9

从上述实验我们可以发现,函数的确有地址,并且贝蒂还可以告诉大家函数名对函数名取地址表示的含义相同,都是指函数的地址哦~ 

  1.2 函数指针变量

   既然函数是有地址的,那我们就可以用指针来接收,而这个指针我们称为函数指针变量

   定义如下:

函数的返回值类型(*指针名)(函数的参数类型)

   我们以上面的加法函数给大家演示一下

int(*pf)(int a, int b) = &Add;
int(*pf)(int , int ) = Add;//省略&,a和b也是可行的
int(*)(int a, int b)//pf的函数指针变量类型

   1.3  函数指针的使用

	int (*pf)(int a, int b) = &Add;
	int ret1 = (*pf)(3, 5);//相当于Add(3,5)
	int ret2 = pf(3, 5);//相当于Add(3,5)

1. 对pf解引用相当于通过pf找到Add函数名,然后输入参数进行使用。

2. 而我们知道&Add==Add,所以我们也能通过直接使用函数指针变量来调用函数。

 贝蒂说:“但是函数指针变量不能像其他指针变量进行+-运算”

2. 两段有趣的代码 

 2.1 typedef的使用

 typedef是一个关键字,它能将复杂的类型简化。

 如:

//如果你觉得unsigned long long写起来麻烦
typedef unsigned long long ull;//可以将其简化为ull
unsigned long long a;
ull b;//也可以这样声明

贝蒂说:“typedef还常常用于结构体的简化哦~,后续会为大家详细介绍哒~” 

2.2 代码解析 

(1)(*(void (*)())0)();//这段代码该如何解释

1. 首先我们从里往外拆分,最里面void(*)()是一个函数指针类型,它的返回类型是空,参数也为空,我们可以将其简化为pf 。

那这段代码我们可以写成这样

	(*(void (*)())0)();
	typedef void(*pf)();
	(*(pf)0)();//简化后

 2. 这下我们比较容易看出这段代码是先将0强制类型转换为函数指针类型,然后对其解引用。

 3. 解引用之后相当于调用在0地址的函数,因为其参数为空所以只有一个单独的().

(2) void (*signal(int , void(*)(int)))(int);//那这段代码呢

 1. 首先signal与()结合说明其是一个函数名,它有两个参数,一个整型,另一个是函数指针类型。

 2. 我们将signal(int ,void(*)(int)单独拿出来,这段代码只剩void(*)(int),这就说明该函数的返回类型是一个函数指针,指向一个参数为int,返回为void的函数。

 3. 我们可以通过typedef进行化简。

	typedef void(*pfun_t)(int);//将void(*)(int)简化
	pfun_t signal(int, pfun_t);//化简之后

3. 计算器

 这是一个简单计算机的模拟实现

#include 
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(" 1:add 2:sub \n");
		printf(" 3:mul 4:div \n");
		printf(" 0:exit \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;
}

通过观察我们发现代码有很多冗余的部分,我们可以通过下面两种方法简化

3.1 函数指针数组

类比指针数组,函数指针数组就是每个数组元素是个函数指针

#include 
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 }; 
	//0元素方便输入
	//p先与[5]结合是个数组,每个元素是个函数指针
	do
	{
		printf(" 1:add 2:sub \n");
		printf(" 3:mul 4:div \n");
		printf(" 0:exit \n");
		printf("请选择:");
		scanf("%d", &input);
		if ((input <= 4 && input >= 1))
		{
			printf("输入操作数:");
			scanf("%d %d", &x, &y);
			ret = (*p[input])(x, y);
			printf("ret = %d\n", ret);
		}
		else if (input == 0)
		{
			printf("退出计算器\n");
		}
		else
		{
			printf("输⼊有误\n");
		}
	} while (input);
		return 0;
}

3.2 回调函数 

 回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。

 简单来说就是通过函数来调用函数

#include 
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;
}
void calc(int(*pf)(int, int))
//用函数指针来接收函数地址
{
	int ret = 0;
	int x, y;
	printf("输入操作数:");
	scanf("%d %d", &x, &y);
	ret = pf(x, y);
	printf("ret = %d\n", ret);
}
int main()
{
	int input = 1;
	do
	{
		printf(" 1:add 2:sub \n");
		printf(" 3:mul 4:div \n");
		printf(" 0:exit \n");
		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("退出程序\n");
			break;
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);
		return 0;
}

4. qsort()函数

4.1 qsort()的使用

1. 声明:void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*))

  • base -- 指向要排序的数组的第一个元素的指针。
  • nitems -- 由 base 指向的数组中元素的个数。
  • size -- 数组中每个元素的大小,以字节为单位。
  • compar -- 用来比较两个元素的函数。

2. 作用:对数组元素进行排序(升序)

3. 返回值:void

举例:

int int_cmp1(const void* p1, const void* p2)
{
    //void*指针不能直接使用
	return (*(int*)p1 - *(int*)p2);//前大于后,则交换
}
void test1()
{
	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_cmp1);
	for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}
int int_cmp2(const void* p1, const void* p2)
{
	return (*(char*)p1 - *(char*)p2);//前大于后,则交换
}
void test2()
{
	char arr[] = { 'b','a','j','c','r' };
	int i = 0;
	qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(char), int_cmp2);
	for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		printf("%c ", arr[i]);
	}
	printf("\n");
}
int main()
{
	test1();//排序整数
	test2();//排序字符
	return 0;
}

 输出结果

0 1 2 3 4 5 6 7 8 9
a b c j r

贝蒂说:“当然我们还可以以字符串,结构体变量为比较依据,这里贝蒂就不一一列举啦~” 

4.2 冒泡排序

 冒泡排序是一种非常常见,用于排序的一种算法 。

(1)算法步骤

比较相邻的元素。如果第一个比第二个大,就交换他们两个。

对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。

针对所有的元素重复以上的步骤,除了最后一个。

持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

(2)动图演示

(3)代码实现 
void bubble_sort(int arr[], int sz) //参数接收数组元素个数
{
	int i = 0;
	for (i = 0; i < sz - 1; i++)
	{
		int flag = 1; //假设这⼀趟已经有序了
		int j = 0;
		for (j = 0; j < sz - i - 1; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				flag = 0; //发⽣交换就说明,⽆序
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
		if (flag == 1) //这⼀趟没交换就说明已经有序,后续无需排序了
			break;
	}
}
int main()
{
	int arr[] = { 3,1,7,5,8,9,0,2,4,6 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz);
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

输出:

0 1 2 3 4 5 6 7 8 9 

4.3 模拟实现qsort() 

虽然qsort本质是以快速排序方式实现的,但是我们也可以用冒泡排序模仿实现一下。

1. 参数部分肯定不会变

void bubble(void *base, int count , int size, int(*cmp )(void *, void *))

 2. 比较函数,因为不同变量类型,所以我们以char*类型来进行比较。

指针大魔王(下)_第1张图片

3. 与比较函数同理,交换每个字节内容,从而实现两个元素的交换

void _swap(void* p1, void* p2, int size)
//p1为第一个元素
//p2为第二元素
//size为交换字节数
{
	int i = 0;
	for (i = 0; i < size; i++)
	{
		char tmp = *((char*)p1 + i);
		*((char*)p1 + i) = *((char*)p2 + i);
		*((char*)p2 + i) = tmp;
	}
}

完整代码

int int_cmp(const void * p1, const void * p2)
{
	return (*(int*)p1 - *(int*)p2);
}
void _swap(void* p1, void* p2, int size)
{
	int i = 0;
	for (i = 0; i < size; i++)
	{
		char tmp = *((char*)p1 + i);
		*((char*)p1 + i) = *((char*)p2 + i);
		*((char*)p2 + i) = tmp;
	}
}
void bubble(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 (cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 0)
			//返回值大于0则交换
			{
				_swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
			}
		}
	}
}
int main()
{
	int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
	int i = 0;
	bubble(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;
}

结言 

                      完结撒花,完结撒花

                          恭喜你,打败指针大魔王哦~

你可能感兴趣的:(C语言,c语言,学习)