2024年Go最新【C语言详解】一步一步带你手撕指针 ~ You got it?,2024年最新我就不信你还听不明白了

img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

int main()
{
int arr[3][5] = { 1,2,3,4,5, 6,7,8,9,10, 11,12,13,14,15 };
print_arr1(arr, 3, 5);
print_arr2(arr, 3, 5);
return 0;
}



> 
> * 数组名arr,表示首元素的地址
> * 但是二维数组的首元素是二维数组的第一行
> * 所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
> * 可以数组指针来接收
> 
> 
> 


结果如下:👇🏻


![在这里插入图片描述](https://img-blog.csdnimg.cn/d9cc24e887d14b75939723cd1e133b0d.png)



> 
> 所以最优解就是:使用数组指针来接收二维数组
> 
> 
> 


![请添加图片描述](https://img-blog.csdnimg.cn/ca9229e0d5da4167b77717c5e31c9433.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5byg5bCP5aeQ55qE54yr,size_20,color_FFFFFF,t_70,g_se,x_16)


### 📜 小练习


学习完了指针数组和数组指针,我们来一起回顾一下并看看下面代码的意思:👇🏻



int arr[5];
int *parr1[10];
int (*parr2)[10];
int (*parr3[10])[5];



> 
> arr是一个整型数组,每个元素是`int`类型的,有5个元素  
>  .  
>  parr1 首先和`[ ]`结合,所以是一个数组,数组10个元素,每个元素的类型是`int*`  
>  .  
>  parr2 首先和`*`结合,所以是一个指针, 指向的数组有10个元素,每个元素的类型是`int`  
>  .  
>  parr3 首先和`[ ]`,所以是一个数组,数组有10个元素,每个元素的类型是:`int(*)[5]`  
>  parr3 是**存放数组指针的数组**  
>  ![在这里插入图片描述](https://img-blog.csdnimg.cn/67de187a6e0748c39be57a69cd97487f.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5byg5bCP5aeQ55qE54yr,size_20,color_FFFFFF,t_70,g_se,x_16)
> 
> 
> 


![请添加图片描述](https://img-blog.csdnimg.cn/ca9229e0d5da4167b77717c5e31c9433.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5byg5bCP5aeQ55qE54yr,size_20,color_FFFFFF,t_70,g_se,x_16)


## 🐾数组参数和指针参数


在写代码的时候难免要把【数组】或者【指针】传给函数,那函数的参数该如何设计呢?


### 🌅 一维数组传参


判断以下接收方式“河里”吗?👇🏻![请添加图片描述](https://img-blog.csdnimg.cn/27809ee77a194fc297d313316e5666f7.bmp)



void test(int arr[])//ok?
{}
void test(int arr[10])//ok?
{}
void test(int arr[100])//ok?
{}
void test(int *arr)//ok?
{}
void test2(int *arr[20])//ok?
{}
void test2(int **arr)//ok?
{}
int main()
{
int arr[10] = {0};
int *arr2[20] = {0};
test(arr);
test2(arr2);
}


以上方式都合理,接下来我们逐一分析一波:![请添加图片描述](https://img-blog.csdnimg.cn/b336123234c448a683f1c1a9b2b84d4c.bmp)


![在这里插入图片描述](https://img-blog.csdnimg.cn/27a3d976712f498198759a5801ccc75f.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5byg5bCP5aeQ55qE54yr,size_20,color_FFFFFF,t_70,g_se,x_16)



> 
> 形参写成数组形式:**形参部分的数组大小可以省略**  
>  形参写成指针形式:要注意传的**元素的类型**,若传的是一级指针,则用二级指针来接收
> 
> 
> 


### 🌅 二维数组传参



void test(int arr[3][5])//ok?
{}
void test(int arr[][])//ok?
{}
void test(int arr[][5])//ok?
{}
int main()
{
int arr[3][5] = {0};
test(arr);
}


分析如下:  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/7d5195a858d848b79ac3a666d40be88d.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5byg5bCP5aeQ55qE54yr,size_20,color_FFFFFF,t_70,g_se,x_16)那我们是不是有疑问说:为什么可以省略二维数组的行数,但不能省略列数❓



> 
> 答:二维数组在内存中的地址排列方式是**按行排列**的**连续存放**,第一行排列完之后再排列第二行,以此类推
> 
> 
> * 如果我们不知道列数,也就不知道一行能放多少个。
> * 只要知道了列数,放完后就会知道放了多少行
> 
> 
> 


继续判断以下接收方式“河里”吗?👇🏻![请添加图片描述](https://img-blog.csdnimg.cn/27809ee77a194fc297d313316e5666f7.bmp)



void test(int *arr)//ok?
{}
void test(int* arr[5])//ok?
{}
void test(int (*arr)[5])//ok?
{}
void test(int **arr)//ok?
{}
int main()
{
int arr[3][5] = {0};
test(arr);
}


分析如下:  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/c31d8a69daeb440f8fb7229799964241.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5byg5bCP5aeQ55qE54yr,size_13,color_FFFFFF,t_70,g_se,x_16)  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/b27c4ad39ca64e45bc2a8cdbb85bffe2.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5byg5bCP5aeQ55qE54yr,size_20,color_FFFFFF,t_70,g_se,x_16)



> 
> 总结:
> 
> 
> 1. 二维数组传参,函数形参的设计只能省略第一个[]的数字。  
>  因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。这样才方便运算。
> 2. 形参类型要相匹配:**指向数组首行的地址,类型要相匹配**
> 
> 
> 


开个小灶:



int arr[10];
test(arr);


![在这里插入图片描述](https://img-blog.csdnimg.cn/e2ecd2d278ea45f0a34b5880a2bdb7d3.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5byg5bCP5aeQ55qE54yr,size_17,color_FFFFFF,t_70,g_se,x_16)



> 
> 🤔形参为数组时,其**本质还是指针** !编译器会把数组转化成指针
> 
> 
> 


![请添加图片描述](https://img-blog.csdnimg.cn/ca9229e0d5da4167b77717c5e31c9433.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5byg5bCP5aeQ55qE54yr,size_20,color_FFFFFF,t_70,g_se,x_16)


### 🌅 一级指针传参



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



> 
> 一级指针传参,形参可以用`一级指针`接收,也可以用数组接收(但不推荐)
> 
> 
> 


思考:  
 当一个函数的参数部分为一级指针的时候,函数能接收什么参数?  
 比如:



void test1(int* p)//test1函数能接收什么参数?
{
//…
}
int main()
{
int a = 10;
int* p = &a;
int arr[10];
test1(arr);
test1(&a);
test1§;
return 0;
}


分析如下:  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/7e3a8150bb884440b555c92bc2ba624c.png)



> 
> 形参为一级指针,实参可以是`数组`,也可以是`一级指针(地址)`
> 
> 
> 


### 🌅 二级指针传参



void test(int** ptr) //二级指针接收
{
printf(“num = %d\n”, **ptr);
}
int main()
{
int n = 10;
int* p = &n;
int** pp = &p;//ppa是一个二级指针
test(pp);//二级指针
test(&p);//取地址一级指针,类型为二级指针
return 0;
}



> 
> 二级指针传参时,形参最好用**二级指针来接收**(指针数组不推荐)
> 
> 
> 


思考:  
 当函数的参数为二级指针的时候,可以接收什么参数?  
 比如:



void test(char** p) {

}
int main()
{
char c = ‘b’;
char* pc = &c;
char** ppc = &pc;
char* arr[10];
test(&pc);//取地址一级指针,类型是二级指针
test(ppc);//传的是二级指针
test(arr);//Ok? ok 因为arr数组名是首元素,char*的地址——类型为char**
return 0;
}


提问:如果arr2是个二维数组,可以吗?  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/3e8fcb14c7df4887a620a9e17e7149b0.png)  
 不可以,要写成`char(*p)[5]`才可以。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/4f38186e20254cc0a17a42326a45a6fb.png)



> 
> 形参为二级指针时,传参可以是**二级指针**,也可以是**数组指针首元素的地址**
> 
> 
> 


![请添加图片描述](https://img-blog.csdnimg.cn/ca9229e0d5da4167b77717c5e31c9433.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5byg5bCP5aeQ55qE54yr,size_20,color_FFFFFF,t_70,g_se,x_16)


## 🐾函数指针


### 🌅 函数指针的定义



> 
> 数组指针:是指向数组的指针。  
>  函数指针:类比可知是指向函数的指针,存放函数地址的指针。
> 
> 
> 



int Add(int x, int y)
{
return x + y;
}
int main()
{
int arr[10];
int(*p)[10] = &arr;//p是一个数组指针变量
printf(“%p\n”, &Add);
printf(“%p\n”, Add);
}


![在这里插入图片描述](https://img-blog.csdnimg.cn/8246486086964d65aa658b9cda75c863.png)



> 
> 我们可以得知:**函数名 == &函数名(完全等价)**  
>  都可以用来表示函数地址
> 
> 
> * 数组名 != &数组名(完全不同)
> * 函数名 == &函数名(完全等价)
> 
> 
> 


### 🌅 函数指针的类型


了解了函数指针的定义,那么函数指针类型该怎么样写呢?



int (*pf)(int x, int y) = &Add;//函数指针变量


分析:  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/0553038f24614790a652582751d7a68e.png)接下来,我们试一下这个:`int test(char* str)`的函数指针咋写?



int test(char* str)
{}
int main()
{
int (*pf)(char*) = &test;
}


那我们的函数的地址要想保存起来,怎么保存?  
 下面我们看代码:



void test()
{
printf(“hehe\n”);
}
//下面pfun1和pfun2哪个有能力存放test函数的地址?
void (*pfun1)();
void *pfun2();


首先,能给存储地址,就要求pfun1或者pfun2是指针,那哪个是指针?  
 答案是:



> 
> pfun1可以存放。pfun1先和`*`结合,说明`pfun1是指针`,指针指向的是一个函数,指向的函数无参数,返回值类型为void。
> 
> 
> 


**阅读两段有趣的代码:**



//代码1
(*(void (*)())0)();
//代码2
void (*signal(int , void(*)(int)))(int);


我们慢慢把代码逐层剖析:  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/65ba0a6d6a9447f6aa5ae9a2d8840a25.png)



> 
> 代码1: ①:首先是把0**强制类型转换**成一个函数指针类型,这就意味着0地址处放着一个返回类型是void,无参的一个函数  
>  ②:调用0地址处的这个函数
> 
> 
> 


![在这里插入图片描述](https://img-blog.csdnimg.cn/65734b4a07ec4eb5a2a91d6244dbbe36.png)


上面的代码是不是太废眼睛了呢?我们接下来用**重定义typedef**简化一下吧:



typedef void(\*pf\_t)(int);
//给函数指针类型void(\*)(int)重新起名叫:pf\_t
pf\_t signal(int, pf\_t);
//替换后等效于void (\*signal(int, void(\*)(int)))(int)

注 :推荐《C陷阱和缺陷》,这本书中提及这两个代码。  
 ![请添加图片描述](https://img-blog.csdnimg.cn/ca9229e0d5da4167b77717c5e31c9433.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5byg5bCP5aeQ55qE54yr,size_20,color_FFFFFF,t_70,g_se,x_16)


## 🐾函数指针数组


### 🌅 函数指针数组的定义


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



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


那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?



int (*parr1[10])();
int *parr210;
int (*)() parr3[10];



> 
> 答案是:parr1  
>  parr1 先和 `[ ]` 结合,说明 parr1是数组,数组的内容是什么呢?  
>  是 `int (*)( )` 类型的函数指针。
> 
> 
> 


函数指针数组的用途:**转移表**


### 🌅 函数指针数组的实现


例子:(计算器)



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;
}
int main()
{
int input = 0;
do{
menu();
int x = 0;
int y = 0;
int ret = 0;
printf(“请选择:> “);
scanf(”%d”, &input);
printf(“请输入2个操作数:> “);
scanf(”%d %d”, &x, &y);
switch (input)
{
case 1:
ret = Add(x, y);
break;
case 2:
ret = Div(x, y);
break;
case 3:
ret = Mul(x, y);
break;
case 4:
ret = Div(x, y);
break;
case 0:
printf(“退出程序\n”);
break;
default:
printf(“重新选择\n”);
break;
}
printf(“ret = %d\n”, ret);
} while (input);

return 0;

}


我在测试中发现了,有bug!  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/66404003965d4798b09beb41de66caca.png)



> 
> 当input等于0和5的时候,发现程序出错了
> 
> 
> 


接下来我们对此进行修改:  
 把以下代码加入到switch语句的case中:



printf(“请输入2个操作数:> “);
scanf(”%d %d”, &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("请输入2个操作数:>");
		scanf("%d%d", &x, &y);
		ret = Add(x, y);
		printf("ret = %d\n", ret);
		break;
	case 2:
		printf("请输入2个操作数:>");
		scanf("%d%d", &x, &y);
		ret = Sub(x, y);
		printf("ret = %d\n", ret);
		break;
	case 3:
		printf("请输入2个操作数:>");
		scanf("%d%d", &x, &y);
		ret = Mul(x, y);
		printf("ret = %d\n", ret);
		break;
	case 4:
		printf("请输入2个操作数:>");
		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;

}



> 
> 但是我觉得还不够好:
> 
> 
> 1. **代码冗余**,出现了多次的printf 、scanf(每个case中都有)
> 2. **可读性低**
> 
> 
> 


对此我们利用函数数组指针来优化,通过函数数组的下标来进行跳转到目标函数:



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;

//转移表
int (\*pfArr[])(int, int) = {0, Add, Sub, Mul, Div};
//pfArr就是函数数组指针
do
{
	menu();
	printf("请选择:>");
	scanf("%d", &input);
	if (input == 0)
	{
		printf("退出计算器\n");
	}
	else if(input >= 1 && input<=4)
	{
		printf("请输入2个操作数:>");
		scanf("%d%d", &x, &y);	
		ret = pfArr[input](x, y);
		printf("ret = %d\n", ret);
	}
	else
	{
		printf("选择错误\n");
	}

} while (input);

return 0;

}


![在这里插入图片描述](https://img-blog.csdnimg.cn/7a1d40e1524a47c0ae676b396979ffb3.png)



> 
> 上面应用了函数指针数组,通过函数数组的下标来进行跳转到对应的目标函数  
>  我们把这种函数指针称为`转移表`
> 
> 
> 


![请添加图片描述](https://img-blog.csdnimg.cn/ca9229e0d5da4167b77717c5e31c9433.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5byg5bCP5aeQ55qE54yr,size_20,color_FFFFFF,t_70,g_se,x_16)


## 🐾指向函数指针数组的指针


指向函数指针数组的指针是一个 `指针`  
 指针指向一个 `数组` ,数组的元素都是 `函数指针` ;  
 如何定义?



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)[5])(const char*) = &pfunArr;
return 0;
}


![在这里插入图片描述](https://img-blog.csdnimg.cn/c0cb82794cb141a29617f29e832df5d6.png)


![请添加图片描述](https://img-blog.csdnimg.cn/ca9229e0d5da4167b77717c5e31c9433.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5byg5bCP5aeQ55qE54yr,size_20,color_FFFFFF,t_70,g_se,x_16)


## 🐾回调函数


### 🌅回调函数的定义


回调函数就是一个**通过函数指针调用的函数**



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


举个简单的例子:



void test()
{
printf(“hehe\n”);
}
void print_hehe(void (*p)())//函数指针接收,该形参与test函数类型相同
{
if (1)
p();
}
int main()
{
print_hehe(test);
return 0;
}


图例分析如下:  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/4f46d83498384238a33c4a6178dc6f84.png)



> 
> 把`test`的地址传给了`print_hehe`函数,通过`print_hehe`函数来调用test函数,所以我们称print\_hehe函数为回调函数
> 
> 
> 


### 🌅回调函数的应用


我们拿switch版本的计算机来实践:



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;
}
void Calc(int (*pf)(int, int))
{
int x = 0;
int y = 0;
printf(“请输入2个操作数:>”);
scanf(“%d %d”, &x, &y);
printf(“%d\n”, pf(x, y));
}
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;

}



> 
> 把冗余的代码封装成一个`Calc`函数。  
>  把输入的函数地址传给Calc,Calc通过传入的地址,从而找到了目标函数
> 
> 
> 


![在这里插入图片描述](https://img-blog.csdnimg.cn/8a8c3e9ebf5a4fe2ba8d3ebfaab73016.png)![请添加图片描述](https://img-blog.csdnimg.cn/ca9229e0d5da4167b77717c5e31c9433.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5byg5bCP5aeQ55qE54yr,size_20,color_FFFFFF,t_70,g_se,x_16)


## 🐾sqort函数


### 🌅sqort函数的定义



> 
> sqort是一个包含在**<stdlib.h>** 头文件下的库函数,主要根据一定的比较条件进行快速排序。  
>  也可以对**所有类型的数据**进行排序,一个函数解决所有类型的排序问题,不需要根据不同的类型些不同的函数,提高效率!🔥
> 
> 
> 


接下来我们来回顾一下冒泡排序:



void bubble_sort(int arr[], int sz)
{
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;
}
}
}
}

