其他分享
首页 > 其他分享> > 【C语言】从入门到入土(进阶指针第二篇)

【C语言】从入门到入土(进阶指针第二篇)

作者:互联网

前言:
本篇是指针的进阶的第二篇,在前面的文章中,我们已经对指针有了一个基础的了解,这进阶指针一里也学了字符指针,指针数组,数组指针,所以我们这一篇继续深入进阶指针,了解指针和其他知识的联系。

如果还没有看过指针篇的同学可以点下方链接先进行学习:
【C语言】从入门到入土(指针篇)

如果还没有看过指针篇的同学可以点下方链接先进行学习:
【C语言】从入门到入土(进阶之指针第一篇)

C语言指针进阶:

一.字符指针

在上一篇中讲到的哦:
【C语言】从入门到入土(进阶之指针第一篇)

二.指针数组

在上一篇中讲到的哦:
【C语言】从入门到入土(进阶之指针第一篇)

三.数组指针

在上一篇中讲到的哦:
【C语言】从入门到入土(进阶之指针第一篇)

1.回顾:

上一篇我们理解了指针数组和数组指针的概念与区别,在这里我们回顾一下,再进入下面的学习,用代码来表示说明:

int main()
{
	//字符数组
	char* ch = 'w';
	char* p = &ch;
	const char* arr = "abcdef";
	
	//指针数组
	int* arr2[10];
	char* arr3[5];
	//先于数组下标结合,再结合指针,指针数组

	//数组指针
	int (*arr4) [5];
	//先与*结合,再结合数组下标,数组指针
	
	return 0;
}

区分数组指针和指针数组,要看变量名先和谁结合,[ ]的优先级是大于*的,所以当要结合的时候,如果*和变量名没有括号围起,可能就是指针数组了。

接下来进入下面的进阶指针学习。


四. 数组传参和指针传参

我们在学习c语言的时候,难免会用到函数,也难免会进行数组传参和指针传参,那数组传参和指针传参是怎么传的呢,我们来研究一下:

1.一维数组传参

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

当我们创建一个数组进行函数传参的时候,我们用到的是数组名,所以传过去的是数组首元素的地址,那么接收的时候,我们也只需要接收一个地址。

因为数组的元素是连续存放的,我们可以根据首元素地址找到剩下的元素,所以在一维数组传参的时候,可以有以下版本:

int main()
{
    //以下函数是在这个主函数的基础上写的
	int arr[10] = { 0 };
	test1(arr);
	test2(arr);
	test3(arr);
	test4(arr);

	return 0;
}
void test1(int arr[])
{
    //因为并不会真实的创建,[]内可以无参数
	printf("可以运行 ");
}
void test2(int arr[10])
{
    //有参数也可以
	printf("可以运行 ");
}
void test3(int arr[100])
{
    //参数大小也没有关系
	printf("可以运行 ");
}
void test4(int * p)
{
    //传过来的是首元素地址,可以直接用指针接收
	printf("可以运行 ");
}

2.指针数组传参

而指针数组的传参,其实也是跟一维数组相近,虽然是换了类型但还是数组,所以我们还是可以按照数组的传参一样:

void test1(int * arr[])
{
	printf("可以运行 ");
}

void test2(int * arr[10])
{
	printf("可以运行 ");
}

void test3(int * arr[100])
{
	printf("可以运行 ");
}

int main()
{
	int * arr[10] = { 0 };
	test1(arr);
	test2(arr);
	test3(arr);

	return 0;
}

而这里唯一一点点特别的就是,指针数组传参传过去的就是指针数组首元素的地址,既然是一级指针的地址,那我们可以用二级指针来接收,所以我们还有这种写法:

void test4(int** p)
{
	printf("可以运行 ");
}

int main()
{
	int * arr[10] = { 0 };
	test4(arr);

	return 0;
}

3.二维数组传参

而二维数组的传参,也可以参照部分一维数组的传参,为什么说是部分呢,因为二维数组传参的时候要规定一行有多少个元素,所以列数是一定不可以省略的:

