目录
? ? ? ? 言C之言,聊C之识,以C会友,共向远方。各位CSDN的各位你们好啊,这里是持续分享C语言知识的小赵同学,今天要分享的C语言知识是深入了解指针(4),在这一章,小赵将会和大家继续聊指针的相关内容。?
那么首先我们要了解的就是什么是回调函数呢?其实回调函数指的就是我们在使用一个函数的时候,我们讲另一个函数以地址的形式传入这个函数中,然后我们在这个函数中,通过函数指针去调用我们传入的函数,而这个我们传入的函数其实也就是我们回调函数。那么废话不多说它的出现究竟可以给我们的代码带来哪些便捷呢?下面我们看下面这样一个代码。
#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;
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
do
{
printf("*************************\n");
printf(" 1:add 2:sub \n");
printf(" 3:mul 4:div \n");
printf(" 0:exit \n");
printf("*************************\n");
printf("请选择:");
scanf("%d", &input);
switch (input)
{
case 1:
printf("输?操作数:");
scanf("%d %d", &x, &y);
ret = add(x, y);
printf("ret = %d\n", ret);
break;
case 2:
printf("输?操作数:");
scanf("%d %d", &x, &y);
ret = sub(x, y);
printf("ret = %d\n", ret);
break;
case 3:
printf("输?操作数:");
scanf("%d %d", &x, &y);
ret = mul(x, y);
printf("ret = %d\n", ret);
break;
case 4:
printf("输?操作数:");
scanf("%d %d", &x, &y);
ret = div(x, y);
printf("ret = %d\n", ret);
break;
case 0:
printf("退出程序\n");
break;
default:
printf("选择错误\n");
break;
}
} while (input);
return 0;
}
这一个代码?其实也就是我们上一章节时候使用的计算器的代码,在上一章其实我们已经用过我们的函数指针数组对这个代码进行过一次优化,那么这个代码能否用我们的回调函数进行优化呢?那么该如何优化呢其实也就是对那他原本的输入数字的方式稍微改一下。
在这里我们先创建一个函数
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(" 0:exit \n");
printf("*************************\n");
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函数,这个函数有什么特点,一是实现快速排序,二是它里面使用了我们的回调函数,可以拿来作为我们的训练运用。
可能有人看到这些英文会一阵头疼,但是没关系,如果我们可以看懂它的列子(即知道它的作用)和它的参数就可以完美使用这个函数了。或者我们也可以试着去翻译这个英文,翻译器之类的使用上,实在不行我们就当时恶补英语好了哈哈。好了我们言归正传吗,我们可以看到这个函数它的参数,第一个是我们要排列的数组,第二个是数组的元素数量,第三个则是我们数组中每个元素的大小,最后一个则是比较函数。(这里的排列一定是针对我们元素是一样的,毕竟我们的数组里面的元素就是一样的,不对吗?),这个函数使用上唯一的烦就是我们要自己搞个比较函数,可以说是伤了很多人的脑袋,但实际上也是比较简单的。
(这个是对我们的比较函数的要求即两个数字相减如果为负数则小的在前,大的在后)?
这里举一个整数比较的例子(这里唯一要注意的是这个比较函数的参数类型是const void*)(这里为什么是const void*,因为const void*可以接受任意类型的值)。
#include <stdio.h>
int int_cmp(const void* p1, const void* p2)
{
return (*(int*)p1 - *(int*)p2);
}
当然我们有的时候要比较的不仅仅是数字还有字母等。
他们要怎么比较呢?这里我们就不得不用我们的字符串函数中的strcmp。
?它的整个比较也是和我们上面的输出其实也是一样的。
int int_cmp(const void* p1, const void* p2)
{
return strcmp((char*)p1, (char*)p2);
}
那么我们在要用哪个的时候实现哪个就行,下面我们来演示两组。
#include <stdio.h>
#include<string.h>
//qosrt函数的使?者得实现?个?较函数
int int_cmp(const void* p1, const void* p2)
{
return strcmp((char*)p1, (char*)p2);
}
int main()
{
int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
int i = 0;
qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), int_cmp);
for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
{
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
struct Stu//结构体后面会聊
{
char name[20];
int age;
};
//假设按照年龄来?较
int cmp_stu_by_age(const void* e1, const void* e2)
{
return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;//结构体指针可以用->来访问结构体内部的东西
}
//strcmp - 是库函数,是专??来?较两个字符串的??的
//假设按照名字来?较
int cmp_stu_by_name(const void* e1, const void* e2)
{
return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
//按照年龄来排序
void test2()
{
struct Stu s[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 15} };
int sz = sizeof(s) / sizeof(s[0]);
qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);
}
//按照名字来排序
void test3()
{
struct Stu s[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 15} };
int sz = sizeof(s) / sizeof(s[0]);
qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
}
int main()
{
test2();
test3();
return 0;
}
那么这样的一个函数究竟是如何实现的呢?下面我们就来试着去模拟一下这样一个函数的实现。
在排序的底层逻辑上我们可以使用我们的冒泡排序去实现我们的我们的排序,由于我们这一次要排的数据不再是单一的整数型,所以我们的数据交换,还要再弄一个函数,或者在原本函数进行改动(但是在原本函数上改动代码会有点偏长,小赵在这里就再弄一个函数去搞定这件事)。
交换函数
void _swap(void* p1, void* p2, int size)
{
int i = 0;
for (i = 0; i < size; i++)//这里其实就是对我们数据的每一个字节进行交换
{
char tmp = *((char*)p1 + i);//char刚好占一个一个字节
*((char*)p1 + i) = *((char*)p2 + i);
*((char*)p2 + i) = tmp;
}
}
冒泡排序
void bubble(void* base, int count, int size, int(*cmp)(void*, void*))//这里主要就是冒泡排序前面的知识。
{
int i = 0;
int j = 0;
for (i = 0; i < count - 1; i++)
{
for (j = 0; j < count - i - 1; j++)
{
if (cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 0)//这里是用了我们的比较函数,如果前面比后面的大
{
_swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
}
}
}
}
?最后浓缩在一起
#include<stdio.h>
int int_cmp(const void* p1, const void* p2)//比较函数
{
return (*(int*)p1 - *(int*)p2);
}
void _swap(void* p1, void* p2, int size)//交换函数
{
int i = 0;
for (i = 0; i < size; i++)
{
char tmp = *((char*)p1 + i);
*((char*)p1 + i) = *((char*)p2 + i);
*((char*)p2 + i) = tmp;
}
}
void bubble(void* base, int count, int size, int(*cmp)(void*, void*))//排序
{
int i = 0;
int j = 0;
for (i = 0; i < count - 1; i++)
{
for (j = 0; j < count - i - 1; 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 main()
{
int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
int i = 0;
bubble(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), int_cmp);
for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
{
printf("%d ", arr[i]);//打印我们的数组
}
printf("\n");
return 0;
}
这样一个函数就可以对我们任意数据进行比较。?
sizeof和strlen函数其实也是我前面一直在和大家重点聊的两个家伙,这两个家伙我们在一起聊的时间其实很多,给小赵的感悟也很多,那么我们下面就再来看看这两个家伙究竟是什么不同,其实说起来也简单。
首先是sizeof这个操作符,这个函数的作用就是就算你给的东西所占的内存大小。而strlen则是统计我们字符串的长度或者字符串数组的长度,到‘\0’为止。可以说这个家伙真正打起来其实是在数组,如果不是在数组中这两个家伙几乎也打不起来。
好了下面我们结合我们的代码来聊
#include <stdio.h>
int main()
{
char arr1[3] = {'a', 'b', 'c'};
char arr2[] = "abc";
printf("%d\n", strlen(arr1));
printf("%d\n", strlen(arr2));
printf("%d\n", sizeof(arr1));
printf("%d\n", sizeof(arr1));
return 0;
}
(这里要提一下我们的sizeof 可不会管你什么\0不\0只要你存在,那你今天就必须被记录下来。)
我们看到在字符串的统计中两个人都是很正常的,唯有在数组中我们的strlen似乎出了问题,那究竟?是什么问题呢?其实也很简单就是我们之前说的,strlen这个函数比较轴它一定要找到‘\0’为止,那么我们的字符串其实是会自动补一个‘\0’在字符串后面的,那我们的数组呢?我们的数组什么都不会补,那么我们死轴死轴的strlen就会一直去找一直去找我们的‘\0’终于它在一片茫茫的内存的黑暗中找到了我们的'\0',并且返回了它的位置,(其实strlen函数这样也有点轴的可爱,不是吗?)那这个问题怎么解决呢?其实就是我们只需要在我们的第一个数组后面补一个‘\0’就可以了。
好了小赵今天的分享就到这里了,如果大家有什么不明白的地方可以在小赵的下方留言哦,同时如果小赵有什么地方说得不对也希望得到大家的指点,谢谢各位家人们的支持。你们的支持是小赵创作的动力,加油。
如果觉得文章对你有帮助的话,还请点赞,关注,收藏支持小赵,如有不足还请指点,小赵及时改正,感谢大家支持!!!