继上次分享指针(上)后,又整理好指针(下)分享给大家,整理得有些仓促,肯定有很多不足的地方,希望大佬们多多指正。
5 指向函数指针数组的指针
上次我们解释了什么是函数指针,那指向函数指针数组的指针就好给大家解释了,我们直接看代码
void test(const char* str) { printf("%s\n", str); } int main() { //函数指针pfun void (*pfun)(const char*) = test; //函数指针的数组pfunArr void (*pfunArr[5])(const char* str); pfunArr[0] = test; //指向函数指针数组pfunArr的指针ppfunArr void (*(*ppfunArr)[5])(const char*) = &pfunArr; return 0; }
6 回调函数
我们首先来看看回调函数的定义是什么?
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
这么看还是有点抽象,那我们用代码来具体了解一下回调函数:
我们自己来实现一下qsort函数
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> #include<string.h> typedef struct student { char name[20]; int age; }stu; int com_stu_age(const void* e1, const void* e2) { return((stu*)e1)->age - ((stu*)e2)->age; } int cmp_stu_name(const void* e1, const void* e2) { return strcmp(((stu*)e1)->name, ((stu*)e2)->name); } void swap(char* buf1, char* buf2, int width) { int i = 0; for (i = 0; i < width; i++) { char ret = *buf1; *buf1 = *buf2; *buf2 = ret; buf1++; buf2++; } } void bubble_sort(void* base, int sz, int width, int(*cmp)(void*, void* )) { int i = 0; for (i = 0; i < sz - 1; i++) { for (int j = 0; j < sz - 1 - i; j++) { if(cmp((char*)base+j*width,(char*)base+width*(j+1))>0) swap((char*)base + j * width, (char*)base + (j + 1)*width ,width); } } } int main() { stu s[3] = { {"guorumeng",19},{"baoxingzhi",18},{"wangwu",20} }; int sz = sizeof(s) / sizeof(s[0]); int width = sizeof(s[0]); //void bubble_sort(s, sz, width, com_stu_name); bubble_sort(s, sz, width, com_stu_age); return 0; }
代码中可以看到我们将函数com_stu_age作为参数传给了bubble_sort函数中,接受该参数的就是一个函数指针,我们调用该函数指针就好了。这样的好处是用户想比较什么类型就定义该类型的比较函数,传入的函数该函数指针都能够接收,调用该函数就变得更加方便。
7 指针和数组的笔试题解析
这里我们重点挑了几个比较容易出错的题来讲解:
char arr[] = {'a','b','c','d','e','f'}; printf("%d\n", sizeof(arr)); printf("%d\n", sizeof(arr+0)); printf("%d\n", strlen(arr)); printf("%d\n", strlen(arr+0));
这里问一下第一个与第二个是相同的吗?第三个和第四个是相同的吗?
那这里我就要问问大家什么时候arr代表整个数组?什么时候代表首元素地址?
1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。
2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
3. 除此之外所有的数组名都表示首元素的地址。
这样我们就好解释了,第一个代表的就是整个数组的大小,第二个代表的是首元素地址,第三个和第四个代表的就是首元素地址,那么这道题就好解决了。
再来看一段有趣的代码:
int a[3][4] = {0}; printf("%d\n",sizeof(a)); printf("%d\n",sizeof(a[0])); printf("%d\n",sizeof(a[0]+1)); printf("%d\n",sizeof(*(a[0]+1))); printf("%d\n",sizeof(a+1)); printf("%d\n",sizeof(*(a+1))); printf("%d\n",sizeof(&a[0]+1)); printf("%d\n",sizeof(*(&a[0]+1))); printf("%d\n",sizeof(*a)); printf("%d\n",sizeof(a[3]));
有了上一题的经验,我们可以知道第一个和第二个都是数组名,只不过第一个代表的就是整个数组元素的大小,第二个代表的是第一行数组元素的大小。第3个代表的是第一行第二个元素的地址。第5个和第7个都是数组第二行的地址。倒数第二个计算的是第一行元素的大小。那么最后一个又是什么意思呢?是越界访问了吗?
答案不是的,我们的知道sizeof后面的内容其实是不会真实计算的,计算的只是类型,就像之前我们计算一个整形变量的大小用sizeof(int)是一样的道理,就相当于计算的是a[0]。
这种题归根结底就是我们是否深刻理解了上面关于整个数组和首元素地址的理解
我们再来加深一下印象:
1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。
2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
3. 除此之外所有的数组名都表示首元素的地址。
8 指针笔试题
这里的一些题就有了一些难度,话不多说,直接上题:
8.1
int main() { int a[5] = { 1, 2, 3, 4, 5 }; int *ptr = (int *)(&a + 1); printf( "%d,%d", *(a + 1), *(ptr - 1)); return 0; } //程序的结果是什么?
这道题算是个热身,难度不大。(a+1)是第二个元素的地址,再解引用得到的元素就是2. &a取出的是整个数组的地址,&a+1就是向后跳过了一个数组,本来取出数组的地址应该用数组指针存放,但是这里强制转化成了一个int*类型,那么(ptr-1)就是向后跳过一个整形,再解引用得到的就是5.
8.2
//由于还没学习结构体,这里告知结构体的大小是20个字节 struct Test { int Num; char *pcName; short sDate; char cha[2]; short sBa[4]; }*p; //假设p 的值为0x100000。 如下表表达式的值分别为多少? //已知,结构体Test类型的变量大小是20个字节 int main() { printf("%p\n", p + 0x1); printf("%p\n", (unsigned long)p + 0x1); printf("%p\n", (unsigned int*)p + 0x1); return 0; }
第一个p+0x1其实就是相当于p+1,由于p是一个结构体指针,在进行+1操作时向后跳过了一个结构体指针,由于结构体类型的变量大小是20字节,转化为16进制为就是0x100014;第二个p转化成无符号的类型,就相当于一个数字,加1后就变成了0x100001.同理,最后一个用地址形式打印的结果有0x100004。
8.3
int main() { int a[4] = { 1, 2, 3, 4 }; int *ptr1 = (int *)(&a + 1); int *ptr2 = (int *)((int)a + 1); printf( "%x,%x", ptr1[-1], *ptr2); return 0; }
这道题与上一题差不多,ptr1[-1]就相当与*(ptr1-1),用16进制打印后的结果为4.第二个将a的首元素地址转化为整形,进行+1操作时进行算数+1运算,然后转化成(int*),就相当于ptr2向前跳动了一个字节,要想得到解引用的结果就得了解该数组中数字的存储方式,由于目前用的大多数编译器采用的是小端存储方式,所以可以简单的表示为01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00
由于现在地址向前跳动了一个字节,现在ptr2指向的就是第一个00,解引用后的结果就是2000000.
8.4
#include <stdio.h> int main() { int a[3][2] = { (0, 1), (2, 3), (4, 5) }; int *p; p = a[0]; printf( "%d", p[0]); return 0; }
这道题有点意思的是出现了逗号表达式,逗号表达式结果是最后一个表达式的值,有了之前对于指针和数组的理解,那么这个结果就很简单了,结果是1。
8.5
int main() { int a[5][5]; int(*p)[4]; p = a; printf( "%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]); return 0; }
这道题中a[4][2]很好理解,关键是p[4][2]怎样理解?
这里画了一张图,有点丑,大家理解一下就好了
本来将a的第一行地址传给一个数组指针应该用int (*)[5]类型的来接受,但是p的类型却是int(*)[4],所以就出现了上面图示的效果,而指针-指针表示的是元素间的个数,所以用%d形式打印的结果就是-4,%p打印结果换算一下就知道了。
8.6
int main() { char *c[] = {"ENTER","NEW","POINT","FIRST"}; char**cp[] = {c+3,c+2,c+1,c}; char***cpp = cp; printf("%s\n", **++cpp); printf("%s\n", *--*++cpp+3); printf("%s\n", *cpp[-2]+3); printf("%s\n", cpp[-1][-1]+1); return 0; }
这道题算是这些题中最复杂的了,为了更好的理解,仍然需要画图来帮助理解
首先来看第一个:先++cpp,这时cpp指向的就是c+2,然后解引用,拿到的就是c+2中的内容,而c+2中存放的是c[2]的地址,再解引用拿到的就是c[2]的内容,c2中存放的就是常量字符串"POINT"首元素的地址,用%s打印出来就是POINT。
第二个:先++cpp,这时cpp指向的就是c+1,然后解引用得到的就是c+1中的值,c+1中存放的是c[1]的地址,进行--操作后存放的地址是c[0]的地址,再解引用后就是c[0]的内容,最后+3拿到的就是常量字符串"ENTER"中E的地址,用%s打印出来就是ER。
第三个:cpp[-2]拿到的是c+3的内容,也就是c[3]的地址,解引用后拿到的是常量字符串"FIRST"首元素地址,+3后拿到的就是S的地址,用%s打印出来就是ST。
第四个:cpp[-1][-1]拿到的c[1]的内容,也就是常量字符串"NEW"首元素地址,+1后拿到的是E的地址,用%s打印后的结果为EW。
好啦,今天的分享就到这里了,希望各位佬们指出文章中不足的地方,栓Q。