首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >【C语言】C语言指针超详细攻略:掌握地址的艺术(下)

【C语言】C语言指针超详细攻略:掌握地址的艺术(下)

作者头像
小年糕是糕手
发布于 2026-01-14 14:40:25
发布于 2026-01-14 14:40:25
2790
举报
文章被收录于专栏:C++学习C++学习
前情回顾:C语言指针超详细攻略:掌握地址的艺术(中)-CSDN博客https://blog.csdn.net/2501_91731683/article/details/149116925?spm=1001.2014.3001.5501

https://blog.csdn.net/2501_91731683/article/details/149116925?spm=1001.2014.3001.5501

一、回调函数

在上一讲内容中我们学习了函数指针数组,并且简单应用做个了简易版计算器,这一章我会为大家介绍另一种应用方式:回调函数

1、介绍

回调函数就是一个通过函数指针调用的函数,我们也可以理解为将一个函数的地址当作参数传入另一个函数

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

下面是一个简单的小例子:

代码语言:javascript
复制
#include<stdio.h>
int  Add(int x, int y)
{
	return x + y;
}
void test(int(*pf)(int, int))
{
	int r = pf(10, 20);
	printf("%d", r);
}
int main()
{
	test(Add);//传入的是Add的地址
	return 0;
}

上述代码的输出结果是30,满足了Add函数,我们没有直接使用Add函数来进行加法操作,我们可以类比数学中的隐函数求导的概念,将他理解为一个隐式调用函数。我们就称Add是回调函数。

2、应用

我们想一下上一篇博客中转移表的那段代码:我们思考一下是否可以用回调函数对他进一步的简化?(上一节转移表代码在:C语言指针超详细攻略:掌握地址的艺术(中)-CSDN博客大家可以自行查看类比)

答案是肯定的,下面我先给出代码,再来为大家分析:

代码语言:javascript
复制
#include<stdio.h>
void menu()
{
	printf("*******************\n");
	printf("***1.Add   2.Sub***\n");
	printf("***3.Mul   4.Div***\n");
	printf("***0.exit       ***\n");
	printf("*******************\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 calc(int(*pf)(int, int))
{
	int ret = 0;
	int x, y;
	printf("请输入操作数:>");
	scanf_s("%d %d", &x, &y);
	ret = pf(x, y);
	printf("ret = %d\n", ret);

}
int main()
{
	int input = 1;
	do
	{
		menu();
		printf("请选择:>");
		scanf_s("%d", &input);
		switch (input)
		{
		case 1:
			calc(Add);
			break;
		case 2:
			calc(Mul);
			break;
		case 3:
			calc(Sub);
			break;
		case 4:
			calc(Div);
			break;
		case 0:
			printf("退出程序");
			break;
		default :
			printf("选择有误,请重新选择:>\n");
			break;
		}
	} while (input);
	return 0;
}

我们看calc函数可以看出来他其实与我们上述介绍中的那个程序十分类似,这段代码的逻辑是我们在原有的转移表的基础上创建了calc函数,用来介绍函数指针,然后通过函数指针我们去调用对应的Add、Sub、Mul、Div函数,以便于实现简易版计算器功能。

二、strcmp函数

strcmp - C++ Reference

在学习qsort函数前我们先来简单学习一下strcmp函数(后期内容中还会详细介绍),它的作用是比较字符串的大小,字符串的大小比较与我们比较数也是有区别的,下面给出一个简单的例子:

代码语言:javascript
复制
#include<stdio.h>
#include<string.h>
int main()
{
	int ret1 = strcmp("abc", "abbbbb");
	int ret2 = strcmp("abc", "abc");
	int ret3 = strcmp("abc", "abcdef");
	//字符串比较大小,不是直接比较长度大小
	//而是比较对应位置上的字符的ASCII码值的大小
	//若前者大于后者输出1;相等输出0;小于输出-1
	return 0;
}

大家可以自行在编译器上运行来看看结果是否与所述一致。

三、qsort函数

1、介绍

我们首先来回顾一下冒泡排序的代码:C语言指针超详细攻略:掌握地址的艺术(中)-CSDN博客

但是从冒泡排序中我们可以知道,他只可以排序整型数据,换成浮点型或者结构体类型数据就不可

以,所以功能就比较单一,因此我们要学习qsort函数,它可以排序任意类型的函数。

qsort - C++ Referencehttps://legacy.cplusplus.com/reference/cstdlib/qsort/?kw=qsort上面是qsort函数的详细说明,下面我为大家翻译讲解一下:

1、使用qsort要包含头文件#include<stdlib.h> 2、void* base:是指针,指向了被排序数组的第一个元素(即我们写一个数组首元素的地址) 3、size_t num:base指向的被排序数组的元素个数 4、size_t size:base指向的被排序数组的大小/长度(一个元素),单位是字节 5、int (*compar)(const void*,const void*):函数指针,指针指向的函数是用来比较被排序数组中的俩个元素的

在这里我们可以看到若P1>P2,则return>0;若P1=P2,则return=0;若P1<P2,则return<0。

我们可以看出qsort是默认升序的,若我们想给他改为降序,则将P1与P2间的符号调换一下即可。

2、排序整型数据

下面我们来用上述知识来用qsort来实现冒泡排序,大家可以自行写代码,合理即可,下面给出我的仅供参考:

代码语言:javascript
复制
#include<stdio.h>
#include<stdlib.h>
int cmp_int(const void* p1, const void* p2)
{
	//这里是强制类型转换
    //p1指向了一个整型数据
    //p2指向了一个整型数据
	//原来俩个指针都是void*类型的,这里我们需要比较的是整型数据的大小,所以转换为int*类型
	//如果我们想要将他改为降序,只需要调换p1与p2的位置即可
	if (*(int*)p1 > *(int*)p2)
		return 1;
	else if (*(int*)p1 < *(int*)p2)
		return -1;
	else
		return 0;
	//这里的代码也可以化简为
	//return(*(int*)p1 - *(int*)p2);
}
void print_arr(int arr[10], int sz)
{
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}
test()
{
	int arr[10] = { 3,2,4,5,6,8,1,3,9,8 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	print_arr(arr, sz);
	qsort(&arr[0], sz, sizeof(arr[0]), cmp_int);
	print_arr(arr, sz);

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

相信这段代码是非常好理解的,使用qsort函数的程序员需要自己写一个比较函数,用来比较俩个整型数据的大小,比较的规则,为升序还是降序,也是由程序员自己来决定的,所以我们可以看出 qsort函数的限制比较小,让我们自己有了更多发挥的空间。

3、排序结构数据

整型排序内容我们肯定已经了解,记下来我们来看如何排序结构数据,在学习这部分内容之前我们

需要先来学习一下结构体成员访问操作符" -> ",学习这个操作符之前我们可以简单复习一下我们之前所学的结构知识:解锁C语言魔法符号!探秘操作符的奇妙世界_无符号的四位整数9乘以有符号的四位整数2为多少-CSDN博客https://blog.csdn.net/2501_91731683/article/details/147838049?spm=1001.2014.3001.5501

https://blog.csdn.net/2501_91731683/article/details/147838049?spm=1001.2014.3001.5501下面给出一串代码为大家讲解:

代码语言:javascript
复制
#include<stdio.h>
struct Stu 
{
	char name[20];
	int age;
};
void test(struct Stu* ps)
{
	//我们可以通过解引用函数指针以此来找到我们需要的打印的内容
	printf("%s\n", (*ps).name);
	printf("%d\n", (*ps).age);
	//当然我们也可以用结构体成员访问操作符->
	printf("%s\n", ps->name);
	printf("%d\n", ps->age);
}
int main()
{
	struct Stu s = {"xiaozhulaoshi",17};
	//我们可以很便捷的打印出想要的内容
	/*printf("%s\n", s.name);
	printf("%s\n", s.age);*/
	//下面我们尝试用函数来实现
	test(&s);
	return 0;
}

我们可以很清晰的看到->操作符的用法,,他的使用方法是:左边是结构体指针,右边是成员名。

接下来我们试着将他应用到排序结构数据中,便可以写出下面这样的代码:

代码语言:javascript
复制
#include<stdio.h>
#include<stdlib.h>
struct Stu
{
	char name[20];
	int age;
};
//cmp_stu_by_age结构数据按照年龄来比较
//p1指向了一个结构体变量
//p2指向了一个结构体变量
int cmp_stu_by_age(const void* p1, const void* p2)
{
	/*return (*(struct Stu*)p1).age - (*(struct Stu*)p2).age;*/
	//这里可以使用结构体成员访问操作符->
	//要注意操作符的优先级问题
	return (((struct Stu*)p1)->age - ((struct Stu*)p2)->age);
}
void print_stu(struct Stu arr[], int sz)
{
	for (int i = 0; i < sz; i++)
	{
		printf("%s: %d\n", arr[i].name, arr[i].age);
	}
	printf("\n");
}
void test()
{
	struct Stu arr[] = { {"xiaozhulaoshi",17},{"xiaofuxing",18},{"xiaozuzong",19} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);

	print_stu(arr, sz);
}
int main()
{
	test();
	return 0;
}

我们前面学习了strcmp函数,所以我们也可以按照字符的大小来比较:

代码语言:javascript
复制
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct Stu
{
	char name[20];
	int age;
};
//cmp_stu_by_name结构数据按照名字来比较
//p1指向了一个结构体变量
//p2指向了一个结构体变量
int cmp_stu_by_name(const void* p1, const void* p2)
{
	//俩个字符串比较大小的时候不可以直接使用减法
	//这时候我们应该使用strcmp函数来比较俩个字符串的大小
	return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
	//如果要降序依旧只需要将p1与p2交换即可
}
void print_stu(struct Stu arr[], int sz)
{
	for (int i = 0; i < sz; i++)
	{
		printf("%s: %d\n", arr[i].name, arr[i].age);
	}
	printf("\n");
}
void test()
{
	struct Stu arr[] = { {"xiaozhulaoshi",17},{"xiaofuxing",18},{"xiaozuzong",19} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
	print_stu(arr, sz);
}
int main()
{
	test();
	return 0;
}

4、qsort函数的模拟实现

使用回调函数,模拟实现qsort(采用冒泡的方式)

代码语言:javascript
复制
#include<stdio.h>
int cmp_int(const void* p1, const void* p2)
{
	return (*(int*)p1 - *(int*)p2);
}
void print_arr(int arr[1], int sz)
{
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
  printf("\n");
}
void Swap(char* buf1, char* buf2, size_t width)
{
	int i = 0;
	char tap = 0;
	for (i = 0; i < width; i++)
	{
		tap = *buf1;
		*buf1 = *buf2;
		*buf2 = tap;
		buf1++;
		buf2++;
	}
}
//排序任意类型的数据
//但是底层依旧是冒泡排序的算法思想
void bubble_sort(void* base, size_t sz, size_t width, int(*cmp)(const void* p1, const void* p2))
{
	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]) //交换
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
			{
				//交换
				Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
			}
		}
	}
}
void test()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[10]);
	print_arr(arr, sz);
	bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
	print_arr(arr, sz);
}
int main()
{
	test();
	return 0;
}
四、sizeof和strlen对比

