C语言----指针进阶(万字详解)

前言

前段时间,学习了指针初阶的知识,这里再简单回顾下。
——————————————————————————-——

  1. 指针是个变量,用来存放地址,地址唯一指向一块内存空间(地址==指针)
  2. 指针的大小是固定的,4个字节(32位平台)或8个字节(64位平台),32位机器=32个地址线->0/1——32个0/1组成的二进制序列=32个地址,需要32个bit位即4个字节存储起来,64位同理
  3. 指针是有类型,指针的类型决定了指针的±整数的步长,指针解引用操作的时候的权限
  4. 指针的运算

1.字符指针

字符指针为char* 类型,一般的使用方法

int main()
{
    
    
	char u = 'a';
	char* p = &u;//p就是字符指针
	*p = 'b';
	return 0;
}

另一种使用方法:

char* p = "abcdef";

这里大家会误以为是把字符串放在指针变量p里,但事实不是这样。本质是把字符串的第一个字符的地址放到p里。“abcdef” 是常量字符串,常量字符串存放在代码区,是不能改变的,所以可以稍作调整,在char* 前面加const,限制* p ,这样就不会改变后面的字符串了。

我们可以验证下p存放的是不是地址:

    printf("%s\n", p);
	printf("%c", *p);

在这里插入图片描述
1.p是地址,从这个地址开始向后打印字符串
2.*p 解引用后得到的是首字符a

一道例题:

int main()
{
    
    
 char str1[] = "hello bit.";
 char str2[] = "hello bit.";
 const char *str3 = "hello bit.";
 const char *str4 = "hello bit.";
 if(str1 ==str2)
 printf("str1 and str2 are same\n");
 else
 printf("str1 and str2 are not same\n");
 
 if(str3 ==str4)
 printf("str3 and str4 are same\n");
 else
 printf("str3 and str4 are not same\n");
 
 return 0;
}

得到的结果是:
在这里插入图片描述
在这里插入图片描述
这里给大家来分析下:
首先创建了两个数组,一个是str1,另一个是str2,字符串hello bit.是用来初始化两个数组的,也就是把字符串hello bit.分别存放到str1和str2里面去,str1和str2是两个不同且独立的空间,并且我们知道数组名是首元素的地址,这两个首元素的地址不在一个空间里,所以他俩不相等。
就好比两个在不同位置或者说不同地址的房子,两个房子里都存放有一本相同的书,不能因为房子存放的某个东西相同就判定两个房子的地址相同。

接下来是两个字符指针,一个是str3,另一个是str4,这两个指针变量指向字符串hello bit.,存放的是首字符的h的地址,所以两者的地址是一样的。

2.指针数组

定义:指针数组是存放指针的数组

int* arr[10];//指针数组

指针数组模拟实现二维数组

