【C语言】指针超详解版(入门必看!)

目录

初阶指针

一、细说指针

1.指针的概念

2.指针所指向的类型

3.指针的类型

4.指针的值(指针所指向的地址)

5.指针的算数运算

6.指针加减整数的意义(注意:看指针类型)

7.指针加减指针的意义

二、野指针

1.野指针是什么?

2.都有什么情况会造成野指针?

3.如何避免野指针的出现

三、指针和数组

四、二级指针

进阶指针

一、字符指针

二、指针数组

1.指针数组的概念

2.指针数组的使用及注意事项

三、数组指针

1.数组指针的概念

2.数组地址(&arr)和数组首元素地址(arr)区别

四、函数指针

1.函数指针的定义

2.函数指针的示例与拓展

五、函数指针数组

1.函数指针数组定义

2.使用函数指针数组实现计算器功能

六、回调函数

1.回调函数的定义

2.回调函数实现计算器的功能

七、qsort函数

1.qsort函数定义及参数的意义

2.qsort函数排序整型数组示例

3.qsort函数排序结构体示例

总结


初阶指针

一、细说指针

1.指针的概念

指针是一个特殊的变量,里面存储的数值被解释为内存里的一个地址。所以说,指针就是地址,这句话是最直观的。

2.指针所指向的类型

当我们通过指针来访问指针所指向的那块内存时,那块内存的数据类型就决定了指针所指向的类型。

     int a=10;
(1)int*pa=&a;
     char ch='c';
(2)char*pc=&ch;

这里我们声明一个整型变量a,这里的指针变量就是pa,pa里存放a的地址,因为a是整形类型,所以我们要在前面加上int,并且pa是指针变量,所以我们还要加上*来表示它是指针。而想要描述指针所指向的类型,我们只需要把定义的指针名字和前面那颗*去掉即可,例如:

(1)int*pa=&a;//指针所指向的类型是int。                                                                            

(2)char*pc=&c;//指针所指向的类型是char想要找出指针所指向的类型是不是超级简单啊?

3.指针的类型

我们可以先类比下数据类型,在C语言中的数据类型都有哪些?有int ,char等等,那指针类型是否也和这些类型一样呢?答案是:对。那指针类型应该怎么表示呢?

(1)int*pa;
(2)char*pb;

我们再来定义两个指针,来看一下指针的类型,这个其实很简单,我们只需要把我们所定义的指针去掉即可,例如:

(1)int*pa;//指针的类型就是int*   

(2)char*pb;//指针的类型就是char*         

想要找出指针的类型是不是very简单啊?

4.指针的值(指针所指向的地址)

指针我们通常叫做指针变量,因为我们通常用指针来存放一个数据的地址,而这个数据通常是变量,那么指针的值是什么?应该怎么写?这里指针的数值通常与我们编译器的环境相关,在x64环境下,也就是32位环境下,所有的类型的指针的值都是一个32位整数,这32位整数通常以十六进制格式输出(%p进行输出)。

5.指针的算数运算

指针的算数运算通常是+或-一个整数。指针的+或-运算的意义和两个数字的+或-是完全不一样的,我给大家举个例子,例如:

#include <stdio.h>   
int main()  
{  
    int arr[10] = {1,2,3,4};  
    int *p = arr;  
    *p = 10;
    printf("%d\n",*p);  
    p++;
    *p = 20;  
    printf("%d\n",*p); 
 
    return 0;  
}  

这里我们定义了一个整型数组arr,我们都知道数组名就是首元素地址,我们上面也说明了地址就是指针,所以我们也可以说数组名也是指针,这里p存放的是1的地址,这里我们解释一下*p:‘*’就是解引用的作用,因为p里存放的是1的地址,我们如果想要找到1,就只需要在p前面加上*即可,这时候就找到1了,然后我们*p=10,就是把1赋值为10,这里数组1的位置就改为了10,然后打印出来,这时候p++,p存放的是1的地址,p++就是2的地址,解引用*p=20,就把2的值改为了20,打印。我们来看一下结果:

