指针!!C语言(第三篇)

目录

一. 二维数组传参的本质

二. 函数指针变量和函数指针数组

三. typedef关键字

四. 转移表

五. 回调函数以及qsort使用举例


一. 二维数组传参的本质

🍟首先我们先回顾一下二维数组是怎样传参的?我们需要传入数组名以及行数和列数,这样才能将一个二维数组传入一个函数中,除了这样我们还能怎么办?首先认识一下二维数组传参的本质,我们知道二维数组其实是一维数组组成的,所以二维数组的数组名也是数组首元素的地址二维数组的首元素的地址就是它的第一行

所以,根据这个规则我们的传参就可以有另一种方式,如下:
//二维数组传参的本质
void test1(int(*p)[5], int r, int c)
{
	for (int i = 0; i < r; i++)
	{
		for (int j = 0; j < c; j++)
		{
			printf("%d ", *(*(p + i) + j));
		}
		printf("\n");
	}
}

int main()
{
	int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
	test1(arr, 3, 5);
	return 0;
}

总结:二维数组传参,形参的部分可以写成数组,也可以写成指针形式。 

二. 函数指针变量和函数指针数组

函数指针变量

🍔什么是函数指针变量呢? 根据前面学习整型指针,数组指针的时候,我们的类比关系,我们不难得出结论: 函数指针变量应该是用来存放函数地址的,未来通过地址能够调用函数的。 那么函数是否有地址呢?毋庸置疑肯定是有的。那么函数地址有什么不一样的地方呢?

首先是函数指针变量的创建:假如现在有一个简单的函数:int Add(int x,int y);{return x+y;},那么它的函数指针变量就是:int (*p) (int,int)=Add,变量int后面的x和y可写可不写:

另外对于函数指针而言,&函数名和函数名都是函数的地址,并无区别。

下面给大家一段代码演示:


int add(int x, int y)
{
	return x + y;
}

int main()
{
	int(*pf)(int, int) = &add;
	printf("%d ", (*pf)(2, 3));
	printf("%d ", (*pf)(4, 5));
	return 0;
}

函数指针数组: 

数组是一个存放相同类型数据的存储空间,所以要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?首先我们要定义一个数组,也就说假如这个变量名是pf,pf要先和[ ]结合,来证明pf是数组,像这样int (*pf[3])();数组的内容是什么呢?是 int (*)() 类型的函数指针。eg: int (*pf[4])(int,int)={    };大括号里存放的是数组。

三. typedef关键字

🧀 typedef 是用来类型重命名的,可以将复杂的类型,简单化
比如,你觉得 unsigned int 写起来不方便,如果能写成 uint 就方便多了,那么我们可以使用:
typedef unsigned int uint;   将unsigned int 重命名为uint
如果是指针类型,能否重命名呢?其实也是可以的,比如,将 int* 重命名为 ptr_t ,这样写:
typedef int * ptr_t ;
但是对于数组指针和函数指针稍微有点区别: 比如我们有数组指针类型 int(*)[5] ,需要重命名为 parr_t ,那可以这样写:
typedef int (* parr_t )[5];→  新的类型名必须在*的右边
函数指针类型的重命名也是⼀样的,比如,将 void(*)(int) 类型重命名为 pf_t ,就可以这样写:
typedef void (* pfun_t )( int );→ 新的类型名必须在*的右边
这些就是typedef关键字的重命名用法。

四. 转移表

转移表其实就是函数指针数组的用途之一,那么我们要如何理解呢?我们通过实现一个计算机来说明,在我们没有学习函数指针数组的时候我们的一般实现如下:

#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("*****    0:exit    ******\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;
}

 我们可以看到在一般实现中我们分别要创建不同的函数然后再分别调用不同的函数来满足我们不同的计算需求,但是其实我们又会发现在我们的运算中,我们的程序看起来有点相似,但又不完全一样,看起来有些许繁琐,所以我们有没有什么办法让代码更加简单一点呢?这时候就要用到我们的函数数组指针了。如下:

int add(int x, int y)
{
	return x + y;
}

int sub(int x, int y)
{
	return x - y;
}

int mul(int x, int y)
{
	return x * y;
}

int div(int x, int y)
{
	return x / y;
}

int main()
{
	int x = 0;
	int y = 0;
	int input = 0;
	int ret = 0;
	int(*pf[5])(int, int) = { 0,add,sub,mul,div };
	do
	{
		printf("*************************\n");
		printf("***** 1:add 2:sub *******\n");
		printf("***** 3:mul 4:div *******\n");
		printf("*****    0:exit    ******\n");
		printf("*************************\n");
		printf("请选择:");
		scanf("%d", &input);
		if ((input <= 4 && input >= 1))
		{
			printf("输⼊操作数:");
			scanf("%d %d", &x, &y);
			ret = (*pf[input])(x, y);
			printf("ret = %d\n", ret);
		}
		else if (input == 0)
		{
			printf("退出计算器\n");
		}
		else
		{
			printf("输⼊有误\n");
		}
		
	} while (input);
	return 0;
}

