对指针的进一步认识(2)

        今天我们就继续上次,继续来较为深入的认识指针。

指针 

目录

指针 

函数指针数组

创建函数指针数组 

用函数指针数组来编写计算器 

方式1(函数和switch语句创建): 

方式2(函数指针数组): 

指向函数指针数组的指针(了解)

创建指向函数指针数组的指针

回调函数 

 用回调函数来修改计算器

qsort函数 

对qsort的简单了解 

对冒泡排序法的回顾

qsort函数的使用 

对compar的理解 

对compar的参数的理解 

应用 

对整型的比较

对结构体进行比较 

小结 

 


 

 

函数指针数组

         函数指针数组应该怎么理解呢,我们还是可以通过其的命名来进行分析。

        我们可以从之前的指针数组进行了解:

1.

char* arr[5]

        这是一个字符指针数组,它是一个数组,用来存放字符指针。

2.

int* arr2[5]

        这是一个整型指针数组,它是一个数组,用来存放整型指针。

        从以上,我们就可以推出,函数指针数组也是一个数组,它是用来存放函数指针的。

创建函数指针数组 

        那么,如何创建一个函数指针数组呢 ?这里我们首先简单的写一个加法函数和一个减法函数:

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

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

        接下来就是创建函数指针数组了,函数指针我们都会,那么我们先将上面两个函数的地址用函数指针接收:

int (*pf1)(int, int) = &Add;
int (*pf2)(int, int) = ⋐

        函数指针数组,它是一个数组,那我们是不是只需要在原来的函数指针上进行改写,再将函数指针存入其中就可以了呢?我们试着创建:

int (*pfArr[2])(int, int) = { &Add,&Sub };

运行结果如下:

3b9066489e0342369349d79ea3b18fae.png

        我们可以得出结论:

        函数指针数组只需要在函数指针的基础上创建一个数组就可以了。

        那么,函数指针数组又有什么用呢?我们能拿它来干什么?接下来,我们会用函数指针数组来创建一个简单的计算器,完成它后,相信自己可以得出答案。 

用函数指针数组来编写计算器 

         这里,肯定会有读者想,编写简单的计算器有什么难的?只需要创建几个函数就可以了,那么,我们首先用之前的方法创建:

方式1(函数和switch语句创建): 

         这里我们就可以运用之前写三子棋和扫雷的主函数方式,代码如下:

int main()
{
	int input = 0;
	do
	{
		menu();
		printf("请选择>:");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			break;
		case 2:
			break;
		case 3:
			break;
		case 4:
			break;
		case 0:
			printf("退出计算器。\n");
			break;
		default:
			printf("选择错误,请重新选择!\n");
			break;
		}
	} while (input);
	return 0;
}

        再对菜单进行创建:

void menu()
{
	printf("************************************\n");
	printf("*******  1.add     2.sub   *********\n");
	printf("*******  3.mul     4.div   *********\n");
	printf("*******       0.exit       *********\n");
	printf("************************************\n");
}

        之后就是对加减乘除的函数的创建了,代码如下:

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

        之后再对switch语句进行修改,将菜单所对应的函数一一引用。

运行结果如下:

bc8c49911c9448e987fea37591e50ccd.png

完整代码如下:

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 menu()
{
	printf("************************************\n");
	printf("*******  1.add     2.sub   *********\n");
	printf("*******  3.mul     4.div   *********\n");
	printf("*******       0.exit       *********\n");
	printf("************************************\n");

}

int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	int ret = 0;
	do
	{
		menu();
		printf("请选择>:");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("请输入两个操作符>:");
			scanf("%d %d", &x, &y);
			ret = Add(x, y);
			printf("%d\n", ret);
			break;
		case 2:
			printf("请输入两个操作符>:");
			scanf("%d %d", &x, &y);
			ret = Sub(x, y);
			printf("%d\n", ret);
			break;
		case 3:
			printf("请输入两个操作符>:");
			scanf("%d %d", &x, &y);
			ret = Mul(x, y);
			printf("%d\n", ret);
			break;
		case 4:
			printf("请输入两个操作符>:");
			scanf("%d %d", &x, &y);
			ret = Div(x, y);
			printf("%d\n", ret);
			break;
		case 0:
			printf("退出计算器。\n");
			break;
		default:
			printf("选择错误,请重新选择!\n");
			break;
		}
	} while (input);
	return 0;
}

         这里我们是不是也可以发现弊端了?没错,switch语句实在太多于冗余了,这样编写及其废时间,接下来,我们就用函数指针数组的方式来进行改写。

方式2(函数指针数组): 

        我们知道函数指针数组是用来存放函数指针的,那么,我们就将函数的地址放在函数指针数组来当中,再通过函数指针数组来引用函数。

        我们首先创建函数指针数组,并将加减乘除函数的地址放进去:

int (*pfArr[5])(int, int) = { Add,Sub,Mul,Div };

        但是这里我们会注意到,Add的下标变成0了,但是在菜单中,Add对应1,而exit对应0,所以,我们在Add的前面加上空指针NULL,就可以避免这种情况了:

int (*pfArr[5])(int, int) = { NULL,Add,Sub,Mul,Div };

        既然是数组,那我们可以用if_else语句来编写:

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

int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	int ret = 0;
	do
	{
		menu();
		printf("请选择>:");
		scanf("%d", &input);
		int (*pfArr[5])(int, int) = { NULL,Add,Sub,Mul,Div };
		if (0 == input)
		{
			printf("退出游戏。\n");
			break;
		}
		else if (input >= 1 && input <= 4)
		{
			printf("请输入两个操作符>:");
			scanf("%d %d", &x, &y);
			ret = pfArr[input](x, y);
			printf("%d\n", ret);
		}
		else
		{
			printf("选择错误,请重新选择!\n");
			break;
		}
	} while (input);
	return 0;
}

运行结果一致:

7fd7f647f8d84fd5b80fae6667ac33f9.png

        这里我们可以明显看到,相对于上面的代码,代码的可读性大大提高了,所以,大家对函数指针数组的认识是不是又进一步加深了?

指向函数指针数组的指针(了解)

        指向函数指针数组的指针运用的并不多,但我们需要对其有一定的了解,就当作是扩展一下视野。

        那么,指向函数指针数组的指针该如何理解呢,这时应该有读者要提出疑惑了,这不对吧,你这搁这里套娃呢,那我还可以有指向函数指针数组指针的指针呢,嘛,这里确实像是在套娃,不过也没有那么夸张,下面我们来举一个例子方便理解。

创建指向函数指针数组的指针

        我们首先来创建一个指向整型指针数组的指针。

        我们创建3个整型,并把它们放进整型指针数组里面,代码如下:

int main()
{
	int a = 10;
	int b = 20;
	int c = 30;
	int* arr[] = { &a,&b,&c };
    return 0;
}

        接着创建指向整型指针数组的指针,它是一个指针,所以优先用():

int* (*p)[3] = &arr;

        基于上面的理解,下面我们尝试来创建指向函数指针数组的指针,为了方便,我们就延用上面计算器当中的函数指针数组来进行创建,

        同样,我们把地址放入函数指针数组里面:

int main()
{
	int(*pfArr)[5](int, int) = { NULL,Add,Sub,Mul,Div };
	return 0;
}

        那么如何创建指向函数指针数组的指针呢?

        既然它是指针,那我们就优先使用():

int* (*p)[5](int, int) = &pfArr;

        这样就算创建好啦。

回调函数 

        回调函数可以说是相当重要, 那么,什么是回调函数呢?

        回调函数就是通过函数指针调用的函数,就是通过地址来被调用的函数,就比如我们之前写好的计算器,其中加法的函数如下:

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

        那么回调函数就是不去直接使用该函数,而是通过另一个函数中存放的它的地址来间接的访问它。我们这里在创建一个函数calc();我们想要实现回调,就可以将Add的地址放进该函数当中,从而实现调用:
        calc(Add);

        这样,我们访问加法函数就可以通过calc来进行访问,这就是回调函数。

        那我们趁热打铁,将上面的计算器通过回调函数进行改造。

 用回调函数来修改计算器

        我们就用calc来进行改写(calc当中放的是地址,那我们就通过函数指针进行接收):

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

void calc(int (*pf)(int, int))
{
	int x = 0;
	int y = 0;
	int ret = 0;
	printf("请输入两个操作符>:");
	scanf("%d %d", &x, &y);
	ret = pf(x, y);
	printf("%d\n", ret);
}

int main()
{
	int input = 0;
	do
	{
		menu();
		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;
}

        这样看起来,是不是所有的改写都比方式1好呢,嘿嘿,确实是这样。

        我们可以看到,用calc()的接收进行调用使switch语句变得简便了很多。

qsort函数 

qsort的简单了解 

1. qsort是一个库函数,底层使用快速排序的方式,对数据进行排序。
2.这个函数是可以直接使用的。
3.这个函数可以用来排序任意类型的数据。

        那么qsort该如何使用呢,在这之前,我们先对冒泡排序法进行回顾,方便对qsort进行理解 

对冒泡排序法的回顾

        冒泡排序法思想就是:两两相邻的元素进行比较,多次进行。 

        这里我们创建一个降序数组:

int arr[] = { 9,8,7,6,5,4,3,2,1 };

        那么冒泡的一趟排序就应该是这样:

dfe0b5ca55304aaeab544782aac37f5a.png

        一趟之后,9就不动了,之后就进行第二次冒泡排序,直到完全成为升序。

        我们要注意的是:需要计算数组长度来确定冒泡排序的趟数,以及我们最后需要得到的是升序数组,从而对循环进行编写,完整代码如下:

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])//简单的交换两个数
			{
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j+1] = tmp;
			}
		}
	}
}