1、sizeof

在学习操作符的时候,我们学习了sizeof, sizeof计算变量所占内存空间大小的,单位是字节,如果操作数是类型的话,计算的是使用类型创建的变量所占内存空间的大小。

sizeof 只关注占用内存空间的大小,不在乎内存中存放什么数据,如下:

代码语言:javascript
复制
#include<stdio.h>
int main()
{
	int a = 10;
	printf("%zu\n", sizeof(a));//4
	printf("%zu\n", sizeof a);//4
	printf("%zu\n", sizeof(int));//4
	printf("%zu\n", sizeof(a + 3.14));//8
	return 0;
}

相信对于前面三个结果大家都非常了解了,第四个结果是8,是在你的意料之中还是出乎了你的预

料,a是int类型的而3.14是double类型的,他们相加的结果是double类型的,所以使用sizeof计算

出来的结果就是8。

2、strlen

学习strlen之前我们首先要记住的就是strlen要包含头文件#include<string.h>。

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

代码语言:javascript
复制
size_t strlen ( const char * str );

统计的是从 strlen 函数的参数 str 中这个地址开始向后, \0之前字符串中字符的个数。 strlen 函数

会一直向后找 \0 字符,直到找到为止,所以可能存在越界查找。

下面我们来看一串代码:

代码语言:javascript
复制
#include<stdio.h>
#include<string.h>
int main()
{
	char arr1[3] = { 'a', 'b', 'c' };
	char arr2[] = "abc";

	printf("%d\n", strlen(arr1));//随机值
	printf("%d\n", strlen(arr2));//3

	printf("%d\n", sizeof(arr1));//3
	printf("%d\n", sizeof(arr2));//4
	return 0;
}

第一个会打印随机值因为没有\0,第二个统计到字符\0之前的字符个数所以是3,而sizeof(数组名) ,数组名表示整个数组,计算的是整个数组的大小,故第三个为3,第四个为4。

3、对比

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-08-27,如有侵权请联系 cloudcommunity@tencent.com 删除
目录
  • 前情回顾:C语言指针超详细攻略:掌握地址的艺术(中)-CSDN博客https://blog.csdn.net/2501_91731683/article/details/149116925?spm=1001.2014.3001.5501
  • 一、回调函数
  • 二、strcmp函数
  • 三、qsort函数
  • 四、sizeof和strlen对比
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档