void test1(int arr[5][10])
{
	printf("执行成功!");
}
void test2(int arr[][10])
{
	printf("执行成功!");
}
void test3(int arr[][])//错误!!!
{
	//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
	printf("执行成功!");
}


int main()
{
	int arr[5][10]={0};

	test1(arr);
	test2(arr);
	test3(arr);
	return 0;
}

但传参的时候我们注意到一点,传参的时候也是将数组名传过去的,数组名是首元素地址这句话在这里也没错,但首元素可就不是指arr[0][0]了,二维数组首元素指的是第一行的元素。

其实就是把二维数组看作一维数组,那么划分的时候呢就是要划分每一行作为一个元素,所以我们取数组名的时候才是取出的首元素的就是第一行的元素。

所以我们二维数组传参的时候就可以考虑一个问题,传过去的本质是什么?我们来分析一下:

void fun(int (*p)[5])
{
	//首先数组名是首元素地址,是地址,*p
	//然后有5个数组元素,加上[5]
	//由于[]比*优先级高,所以要(*p)[5]
	//最后这个数组是int类型的
	//所以是int (*p)[5]
	printf("执行成功!");

}

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

	return 0;
}

总结:二维数组传参可以是二维数组数组接收,也可以是指针数组接收,但不能是二级指针接收,这是两码事。


4.一级指针 传参

一级指针传参,我们先来看,传过去的是指针嘛,我们直接用指针接收就可以了:

void fun(int * p,int sz)
{
    //首元素地址传过来指针接收
    //其实理论上写数组也可以,如int p[];
    //因为数组理论上也是得到地址
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));
	}
}

int main()
{
	int arr[] = { 1,2,3,4,5 };
	int* p = arr;
	int sz = sizeof(arr) / sizeof(arr[0]);
	fun(p, sz);
	return 0;
}

总结一下:
一级指针接收可以接收什么参数:

void test(int* p)
{
	printf("执行成功 \n");

}

int main()
{
	int a = 10;
	int arr[5] = { 1,2,3,4,5 };
	int* p = &a;

	test(&a);//a的地址
	test(p);//指针储存的a的地址
	test(arr);//数组名表示首元素地址
	test(arr[0]);//首元素地址
	test(NULL);//空指针(慎用,因为不知道你需要干什么)

	return 0;
}

5.二级指针传参

按照惯例,传啥接啥嘛,二级指针传参,我直接用二级指针接收就可以了,这是比较简单的:

void test(int** p)
{
	printf("执行成功!");
}
int main()
{
	int a = 10;
	int * pa = &a;
	int** paa = &pa;
	test(paa);

	return 0;
}

但是我们同时也要思考,二级指针传过去可以二级指针接收,那二级指针能接收的东西有什么呢:

void test(int** p)
{
	printf("执行成功!");
}
int main()
{
	int a = 10;
	int * pa = &a;
	int** paa = &pa;
    int * arr[5];

	test(paa);//首先这个肯定是没有问题的
    test(&pa);//既然paa等于&pa,所以这个也可以
    test(arr);//一级指针数组,传过去首元素地址,同样是地址的地址
	return 0;
}

五. 函数指针

函数有地址吗,注意:函数也是创建在栈上的,也是需要空间的,所以函数也有地址,也就是也会有函数指针,具体想知道函数的创建与销毁可以看一下这一篇文章:

【C语言】函数栈帧的创建与销毁

那么函数指针是什么样子的呢,我们看一下代码:

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

int main()
{
    printf("%p \n", Add)
	printf("%p \n", &Add);
	return 0;
}

显然,我们打印的是函数的地址,说明函数确实也是有地址的,也就是函数指针是存在的,同时这里的函数名就不像数组名一样是首元素地址了,函数首元素地址,这听起来也不合适,函数名和取地址函数名是一样的。那如果我创建一个指针去储存函数地址,要用什么样子的指针呢,我们来分析一波。

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