这里确实和我们所说的一样,这里的减法也是一样的,我就不和大家过多赘述了,大家可以自己动手尝试一下。

6.指针加减整数的意义(注意:看指针类型

指针类型的不同决定了指针加减整数所跳过字节数的不同。我给大家举个例子,例如:

从上图中我们可以看出(十六进制数):
int*类型的指针 + 1 是跳过四个字节
double*类型的指针 + 1 是跳过八个字节
char*类型的指针 + 1 是跳过一个字节   

聪明的你发现:
int 类型变量的大小刚好是4个字节
double类型变量的大小刚好是8个字节
char类型变量的大小刚好是1个字节
我们的到以下结论:指针±整数对应的是指针向前/向后移动的大小(指针指向变量类型大小 * 整数)

7.指针加减指针的意义

指针加减整数有意义,那指针加减指针呢?哈哈哈,博主在这里给大家设了个陷阱,看哪个小可爱跳进去了呢?我们指针+指针是没有意义的,而指针-指针是有意义的。

指针-指针:指针-指针的结果是两个指针之间所隔的元素个数,这种指针与指针之间的算术运算通常用于计算数组中两个元素之间的距离(所隔的元素个数),这里也一样给大家举个例子:

                                       

我们可以看到数组中1和10之间所隔元素(距离)是9。

二、野指针

1.野指针是什么?

野指针是指指向未知内存位置或者已经释放内存的指针。

2.都有什么情况会造成野指针?

引用未初始化的指针、访问已释放内存、数组边界越界等行为都可能导致野指针。 

(1)指针未初始化:我们在声明一个指针时一定要初始化,不然就会报错。

//1.指针未初始化
int mian()
{
	int* p;//局部变量指针未初始化,为随机值
	*p = 10;
	return 0;
}

(2)指针越界访问:数组有10个元素,但是在for循环中循环了11次,就导致了指针越界访问。

//2.指针越界访问
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = &arr[0];//int* p = arr;数组名就是数组首元素地址
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i = 0;
	for (i = 0; i <= sz; i++)//越界访问了,循环了11次,指针最后指向了10后面的元素
	{
		printf("%d ", *p);
		p++;
	}
	return 0;
}

(3)指针指向的空间释放:test函数返回一个n的地址放在p里,但是test函数执行完毕之后n的值

就返回给操作系统了,因此,这里打印*p的结果就是一个随机值。

//3.指针指向的空间释放
int* test()
{
	int n = 10;
	//....
	return &n;
}
int main()
{
	int* p = test();
	printf("呵呵\n");
	printf("%d\n", *p);//此时p存放的是n的地址,
	                   //但是在执行完test()函数后n的值返回给操作系统了
	return 0;
}
3.如何避免野指针的出现

1.指针初始化
2.小心指针越界
3.指针指向空间释放,及时置NULL
4.避免返回局部变量的地址
5.指针使用之前检查有效性

三、指针和数组

首先给大家看一组例子:

因为我们都知道数组名:arr就是首元素地址,而pa里存放的是&arr[0]也是首元素地址,所以二者打印结果固然一样。

根据以上我们得出结论:数组名绝大多数情况下都是首元素地址   

当然这是绝大多数情况,那什么情况下有例外呢?有的兄弟有的,像这样的例外还有2个,请看: 

  • sizeof(数组名):这里的数组名可就不是首元素地址了,而是数组的地址,这里大家要区分开
  • &(数组名):这里的数组名也不是首元素地址,而是数组的地址     

&数组名[下标]与指针+-整数有什么不同吗?   

我们可以看到打印出来的结果是完全相同的,所以我们得出:指针(首元素地址)+-整数与&数组名[下标]是相同的,可以相互替换      

可以用指针(地址)+-整数遍历数组吗?

