掌握指针这块知识的重要性不言而喻,当你觉得自己已经差不多掌握指针的时候,不妨看看下面8道面试题(题目从简单到困难,读者根据自己水平选择题目难度),答案在文末公布,如果你都做对了,那说明你对指针这一块知识的掌握还是非常不错的,当然了,如果你的表现还欠那么一点点,不妨看看这篇文章对指针的详解。
问下面8道面试题分别输出什么?
1.
int main()
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
int *p;
p = a[0];
printf( "%d", p[0]);
return 0;
}
2.
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
int *ptr = (int *)(&a + 1);
printf( "%d,%d", *(a + 1), *(ptr - 1));
return 0;
}
3.
int main()
{
int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int *ptr1 = (int *)(&aa + 1);
int *ptr2 = (int *)(*(aa + 1));
printf( "%d,%d", *(ptr1 - 1), *(ptr2 - 1));
return 0;
}
4.
int main()
{
int a[5][5];
int(*p)[4];
p = (int(*)[4])a;
printf( "%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
return 0;
}
5.
struct Test
{
int Num;
char *pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
int main()
{
printf("%p\n", p + 0x1);
printf("%p\n", (unsigned long)p + 0x1);
printf("%p\n", (unsigned int*)p + 0x1);
return 0;
}
6.
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;
}//这里采用小端存储
7.
int main()
{
char *a[] = {"work","at","alibaba"};
char**pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
8.
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;
}
字符指针是一种使用起来相对比较简单的指针,这里讲一讲其比较特殊的一种使用方法
int main()
{
char*p="hello world!";
return 0;
}
这是把字符串常量 “hello world!” 的首字符 ‘ h ’ 的地址存到指针变量 p 中,通过p就可以找到该字符串的首字符,继而找到整个字符串,而该字符串则是存到代码区中去了。
指针数组是存放指针的数组,与我们平时使用的数组没有多大区别,只不过指针数组的元素是指针而已。
char* arr1[3];//一级字符指针数组
int* arr2[3];//一级整型指针数组
int** arr3[3];//二级整型指针数组
数组指针是指向数组的指针。如指向一个大小为10个整型的数组的指针为:
int (*ptr)[10];
ptr先与*结合,表明ptr是指针变量,接着指向大小为10个整型的数组,如果没有括号则是定义指针数组。接下来我们可以看看以下代码表示什么意思?
int (*ptr[3])[5];
ptr先与[3]结合,表明ptr是数组名,接着与*结合,表明数组元素为指针类型,最后与[5]结合,表明这3个指针都是指向大小为5个整型的数组。
其实ptr就相当于指向一个三维数组,该数组有3行1列且高为5,即ptr[3][1][5]。
这里补充一下数组名的理解:
数组名都是表示首元素的地址,但有2个例外:
1.sizeof(数组名)
此时数组名表示整个数组的地址,sizeof计算的是整个数组的大小,单位是字节。
2.&数组名
这里的数组名表示的是整个数组的地址,因而取地址符取出的是整个数组的地址。
对于数组 int arr[5] 来说,arr与&arr表示的地址是一样的,但他们的数据类型不一样,导致他们在+1时跳跃的距离不一样,如arr+1,地址跳跃了4(一个int类型的大小),而&arr+1则跳跃了20(4*5=20)。
函数指针是指向函数的指针,其形式为:
函数返回值类型 (*函数指针变量名)(参数类型);
例:
//定义一个函数
int add(int x,int y)
{
return x+y;
}
int main(void)
{
int (*p)(int,int);//定义函数指针
p=add;//1
p=&add;//2
//1和2这两种赋值方式没有什么区别
int result=0;
result=p(3,5);//3
result=(*p)(3,5);//4
//3和4这两种使用方式也没有什么区别
return 0;
}
函数指针看上去是比较容易上手的,接下来我们看看两段有意思的代码:
1.
(*(void(*)())0)();
该代码是调用0地址处的函数。
2.
void (*signal(int,void(*)(int)))(int);
该代码是signal函数的声明,相当于
void (*fun)(int);
fun signal(int,fun);
感兴趣的读者可以自行分析原因,这里不过多赘述。
函数指针数组是存放函数指针的数组,其定义方式为:
函数返回类型 (*数组名[元素个数])(函数参数列表);
函数指针数组比较大的一个用途就是在转移表上。
如在两位整数的加减乘除运算中,要编写4个函数,输入1调用加函数,输入2调用减函数,以此类推,那我们就要用很多个if语句或case来选择调用哪个函数,但当我们使用函数指针数组时,代码就会大大简化。只需要一个intput接收输入的值,接着调用下标为intput的函数指针指向的函数就行了。
int (*p[4])(int int);
...//数组赋值
scanf("%d",&input);
int result=(*(p[input]))(1,7);//p为函数指针数组名,返回结果由result接收
//自己要安排好哪个函数在数组的什么位置
其定义形式为
函数返回类型 (*(*指针变量名)[所指向的函数指针数组的元素个数])(参数列表)
void (*pfun1[5])(int);//定义函数指针数组
void (*(*ppfun2)[5])(int)=&pfun1;//定义指向函数指针数组的指针并指向pfun1
倘如读者了解了数组指针和函数指针,理解这个应该不成问题。
面试题答案
1
2,5
10,5
fffffffc,-4
100020 100001 100004
4,2000000
at
POINT ER ST EW
分析:
1.
int main()
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
int *p;
p = a[0];
printf( "%d", p[0]);
return 0;
}
2.
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
int *ptr = (int *)(&a + 1);
printf( "%d,%d", *(a + 1), *(ptr - 1));
return 0;
}
int main()
{
int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int *ptr1 = (int *)(&aa + 1);
int *ptr2 = (int *)(*(aa + 1));
printf( "%d,%d", *(ptr1 - 1), *(ptr2 - 1));
return 0;
}
*(ptr1 - 1)的计算方法和第2题一样,这里就不讲了
4.
int main()
{
int a[5][5];
int(*p)[4];
p =(int(*)[4])a;
printf( "%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
return 0;
}
5.
struct Test
{
int Num;
char *pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
int main()
{
printf("%p\n", p + 0x1);
printf("%p\n", (unsigned long)p + 0x1);
printf("%p\n", (unsigned int*)p + 0x1);
return 0;
}
在64位机器下,这里结构体Test的大小经过计算是32个字节(64位机器下,指针的大小是8个字节),如果结构体的大小不会计算的,可以去看看我的另一篇关于如何计算结构体大小的文章
亦星编程
0x1是10进制的1
p是结构体指针,指向的是一个Test结构体的地址,p+0x1跳过一个结构体类型的大小,即要加32,最后以16进制打印就是0x100020
(unsigned long)p + 0x1中p被强制类型转化为无符号长整型(已经不是指针了,而是一个整数),整数0x100000加1就是0x100001,最后以16进制打印就是100001
unsigned int*)p + 0x1中,p被强制转换为无符号整型指针,p指向的是一个无符号整数的地址,加1跳过4个字节(一个无符号整型的大小),最后以16进制打印就是0x100004。
6.
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;
}//这里采用小端存储
&a+1跳过整个数组,接着转化int*指针,故ptr[-1]就是4
7.
int main()
{
char *a[] = {"work","at","alibaba"};
char**pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
8.
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;
}
其实就是指向内存某个地方的地址,尽管该地址处的数据不变,但由于指针类型不一样,读取数据的方式就不同,结果就不一样,如整型指针解引用会在该地址处向后读取4个字节的数据,而字符型指针解引用只会在该地址处向后读取一个字节的数据。以上就是本文的所有内容,如果有什么不对的地方,恳请指正,当然了,如果你觉得本文对你有帮助,不妨动动小手给博主一个点赞收藏加关注吧。