int(*pf[5])(int, int) = { 0,add,sub,mul,div }; 我们将四个不同的运算放在一个函数指针数组中,然后在实际的选择中,当我们需要哪一个运算的时候我们使用解引用*pf[input](x,y),当我们input选择1的时候,就是调用下标为1的元素,即add,也就说现在我们直接调用了add这个函数,又传入了两个参数x和y,这样就避免了大量的重复代码,使我们的代码更加高效。而且这也是函数指针数组的一个很好的例子。

五. 回调函数以及qsort使用举例

学到现在我们今天的重点就来了,也就说我们马上要讲的和回调函数以及对qsort的认识和使用举例,首先来认识一下什么是回调函数,回调函数就是一个通过函数指针调用的函数

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

我们还是以计算机的实现来说明这个问题,首先还是拿来我们的一般实现:

#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("*****    0:exit    ******\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;
}

下面是我们使用回调函数的方法:

int add(int x, int y)
{
	return x + y;
}

int sub(int x, int y)
{
	return x - y;
}

int mul(int x, int y)
{
	return x * y;
}

int div(int x, int y)
{
	return x / y;
}

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 = 0;
	int ret = 0;
	do
	{
		printf("*************************\n");
		printf("***** 1:add 2:sub *******\n");
		printf("***** 3:mul 4:div *******\n");
		printf("*****    0:exit    ******\n");
		printf("*************************\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;
}

从代码中我们可以看出来我们创建了一个calc函数里面放入了函数指针,在后面的使用中我们使用calc这个函数来调用我们要使用的函数,比如当我们输入1的时候,是calc(add),也就说现在指针pf指向的是add这个函数,就实现了加法。

🍳qsort使用举例:

我们来认识一个新的函数叫做qsort函数,在之前我们讲过使用冒泡排序来实现对于数值的升序或降序排列,但是都有就局限性,比如只能对整型数值进行排列,现在我们使用qsort函数可以对任意类型的数据进行排序

下面我们就使用qsort函数来实现一些排列其他类型的数据,首先可以排列整型数据

#include <stdio.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;
}

 int int_cmp(const void *p1, const void *p2);
 其返回值具有以下含义:
- 如果返回值小于 0( <0 ),那么 p1 所指向元素会被排在 p2 所指向元素的前面。
- 如果返回值等于 0( =0 ),那么 p1 所指向元素与 p2 所指向元素的顺序不确定。
- 如果返回值大于 0( >0 ),那么 p1 所指向元素会被排在 p2 所指向元素的后面
所以qsort函数是默认升序排列的。另外还有一点要提醒大家就是使用函数指针时候要注意我们使用的是void类型的指针,但是void类型是不能进行比较和计算的,所以我们要进行强制类型转换之后再进行计算

🍜使用qsort排序结构数据

除了整型之外,我们还可以排列其他的类型数据,比如比较字符串,结构题体等,让我们实现一下代码:

//比较结构体
//创建一个结构体
#include <stdlib.h>
struct Stu //学⽣
{
	char name[20];//名字
	int age;//年龄
};

int cmp_name(const void* p1, const void* p2)
{
	return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}

void test1()
{
	struct Stu s[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 15} };
	qsort(s, sizeof(s) / sizeof(s[0]), sizeof(s[0]), cmp_name);
	for (int i = 0; i < 3; i++)
	{
		printf("%s ", s[i]);
	}
}

int main()
{
	test1();
	return 0;
}

 

这里说明一下,strcmp函数是用来比较字符串的,它的头文件是#include <string.h>,比较的是ASCII码值,还有就是结构体比较除了使用操作符(.)之外,还可以直接使用箭头(→)的方式

🍢qsort函数的模拟实现:

那么qsort函数究竟是怎样实现的呢?我们可以使用回调函数,模拟实现qsort(采用冒泡的方式)。

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)
			{
				_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;

这个函数 int_cmp  用于比较两个 void*  类型的指针所指向的整数。通过将指针强制转换为 int*  类型,然后计算它们所指向的值的差值来确定大小关系。返回值大于 0 表示 p1  所指的值大于 p2  所指的值,小于 0 表示 p1  所指的值小于 p2  所指的值,等于 0 表示两者相等。 

_swap  函数用于交换两个指针所指向的内存区域的值。它通过一个循环,逐个字节地交换两个区域的内容。循环的次数由 size  参数决定,以确保交换指定大小的数据。

bubble  函数实现了冒泡排序的逻辑。
 -  void* base  表示要排序的数组的起始地址。
-  int count  是数组中的元素个数。
-  int size  是每个元素的大小。
-  int(*cmp)(void*, void*)  是一个函数指针,用于指定比较元素大小的规则。
函数通过两层循环来比较相邻的元素。如果相邻元素的顺序不符合比较规则(即  cmp  函数返回值大于 0 ),就调用  _swap  函数交换它们的位置。

 

 

  • 117
    点赞
  • 102
    收藏
    觉得还不错? 一键收藏
  • 85
    评论
评论 85
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值