我们可以看到打印的结果也是一样的,这里我们就运用到了指针+-整数的运算了,pa表示首元素地址,+-i就一个一个往后跳,知道数组遍历完全,这里我们又得到结论:指针(地址)+-整数也可以遍历整个数组

总结:我们得到以下结论:     

(1)指针(首元素地址)+-整数与&数组名[下标]是相同的       

(2)指针(地址)+-整数也可以遍历整个数组   

(3)数组首元素地址和数组名是相同的,都代表首元素地址(指针)

四、二级指针

变量的地址是存放在指针里,那么指针的地址能否存放在指针?                                                       答案是yes,指针变量也是有地址的,而存放指针变量地址的指针叫做二级指针,给大家举个最简单的例子:

这里我们把pa指针变量的地址存放在ppa里,我们要想找到变量a的值,我们就要先对ppa解引用找到pa的地址,在对pa解引用,找到a的值。那么有二级指针有没有三级指针、四级指针、五级指针呢?答案是:有的兄弟有的,这里多级指针的概念和书写和二级指针是一样的,博主就不和大家一一赘述了,下面我们晋级到:进阶指针了,大家准备好了吗?

进阶指针

一、字符指针

最常见的方法就是先创建一个字符变量,再创建一个字符指针变量,然后把字符变量赋给字符指针变量,即以下这个代码。

int main()
{
	char ch = 'w';
	char* pc = &ch;
	printf("%p\n", pc);
	return 0;
}

而还有一种办法是创建一个字符数组,把字符串的首元素地址传给字符指针,即下面代码               但是要注意的是这里pc存的是首元素字符a的地址。

int main()
{
	char* pc = "abcdef";
	printf("%p", pc);
}

二、指针数组

1.指针数组的概念

我们可以类比整型数组和字符数组来对指针数组进行推导,整型数组里存放的都是整型,字符数组里存放的都是字符,那么指针数组里存放的就都是指针咯?是的,指针数组本质是数组,数组里存放的都是指针类型,那么指针数组应该怎样写呢?我给大家举个例子:

int main()
{
	int a = 10;
	int b = 20;
	int c = 30;
	//int* pa = &a;
	//int* pb = &b;
	//int* pc = &c;
    int* arr[] = { &a,&b,&c };//int*代表arr存放的是指针
	return 0;
}

当我们定义了很多整型类型的数据时,我们需要一个指针一个指针的去存放它的地址,这样会造成代码冗余(重复的代码),为了避免这种情况,我们可以创建指针数组,int*代表arr存放的是指针。

2.指针数组的使用及注意事项

我们可以利用指针数组来实现一个二维数组的打印,具体要怎么实现呢?我给大家写了个非常简单的代码,供大家参考

int main()
{
	int arr1[] = { 1,2,3,4 };
	int arr2[] = { 2,3,4,5 };
	int arr3[] = { 3,4,5,6 };
	int* arr[] = { arr1,arr2,arr3 };//arr就是指针数组
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 4 ; j++)
		{
			printf("%d ", arr[i][j]);//i来找哪一行,j来找具体哪个元素
		}
		printf("\n");
	}
	return 0;
}

这里我创建了三个整型数组,然后创建一个指针数组,把三个整型的数组名传进去,这里的数组名就是各个数组的首元素地址了,因为我们定义了三个整型数组,所以要实现二维数组的每一行的循环就要循环三次,所以i<3,要找到每一行(一维数组)的每个元素,我们就定义j<4,循环4次即可,这里大家一定要注意听我下面讲的!!!   

                                                        !!!重中之重   !!!    

 这里大家思考一下arr[i]到底表示什么?i是我们要找哪一行,所以这里arr[i]代表的就是每一行的地址,例如arr[0]就是arr1也就是1的地址,那么arr[0][0]就是arr1的第一个元素数字1了,大家可以看下打印出来的结果参考一下

这样我们就实现了利用指针数组模拟二维数组的打印。下面我给大家出一道题来考一下大家

