电子商务网站建设项目,上海智能网站建设,电商网站及企业微信订烟,国家企业查询官网入口前言✈上回说到#xff0c;我们学习了一些与指针相关的数据类型#xff0c;如指针数组#xff0c;数组指针#xff0c;函数指针等等#xff0c;我们还学习了转移表的基本概念#xff0c;学会了如何利用转移表来实现一个简易计算器。详情请点击传送门#xff1a;【C语言】…前言✈上回说到我们学习了一些与指针相关的数据类型如指针数组数组指针函数指针等等我们还学习了转移表的基本概念学会了如何利用转移表来实现一个简易计算器。详情请点击传送门【C语言】深度理解指针上本期我们将继续指针的话题学习有关回调函数的相关内容以及分析一些与指针相关的常见笔试题。事不宜迟让我们进入今天的第一个主题----回调函数。2. 回调函数2.1 定义首先回调函数是什么意思呢回调函数就是一个通过函数指针调用的函数。如果你把函数的指针地址作为参数传递给另一个函数当这个指针被用来调用其所指向的函数时我们就说这是回调函数。回调函数不是由该函数的实现方直接调用而是在特定的事件或条件发生时由另外的一方调用的用于对该事件或条件进行响应。2.2 qsort函数在stdlib.h头文件中有个函数叫qsort它的用途是用来进行快速排序。它有个特点就是我们通过它可以排序任何类型的数据这便是使用了回调函数的思想我们来看看它的函数原型参数作用base用于接收需要排序数组的首元素地址由于不知道传入类型类型为void*num表示数组共有几个元素用于确定循环的次数width表示数组每个元素所占的字节数用于确定指针移动的步长指向相应元素进行比较compare函数指针即回调函数。指向用户外部设计的比较函数调用函数对数组元素进行比较然后排序。在使用qsort时我们用户只需要设计compare指向的回调函数即可。由于用户知道要排序什么类型的数据因此可以设计对应的比较函数以供qsort函数内部进行回调。这就是为什么qsort可以排序任意类型数据的原因。其关系如下例如我们可以使用它排序整形数据#includestdlib.h
#includestdio.h
//递增
int cmp_int1(const void* p1, const void* p2) //比较函数参数要与qsort中的函数指针指向函数类型一致
{return *((int*)p1) - *((int*)p2); //p1大于p2,返回正数小于返回负数等于返回0
}
int main()
{int arr[10] { 8,5,4,6,4,7,8,1,9,4 };int sz sizeof(arr) / sizeof(arr[0]);qsort(arr, sz, sizeof(arr[0]), cmp_int1);//打印for (int i 0; i sz; i){printf(%d , arr[i]);}return 0;
}这是升序排列那么如果我们需要进行降序排序呢很简单将p1与p2对调即可(这是因为qsort当回调函数返回正数时会进行操作)//递减
int cmp_int2(const void* p1, const void* p2) //比较函数参数要与qsort中的函数指针指向函数类型一致
{return *((int*)p2) - *((int*)p1); //p1小于p2,返回正数小于返回负数等于返回0
}我们还可以使用qsort来排序一个结构体按照姓名升序排列#includestdlib.h
#includestdio.h
#includestring.h
struct student
{char name[20];int age;
};
int cmp_name(const void* p1, const void* p2)//对结构体中的姓名进行排序
{//字符串的比较用strcmpstrcmp相等返回0大于返回1小于返回-1与设定的返回值相对应return strcmp(((struct student*)p1)-name, ((struct student*)p2)-name);
}
int main()
{struct student arr[3] { {zhangsan,16},{lisi,14},{lihau,18} };int sz sizeof(arr) / sizeof(arr[0]);qsort(arr, sz, sizeof(arr[0]), cmp_name);//打印for (int i 0; i sz; i){printf(%s %d\n, arr[i].name, arr[i].age);}return 0;
}2.3 回调函数的应用学习了qsort是如何借助回调函数来实现排序任意类型的数据我们是不是可以模拟qsort来实现我们曾经写过的冒泡排序函数使得这个冒泡排序函数可以排序任意类型的数据。下面我们逐步分析如何实现这样的一个代码由于排序的数组类型不固定因此我们第一个参数采用void*类型的指针接收void*类型的指针被称为万能指针可以接收任意类型的指针第二个参数与第三个参数分别是数组元素个数和每个元素的大小最后一个参数即为函数指针指向回调函数。//冒泡排序模拟qsort的思想
void my_bubble_sort(void* arr, size_t sz, size_t k, int (*cmp)(const void*, const void*))
{//实现
}接下来我们来实现冒泡排序算法由于我们不知道数组元素的类型因此不能直接通过下标来定位某个元素那要怎么办呢这里最巧妙的点来了我们知道char*类型的指针的步长为1个字节并且数组每个元素的所占的字节数我们又是已知的(参数k)那么我们是不是就可以将arr强制类型转换为char*然后加上k的整数倍即可得到相应元素所在地址。怎么样是不是太巧妙了下面来看代码void my_bubble_sort(void* arr, size_t sz, size_t k, int (*cmp)(const void*, const void*))
{for (int i 0; i sz - 1; i){for (int j 0; j sz - i - 1; j){//对相邻两个元素进行比较if (cmp((char*)arr j * k, (char*)arr (j 1) * k)0){//swap交换两个元素}}}
}由于cmp()为回调函数是用户根据需求所设计的因此我们的冒泡排序函数只剩下最后一个函数swap()了用于交换两个元素。那么swap()函数的参数要如何设计呢用int*还是用flost*都不是这里我们采用char*来接收操作数的地址并且再用一个参数来接收元素的大小(原因下面解释函数原型如下void swap(char* p1, char* p2, int k) //p1,p2指向操作数首地址k为每个元素的字节数最后我们来实现这个函数。因为我们不知道元素的类型所以我们交换只能一个字节一个字节交换。而对char*指针解引用即向后访问一个字节又已知元素的大小为k个字节我们通过循环k次即可将k个字节全部交换完毕。这也是为什么参数要以char*类型接收以及要接收元素的大小的原因。实现如下void swap(char* p1, char* p2, int k)
{//循环k次k个字节依次交换for (int i 1; i k; i){//交换char temp *p1;*p1 *p2;*p2 temp;//指向下一字节p1;p2;}
}至此我们完成的整个冒泡函数的实现总代码如下void swap(char* p1, char* p2, int k)
{for (int i 1; i k; i){char temp *p1;*p1 *p2;*p2 temp;p1;p2;}
}
void my_bubble_sort(void* arr, size_t sz, size_t k, int (*cmp)(const void*, const void*))
{for (int i 0; i sz - 1; i){for (int j 0; j sz - i - 1; j){if (cmp((char*)arr j * k, (char*)arr (j 1) * k)0){swap((char*)arr j * k, (char*)arr (j 1) * k, k);}}}
}我们可以用来排序整形#includestdlib.h
#includestdio.h
//递增
int cmp_int1(const void* p1, const void* p2) //回调函数用于比较整形
{return *((int*)p1) - *((int*)p2);
}
int main()
{int arr[10] { 8,5,4,6,4,7,8,1,9,4 };int sz sizeof(arr) / sizeof(arr[0]);my_bubble_sort(arr, sz, sizeof(arr[0]), cmp_name);//打印for (int i 0; i sz; i){printf(%d , arr[i]);}return 0;
}也可以用来排序结构体等等#includestdlib.h
#includestdio.h
#includestring.h
struct student
{char name[20];int age;
};
int cmp_name(const void* p1, const void* p2) 回调函数用于比较结构体中的姓名
{return strcmp(((struct student*)p1)-name, ((struct student*)p2)-name);
}
int main()
{struct student arr[3] { {zhangsan,16},{lisi,14},{lihau,18} };int sz sizeof(arr) / sizeof(arr[0]);my_bubble_sort(arr, sz, sizeof(arr[0]), cmp_name);//打印for (int i 0; i sz; i){printf(%s %d\n, arr[i].name, arr[i].age);}return 0;
}3. 指针和数组笔试题学习了那么多关于指针的知识点我们也要学会如何运用。下面给各位带来了一些有关指针与数组的笔试题作为饭后甜点。事不宜迟让我们马上开始品尝吧3.1 一维整形数组首先是一维整形数组求下列十条语句输出的结果//一维数组int a[] {1,2,3,4};printf(%d\n,sizeof(a));//1printf(%d\n,sizeof(a0));//2printf(%d\n,sizeof(*a));//3printf(%d\n,sizeof(a1));//4printf(%d\n,sizeof(a[1]));//5printf(%d\n,sizeof(a));//6printf(%d\n,sizeof(*a));//7printf(%d\n,sizeof(a1));//8printf(%d\n,sizeof(a[0]));//9printf(%d\n,sizeof(a[0]1));//10答案及解释如下表语句编号答案解释116数组名单独放在sizeof内部代表整个数组即求整个数组所占空间24/8由于数组名并非单独在sizeof内部其代表首元素地址求的就是指针的大小34同理a为首元素地址对其解引用得到首元素即求首元素所占空间44/8首元素地址加1向后移动4个字节还是指针求的为指针的大小54求下标为1的元素即第二个整形元素所占空间64/8数组名取出的是整个数组的地址因此求的为指针的大小716取出整个数组的地址然后解引用最后得到整个数组因此求整个数组所占空间84/8取出整个数组的地址然后加1还是指针求的为指针的大小94/8取出数组第一个元素的地址因此求的为指针的大小104/8取出数组第一个元素的地址然后加1指向下一个元素还是指针求的为指针的大小怎么样你都做对了吗3.2 二维整形数组同样求下列几条语句输出的结果//二维数组int a[3][4] {0};printf(%d\n,sizeof(a));//1printf(%d\n,sizeof(a[0][0]));//2printf(%d\n,sizeof(a[0]));//3printf(%d\n,sizeof(a[0]1));//4printf(%d\n,sizeof(*(a[0]1)));//5printf(%d\n,sizeof(a1));//6printf(%d\n,sizeof(*(a1)));//7printf(%d\n,sizeof(a[0]1));//8printf(%d\n,sizeof(*(a[0]1)));//9printf(%d\n,sizeof(*a));//10printf(%d\n,sizeof(a[3]));//11答案及解释如下表语句编号答案解释148数组名单独放在sizeof内部代表整个数组即求整个数组所占空间24a[0][0]代表第一行第一列元素,即求第一行第一列元素所占空间316a[0]代表第一行数组即第一行数组的数组名数组名单独放在sizeof内部求的为整个数组的大小即第一行一维数组的大小。44/8a[0]为第一行数组的数组名但没有单独放在sizeof内部因此为第一行一维数组首元素地址加一后为第二个元素地址因此求的是整形指针的大小54上面说到a[0]1为第一行数组第二个元素地址解引用后即为第一行数组的第二个元素求的为整形元素的大小64/8a数组名没有单独在sizeof内部因此为首元素地址即第一行数组的地址加一后为第二行数组的地址为指针求的为数组指针的大小716由6可得a1为第二行数组的地址解引用后为第二行数组求的即为第二行一维数组的大小84/8a[0]为第一行数组名取地址后取出的是第一行数组的地址加1后为第二行数组的地址为指针求的数组指针的大小916由8得a[0]1为第二行数组的地址解引用后为第二行数组求的是第二行一维数组的大小1016数组名没有单独放在sizeof内部代表首元素地址即第一行数组的地址解引用后得到第一行数组求的是第一行一维数组的大小11161.sizeof()并不会对括号内部的表达式进行运算操作即sizeof(arr[3])并不会去访问arr[3]因此不构成越界访问。2.sizeof()是通过变量的类型属性来确定变量所占空间的大小arr[3]的类型是一个一维数组因此sizeof(arr[3])求的即为一维数组的大小3.3 字符数组根据初始化方式的不同我们分为三组题我们先来看第一组//用字符初始化数组数组存放的内容为abcdef
char arr[] {a,b,c,d,e,f};printf(%d\n, sizeof(arr)); //1printf(%d\n, sizeof(arr0));//2printf(%d\n, sizeof(*arr));//3printf(%d\n, sizeof(arr[1]));//4printf(%d\n, sizeof(arr));//5printf(%d\n, sizeof(arr1));//6printf(%d\n, sizeof(arr[0]1));//7printf(%d\n, strlen(arr));//8printf(%d\n, strlen(arr0));//9printf(%d\n, strlen(*arr));//10printf(%d\n, strlen(arr[1]));//11printf(%d\n, strlen(arr));//12printf(%d\n, strlen(arr1));//13printf(%d\n, strlen(arr[0]1));//14语句编号答案解释16数组名单独放在sizeof()内部代表整个数组求的为整个数组的大小24/8数组名没有单独放于内部代表首元素地址最终求的为指针的大小31arr代表首元素地址解引用后得到首元素求的为第一个字符的大小41arr[1]为数组第2个元素求的为第二个字符的大小54/8数组名取出整个数组的地址因此求的是字符数组指针的大小64/8arr为字符数组的地址加1后越过一个字符数组类型属性还是指针求的是字符数组指针的大小74/8取出第一个元素的地址然后加1指向第二个元素还是指针因此求的是字符指针的大小8随机值由于strlen()遇到\0停止计数但是我们的字符数组中不存在\0所以会继续向后统计直到遇到\0。何时遇到\0是我们不可预知的为随机值9随机值在strlen()中arr与arr0都表示首元素地址由于不知道何时遇到\0因此为随机值10非法访问arr为首元素地址解引用后为首元素。本条语句实际上是将首元素的字面值作为地址传入函数向后进行统计显然造成了非法访问11非法访问arr[1]代表数组第二个元素同理会造成非法访问12随机值arr为字符数组的地址指向数组开头。由于不知道\0的位置因此为随机值13随机值arr为数组的地址加1后越过一个数组,指向后续元素。但是我们任然不知道\0的位置因此为随机值(这里的随机值比第12的随机值小6)14随机值arr[0]为数组首元素地址加1后为第2个元素地址。因此不用多说这里还是随机值只不过这个随机值比第12小1紧接着我们来看第二组char arr[] abcdef; //用字符串初始化数组,相当于将字符串拷贝到数组中printf(%d\n, sizeof(arr));//1printf(%d\n, sizeof(arr0));//2printf(%d\n, sizeof(*arr));//3printf(%d\n, sizeof(arr[1]));//4printf(%d\n, sizeof(arr));//5printf(%d\n, sizeof(arr1));//6printf(%d\n, sizeof(arr[0]1));//7printf(%d\n, strlen(arr));//8printf(%d\n, strlen(arr0));//9printf(%d\n, strlen(*arr));//10printf(%d\n, strlen(arr[1]));//11printf(%d\n, strlen(arr));//12printf(%d\n, strlen(arr1));//13printf(%d\n, strlen(arr[0]1));//14语句编号答案解释17数组名单独放在sizeof()内部代表整个数组求的为整个数组的所占空间包括字符串末尾隐藏的\024/8数组名没有单独放于内部代表首元素地址最终求的为指针的大小31arr代表首元素地址解引用后得到首元素求的为第一个字符的大小41arr[1]为数组第2个元素求的为第二个字符的大小54/8数组名取出整个数组的地址因此求的是字符数组指针的大小64/8arr为字符数组的地址加1后越过一个字符数组类型属性还是指针求的是字符数组指针的大小74/8取出第一个元素的地址然后加1指向第二个元素还是指针因此求的是字符指针的大小86由于字符串以\0结尾末尾隐藏了\0因此strlen计算的结果即为字符串的长度(不包括末尾隐藏的\0)96在strlen()中arr与arr0都表示首元素地址因此结果为字符串长度10非法访问arr为首元素地址解引用后为首元素。本条语句实际上是将首元素的字面值作为地址传入函数向后进行统计显然造成了非法访问11非法访问arr[1]代表数组第二个元素同理会造成非法访问126arr为字符数组的地址指向数组开头。于是从数组开头向后统计字符串个数直到遇到\0停止因此结果为字符串的长度13随机值arr为数组的地址加1后越过一个数组,即越过了字符串末尾的\0指向下一元素。由于不知道后续的\0处于何处因此为随机值145arr[0]为数组首元素地址加1后为第2个元素地址即从第二个元素开始向后统计字符的个数。结果为字符串个数-1最后一组是将字符串赋给一个字符指针如下char* p abcdef;//字符串初始化字符指针指针指向字符串首元素地址printf(%d\n, sizeof(p));//1printf(%d\n, sizeof(p 1));//2printf(%d\n, sizeof(*p));//3printf(%d\n, sizeof(p[0]));//4printf(%d\n, sizeof(p));//5printf(%d\n, sizeof(p 1));//6printf(%d\n, sizeof(p[0] 1));//7printf(%d\n, strlen(p));//8printf(%d\n, strlen(p 1));//9printf(%d\n, strlen(*p));//10printf(%d\n, strlen(p[0]));//11printf(%d\n, strlen(p));//12printf(%d\n, strlen(p 1));//13printf(%d\n, strlen(p[0] 1));//14语句编号答案解释14/8p是个指针存放字符串首元素地址因此结果为一个指针的大小24/8p加1指向字符串常量中第2个字符的地址还是指针因此结果为指针的大小31对p指针解引用后得到a求的就为字符a的大小41p[0]与*(p0等价因此最终得到字符a求的为字符a的大小54/8对一级指针变量取地址即为二级指针二级指针也是指针因此结果为指针的大小64/8p为二级指针加1越过4个字节空间还是个指针结果为指针的大小74/8p[0]为字符串首元素p[0]即为字符串首元素地址加1后为第二个元素地址是个指针因此结果为指针的大小86p存放着字符串首元素的地址strlen(p)求得的即为字符串长度95p1指向字符串第二个元素因此结果为字符串长度-110非法访问*p得到首元素astrlen(*p)即把a的ASCII码值当作地址传入strlen()函数向后进行计数显然会形成非法访问11非法访问p[0]等价于*(p0,因此结果与10相同形成非法访问12随机值传入p的地址函数从p的低地址处开始向后统计有几个字符。由于我们不知道\0的位置因此最终结果为随机值13随机值p取出p的地址加1后相当于越过指针变量p指向下一地址但我们还是不知道\0的位置因此最终结果为随机值145p[0]相当于*(p0,由于*和可以互相抵消因此p[0]相当于p。所以本语句结果和第9条语句一样都为字符串长度-13.4 总结数组名的意义1. sizeof(数组名)这里的数组名代表整个数组计算的是整个数组的大小2. 数组名这里的数组名表示整个数组取出的是整个数组的地址。3. 除了以上两种情况其他所有的数组名都代表首元素地址。 以上就是本期的全部内容啦制作不易能否点个赞再走呢