int main()
{ 
    int (*p)(int,int)=&Add
    //p是用来存放地址的,p就是函数指针变量
	printf("%p \n", &Add);
	return 0;
}

①函数指针是指针,我们先写一个*p
②函数指针如果要接收参数,就写上参数
③这里是Add函数,所以就是 *p(int , int)
④然后函数返回是int类型,所以是int (*p)(int,int)

那么指针也是有类型的,比如整形指针,数组指针,所以函数指针也是有类型的,我们继续用类比法,前面的指针都是去掉变量名就是指针的类型,这里也不例外,这里我们创建的函数指针储存Add函数的地址的函数指针,他的类型就是:int (*)(int,int)


Q1: 函数指针的用处是在哪呢?

我们来看一下:

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

int main()
{
	int (*p)(int, int) = &Add;
	int ret = Add(2, 3);
	printf("%d", ret);

	ret = (*p)(3, 4);//调用函数指针进行函数调用
	//既然指针储存了函数地址,解引用就可以知道这个函数
	//函数指针中已经标明变量了,直接放入参数就可以了
	printf("%d", ret);

	return 0;
}

Q2:前面上到Add和&Add是一样的,那调用的时候和函数指针的关系是什么?

我们来看一下前面的代码:

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

int main()
{
	int (*pt)(int, int) = Add;
	//和加取地址符一个意思
	int (*pt)(int, int) = &Add;
	int ret = Add(2, 3);
	printf("%d", ret);

	ret = (*pt)(3, 4);
	printf("%d", ret);

	return 0;
}

这里我们发现,函数名Add赋给pt之后,Add和pt应该是一样的了,我们可以把Add赋给pt,就说明两者的类型是一样的。如果这句话成立的话,那我们用Add(2,3)调用函数的时候,是不是也可以理解为pt(2,3) ,也就是去除*ret = (*pt)(3, 4);即为ret = pt(3, 4);

我们尝试一个代码测验一下:

代码中先进行Add调用函数,正常运行,然后*pt调用函数把ret的值覆盖并变成自己调用后的返回值,pt同样是覆盖并返回自己调用的函数值,说明pt确实是和Add一样。

其实函数指针的变量名存的就是函数的地址了,但是为了让初学者更容易理解,一般是将指针解引用找到函数的地址,然后调用,实际上这里的解引用就是一个摆设,加多个解引用也没什么事情发生。


学完上面的内容,我们来两道有趣小题:

  1. (*(void (*)())0)();这个代码是什么意思?
  2. void (*signal(int , void(*)(int)))(int);这个代码如何理解?

首先我们来看第一个,突破口在那里呢,突破口就在这个数字0,数字是变量啊,那前面的一长串又是什么,我们来一步一步分析:

① 首先先将代码拉宽一点看看:(* ( void (*)() ) 0)();

②这里的0是变量,那前面一个就是一个类型,但是前面并不是int 类型,而是一长串的东西,我们来分析0前面括号内容:void (*)(),类型+解引用括号+括号,是不是很像int (*pt) (int,int),没错就是函数指针去掉变量名,也就是函数指针的类型。

③所以void (*)()是函数指针的类型,将类型为int的0强制类型转换为函数指针类型,而这个类型是void (*)()无返回无传参。

④然后最前面的*登场,解引用函数指针找到函数指针的地址,然后最后面的括号再传参,就是一个0的函数指针解引用又再调用函数。

解析:
1. 代码中把0强制类型转换为类型为void (*)()的一个函数的地址。
2. 解引用0地址,就是去0地址处的这个函数,被调用的函数是无参,返回类型是void

这里一眼看下去确实有点迷糊,但是我们要知道入手点,就能慢慢解开了。理解这个代码的含义,有助于我们理解前面的知识。


然后我们来看第二个,先分块看看:

void ( *signal(int , void(*)(int) ) )(int);在这里,我们没有看见数字,那我们就从其他地方入手,比如signal,因为其他都是类型和解引用。