int main()
{
    
    
	int arr1[] = {
    
     1,2,3,4,5 };
	int arr2[] = {
    
     2,3,4,5,6 };
	int arr3[] = {
    
     3,4,5,6,7 };
	int* arr[3] = {
    
     arr1,arr2,arr3 };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
    
    
		int j = 0;
		for (j = 0; j < 5; j++)
		{
    
    
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

打印出:
在这里插入图片描述
打印出来的结果看起来是二维数组,但它不是真正的二维数组,是用指针数组模拟出来的。指针数组里的每个元素是不同的数组,这3个数组是独立开辟出来的不同的空间,(也可以理解为这3个数组毫无相关)但是把这3个数组存进指针数组里存的是数组的首地址,然后指针数组arr 通过这3个数组的首地址向后打印其数组的内容。
在这里插入图片描述

3.数组指针

3.1数组指针的定义

数组指针是什么,我们可以类比一下
整型指针:指向整型变量的指针,存放整型变量的地址的指针变量
字符指针:指向字符变量的指针,存放字符变量的地址的指针变量
数组指针 -> 指向数组的指针,存放的是数组的地址的指针变量

int(*p)[10];

数组指针与指针数组有时候容易混乱,这里给大家捋一捋

    int* p1[10];//指针数组
	int(*p2)[10];//数组指针

[ ] 的优先级对于 * ,所以p1与[10]先结合,是数组,存入数组的每个元素是 int *;
(*p2)先执行 *,说明p2是指针,指向的是int [10],int [10]是数组,所以p2是数组指针变量

3.2 &数组名与数组名

数组名是数组首元素的地址
但是有两个例外:
sizeof(数组名)表示的是整个数组的大小,单位是字节;
&数组名表示的是整个数组,取出的是整个数组的地址。

	int arr[10] = {
    
     0 };//一个元素4个字节
	printf("%p\n", arr);
	printf("%p\n", &arr[0]);
	printf("%d\n", sizeof(arr));

可以发现arr与&arr[0]的地址相同
在这里插入图片描述
那么arr与&arr有什么联系呢?

	int arr[10] = {
    
     0 };
	printf("%p\n", arr);
	printf("%p\n", &arr[0]);
	printf("%p\n", &arr);

在这里插入图片描述
我们已经知道数组名是首元素的地址, &arr[0]也是,但&arr为什么也是首元素的地址呢。其实单单只打印值看不出区别,因为它们的值都是从首元素地址开始的,但是它们的类型不一样。
我们可以打开调试窗口:
在这里插入图片描述
arr的类型是int * 类型
&arr[0]的类型是int * 类型
&arr的类型是int (*)[10](数组指针类型)
不同的类型属性和功能是不一样的
看下面代码:

int arr[10] = {
    
     0 };
	printf("%p\n", arr);
	printf("%p\n", arr+1);
	printf("%p\n", &arr[0]);
	printf("%p\n", &arr[0]+1);
	printf("%p\n", &arr);
	printf("%p\n", &arr+1);

在这里插入图片描述
从中可以发现arr到arr+1差4个字节;&arr[0]到&arr[0]+1差4个字节;&arr到&arr+1差40个字节,也就是说&数组名+1跳过的是整个数组。
在这里插入图片描述
总结:
对于数组名来说,&数组名得到的是数组的地址,而不是数组首元素的地址
&arr的类型是数组指针类型,即int (*)[10],对数组指针解引用得到的是数组
&数组+1,跳过的是整个数组的大小

int main()
{
    
    
	int arr[10] = {
    
     1,2,3,4,5,6,7,8,9,10 };
	int(*p)[10] = &arr;//数组的地址,存储到数组指针变量
	return 0;
}

3.3数组指针的使用

打印数组里的元素:

int main()
{
    
    
	int arr[10] = {
    
     1,2,3,4,5,6,7,8,9,10 };
	int(*p)[10] = &arr;//p->&arr,*p->*&arr->arr
	int i = 0;
	for (i = 0; i < 10; i++)
	{
    
    
		printf("%d ", *((*p) + i));
		//printf("%d ", (*p)[i]);另一种写法
	}
	return 0;
}

但我们发现数组指针这样使用很变扭,所以数组指针不是这样使用的
数组指针一般使用在二维数组上
我们知道二维数组其实是一维数组的数组,数组名也是数组首元素的地址,即第一行的地址 = 一维数组的地址 = 数组的地址,数组的地址传过去应该用指针来接收,所以二维数组传参,形参可以是指针的形式。

void test(int(*p)[5], int r, int c)
{
    
    
	int i = 0;
	for (i = 0; i < 3; i++)
	{
    
    
		int j = 0;
		for (j = 0; j < 5; 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 };
	test(arr, 3, 5);
	return 0;
}

在这里插入图片描述
p指向的是第一行,当p+i时随着i的值的变化,p指向第二行、第三行,解引用p+i,相当于得到这一行数组的地址,再加j,表示这一行数组的某个元素的地址,最后的解引用,就打印这一行这一列的元素。
在这里插入图片描述
补充一下:
一维数组传参,形参的部分可以是数组,也可以是指针

void test1(int arr[5],int a)
{
    
    }
void test2(int* p, int a)
{
    
    }
int main()
{
    
    
	int arr[5] = {
    
     0 };
	test1(arr, 5);
	test2(arr, 5);
	return 0;
}

两种其实是一样的,但要注意的是传过去形式上是数组,本质是指针

二维数组传参,形参的部分可以是数组,也可以是指针

void test3(char arr[3][5], int r, int c)
{
    
    }
void test4(char (*p)[5], int r, int c)
{
    
    }
int main()
{
    
    
	char arr[3][5] = {
    
     0 };
	test3(arr, 3, 5);
	test4(arr, 3, 5);
	return 0;
}

4.数组参数、指针参数

4.1一维数组传参

#include <stdio.h>
1.void test(int arr[])//yes
{
    
    }
2.void test(int arr[10])//yes
{
    
    }
3.void test(int *arr)//yes
{
    
    }
4.void test2(int *arr[20])//yes
{
    
    }
5.void test2(int **arr)//yes
{
    
    }
int main()
{
    
    
 int arr[10] = {
    
    0};//每个元素是整型,即整数
 int *arr2[20] = {
    
    0};//每个元素是指针,即存放的是地址
 test(arr);
 test2(arr2);
}

1.数组传参,数组接收,正确。可省略数组的大小,因为传过去不会真实的创建数组,传的是地址。
2.还是数组数组传参,数组接收,正确。【】里的大小可自己定。
3.数组名是首元素的地址,传过去是指针,正确。
4.数组传参,数组接收,正确。
5.数组里的每个元素是一级指针,int *类型,传过去的是一级指针的地址,要用二级指针来接收,正确。

4.2二维数组传参

1.void test(int arr[3][5])//yes
{
    
    }
2.void test(int arr[][])//no
{
    
    }
3.void test(int arr[][5])//yes
{
    
    }
4.void test(int *arr)//no
{
    
    }
5.void test(int* arr[5])//no
{
    
    }
6.void test(int (*arr)[5])//yes
{
    
    }
7.void test(int **arr)//no
{
    
    }
int main()
{
    
    
 int arr[3][5] = {
    
    0};
 test(arr);
}

1.数组传参,数组接收,正确。
2.二维数组传参,形参部分行可以省略,列不能省略,错误。因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素,这样才方便运算。
3.正确。
4.传过去的是一行的地址,错误。
5.形参是指针数组,传过去那么是数组指针,那么是二维数组,所以错误。
6.形参是数组指针,正确。
7.数组名是首元素的地址,要用一级指针来接收;二级指针是来接收一级指针的地址,所以错误。

4.3一级指针传参

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]);
 print(p, sz);
 return 0;
}