int main()
{
	char str1[] = "hello.";
	char str2[] = "hello.";

	const char* str3 = "hello.";
	const char* str4 = "hello.";

	if (str1 == str2)
		printf("str1 and str2 are same\n");        //1
	else
		printf("str1 and str2 are not same\n");    //2

	if(str3==str4)
		printf("str3 and str4 are same\n");        //3
	else
		printf("str3 and str4 are not same\n");    //4

	return 0;
}

我把打印分为了1234,大家可以先看一下代码,来思考一下它打印是什么?                                    这里str1和str2分别是两个数组的数组名,代表的分别是各自数组首元素地址,而str3和str4是指针数组,他们两个所指向的都是首元素h的地址,这里我们分析好题以后就可以看判断条件了,首先str1==str2,这里把数组名作为判断条件,数组名是首元素的地址,他们两个分别存放的是各自数组的首元素地址,比的是两个数组首元素地址而不是数组内容,所以判断不成立,执行else即这里的编号2,接着往下判断str3==str4,这里str3指向的是h的地址,而str4指向的也是h的地址,所以这里判断成立执行if的printf,即编号3.                                                                                                到这里,我相信大家对指针数组的陷阱已经清楚了,接下来,我们要来更难的了。

三、数组指针

1.数组指针的概念

我们同样可以用类比的方法来对数组指针进行推导,整型指针:指向整型的指针,那么数组指针就是指向数组的指针,那么数组指针应该怎么去写呢?                                                                         首先数组指针的本质是一个指针,里面存放的是数组,所以我们就可以自定义*p来表示一个指针对吧?那么数组指针就是*p[  ]来表示一个指针指向数组这样写对吧?答案是:NO!而正确的写法是(*p)[  ],而*p[  ]所表示的是p先于[  ]结合,这里p就成为了数组名了,所以我们就要先括号让*和p先结合形成指针,再加上[  ],这就是数组指针的正确写法,但这还没有结束,如果这个数组指针指向的数组里存放的都是整型的话,我们就要在前面加上int ,完整的写法就是:int (*p)[  ],这里[  ]里面就是数组的元素个数,我给大家举两个例子看一下 (第一个是整形数组指针,第二个是字符数组指针和数组(字符指针类型)指针)

int main()
{
	int arr[10] = { 0 };
	//arr;//首元素地址
	//&arr[0];
	int(*p)[10] = &arr;//取出的是整个数组的地址--数组的地址
	                   //存放的是数组的地址
	                   //p就是数组指针变量,p指向的就是数组arr
	return 0;
}
int main()
{
	//char arr[5];
	//char(*p)[5]=&arr;

	char* arr[5];
	char*(*p)[5] = &arr;
	return 0;
}

如过这里数组的类型是char*类型,我们就在前面写char*就可以了。

2.数组地址(&arr)和数组首元素地址(arr)区别

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

	printf("%p\n", &arr);    //取出数组的地址
	printf("%p\n", arr);     //取出数组首元素的地址
	printf("%p\n", &arr[0]); //取出数组首元素的地址
	return 0;
}

我们再来看打印结果,我们发现数组的地址和数组首元素地址相同,那么数组的地址就是数组首元素地址咯?那我们分别进行+1算数运算,来看一下他们分别跳过多少字节

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

	printf("&arr+1   =%p\n", &arr+1);    //取出数组+1的地址
	printf("arr+1    =%p\n", arr+1);     //取出数组首元素+1的地址
	printf("&arr[0]+1=%p\n", &arr[0]+1); //取出数组首元素+1的地址
	return 0;
}

这时候我们发现数组的地址+1的地址不等于数组首元素+1的地址,他们之间相差的数字是什么呢?我们看打印结果十六进制数A0和7C相差了十进制数的36,一个整型的是4个字节,这里相差了9个整型,就相当于数组的地址+1,跳过了整个数组。结果是这样的吗?还真是,结论:数组的地址+1跳过的就是整个数组

四、函数指针