①signal后面跟一个括号,里面有两个东西,第一个是int类型,第二个是函数指针类型,但是并不是传参,而是类型说明,signal是一个函数说明。

②然后我们再看,除去signal(int , void(*)(int) ),剩下的就是void ( * )(int);,这不就是一个函数指针类型吗,所以signal函数返回类型是void ( * )(int);

解析:
1.signal函数有两个参数,第一个是int类型,第二个是void(*)(int)函数指针类型
2.signal函数的返回类型依然是void(*)(int)函数指针类型。

既然我们signal函数的参数是函数指针类型,返回类型也是这个,那我们能不能简化一下呢,有的,那就是typedef

typedef void(*pfun_t)(int);
//typedef 重起一个名字
//如typedef int i1就是把int 类型多起一个名字叫i1
//但这里语法规定void(*)(int)这种类型名字要在*的旁边

int main()
{
	void (*signal(int, void(*)(int)))(int);
    //这里两种形式是相同的意思
	pfun_t signal(int, pfun_t);
    //将过长的代码简化
	return 0;
}

六. 函数指针数组

数组是一个存放相同类型数据的存储空间,那我们已经学习了指针数组:

int *arr[10];
//数组的每个元素是int*

那个整形指针数组是存放整形指针的数组,那么函数指针数组就是存放函数指针的数组,里面存放着多个函数指针。

我们用代码来演示说明:

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 (*pf1)(int, int) = Add;
	int (*pf1)(int, int) = Sub;
	int (*pf1)(int, int) = Mul;
	int (*pf1)(int, int) = Div;

	return 0;
}

在这里我们定义了多个函数指针,但是和之前定义整形变量一样,万一不止4个,是10个100个呢,这时候我们就要用到函数指针数组了:

int main()
{
	/*int (*pf1)(int, int) = Add;
	int (*pf1)(int, int) = Sub;
	int (*pf1)(int, int) = Mul;
	int (*pf1)(int, int) = Div;*/
	//四行代码一行搞定
	
	int (*pfArr[4])(int, int) = {Add,Sub,Mul,Div};
    //函数指针数组

	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 main()
{
	int x = 0;
	int y = 0;
	int input = 0;
	int ret = 0;
	menu();
	printf("请选择算法:");
	scanf("%d", &input);//输入选择加减乘除
	
	do {
		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");

		}
	} while (input);//当遇到0退出程序

	return 0;
}

这里就写出来一个简单的加减乘除的计算机,但是我们如果还想计算更多的值呢,有没有更简便更实用的代码,这就用到了函数指针数组

//这里省略了上面的加减乘除的函数

//省略了上面的菜单

int main()
{
	int input = 0;
	do {
		int ret = 0;
		int x = 0;
		int y = 0;
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		int (*pfArr[5])(int, int) = { 0,Add,Sub,Mul,Div };
        //用函数指针数组存储函数指针

		if (input == 0)
		{
			printf("退出计算机\n");
		}
		else if (input >= 1 && input <= 4)
		{
			printf("请输入两个操作数:>");
			scanf("%d %d", &x, &y);
			ret = pfArr[input](x, y);
			//input直接选择了那种算法
			
			printf("%d\n", ret);
		}

		else
		{
			printf("选择错误");
		}
	} while (input);

	return 0;
}

在这里,我们用函数指针数组来存储函数指针,当我们要增加算法也只需要在数组里面添加就可以了,我们在创建数组的时候,用0作为首元素,这样子当我们输入其他的数组的时候,刚好对应上其算法。

然后,除了可以转变为函数指针数组的形式,其实我们也可以通过函数指针去简化第一个代码,也就是有多个case 的代码:

//在这里,我们可以通过观察case里相近的东西
//然后做一个函数去直接调用

//这里省略了加减乘除的函数
int 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("ret = %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");

		}
	} while (input);

	return 0;
}