一级指针传参,形参部分用一级指针来接收。通过一级指针找到数组的首元素的地址,然后解引用打印。

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

void test(char* p)
{
    
    }
int main()
{
    
    
	char ch = 'e';
	test(&ch);

	char* pa = &ch;
	test(pa);

	char arr[] = "abcd";
	test(arr);
	return 0;
}

只要传过去的类型与形参的类型是匹配的就OK,形参是一级指针,可以传ch的地址、char * 的指针、数组名(char类型)

4.4二级指针传参

void test(int** ptr)
{
    
    
 printf("num = %d\n", **ptr); 
}
int main()
{
    
    
 int n = 10;
 int*p = &n;
 int **pp = &p;
 test(pp);
 test(&p);
 return 0;
}

进入主函数,&n放在指针变量p里面(一级指针),然后再一级指针变量取地址,放在pp里面(二级指针)
&p—>形参是二级指针的形式
pp—>形参是二级指针的形式

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

void test(char** pa)
{
    
    }
int main()
{
    
    
	char n = 'a';
	char* p = &n;
	char** pp = &p;
	char* arr[5] = {
    
     0 };

	test(&p);
	test(pp);
	test(arr);
	return 0;
}

&p是一级指针的地址,传过去用二级指针来接收;pp(二级指针)传参,形参用二级指针来接收;arr是指针数组,数组名是首元素的地址,它的每个元素是char*,传的是char*的地址。

5.函数指针

函数指针是什么?
我们知道数组指针是指向数组的指针,那么函数指针就是指向函数的指针。
函数的地址怎么表示?

int add(int x, int y)
{
    
    
	return x + y;
}
int main()
{
    
    
	printf("%p\n", add);
	printf("%p\n", &add);
	return 0;
}

在这里插入图片描述
可以看出函数名是函数的地址,&函数名也是函数的地址。

如果我们要用一个变量把函数的地址存起来,假设用pf,那么pf的类型就是函数指针变量
写法:
先 *pf 说明pf是指针,用括号括起来,这个指针指向的是函数参数,函数参数用括号括起来(注意参数的类型),并且最左边要有函数的返回类型。

int add(int x, int y)
{
    
    
	return x + y;
}
int main()
{
    
    
	int arr[10] = {
    
     0 };
	int(*pa)[10] = &arr;//数组指针
	int (*pf)(int, int) = &add;//函数指针,pf是函数指针变量
	int (*)(int, int)//函数指针类型
	return 0;
}

函数指针间接访问调用函数:

int add(int x, int y)
{
    
    
	return x + y;
}
int main()
{
    
    
	int (*pf)(int, int) = &add;//&去掉也行
	int r = add(3, 4);
	printf("%d\n", r);
	int m = (*pf)(5, 6);
	int m = pf(5, 6);//另一种写法,注意加上*时一定要带上括号
	printf("%d\n", m);
	return 0;
}

在这里插入图片描述
两段有趣的代码:
一:

    (*(void (*)())0)();

分析:
第一眼看到这样的代码,感觉很头晕,我们可以先拆分下,这样看得更通俗易懂点
这里的0是整数,可以把它当成整型;void (*)()是函数指针类型,()里放类型,说明是强制类型转换,把0强制转换成函数指针类型,然后地址解引用调用0地址处的函数,本质上是函数调用。
在这里插入图片描述
二:

void (*signal(int , void(*)(int)))(int)

分析:
signal是函数声名,函数参数是int 类型和void(*)(int)函数指针类型,该函数指针指向一个int类型的参数,返回类型是void;signal函数的返回类型也是函数指针类型,指向的函数有一个int 类型的参数,返回类型是void。

这段代码可以进行简化,(用typedef对类型进行重命名)
typedef的使用,比如:

typedef unsigned int uint;
typedef int* ptr;

unsigned int—>uint
int* ---->ptr
这里注意一下,对函数指针或数组指针重命名,重命名的名字要放在括号里面(语法规定)

typedef int(*par)[10];
typedef int(*pfr)(int, int);

接下来简化这段代码:

typedef void(*pf_t)(int);
pf_t signal(int, pf_t);

把函数指针类型重命名为pf_t,signal的参数是int和pf_t,返回类型也是pf_t

6.函数指针数组

前面我们学习过整型指针数组和字符指针数组

int* arr1[5];//每个元素是整型指针类型
char* arr2[4];//每个元素是字符指针类型

那么函数指针数组就是数组的每个元素是函数指针类型

int (*pf1)(int, int) = add;
int (*pf2)(int, int) = mul;
int (*pf3)(int, int) = sub;
int (*pf4)(int, int) = div;
int (*parr[4])(int, int) = {
    
     add,mul,sub,div };//函数指针数组

6.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;
}
void menu()
{
    
    
	printf("1.add 2.sub 3.mul 4.div\n");
}
int main()
{
    
    
	int input = 0;
	int x = 0;
	int y = 0;
	int ret = 0;
	//函数指针数组-转移表
	int (*parr[5])(int, int) = {
    
     NULL,add,sub,mul,div };
	//                           0     1   2   3   4
	do
	{
    
    
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		if (input >= 1 && input <= 4)
		{
    
    
			printf("请输入两个操作数:");
			scanf("%d %d", &x, &y);
			ret = parr[input](x, y);
			printf("%d\n", ret);
		}
		else if (input == 0)
		{
    
    
			printf("退出计算器\n");
		}
		else
		{
    
    
			printf("选择错误\n");
		}

	} while (input);

	return 0;
}

使用函数指针数组可以简化代码,当要选择进入某个函数时不需要大量的case,防止代码冗余。

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

定义:指向函数指针数组的指针是一个 指针 指针指向一个 数组 ,数组的元素都是 函数指针

int (*pf)(int, int);//函数指针
int (*pfarr[4])(int, int);//函数指针数组
//&pfarr----->函数指针数组的地址
int (*(*p)[4])(int, int) = &pfarr;
//p就是指向函数指针数组的指针

在p的旁边加上 * ,用括号括起来,说明p是指针;指针指向的是数组,数组的每个元素是函数指针。

总结:
数组:数组名是数组首元素的地址;&数组名是整个数组的地址
函数:函数名是函数的地址;&函数名也是函数的地址

8.回调函数

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

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("1.add 2.sub 3.mul 4.div\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;
	int x = 0;
	int y = 0;
	int ret = 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;
}

在这里插入图片描述

8.1qsort函数的使用

qsort函数的特点:
1.快速排序的方法
2.适合于任意类型数据的排序

qsort函数的参数:
在这里插入图片描述

void qsort(void* base,//指向了需要排序的数组的第一个元素
    size_t num,//排序的元素个数
    size_t size,//一个元素的大小,单位是字节
    int (*compar)(const void*, const void*));
    //函数指针类型,指向的函数能够比较base指向数组中的两个元素

注意:
void * 的指针是无具体类型的指针,可以接收任意类型的地址,但不能直接解引用操作,也不能直接进行指针运算

比如:

 	int a = 10;
	float f = 3.12;
	int* pa = &a;//yes
	char* pc = &a;//no
	void* pv = &a;//yes
	pv = &f;//yes
	*pv;//no
	pv++;//no

qsort函数排序整型数据:

int cmp_int(const void* p1, const void* p2)
{
    
    
    //     强制类型转换为int*类型再解引用
	return (*(int*)p1 - *(int*)p2);//将p1与p2的位置互换是降序
}
void print(int arr[], int sz)
{
    
    
	int i = 0;
	for (i = 0; i < sz; i++)
	{
    
    
		printf("%d ", arr[i]);
	}
}
test1()
{
    
    
	int arr[10] = {
    
     3,5,6,2,1,9,7,8,4,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	//默认是升序
	qsort(arr, sz, sizeof(arr[0]), cmp_int);
	print(arr, sz);
}
int main()
{
    
    
	test1();
	return 0;
}

在这里插入图片描述
qsort函数排序结构体:
排序年龄:

struct stu
{
    
    
	char name[20];
	int age;
};
int cmp_stu_age(const void* p1, const void* p2)
{
    
    
	return ((struct stu*)p1)->age - ((struct stu*)p2)->age;
}
void test2()
{
    
    
	struct stu arr[] = {
    
     {
    
    "zhangsan",22},{
    
    "lisi",45},{
    
    "xiaoming",37} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_stu_age);
}
int main()
{
    
    
	test2();
	return 0;
}

在这里插入图片描述
排序名字:

struct stu
{
    
    
	char name[20];
	int age;
};
int cmp_stu_name(const void* p1, const void* p2)
{
    
    
	return strcmp(((struct stu*)p1)->name, ((struct stu*)p2)->name);
}
void test3()
{
    
    
	struct stu arr[] = {
    
     {
    
    "zhangsan",22},{
    
    "lisi",45},{
    
    "xiaoming",37} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_stu_name);
}
int main()
{
    
    
	test3();
	return 0;
}

注意:这里比较名字时,两个名字不能相减,因为名字是字符串,比较字符串要用strcmp函数
在这里插入图片描述

8.2qsort函数的模拟实现

使用冒泡排序的思想,实现一个功能类似qsort的函数
我们之前写过冒泡排序:

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 t = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = t;
			}
		}
	}
}

但如果还是按照这样写,难免有问题。

问题1:参数只能接收整型数组
解决方法:用void * 的指针,可以接收任意数据类型,同时传元素个数和一个元素的大小

问题2:对于不同类型的数据,整型比较大小可以用>、<、==,但是结构体比较大小就不一定了,如果是比较年龄,可以用前面的符号,如果是比较名字就不行了,也有可能是其他的比较方法
解决方法:参数的部分加上函数指针,将两个元素的比较方法,以函数参数的形式传递

排序整型数据:

void print(int* arr, int sz)
{
    
    
	int i = 0;
	for (i = 0; i < sz; i++)
	{
    
    
		printf("%d ", arr[i]);
	}
}
void swap(char* buf1, char* buf2, int size)
{
    
    
	int i = 0;
	char t = 0;
	for (i = 0; i < size; i++)
	{
    
    
		t = *buf1;
		*buf1 = *buf2;
		*buf2 = t;
		buf1++;
		buf2++;
	}
}
void bubble_sort(void* base, int num, int size, int (*cmp)(const void*, const void*))
{
    
    
	int i = 0;
	for (i = 0; i < num - 1; i++)
	{
    
    
		int j = 0;
		for (j = 0; j < num - 1 - i; 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 cmp_int(const void* p1, const void* p2)
{
    
    
	return (*(int*)p1 - *(int*)p2);
}
void test1()
{
    
    
	int arr[10] = {
    
     4,2,8,6,0,3,7,9,1,5 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr,sz,sizeof(arr[0]),cmp_int);
	print(arr, sz);
}
int main()
{
    
    
	test1();
	return 0;
}

在这里插入图片描述
分析:
一:bubble_sort函数传参
bubble_sort函数传过去第一个参数不能是int 类型,因为假如要传的数组是结构体的其他数据类型,就写死了,用void * 可以接收任意的数据类型,void * 后面的名字可以自定义;第二个是元素的个数;第三个是一个元素的大小,因为void * 只知道从哪开始,不知道传过来的是什么数据类型的元素;第四个是函数指针,把要传的两个元素的地址传过来(写成const void * 是因为都不知道要排序的是什么数据、元素是什么类型,所以用void * ;用const是因为仅仅比较两个元素,而不是改变元素)

二:cmp_int函数
cmp_int函数比较方法与前面同,通过这个函数内部来比较大小

三:bubble_sort函数内部
首先要确定趟数,和一趟内部比较的对数,两个元素比较要把arr[j]和arr[j+1]的地址传给cmp;假设是升序,cmp返回>0,,交换(如果不写>0,负数非0,为真,也进入条件)。
计算 j 和 j+1 的元素的地址:
在这里插入图片描述
四:swap函数----交换
交换的是判断条件两个指针所指向的元素,把它们作为参数传过去,还有一个元素的大小;因为参数是强转成char * 传过去的,所以swap函数参数是char * 和size。然后交换两个元素,一个元素是4个字节,每交换一个字节地址往后跳一个字节。
在这里插入图片描述

总结:
在这里插入图片描述

以上就是关于指针进阶的内容,如果有错误和不足,欢迎大家在评论区指出,感谢您的观看。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/2301_77459845/article/details/131574243