1.函数指针的定义

函数指针的本质还是一个指针,它是指向函数的指针,函数指针的类型由函数的返回值类型和函数参数组成,可以定义如下: 

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

给大家举个例子就明白了

int test(int x,int y)
{
    return x+y;
}
int mian()
{
    int (*pt)(int,int)=&test;
    int a=10;
    int b=20;
    int c=(*pt)(a,b);
    printf("%d",c); 
    return 0;
}
2.函数指针的示例与拓展

这里就利用函数指针,把test()函数存放在函数指针里。                                                                我们再来打印一下函数的地址看一下&testtest一样吗?

我们可以看到这里打印函数的地址时,使用&函数名和函数名打印出来的地址是一模一样的            所以我们继续推导更多的写法        

int test(int x,int y)
{
    return x+y;
}
int main()
{
    int (*pt)(int,int)=&test;
    int a=10;
    int b=20;
    
    int test1=(*pt)(a,b);//使用函数指针调用函数
    int test2=test(a,b); //使用函数名调用函数
    int test3=pt(a,b);   //pt==&test那么pt(a,b)打印结果也是一样的
    
    printf("%d\n",test1); 
    printf("%d\n",test2); 
    printf("%d\n",test3); 
    return 0;
}

这里可以看到打印结果都是一样的,所以不管用以上哪种方法都是一样的,注意:这里pt(a,b)与(*pt)(a,b)都是一样的,加不加解引用都是可以的

五、函数指针数组

1.函数指针数组定义

函数指针数组是存放函数指针的数组,它的写法是这样的

函数返回值类型 ( *指针变量名[  ] )(函数参数类型)[  ]代表数组的个数(函数的个数)

给大家举个例子:

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(*p)(int, int) = &add;//p是函数指针变量
	
	//函数指针数组--存放函数指针的数组
	int(*p[4])(int, int) = { add,sub,mul,div };
	for (int i = 0; i < 4; i++)
	{
		int r = p[i](9,3);
		printf("%d\n", r);
	}
	return 0;
}

这里我定义了四个函数分别是add,sub,mul,div分别表示加减乘除,那如果我们想要创建函数指针,就要创建四个函数指针,这样就会造成代码的冗余,所以我们可以设置一个函数指针数组,数组里存放这四个函数指针,函数指针数组与函数指针的写法非常相似,只需要在指针变量名之后加上[  ]即可,这里方括号里是函数的个数,然后我们用for循环进行加减乘除,这里p[i](参数,参数),i表示的就是add,sub,mul,div,里面的参数就是自己要进行加减乘除的数字,打印出来的结果大家可以看一下:

2.使用函数指针数组实现计算器功能

9与3的加减乘除的结果就可以打印出来了了,那我们是不是可以完成一个计算器功能的程序呢?我们可以设置一个目录,输入1代表算加法,输入2算减法,输入3算乘法,输入4算除法,输入0就退出计算器,输入这些数字之外的数字我们就可以提醒用户输入错误,请重新输入,这时候就要用到do while循环了,这时候就可以把加减乘除函数的函数指针(地址)放进函数指针数组里,我们都知道数组的下标是从0开始的,但我们输入1要进行加法,这时候我们可以在函数指针数组第一个元素命名为NULL,这时候我们输入的1就和加法对应上了,然后用户输入两个操作数,输入他要进行的算数运算,我们在定义一个变量来接受函数返回的值,就要用到函数指针数组了,这时候我们的计算器就实现了,我们再看一下他具体的功能实现:

//计算器
void menu()
{
	printf("************************\n");
	printf("****** 1.add  2.sub ****\n");
	printf("****** 3.mul  4.div ****\n");
	printf("******    0.exit    ****\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;
	int x = 0;
	int y = 0;
	do {
		menu();
		printf("请选择\n");
		scanf("%d", &input);
		if (input == 0)
		{
			printf("退出计算器\n");
		}
		else if (input > 0 && input <= 4)
		{
			int (*p[5])(int, int) = { NULL,add,sub,mul,div };
			//                        0    1   2   3   4
			printf("请选择两个操作数\n");
			scanf("%d%d", &x, &y);
			int r = p[input](x, y);
			printf("%d\n", r);
		}
		else
		{
			printf("输入错误,请重新选择\n");
		}
	} while (input);
	return 0;
}

六、回调函数

1.回调函数的定义

回调函数是将一个函数的函数指针类型作为参数实现那个函数的函数,这里大家可能不太懂什么意思,我给大家写个最简单的回调函数的代码来解释一下:

int add(int x, int y)//回调函数
{
	return x + y;
}

void test(int (*p)(int, int))
{
	int r =p(3,5);
	printf("%d", r);
}

int main()
{
	test(add);
	return 0;
}

这里我们定义了一个add()函数来实现两个数相加的功能,然后又定义了一个test()函数,  test()函数里的参数是add()函数的函数指针类型,test()函数里要实现的就是传过去两个参数,实现add函数的功能,最后打印出来,在main()函数里只需要写出test(add)即可,这里的add()函数就被称为会回调函数。

2.回调函数实现计算器的功能
void menu()
{
	printf("************************\n");
	printf("****** 1.add  2.sub ****\n");
	printf("****** 3.mul  4.div ****\n");
	printf("******    0.exit    ****\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 Cal(int (*p)(int,int))
{
	int x = 0;
	int y = 0;
	int r = 0;
	printf("请输入两个操作数\n");
	scanf("%d%d", &x, &y);
	r = p(x, y);
	printf("%d\n", r);
}
int main()
{
	int input=0;
	int x = 0;
	int y = 0;
	do {
		menu();
		printf("请选择\n");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			Cal(add);
			break;
		case 2:
			Cal(sub);
			break;
		case 3:
			Cal(mul);
			break;
		case 4:
			Cal(div);
			break;
		case 0:
			printf("退出计算器\n");
			break;
		default:
			printf("选择错误,请重新选择\n");
			break;
		}
	} while (input);
	return 0;
}

这里的目录和do while循环和上文是一样的,我就给大家解析一下回调函数部分吧,大家注意听哦 这里我们定义了一个Cal函数,它的目的就是打印出用户输入两个数字加减乘除的结果,所以Cal函数的参数就加减乘除函数指针,main()函数里也只需要写出Cal(add/sub/mul/div)根据用户的需要我们可以运用到switch语句,来进行开关操作,case1:Cal(add)即可,后面也是一样的,这里是不是也非常简单啊?

七、qsort函数

1.qsort函数定义及参数的意义

qsort函数原名是quickly sort,快速排序的意思,它的功能就是快速排序,但是你可别小瞧它哦,我们都学过冒泡排序,我们都知道冒泡排序只能排序整型类型的数据,而qsort函数可以排序任意类型的数据。     

qsort参数都有什么?每个参数的意义又都是什么呢?

void qsort(void* base,//是指针,指向了被排序数组的第一个元素
                 size_t num,//base指向的被排序数组的元素个数
                 size_t size,//base指向的被排序数组的元素个数的大小(长度),单位是字节
                 int (*compar)(const void*,const void*)//函数指针,指向的函数是用来比较被排序   数组的两个元素的

注意:qsort函数的头文件是#include<stdlib.h>

这里的第一个参数base就是数组名(首元素地址),因为我们可以利用qsort函数排序任意类型的数据,所以我们这里就用void*来作为参数,来表示它是一个指针类型,

第二个参数是这个数组里元素的个数类型是size_t类型     

 第三个参数是base数组里元素的字节数类型也是size_t类型                                                           

第四个参数是一个函数指针,这个函数指针里的参数是两个void*类型代表是两个void*指针,而这个函数指针要指向的函数是这个数组里要排序的两个元素的比较。

2.qsort函数排序整型数组示例
#include <stdio.h>
#include <stdlib.h>
int com_int(const void* p1, const void* p2)
{
	return *(int*)p1 - *(int*)p2;
}

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

//测试qsort函数,排序整型数组
void test()
{
	int arr[10] = { 6,5,4,7,8,3,9,2,1,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	print_arr(arr, sz);
	//排序
	//使用qsort函数的程序员,就得自己写一个比较函数,来比较两个整形数据的的大小
	qsort(arr, sz, sizeof(arr[0]), com_int);
	print_arr(arr, sz);
}

qsor的第四个参数的注意事项:返回值为两数相减的值,因为比较函数里的参数是void*类型,首先我们先要强制转换为int*类型,为了我们后续方便,此时(int*)p就是第一个元素的地址了,我们再对(int*)p进行解引用就得到了这个元素,第二个数也是同样的操作,这样两个数相减就得到了一个整型,那么为什么要返回一个整型的数呢?因为qsort的返回值大于1就代表比较函数里的第一个参数的值大于第二个参数,如果返回值小于1就代表比较函数里的第一个参数的值小于第二个参数的值,如果返回值等于0就代表,两个参数的值是相等的。

这里我们首先要有一个数组,我们就定义这个数组是整型数组arr,因为qsort函数有4个参数,所以我们把这四个参数全部找出来,首先计算出元素个数得到qsort第二个参数,然后计算数组里的元素占多少字节,整型类型都是4个字节,这样我们得到了qsort的第三个参数,最后一个参数需要我们再写一个比较函数,我们就把这个比较函数定义为com_int,比较函数com_int里的两个参数是const void *类型,代表不可改的指针类型,比较函数com_int直接返回两个数相减的值,首先强制类型转换然后再解引用得到两个元素,我们再做减法即可,这里我们要求数组升序排列,所以就用   *(int*)p1 - *(int*)p2如果返回值大于1,代表前面的数字比后面的大,就要进行交换,把小的数字拍到前面。那如果我们要进行降序呢?很简单*(int*)p2 - *(int*)p1即可,如果返回值大于1,代表后面的数字比前面的数字大,就要进行交换,把大的数字排到前面。

3.qsort函数排序结构体示例
struct stu
{
	char name[20];
	int age;
};

//字符串比较大小不是比较长度,而是比较对应字符的ASCALL值大小
int cmp_arr_by_name(const void* p1, const void* p2)
{
	return strcmp(((struct stu*)p1)->name, ((struct stu*)p2)->name);
}

void test2()
{
	struct stu arr[] = { {"zhangsan",12},{"wangwu",18},{"lisi",10} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	//qsort(arr, sz, sizeof(arr[0]), cmp_arr_by_age);
	qsort(arr, sz, sizeof(arr[0]), cmp_arr_by_name);
}

这里先创建一个结构体,创建一个结构体数组,里面放三个人的姓名和年龄,注意这里:我们比较函数里在比较字符串的时候,字符串比较大小不是比较长度,而是比较对应字符的ASCALL值大小,这里因为是结构体指针,所以就要((struct stu*)p1),这里就得到结构体了,因为我们要排序的是结构体的姓名,所以就要用到->来指向要排序结构体的哪个成员信息,而字符串比较大小不是比较长度,而是比较对应字符的ASCALL值大,这里比较字符串就要用到strcmp操作符,它是怎么用的呢?

strcmp(字符串,字符串)==0   //代表两个字符串相等

总结

总的来说指针基本上就这些内容了,大家可以反复观看,如果有什么不懂的地方欢迎大家在评论区留言,博主看到都会回复的,大家如果看到评论区的问题也可以相互帮助解答一下,最后,博客制作不易,请大家动动发财的小手给博主点个赞,你们的认可是我最大的动力,最后祝大家在C语言这门课程中进步,希望大家可以和我一起进步!!!我们共同进步!!!                                        如果这篇文章对你有用的话,希望大家给我一个三连支持一下叭[玫瑰] 

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值