在之前,我们如果要函数调用去执行一串的代码的时候,可能需要写几个函数,但是现在我们利用函数指针去调用,直接用一个函数就可以解决了,当我们在主函数中调用到calc函数的时候,由calc函数再通过函数指针调用到那一个算法函数里,大大减少了 代码的冗余。


七. 指向函数指针数组的指针

我们有指向整形数组的指针,有指向整形指针数组的指针,那也会有指向函数指针数组的指针吧?答案是:有的

我们继续来一步一步推一下:

    int arr[10];
	int(*p)[10] = &arr;
	//p是指向整形数组的指针
    int* arr[10];//整形指针数组
	int* (*p)[10] = &arr;//存储整形指针数组的指针
	//p是指向整形指针数组的指针

那么函数指针数组的指针,也可以一步一步来得到:

int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int (*pf)(int, int) = Add;//pf是函数指针
	int(*pfArr[5])(int , int);//pfArr是一个函数指针数组
	int (*(*ppfArr)[5])(int, int);//ppfArr是指向函数指针数组的指针
}

最后一个ppfArr是指向函数指针数组的指针,如何理解呢,首先ppfArr先与*结合,是一个指针。然后这个指针指向数组[5],去掉(*ppfArr)[5]后剩下的就是类型,为int (*)(int, int),也就是函数指针类型,所以这ppfArr是一个指向函数指针数组的指针。

例子:

void test(const char* str)
{
 printf("%s\n", str);
}
int main()
{
 //函数指针pfun
 void (*pfun)(const char*) = test;
 //函数指针的数组pfunArr
 void (*pfunArr[5])(const char* str);
 pfunArr[0] = test;
 //指向函数指针数组pfunArr的指针ppfunArr
 void (*(*ppfunArr)[10])(const char*) = &pfunArr;
 return 0;
}

八. 回调函数

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

像我们在上面的calc函数回调的Add之类的就是回调函数,通常以一种函数指针的形式进行。通过函数指针响应相应的函数,所以我们的函数指针是非常有用的。

在这里学习回调函数之前,我们先学习一个库函数——qsort。我们之前学习过的排序有冒泡排序,选择排序,插入排序,而这个qsort则是一个快速排序。我们可以去cplusplus查看:qsort c++ reference

qsort是一个快速排序的库函数,我们先来看看他的参数:

void qsort(void* base,//void *是无具体类型指针
           size_t num,
           size_t size,
	       int (*cmp)(const void* e1, const void* e2)//函数指针
	      );

第一个void* base,当我们把一个数组传进去的时候,有不同的类型,如果我们写的是int * base,那传字符型的时候就会报错。所以我们使用无具体类型指针接收,传任意类型的地址都可以接收,但缺点是因为无具体类型不能进行运算。

第二个是 size_t num,意义是待排序的元素个数,第三个是 size_t size是一个元素的大小,单位是字节。也就是接收了数组的信息。

而最后一个是int (*cmp)(const void* e1, const void* e2),是一个函数指针。当我们排序的时候不是整形的时候,如结构体的时候,有时候不知道哪两个进行比较,这时候就有const void*e1, const void* e2,就是将这两个进行比较。cmp指向的是:排序时,用来指向比较2个元素的函数的指针。

上代码:

#include <stdlib.h>//qsort库函数需引头文件

//测试排序整形数组
int cmp_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}
int main()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1 };
	int sz = sizeof(arr) / sizeof(arr[0]);//元素个数

	qsort(arr, sz, sizeof(arr[0]), cmp_int);
	//   数组 元素个数  元素大小   排序比较

	int i = 0;
	for (i = 0; i < sz; i++)//打印
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

在这里,cmp_int 就是回调函数,那函数里面是如何理解的呢,我们来分析一下:首先传进去的两个无类型指针,我们这里想比较的是整形,所以解引用整形指针拿到这个数据。然后就是比较,在这里qsort函数有以下规定,当第一个元素大于第二个元素,返回值大于0;相等返回0;小于返回值小于0。所以我只需要返回第一个元素减第二个元素就可以了。

在这里插入图片描述