void print_arr(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf(“%d “, arr[i]);
}
printf(”\n”);
}

int main()
{
int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };// 排序为升序
int sz = sizeof(arr) / sizeof(arr[0]);

print\_arr(arr, sz);
bubble\_sort(arr, sz);
print\_arr(arr, sz);

return 0;

}



> 
> 我们发现:冒泡排序只能对整数进行排序,而不能对其他其他类型的数据进行排序。
> 
> 
> 


对此我们来看看qsort这个函数:  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/b774ad4ae5e046a4b44178ea04f7d21d.png)



void qsort(void *base,
size_t num,
size_t width,
int (*cmp)(const void *, const void*))



> 
> **(1):base 👉** 待排序数组首地址(可直接输入待排序数组名,或是指向数组的指针)  
>  .
> 
> 
> **(2):num👉**数组中待排序元素数量(可以用sizeof()来求)  
>  .  
>  **(3):width👉** 各元素的占用空间大小(可以用sizeof(arr[0])来求)  
>  .  
>  **(3):cmp 👉** 用来比较待排序数据种两个元素的函数。如果,返回的是大于0的数字表示第一个元素大于第二个元素、等于0的话就是表示第一个元素等于第二个元素、小于0的话就是第一个元素小于第二个元素。
> 
> 
> 


