您的位置:首页 > 游戏 > 手游 > 杭州余杭网站制作_seo网站优化案例_长春百度网站优化_静态网页制作

杭州余杭网站制作_seo网站优化案例_长春百度网站优化_静态网页制作

2025/3/9 22:35:57 来源:https://blog.csdn.net/2401_88739751/article/details/146013700  浏览:    关键词:杭州余杭网站制作_seo网站优化案例_长春百度网站优化_静态网页制作
杭州余杭网站制作_seo网站优化案例_长春百度网站优化_静态网页制作

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

文章目录

  • 第七章、C语言指针全解(4)终章:混沌终焉!指针圣域的湮灭与重构!
    • 一、回调函数:时间线跳跃的暗码
    • 二、qsort:混沌数据的归一仪式
      • qsort 的降序排列:
    • 三、冒泡排序模拟qsort:自制维度规整引擎
    • 四、sizeof vs strlen:虚空测量的双生诅咒
      • sizeof
      • strlen
    • 五、数组/指针题解:次元裂隙的审判日
      • 一维数组:
      • 字符数组:
        • sizeof 系列
        • strlen系列
      • 二维数组:

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

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

回调函数不是普通调用,是向程序注入未来指令的时光胶囊!它允许你在运行时逆转因果链,让函数行为在事后被重新定义!
回调函数是代码的延时炸弹,触发时机由宿主函数全权掌控!

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

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

上面的概念多少有些生硬,接下来给个例子让大家清晰的进行理解:
函数指针转递方—— 中间调用方——被调函数方(回调函数) 之间的数据交互逻辑
这里可以用上篇博客的计算器进行回调函数改造:

#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函数:

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 排列整型数组:
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对结构体的排序之前,我们先回忆关于访问结构体内容的两种方法:

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

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

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

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

  • 结构体的大小比较就取决于,你要用里面具体哪种变量来比较。比如以下的例子,按姓名(长度),或按年龄大小比较
  • 无论你想比较什么类型的数据,写cmp函数时,形式参数都要写成void* 类型,和qsort函数的设定保持一致。
  • 以下是按姓名比的,姓名是字符串,字符串比较是使用strcmp函数
  • strcmp 函数比较大小后的返回值和qsort函数中cmp要求的返回值相吻合
#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函数的写法:

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

qsort 的降序排列:

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

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

cmp_stu_by_age(const void*p1,const void* p2)
{return = ((struct Stu*)p2) -> age - ((struct Stu*)p1) -> age;
}

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

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

接下来我们将bubble_sort改造成通用的排序算法,可以排任意类型的数据:
思考逻辑:
1、由于我们不知道将来要给什么样类型的数组进行排序,所以接收数组的形参类型为void*。

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 只关注占⽤内存空间的⼤⼩,不在乎内存中存放什么数据。
比如:

#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 系列
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该数组第二个元素的地址
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
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系列
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));//随机数  从第二个元素开始读取
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		从第二个元素开始读取
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

二维数组:

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!

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com