我们再来看看用qsort函数去结构体排序:

struct Stu//创建结构体
{
	char name[20];
	int age;
};

int cmp_by_name(const void* e1, const void* e2)//用名字比较!
{
	return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
	//当我们想访问结构体的时候,先把e1,e2强制类型转换为结构体
	//然后->访问name,对比两个字符串使用strcmp库函数
	//且刚好strcmp的返回值和qsort比较的返回值一样
	//直接return 返回即可
}

void test2()
{
	struct Stu s[3] = { {"zhangsan",15}, {"lisi",20},{"wangwu",18} };
	//创建结构体变量
	int sz = sizeof(s) / sizeof(s[0]);
	qsort(s, sz, sizeof(s[0]), cmp_by_name);
}

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

这之前学习库函数的时候,我们理解其他原理之后,也仿制了一个类似的自定义函数,在这里我们学习完qsort函数,当然要尝试做一个类似的自定义函数:

在之前,我们学习了排序整形数组的冒泡排序,我们可以通过他与qosrt结合,来制作一个自定义函数,可以容纳多种类型的排序。我们来写一个自定义函数my_qsort.

int cmp_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}

//交换函数
void Swap(char * buff1,char * buff2,int width)
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char tmp = *buff1;
		*buff1 = *buff2;
		*buff2 = tmp;
		buff1++;
		buff2++;
	}
}

//自定义qsort
//使用回调函数实现一个通用的冒泡排序函数
void my_qsort(void* base, size_t num, size_t width,
	int (*cmp)(const void*e1, const void*e2))
{
	int i = 0;
	for (i = 0; i < num; i++)
	{
		int j = 0;
		for (j = 0; j < num - 1 - i; j++)
		{
			if (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[] = { 9,8,7,6,5,4,3,2,1 };
	int sz = sizeof(arr) / sizeof(arr[0]);

	my_qsort(arr, sz, sizeof(arr[0]), cmp_int);
	//自定义快速排序函数
}

int main()
{
	test3();//主函数调用test3()进行测试
	return 0;
}

完整代码就在这上面了,我们来解释一下:cmp_intint main以及test3就不用解释了,这几个就是后面测试的时候使用的几个函数,整体看向my_qsortSwap函数:

①首先我们要包容各种类型比较以及实现qsort的功能,我们的参数就应该仿照qsort,所以我们my_qsort的参数类型就是qsort的参数类型。

②然后进入函数内部,我们需要一个一个元素进行对比,所以我们使用冒泡排序的方法进行一个一个的比较。为了仿照qsort的返回值判断方法,我们也是当返回值大于0的时候才进行换位。

③那么对比这里就比较难了,首先进行对比我们需要换成一种可以对比的类型,这里我们选择char类型,是因为char是一个字节的类型,当后面的元素进行比较的时候,我们加上width元素宽度就可以知道后面的元素了。然后对比完第一个和第二个,就要到第二个和第三个比较,所以我们使用j*width表示现在这一次比较需要比第一次的元素跳过多少个字节。最后我们的对比就是:(char*)base+ j * width(char*)base + (j + 1) * width)的对比了。

④如果对比的返回值大于0,则进入交换函数Swap。由于我们不知道我们需要交换的类型是什么,然后我们可以同样用char来表示,无论那个类型一个一个字节交换之后,整体就相当于交换了。所以我们传进去的参数是两个对比的和元素的宽度,然后循环元素宽度次交换就可以把整体交换完了。

⑤最后就相当于把qsort函数的自定义版完成了,然后我们尝试使用一次得到的结果也是成功的,当然我们这里只测试了整形,如果想测试其他的大家可以自己去尝试一下哦。


好啦,本篇的内容就到这里,关于指针进阶的内容也到这里啦,最后也请继续关注我哦。关注一波,互相学习,共同进步。

还有一件事:

标签:arr,进阶,int,void,C语言,数组,函数指针,第二篇,指针
来源: https://blog.csdn.net/weixin_54225715/article/details/120276065