【C语言】指针进阶---指针你真的学懂了吗?

🧸🧸🧸大家好,我是猪皮兄弟🧸🧸🧸

在这里插入图片描述

这里是下面要讲的知识内容🥳🥳🥳

🚒前言

    今天我们学习的内容是指针进阶。


一、🚀知识点分类

今天的指针进阶包括字符指针,数组指针,指针数组,数组传参和指针传参,函数指针,函数指针数组,指向函数指针数组的指针,回调函数等等。


二、🚀传参

1.⛄一维数组传参

//形参写成数组的形式和指针的形式都是可以的
//这里不会创建真实的数组,所以这里的大小写成多少都没有必要
//因为传过去的仅仅是数组的首元素地址
void test1(int arr[10]);
void test1(int arr[]);
void test1(int *arr);
void test2(int**arr);
void test2(int*arr[]);
int main()
{
	int arr1[10]={0};
	int*arr2[20]={0};
	test1(arr1);
	test2(arr2);
	return 0;
}

2.⛄二维数组的传参

二维数组的首元素代表的其实是第一行的地址

1.形参写成数组的形式
test(int arr[3][5]);因为传的是第一行的地址,所以这里的3也是没有意义的,只需要列就可以了(列是不能省略的)
2.形参写成指针
void test(int (*p)[5]);
这是数组指针,*p代表它是指针,[5]代表这个数组有五个元素,int 代表每个元素的类型是int

千万不能写成void test(int(*p)[3][5]);这表示的是整个二维数组,而传参只需要第一行的地址

三、🚀各类指针及数组

1.⛄数组名的特殊例子

数组名一般表示首元素地址,但是有两个例外
1.sizeof(数组名):sizeof里面跟一个单独的数组名计算的是整个数组的大小。
2.&数组名:&后面跟一个单独的数组名取出的是整个数组的地址

2.⛄函数指针

函数指针–>指向函数的指针
函数指针的形式:
比如函数
int Add(int x,int y)
那么函数指针就是
int (*pf)(int,int);

int Add(int x,int y)
{
	return x+y;
}
int test(char*str)
{

}
int main()
{
	int arr[10];
	int(*p)[10]=&arr;
	printf("%p\n",&Add);
	printf("%p\n",Add);//可以说明Add和&Add的地址是相同的
	//写法不同,但意义一样,得到的都是函数的地址
	int(*pf)(int,int)=Add;
	//等同于
    //int(*pf)(int,int)=&Add;
    
    int ret=(*pf)(2,3);
    //int ret=(********pf)(2,3);
    //int ret=Add(2,3);
	
	//这里的pf就是函数指针
	int(*pf2)(char*)=test;//&test
	printf("%d\n",(*pf)(2,3));
	printf("%d\n",ret);
	return 0;
}

对函数指针例子的解释

int main()
{
//1.
	(*(void(*)())0)();
	//void(*)()就是某一个函数指针的类型
	//(void(*)())0也就是把0强制类型转换成这样的函数指针
	//意味着0地址处放着这样一个函数,哪个函数的地址为0(这里知识假设访问0这个地址)
	//应用程序是不能访问0这个地址的
	//和int (*pf)(int,int)=Add;类比,也就是在pf处放了一个函数
	//然后对这个地址解引用*(void(*)())0找到这个函数,再调用*(void(*)())0()
	
//2.
	void (*signal(int, void(*)(int)))(int);
	//void(*)(int)是signal的第二个参数,是一个函数指针
	//signal函数的放回类型是一个函数指针
}

3.⛄函数指针数组

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()
{
	//指针数组
	//字符指针数组
	char*arr[5];
	//整型指针数组
	int*arr2[4];
	int(*pf1)(int,int)=Add;
	int(*pf2)(int,int)=Sub;
	int(*pf3)(int,int)=Mul;
	int(*pf4)(int,int)=Div;
	//函数指针数组
	int (*pf[4])(int,int)={Add,Sub,Mul,Div};
	printf("%d",pf[0](1,2));
	return 0;
	//先是pf[4]代表是个数组,大小为4
	//每个元素的类型是int (*)(int,int);
}