ps:程序员们一定要学会看英文资料,对此我们可以下载一个MSDN(可以私聊我拿安装包哦),里面会有各种函数的解析(英文),以后的工作也会接触到英文资料等,加油吧!



> 
> 那么为什么qsort函数可以排序多种数据类型呢❓
> 
> 
> 


首先:因为`void* base`指针和`num(待排序数组的元素个数)`、`size(待排序数组的元素大小)`可以描述出任意类型。


眼尖的同学会发现👉**参数base的类型是viod**\*



> 
> 因为void\*“**海纳百川**”,无具体类型,它可以接收任意类型的指针。
> 
> 
> 


接下来我们来看`*cmp`函数:



> 
> 这个比较函数指定元素的比较方式,要求使用者自行定义函数。  
>  ![在这里插入图片描述](https://img-blog.csdnimg.cn/ee89e1ad52864ae39a46a72205d074cd.png)  
>  elem1小于elem2,返回值小于0  
>  elem1大于elem2,返回值大于0  
>  elem1等于elem2,返回值为0
> 
> 
> 


### 🌅sqort函数的使用



> 
> 题目:将arr数组当中元素进行排序,用qsort函数实现!
> 
> 
> 



int int_cmp(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
void print_arr(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf(“%d “, arr[i]);
}
printf(”\n”);
}
int main()
{
int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), int_cmp);
print_arr(arr, sz);
return 0;
}


