第七章、C语言指针全解(4)终章:混沌终焉!指针圣域的湮灭与重构!

第七章、C语言指针全解(4)终章:混沌终焉!指针圣域的湮灭与重构!

文章目录

各位blogmem,欢迎来到回调地狱的入口!这里是函数指针的暴走领域,是qsort的混沌祭坛------你们将见证动态逻辑的基因突变!握紧你们的比较函数,这是突破稳定代码的最后防线!

一、回调函数:时间线跳跃的暗码

回调函数不是普通调用,是向程序注入未来指令的时光胶囊!它允许你在运行时逆转因果链,让函数行为在事后被重新定义!

回调函数是代码的延时炸弹,触发时机由宿主函数全权掌控!

  • 回调函数就是⼀个通过函数指针调⽤函数。

如果你把函数的指针 (地址)作为参数传递给另⼀个函数 ,当这个指针被⽤来调⽤其所指向的函数 时,被调⽤的函数 就是回调函数回调函数不是被直接调⽤ ,⽽是在特定的事件或条件发⽣时由**另外的⼀⽅调⽤**的,⽤于对该事件或条件进⾏响应。

上面的概念多少有些生硬,接下来给个例子让大家清晰的进行理解:
函数指针转递方------ 中间调用方------被调函数方(回调函数) 之间的数据交互逻辑

这里可以用上篇博客的计算器进行回调函数改造:

c 复制代码
#include <stdio.h>
int add(int a, int b)
{
 return a + b;
}
int sub(int a, int b)
{
 return a - b;
}
int mul(int a, int b)
{
return a * b;
}
int div(int a, int b)
{
 return a / b;
}
//以上都为回调函数
void calc(int(*pf)(int, int))	//函数调用方
{
 int ret = 0;
 int x, y;
 printf("输⼊操作数:");
 scanf("%d %d", &x, &y);
 ret = pf(x, y);
 printf("ret = %d\n", ret);
}
int main()
{
  int input = 1;
  do
  {
	printf("*************************\n");
	printf(" 1:add                2:sub \n");
	printf(" 3:mul                 4:div \n");
	 printf("*************************\n");
 	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;
}

二、qsort:混沌数据的归一仪式

qsort不是排序,是以比较函数为祭品的维度规整!通过回调函数,你能定义任意数据的混沌度衡量法则!

qsort的第四个参数是向编译器递交的灵魂契约,错配类型将引发未定义行为的降维打击!

qsort是C语言中的一个库函数:

这个函数是用来给函数进行排序的 ,qsort底层使用的是快速排序的算法思想。(默认为升序排列
头文件为:stdlib.h

像之前我们学过冒泡排序 的排序方法,但只学过用在排列整型数组 的情况。

那当我们要排序字符数组、字符串、浮点数、结构体 时,很显然冒泡排序是行不通的,这时qsort的含金量就体现出来了。

qsort函数:

c 复制代码
void qsort(void * base, //指向待排数组的第一个元素的指针
			size_t num, //base指向数组中元素的个数
			size_t size, //base指向数组中一个元素的大小,单位是字节。
			int  (*cmp)(const void *, const void*)//函数指针 ------ 传递函数的地址
			);
  • 其中,第四个参数是用来比较 的函数指针,具体用来比较什么类型,用户需要自己设定
  • cmp函数中,第一个传参的值 > 第二个传参的值 时返回 >0的数字
  • cmp函数中,第一个传参的值 < 第二个传参的值 时返回 <0的数字
  • cmp函数 中,第一个传参的值 = 第二个传参的值 时返回 0.
    接下来测试qsort 排列整型数组:
c 复制代码
cmp_int(const void* p1,const void *p2) //此函数是用来比较元素的。
{
 	if(*{int *)p1 > *(int *)p2) //因为接收参数使用的时void *类型的指针,不能对其直接解引用,所以要对其强制类型转换再进行解引用操作。
 	return 1;
 	else if(*(int *)p1 < *(int *)p2)
 	return -1;
 	else 
 	return 0;
}

void Print(int arr[],size sz)
{
	for(int i = 0;i < sz;i ++)
	{
		printf("%d ",arr[i]);
	}
}
#include<stdio.h>
int main()
{
	int arr[] = {3,1,7,9,4,2,6,5,8,0};
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr,sz,sizeof(arr[0]),cmp_int);
	Print(arr,sz);
	return 0;
}

在继续测试qsort对结构体的排序之前,我们先回忆关于访问结构体内容的两种方法:

c 复制代码
struct Stu
{
	char name[20];
	int age;
}
int main()
{
	struct Stu s = {{"zhangsan"},{20}};
	struct 	Stu *ps = &s; //访问结构体内容要通过指针来进行。
}
  • 结构体成员的直接访问:结构体变量名 + .(点操作符)访问

这里也可以"多此一举"的用结构体指针变量 解引用再 + . 访问

c 复制代码
printf("%s %d",ps.name,ps.age);
printf("%s %d",(*ps).name,(*ps).age);
  • 结构体成员的间接访问:结构体指针->成员名
c 复制代码
printf("%s %d",ps->name,ps->age);

接下来再使用qsort排序结构数据

  • 结构体的大小比较就取决于,你要用里面具体哪种变量 来比较。比如以下的例子,按姓名(长度),或按年龄大小比较
  • 无论你想比较什么类型的数据,写cmp函数 时,形式参数 都要写成void* 类型,和qsort函数的设定保持一致。
  • 以下是按姓名比的,姓名是字符串,字符串比较是使用strcmp函数
  • strcmp 函数比较大小后的返回值和qsort函数中cmp要求的返回值相吻合
c 复制代码
#include<stdio.h>
#include<string.h>
cmp_stu_by_name(const void* p1,const void* p2)
{
	strcmp(((struct Stu*)p1)->name > ((struct Stu*)p2)->age) //先强制类型转换成结构体指针再指向成员名。
}
#include<stdio.h>
struct Stu //学生
{
	char	name[20] ; // 姓名
	int age; //年龄
};
int  main
{
	struct Stu arr[] = { {"zhangsan",20},{("lisi",30},{"wangwu",18}};//定义结构体变量
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr,sz,sizeof(arr[0]),cmp_stu_by_name)
	
}

按年龄来比时,cmp函数的写法:

c 复制代码
cmp_stu_by_age(const void*p1,const void* p2)
{
	return = ((struct Stu*)p1) -> age - ((struct Stu*)p2) -> age;
	//这里年龄相减的正负0恰好和他们的大小比较所对应的返回值相符。 
}

qsort 的降序排列:

由于qsort内置默认为升序 排列,所以要想让它的作用效果为降序 ,那只能更改cmp函数的逻辑

这里以上面的cmp代码为例,要想降序该这样修改:

c 复制代码
cmp_stu_by_age(const void*p1,const void* p2)
{
	return = ((struct Stu*)p2) -> age - ((struct Stu*)p1) -> age;
}

三、冒泡排序模拟qsort:自制维度规整引擎

手写泛型排序?这是在内存荒漠中徒手搭建巴别塔!需要精准操控每一个字节的量子态!

接下来我们将bubble_sort改造成通用的排序算法,可以排任意类型的数据:

思考逻辑:

1、由于我们不知道将来要给什么样类型的数组进行排序,所以接收数组的形参类型为void*。

c 复制代码
void Swap(char* buf1,char* buf2,size_t width)
{
	//这里并不知道数组里的类型是什么样的,所以并不能强制类型转换来比较,否则这个函数并不通用。
	//所以我们采取一个字节一个字节的比较:
	//这里传入了width 我们就可以将width个字节为一循环比较:
	char tmp = 0;
	for(int i = 0; i < width;i++)
	{
		tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1 ++;//向后推进指针直到该元素被置换完成
		buf2 ++;//
	}
	
	
}
void bubble_sort(void* base,size_t sz,size_t width,int (*cmp)(const void*p1,const void* p2))
{
	//趟数:
	for(int i = 0; i < sz - 1; i ++)
	{
		//一趟内部的两两比较:
		for(int j = 0;j < sz - i - 1;j ++)
		{	
			//比较两个元素:
			// if(arr[j]>arr[j+1])
			 if(cmp((char*)base + j*width,(char*)base + (j + 1)*width)>0)
			 {
			 	//交换两个元素:
			 	Swap((char*)base + j*width,(char*)base + (j + 1)*width,width); 
			 }
		}
	}
}

四、sizeof vs strlen:虚空测量的双生诅咒

sizeof

sizeof 计算变量所占内存内存空间⼤⼩的,单位是字节,如果操作数是类型的话,计算的是使⽤类型创建的变量所占内存空间的⼤⼩。sizeof是操作符不是函数

sizeof 只关注占⽤内存空间的⼤⼩,不在乎内存中存放什么数据。

比如:

c 复制代码
#inculde <stdio.h>
int main()
{
 int a = 10;
 printf("%d\n", sizeof(a));
 printf("%d\n", sizeof a);
 printf("%d\n", sizeof(int));
 
 return 0;
}

strlen

strlen 是C语⾔库函数,功能是求字符串⻓度。函数原型如下:

size_t strlen ( const char * str );

统计的是从 strlen 函数的参数 str 中这个地址开始向后, \0 之前字符串中字符的个数。strlen 函数会⼀直向后找 \0 字符,直到找到为⽌,所以可能存在越界查找。

五、数组/指针题解:次元裂隙的审判日

一维数组:

int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));			//16     数组名单独放在sizeof 内部,计算的是整个数组的大小
printf("%d\n",sizeof(a+0));			//4 /8   这里的a是数组名表示首元素的地址,地址大小只和环境有关。
printf("%d\n",sizeof(*a));			//1		 这里a同样是首元素的地址
printf("%d\n",sizeof(a+1));			//4 / 8
printf("%d\n",sizeof(a[1]));		//2
printf("%d\n",sizeof(&a));			//4 / 8	 这里的数组名表示整个数组,&a就是整个数组的地址,数组的地址也是地址,并不是数组的地址就nb,同样也是4或8字节。
printf("%d\n",sizeof(*&a));			//16     这里的*和&抵消,所以sizeof(*&a)  ==  sizeof(a)
printf("%d\n",sizeof(&a+1));		//4 / 8  &a是数组的地址,&a+1是跳过整个数组后的那个位置的地址
printf("%d\n",sizeof(&a[0]); 		//4/8
printf("%d\n",sizeof(&a[0]+1);		//4/8

字符数组:

sizeof 系列
c 复制代码
char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", sizeof(arr));		//6		这里arr表示整个数组
printf("%d\n", sizeof(arr+0));  	//4/8	这里arr表示首元素地址	
printf("%d\n", sizeof(*arr));		//1		这里arr表示该数组的首元素
printf("%d\n", sizeof(arr[1])); 	//1		这里arr[1]表示该数组的第二个元素
printf("%d\n", sizeof(&arr));		//4/8	这里&arr表示整个数组的地址
printf("%d\n", sizeof(&arr+1));		//4/8	这里&arr+1是跳过整个数组后的那个地址
printf("%d\n", sizeof(&arr[0]+1));	//4/8	这里&arr[0]+1该数组第二个元素的地址
c 复制代码
char arr[] = "abcdef";
printf("%d\n", sizeof(arr));		//7		这里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
c 复制代码
char *p = "abcdef";		
printf("%d\n", sizeof(p));			//4/8		p是指向字符串首元素的指针变量,计算的是指针变量p的大小
printf("%d\n", sizeof(p+1));		//4/8		p+1是1第二个元素的地址 地址的大小就是4/8个字节
printf("%d\n", sizeof(*p));			//1
printf("%d\n", sizeof(p[0]));		//1
printf("%d\n", sizeof(&p));			//4/8		&p是指针变量p的地址 &p ---char** 二级指针
printf("%d\n", sizeof(&p+1));		//4/8		&p +1是跳过了p变量,指向了p的后面
printf("%d\n", sizeof(&p[0]+1));	//4/8		&p[0] == &*(p+0) == p  所以&p[0]+1就是第二个元素'b'的地址
strlen系列
c 复制代码
char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", strlen(arr));	//随机数    这里的arr为首元素地址,传入strlen中会从该地址开始往后读取直到遇到\0.而该数组arr中无\0,strlen会持续读取越界到数组外直到遇到\0
printf("%d\n", strlen(arr+0));	//随机数    arr+0是首元素的地址,传到strlen中从该地址向后读取直到遇到\0
printf("%d\n", strlen(*arr));	//程序崩溃  *arr 为首元素,将'a'传入strlen相当于其ASCLL码'97'传入按地址读取,但并无权限无法读取。
printf("%d\n", strlen(arr[1])); //程序崩溃  这里arr[1]为数组第二个元素'b'。
printf("%d\n", strlen(&arr));	//随机数    这里&arr为整个数组的地址,传入strlen依然是从第一个元素开始读取
printf("%d\n", strlen(&arr+1));	//随机数    从第二个元素开始读取
printf("%d\n", strlen(&arr[0]+1));//随机数  从第二个元素开始读取
c 复制代码
char arr[] = "abcdef";
printf("%d\n", strlen(arr));	 //6
printf("%d\n", strlen(arr+0));	 //6
printf("%d\n", strlen(*arr));  	 //程序崩溃
printf("%d\n", strlen(arr[1]));  //程序崩溃
printf("%d\n", strlen(&arr));	 //6
printf("%d\n", strlen(&arr+1));	 //随机数
printf("%d\n", strlen(&arr[0]+1));//5		从第二个元素开始读取
c 复制代码
char *p = "abcdef";
printf("%d\n", strlen(p));		 //6
printf("%d\n", strlen(p+1));	 //5
printf("%d\n", strlen(*p));		 //程序崩溃
printf("%d\n", strlen(p[0]));	 //程序崩溃
printf("%d\n", strlen(&p));		 //随机数
printf("%d\n", strlen(&p+1));	 //随机数
printf("%d\n", strlen(&p[0]+1)); //5

二维数组:

c 复制代码
int a[3][4] = {0};
printf("%d\n",sizeof(a));			//48	a作为数组名单独放在了sizeof内部,计算的是整个数组的大小
printf("%d\n",sizeof(a[0][0]));		//4		计算的是第一行第一个元素的大小
printf("%d\n",sizeof(a[0]));		//16	a[0]是第一行的数组名,单独放在sizeof内部,计算的是数组的大小
printf("%d\n",sizeof(a[0]+1));		//4/8 	a[0]是第一行的数组名,但没有单独放在数的内部,那么只能数组首元素的地址------&a[0][0]。 +1是第一行第二个元素的地址------*a[0][1]
printf("%d\n",sizeof(*(a[0]+1)));	//4		在上面的基础上解引用是第一行第二个元素 
printf("%d\n",sizeof(a+1));			//4/8	a是二维数组的数组名,并没有单独放在sizeof内部,a表示首元素的地址---也就是第一行的地址,+1后就成了第二行的地址	
printf("%d\n",sizeof(*(a+1)));		//16    第二行的地址解引用后就是第二行的所有元素
printf("%d\n",sizeof(&a[0]+1));		//4/8	a[0]是第一行的数组名,&数组名其实就是第一行的地址,&a[0]+1就是第二行的地址
printf("%d\n",sizeof(*(&a[0]+1)));	//16	第二行的地址解引用是整个第二行的所有元素
printf("%d\n",sizeof(*a));			//4/8	这里的a是二维数组的数组名,没有单独放在sizeof中,所以是首元素(第一行)的地址
printf("%d\n",sizeof(a[3]));		//16	a[3]是第四行的数组名,单独放在了sizeof中,所以是整个第三行的元素。
//这个情况并没有越界,因为sizeof内部并不会真实计算,所以不会去访问.

所以把握好以上的关键点如下:

  • 数组名 是否单独在sizeof内部,如果 ,此处的数组名表示整个数组元素 ,如果不是 ,则此处数组名表示的是该数组首元素的地址
  • sizeof 如果传入的是字符串 ,要记得加上\0
  • strlen 中传入只能是地址,才能从该地址处向后读取。如果传入的是元素,则会程序崩溃。
  • strlen计算的是\0之前的字符串长度 。字符数组(字符串中)的元素不包含\0时 ,结果都为随机数
  • sizeof中如果传入的是二维数组:例如该二维数组为a[3][4]
    单独传入a 时表示的是该二维数组的整个元素
    传入a,但不单独时 ,表示的是该二维数组的首元素地址 ,也就是第一行的数组元素的地址
    单独传入a[2]时 ,表示的是该第三行的数组的整个元素
    传入a[2],但不单独时 ,表示的是第三行数组的首元素地址 ,也就是第三行第一个元素的地址
    传入a[5]时,并不会造成越界,因为sizeof内部并不会实际计算,所以也能照常输出

当回调函数开始递归自指,整个逻辑宇宙将归于虚无!

El Psy Kongroo!

相关推荐
*.✧屠苏隐遥(ノ◕ヮ◕)ノ*.✧3 分钟前
C语言_数据结构总结4:不带头结点的单链表
c语言·开发语言·数据结构·算法·链表·visualstudio·visual studio
a李兆洋15 分钟前
力扣 Hot 100 刷题记录 - LRU 缓存
算法·leetcode·缓存
极客代码17 分钟前
Linux IPC:System V共享内存汇总整理
linux·c语言·开发语言·并发·共享内存·通信·system v
算法与编程之美26 分钟前
冒泡排序
java·开发语言·数据结构·算法·排序算法
卑微小文29 分钟前
企业级IP代理安全防护:数据泄露风险的5个关键防御点
前端·后端·算法
Erik_LinX1 小时前
算法日记36:leetcode095最长公共子序列(线性DP)
算法
2301_766536051 小时前
刷leetcode hot100--动态规划3.11
算法·leetcode·动态规划
VT.馒头1 小时前
【力扣】2629. 复合函数——函数组合
前端·javascript·算法·leetcode
DOMINICHZL1 小时前
卡尔曼滤波算法从理论到实践:在STM32中的嵌入式实现
stm32·嵌入式硬件·算法
CodeJourney.1 小时前
光储直流微电网:能源转型的关键力量
数据库·人工智能·算法·能源