4.⛄函数指针数组实现运算器

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    ***\n");
	printf("***     2.Sub    ***\n");
	printf("***     3.Mul    ***\n");
	printf("***     4.Div    ***\n");
	printf("***     0.Exit   ***\n");
}
//实现计算器
int main()
{
	int input;
	int x,y;
	int ret;
	int (*pf[5])(int,int) = (0,Add,Sub,Mul,Div);
	//这样子初始化是因为数组是从0开始的
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d\n",&input);
		if(input==0)
		{
			printf("退出计算器.....\n");
			break;
		}
		else if(input>=1&&input<=4)
		{
			printf("请输入两个操作数:>");
			scanf("%d %d",&x,&y);
			ret=pf[input](x,y);
			printf("ret=%d\n",ret);
		}
		else 
		{
			printf("选择错误\n");
		}
	}while(input);
	return 0}

三、🚀回调函数与qsort函数

1.⛄回调函数

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

void test()
{
	printf("zhupi\n");
}
void print_zhupi(void(*pf)())
{
	if(某个条件)//如果某个条件或时间发生,则响应
	pf();
}
int main()
{
	print_zhupi(test);
	return 0;
}

用回调函数完成计算器

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,y;
	int ret=0;
	printf("请输入两个操作数:>");
	scanf("%d %d",&x,&y);
	ret=pf(x,y);
	printf("%d\n",ret);
}
void menu()
{
	printf("********************\n");
	printf("***     1.Add    ***\n");
	printf("***     2.Sub    ***\n");
	printf("***     3.Mul    ***\n");
	printf("***     4.Div    ***\n");
	printf("***     0.Exit   ***\n");
}
//实现计算器
int main()
{
	int input;
	int x,y;
	int ret;
	int (*pf[5])(int,int) = (0,Add,Sub,Mul,Div);
	//这样子初始化是因为数组是从0开始的
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d\n",&input);
		switch(input)
		{
		case 1:
			calc(Add);
			break;
		case 2:
			calc(Sub);
			break;
		case 3:
			calc(Mul);
			break;
		case 1:
			calc(Div);
			break;
		case 0:
			exit(-1);
			
		}
		
	return 0}

2.⛄qsort函数

qsort函数是一个库函数,是基于快速排序算法实现的一个排序的函数
qsort需要引的头文件是#include <stdlib.h>
qsort这个函数任意类型的数据它都可以排序
我们自己写的函数不一定能够做到

比如我们的冒泡排序

void bubble_sort(int arr[],int len)
{
	for(int i=0;i<len-1;i++)
	{
		for(int j=0;j<len-1-i;j++)
		{
			if(arr[j]>arr[j+1])
			{
				int temp=arr[j];
				arr[j]=arr[j+1];
				arr[j+1]=temp;
			}
		}
	}
}
int main()
{
	int arr[]={9,8,7,6,5,4,3,2,1,0};
	int len=sizeof(arr)/sizeof(arr[0]);
	bubble_sort(arr,len);
	for(int i=0;i<len;i++)
	{
		printf("%d ",arr[i]);
	}
	return 0;
}
//可以看出,并不具有通用性

qsort:
在这里插入图片描述
qsort里自己定义的排序函数需要返回>0 <0 =0的数字,得到结果之后qsort就可以根据返回的数进行排序
现在对qsort的参数进行分析

void qsort(void*base,
			size_t num,
			size_t width,
			int(*cmmp)(const void*e1,const void*e2));
//base是待排序数据的其实位置
//num是数组的元素个数
//width是每个元素的大小
//elem1 elem2是待比较的两个元素的地址
//这个比较函数是要求qsort函数的使用者自定义一个比较函数
//因为元素类型不同,使用者根据实际情况,提供一个比较函数

举例=>