结果:  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/ccc72f7bde5740c3b098b770ffa3e1d2.png)



> 
> 排序的整型数据:用`>` `<`  
>  排序的结构体数据:需要使用者提供一个函数,实现两个数据的比较  
>  **那如何用qsort排序结构体呢?**
> 
> 
> 



#include<stdlib.h>
#include<string.h>
#include<stdio.h>
struct Stu
{
char name[20];
int age;
};
int sort_by_age(const void* e1, const void* e2)//年龄
{
return ((struct Stu*)e1) -> age - ((struct Stu*)e2) -> age;//升序

}
int sort_by_name(const void* e1, const void* e2)//名字
{
return strcmp(((struct Stu*)e1) -> name, ((struct Stu*)e2) -> name);//升序
}
int main()
{
struct Stu stu[] = { {“zhangsan”, 30}, {“lisi”, 34}, {“wangwu”, 20} };
//按年龄来
qsort(stu, sizeof(s) / sizeof(s[0]), sizeof(s[0]), sort_by_age);
//按名字来
qsort(stu, sizeof(s) / sizeof(s[0]), sizeof(s[0]), sort_by_name);
return 0;
}



> 
> **e1 - e2为升序;e2 - e1为降序**
> 
> 
> 



![img](https://img-blog.csdnimg.cn/img_convert/5544a4e9cf1df707701495e87f37f190.png)
![img](https://img-blog.csdnimg.cn/img_convert/fff5ad1c65a05e3a3d6ebdc4fb48eaf4.png)

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.csdn.net/topics/618658159)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

(https://img-blog.csdnimg.cn/ccc72f7bde5740c3b098b770ffa3e1d2.png)



> 
> 排序的整型数据:用`>` `<`  
>  排序的结构体数据:需要使用者提供一个函数,实现两个数据的比较  
>  **那如何用qsort排序结构体呢?**
> 
> 
> 



#include<stdlib.h>
#include<string.h>
#include<stdio.h>
struct Stu
{
char name[20];
int age;
};
int sort_by_age(const void* e1, const void* e2)//年龄
{
return ((struct Stu*)e1) -> age - ((struct Stu*)e2) -> age;//升序

}
int sort_by_name(const void* e1, const void* e2)//名字
{
return strcmp(((struct Stu*)e1) -> name, ((struct Stu*)e2) -> name);//升序
}
int main()
{
struct Stu stu[] = { {“zhangsan”, 30}, {“lisi”, 34}, {“wangwu”, 20} };
//按年龄来
qsort(stu, sizeof(s) / sizeof(s[0]), sizeof(s[0]), sort_by_age);
//按名字来
qsort(stu, sizeof(s) / sizeof(s[0]), sizeof(s[0]), sort_by_name);
return 0;
}



> 
> **e1 - e2为升序;e2 - e1为降序**
> 
> 
> 



[外链图片转存中...(img-ODKhNGaT-1715384777720)]
[外链图片转存中...(img-NtO3p4H0-1715384777721)]

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.csdn.net/topics/618658159)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

  • 15
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值