int main()
{
	int i = 0;
	int arr[9] = { 9,8,7,6,5,4,3,2,1 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz);
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

运行结果如下:

14b1da68eb044d1f87909330657a0d44.png

qsort函数的使用 

        qsort函数该如何使用呢,这里我们访问 qsort - C++ Reference (cplusplus.com)

        我们可以看到:

b3cbfc37f90c4b589caadc74cd848383.png

那么这里面的参数都是什么意思呢,我们往下看:

1a0a1522e4c34846a38777e75df54f2a.png

        从这里我们了解到,void* base是待排序数组的第一个元素的地址,size_t num是待排序数组的元素个数,size_t size是指待排序数组当中一个元素的大小。

        前面三个参数都还好理解,到了最后一个参数我们可以发现,int (*compar)(const void*, const void*))是一个函数指针,其中void*又是啥?这里我们单独列出来理解。

对compar的理解 

         从命名上我们可以得出,compar是用来比较大小的函数,我们从对qsort的简单了解中可以知道,qsort是可以比较任意类型的数据的,比如:

        1.整型数组,两个整型之间是可以用>来进行比较的。
        2.结构体数组,两个结构体是不能用>来进行比较的。

        也就是说不同的数据类型,比较出大小的方法数有所差异的,所以我们使用qsort比较大小的方法是需要自己写出来的。

对compar的参数的理解 

int (*compar)(const void* e1, const void* e2)) 

        这里我们将第一个参数设为e1,第二个参数设为e2,它们都是void*类型的,那么什么是void*呢?

        1.我们首先需要知道void*的指针是不能进行解引用的,它也不能进行+-整数的操作。

        2.其次,void*类型的指针是用来存放任何类型指针的地址的。

        这里我们举一个例子:

int main()
{
	char c = 'w';
	int a = 100;
	char* pc = &c;
	return 0;
}

        这里我们创建了一个字符和一个整型,我们可以看到,&c是可以放到char* pc里面的,但如果:

int* p = &c

        将字符的地址放在int*当中是错误的,运行时会有警告,但是如果把它们放进void*当时,我们就可以发现不会出任何的警告,从而,我们可以得出结论:

        void*是无任何类型的指针。

        对void*进行了解后,我们就来对qsort函数来进行运用。

应用 

        我们知道,qsort是可以比较任意数据的,我们就简单一点,用qsort来实现对整型和对结构体的比较 。

对整型的比较

        我们这里将一个降序数列使用qsort进行排序,但是,比较的函数是需要自己编写的,

那么,既然比较函数是自己编写的,但void*又不能直接解引用,那我们应该如何操作呢?

其实很简单啦,强制类型转换就可以了,那在强制转化后,又如何返回呢,这里我们回到网站:

5b18518546804a90aed032a74ba626b1.png

        这里我们可以知道,compar的返回值是让两个参数相减,让相减的值于0进行比较,这样就很好编写了,代码如下:

#include<stdio.h>
#include<stdlib.h>
int cmp_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}

void test1()
{
	int i = 0;
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz,sizeof(arr[0]), cmp_int);
		for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}

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

运行结果如下:

feba031321b84d9fbe9e285d23e494bf.png

对结构体进行比较 

        那么结构体怎么计较呢?
        答:想怎么比就怎么比(狗头狗头)
        其实是按照你自己定义的来比,可以按照年龄,也可以按照名字 。

        这里我们首先创建一个结构体;

struct Stu
{
	char name[20];
	int age;
};

        这里就出现一个问题,如何将结构体当中的类型进行强转并排序呢,只需要用->就可以啦,代码如下:

int com_stu_by_age(const void* e1, const void* e2)
{
	return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}

完整代码:

#include<stdio.h>
#include<stdlib.h>
struct Stu
{
	char name[20];
	int age;
};
//按年龄
int com_stu_by_age(const void* e1, const void* e2)
{
	return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
void test2()
{
	struct Stu arr[] = { {"zhangsan",20},{"lisi",30},{"wangwu",15}};
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), com_stu_by_age);
}

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

        这里我们进行调试:

比较前:

caae8b402e1844c28f4542919781ac40.png

比较后:

810cccf792154dbd8ccd7090073be14a.png

         按名字:

        名字是字符,字符可不能直接使用,这里我们使用strcmp来对字符进行比较,当然也得引用其头文件,这时又出现了一个问题,那么字符的话,返回类型应该写什么呢?

        我们在网站中搜索,我们可以看到:

 6b04922259a04f66836e14bd4780fb7a.png

        strcmp的返回值恰好是int,这里我们就不必担心,放心大胆的写:

struct Stu
{
	char name[20];
	int age;
};
int com_stu_by_name(const void* e1, const void* e2)
{
	return strcmp(((struct Stu*)e1)->name , ((struct Stu*)e2)->name);
}//按字典顺序排序
void test3()
{
	struct Stu arr[] = { {"zhangsan",20},{"lisi",30},{"wangwu",35} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), com_stu_by_name);
}

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

        这里我们进行调试:

比较前:

d22be775a1bf4958bc78c5b202e2ed53.png

比较后:

3645c7305e8f4f1596a464ae31a6116c.png

小结 

        这次对指针的进一步了解到这里就结束啦,指针真是神奇且有趣,在我们的学习中,它又是不可避免的一个难点,相信在我们对指针有了进一步的认识,我们接下来对指针的运用也会渐渐变得得心应手,但我们依然不可疏忽,我们未来在C语言的道路还有很长,我们还是需要一步一步的前进,好啦,我们下次再见! 

 

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值