int cmp_int(const void*e1,const void*e2)
{
	//void*是无确切类型的指针,无法直接解引用
	//因为指针的类型就代表着解引用时能访问几个字节的权限,而void*是不明确的;

	//void*是一个垃圾桶,什么类型都能往里放,到时候强制类型转换就可以了;

//1.
	if(*(int*)e1>*(int*)e2)
	{
		return 1;
	}
	else if(*(int*)e1<*(int*)e2)
	{
		return -1;
	}
	else
	{
		return 0;
	}
//2.
	return (*(int*)e1-*(int*)e2;
}
void test()
{
	int arr[]={9,8,7,6,5,4,3,2,1,0};
	int len=sizeof(arr)/sizeof(arr[0]);
	qsort(arr,len,sizeof(arr[0]),cmp_int);
	for(int i=0;i<10;i++)
	{
		printf("%d ",arr[i]);
	}
}
int main()
{
	test();
	return 0;
}

使用qsort排序结构体

struct Stu
{
	char mame[20];
	int age;
	double score;

};
int cmp_stu_by_name(const void*e1,const void*e2)
{
	return strcmp(((struct Stu*)e1)->mame, ((struct Stu*)e2)->mame);
}
void test()
{
	struct Stu arr[3] = { {"zhangsan",20,55.5},{"lisi",30,88.0},{"wangwu",10,90.0} };
	int sz = sizeof(arr) / sizeof(arr[0]);

	qsort(arr,sz,sizeof(arr[0]),cmp_stu_by_name);

}

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

3.⛄通用的冒泡排序

int cmp_int(const void* e1, const void* e2)//比较什么自己决定
{
	return *(int*)e1 - *(int*)e2;
}
void Swap(char*buf1,char*buf2,int width)
{
	for (int i = 0; i < width; i++)
	{
		char temp = *buf1;
		*buf1 = *buf2;
		*buf2 = temp;
		buf1++;
		buf2++;
	}
	//一个字节一个字节的交换
}

void bubble_sort(void* base, size_t num, size_t width, int (*cmp)(const void* e1, const void* e2))
{
	for (int i = 0; i < num - 1; i++)
	{
		for (int j = 0; j < num - 1 - i; j++)
		{
			//比较,因为知道每个元素的宽度,所以强制类型转换成char*
			if (cmp((char*)base+j*width,(char*)base+(j+1)*width)>0)
			{
				//交换
				Swap((char*)base + j * width, (char*)base + (j + 1) * width,width);
			}
		}
	}
}
void print_arr(int * base, int sz)
{
	for (int i = 0; i < sz; i++)
	{
		printf("%d ",*(base+i));
	}
}
void test2()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
	print_arr(arr,sz);

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

四、🚀各种sizeof和strlen的计算!!!

1.⛄一维数组

int main()
{
	int a[] = { 1,2,3,4 };
	printf("%d\n",sizeof(a));//16
	printf("%d\n",sizeof(a+0));//4/8,括号里的不是单独的数组名,所以表示的是首元素的地址,地址也就是指针
	printf("%d\n",sizeof(*a));//4  不是单独的数组名,所以a代表首元素地址,解引用,就是第一个元素
	printf("%d\n",sizeof(a+1));//4/8  计算的是第二个元素的地址的大小
	printf("%d\n",sizeof(a[1]));//4  计算的是第二个元素的大小
	printf("%d\n",sizeof(&a));//4/8  &a取出的 是数组的地址,数组的地址也是地址,是地址(指针)大小就是4/8个字节
	printf("%d\n",sizeof(*&a));//16 &a取出的是整个数组的地址,再解引用得到的就是整个数组,所以计算的是整个数组的大小
	printf("%d\n",sizeof(&a+1));//4/8  &a拿到的是整个数组的地址,+1是跳过整个数组,但是也是地址   
	printf("%d\n",sizeof(&a[0]));//4/8  代表的是第一个元素的地址
	printf("%d\n",sizeof(&a[0]+1));//4/8 代表的是第二个元素的地址
	return 0;
}

2.⛄字符数组

int main()
{
	char arr[] = {'a','b','c','d','e','f'};
	printf("%d\n",sizeof(arr));//6
	printf("%d\n",sizeof(arr+0));//4/8
	printf("%d\n",sizeof(*arr));//1
	printf("%d\n",sizeof(arr[1]));//1
	printf("%d\n",sizeof(&arr));//4/8
	printf("%d\n",sizeof(&arr+1));//4/8
	printf("%d\n",sizeof(&arr[0]+1));//4/8
	return 0;
}

int main()
{
	char arr[] = { 'a','b','c','d','e','f' };
	//strlen寻找结束表示\0
	printf("%d\n",strlen(arr));//随机值,arr数组中没有\0,所以strlen函数会继续往后找\0,统计\0之前出现的字符个数
	printf("%d\n",strlen(arr+0));//随机值
	printf("%d\n",strlen(*arr));//error  arr是数组首元素地址,*arr是数组的首元素,'a'-97,strlen把97当成一个地址,从这个地址出发
								//向后找\0,可能会出现野指针问题,访问内存出错的问题,所以这个代码本身上是错误的,并没有得到一个有效的地址
								//在指针变量的眼里,什么都是地址,因为他天生存在的意义就是存放地址
	printf("%d\n",strlen(arr[1]));//error
	printf("%d\n",strlen(&arr));//随机值,找\0
	printf("%d\n",strlen(&arr+1));//随机值
	printf("%d\n",strlen(&arr[0]+1));//随机值
	return 0;
}

int main()
{
	char arr[] = "abcdef";
	printf("%d\n",sizeof(arr));//7
	printf("%d\n",sizeof(arr+0));//4/8
	printf("%d\n",sizeof(*arr));//1
	printf("%d\n",sizeof(arr[1]));//1
	printf("%d\n",sizeof(&arr));//4/8
	printf("%d\n",sizeof(&arr+1));//4/8
	printf("%d\n",sizeof(&arr[0]+1));//4/8


	printf("%d\n", strlen(arr));//6
	printf("%d\n", strlen(arr + 0));//6
	printf("%d\n", strlen(*arr));//error
	printf("%d\n", strlen(arr[1]));//error
	printf("%d\n", strlen(&arr));//6
	printf("%d\n", strlen(&arr + 1));//随机值
	printf("%d\n", strlen(&arr[0] + 1));//5
	return 0;
}

int main()
{
	char* p = "abcdef";//常量字符串
	//p是'a'的地址
	printf("%d\n",sizeof(p));//4/8
	printf("%d\n",sizeof(p+1));//4/8  p+1是'b'的地址
	printf("%d\n",sizeof(*p));//18
	printf("%d\n",sizeof(p[0]));//1
	printf("%d\n",sizeof(&p));//4/8  &p是指针的地址,二重指针
	printf("%d\n",sizeof(&p+1));//4/8
	printf("%d\n",sizeof(&p[0]+1));//4/8

	printf("%d\n",strlen(p));//6
	printf("%d\n",strlen(p+1));//5
	printf("%d\n",strlen(*p));//error
	printf("%d\n",strlen(p[0]));//error
	printf("%d\n",strlen(&p));//随机值
	printf("%d\n",strlen(&p+1));//随机值
	printf("%d\n",strlen(&p[0]+1));//5
	return 0;
}

3.⛄二维数组

int main()
{
	int a[3][4] = { 0 };
	printf("%d\n",sizeof(a));//48   sizeof(数组名)计算的是整个数组的大小
	printf("%d\n",sizeof(a[0][0]));//4
	printf("%d\n",sizeof(a[0]));//16  a[0]是*(a+0)是第一行的首元素地址,也就是第一行的数组名,所以sizeof(a[0])其实计算的是第一行的大小
	printf("%d\n",sizeof(a[0]+1));//4/8  a[0]是第一行的数组名,a[0]+1也就是第一行的第二个元素的地址
	printf("%d\n",sizeof(*(a[0]+1)));//4 也就是a[0][1]
	printf("%d\n",sizeof(a+1));//4/8 二维数组的数组名代表的是第一行的地址,所以a+1代表的是第二行的地址
	printf("%d\n",sizeof(*(a+1)));//16 a+1是第二行的地址,解引用了之后就是第二行的首元素地址,所以计算的是第二行的大小
	printf("%d\n",sizeof(&a[0]+1));//4/8  代表的是第二行的地址
	printf("%d\n",sizeof(*(&a[0]+1)));//16 括号里表示的是第二行的首元素地址 也就是第二行的数组名,计算的是第二行数组的大小
	printf("%d\n",sizeof(*a));//16 计算的是第一行数组的地址
	printf("%d\n",sizeof(a[3]));//16 a[3]是第四行的首元素地址
	return 0;
}

、🚀

        当你看到这里,相信上面的内容已经倒背如流了吧😶‍🌫️😶‍🌫️😶‍🌫️。各位如果觉得有帮助的话,动动小手指👈👇👉点点点。一键三连有问题吗?没有问题,这都是什么?人情世故。创作不易,多多支持。在这里插入图片描述

  • 23
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 20
    评论
评论 20
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猪皮兄弟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值