指针笔试题①
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取出的是整个数组的地址,加一跳过整个数组,指向5末尾的那个地址处,转化为整型指针。
打印时:a是首元素地址,加一指向2,(跳过四个字节),解引用为2,后面那个因为本身是整型指针,减一向左移动4个字节,所以指向5,解引用为5,打印出5。
指针笔试题②
//由于还没学习结构体,这里告知结构体的大小是20个字节
struct Test
{
int Num;
char* pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
int main()
{
p = (struct Test*)0x100000;
printf("%p\n", p + 0x1);
printf("%p\n", (unsigned long)p + 0x1);
printf("%p\n", (unsigned int*)p + 0x1);
return 0;
}
主要考察指针加减整数。test结构体占20个字节(也就是说一个结构体变量的首元素地址,与末尾的那个地址之间相差20个字节。)
printf1:p是一个struct Test*的指针变量。设定为0x100000。 +0x1,就是加1,只是16进制表示的1而已。p+1,所以跳过20个字节,所以为00100014。(前面的9用于补全)
printf2:把p转化为长整型。存储的就是0x100000(16进制)。加一就是0x100001 这个16进制数字再以%p,即地址的形式打印出来,即00100001。
printf3:p转化为整型指针,加一跳过一个整型的宽度,即跳过4个字节,再以%p打印出来即00100004
指针笔试题③
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,整个数组的地址,加一跳过整个数组,指向4后面的那个地址,转化为整型指针。
ptr1[-1] = *(ptr1-1) 即向前跳过一个整型,指向4,解引用即4
ptr2:a首元素地址,转化为整型加一,如果此时再把此整型转化为地址,其实是比原来的地址向后跳过一个字节(int四个字节)。
内存布局(小端存储):01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 则此时ptr2指向的是01后面的那个00 此时再解引用:整型指针访问四个字节,小端存储模式取出,低地址放在低字节处,高地址放在高字节处,即02 00 00 00 再以%x(16进制整数)形式打印出来,即2000000
指针笔试题④
#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;
}
a[0],即二维数组中第一行的那个一维数组的数组名,数组名表示首元素地址,即第一行的第一个整型的地址,p[0] = *(p+0) 即第一行的第一个整型
注意此处初始化列表中是圆括号(),里面是逗号表达式。所以相当于int a[3][2] = {1,3,5}; 即{ {1,3}, {5,0}, {0,0}}; 所以打印出来是1
指针笔试题⑤
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;
}
5*5的二维数组a,指向含有四个整形的数组的指针p,p=a,a表示首元素地址,即第一行的那个一维数组的地址,这里地址减去地址,表示的是地址之间的元素个数差值(带正负)。如下图,&p[4][2] - &a[4][2] = -4。
第一个以%p形式打印出来,即16进制形式,-4的补码为 111111111111111111111111111111111100,以16进制打印出来就是ff ff ff fc
第二个以%d形式打印出来,就是-4

注意p是一个指向四个整形的数组的指针,图中红框为p眼中没加一之后所指向的数组。宽度为4个整型。
指针笔试题⑥
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存储的是10后面的那个地址
*(aa+1) 就是aa[1] 就是第二行的那个一维数组的数组名,也表示第二行的首元素地址,即6的地址
打印出来是10,5 都是向左跳过一个整型
指针笔试题⑦
#include <stdio.h>
int main()
{
char *a[] = {"work","at","alibaba"};
char**pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
略,打印出来是at
(* )指针笔试题⑧
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;
}

内存布局如上图所示,字符指针数组存储的是四个字符串常量的首元素地址,cp分别交错存储数组c中元素的地址,cpp存储一开始存储的是cp的首元素地址,char** 的地址为char***
1:**++cpp 假设cp数组首元素地址是0x12ff40 , 加加之后即0x12ff41,而cp数组中的元素在内存中又是连续存储的,所以++之后指向的就是cp的第二个元素,再两次解引用,第一次解引用得到cp[1],这里存储的是&c[2],再解引用得到c[2],这里存储的是常量字符串"point"的首元素地址,即p的地址,再以%s打印出来,就是point
后面的与1类似,不做详细解答了,其实可以发现不管哪次printf,里面都包含两次解引用,最后都得到某个常量字符串的首元素地址,后面如果再加一个整数,就是向后移动若干字符。
最后结果为 point,er,st,ew。

注意上图中,cp数组的第二个元素起初为c+2,在进行最后一个printf打印时,不会改变c+2为c+1,它指向的仍然是c[2]。
而第二个printf,++cpp后cpp指向的是cp[2] ,再解引用,得到cp[2],这时候执行-- 操作,起初cp[2]存储的是c[1]的地址,即c+1,执行--操作后,就是c,即c数组的首元素地址,或者也可以理解为向前移动了一个元素,而这个元素是char*,即指针类型,所以向前跳过4个字节指向c数组首元素。这里是会改变cp[2]存储的内容的,这里所说的就是上上图中cp[2]元素的蓝线变成了红线
下面的代码随缘看看
//数组名是什么呢?
//数组名通常来说是数组首元素的地址
//但是有2个例外:
//1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小
//2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址
int main()
{
int a[] = { 1,2,3,4 };
0 1 2 3
int (*p)[4] = &a;
printf("%d\n", sizeof(a));//4*4 = 16
printf("%d\n", sizeof(a + 0));//4/8 a+0是数组第一个元素的地址,是地址,大小就是4/8个字节
printf("%d\n", sizeof(*a)); //4 a表示数组首元素的地址,*a表示数组的第一个元素,sizeof(*a)就是第一个元素的大小-4
printf("%d\n", sizeof(a + 1));//4/8 a表示数组首元素的地址,a+1数组第二个元素的地址,sizeof(a+1)就是第二个元素的地址的大小
printf("%d\n", sizeof(a[1]));//4 计算的是第二个元素的大小
printf("%d\n", sizeof(&a));//4/8 &a取出的是数组的地址,数组的地址也是地址呀,是地址大小就是4/8字节
printf("%d\n", sizeof(*&a));//16 计算的整个数组的大小
printf("%d\n", sizeof(&a + 1));//4/8 - &a是数组的地址,+1跳过整个数组,产生的4后边位置的地址
printf("%d\n", sizeof(&a[0]));//4/8 取出的数组第一个元素的地址
printf("%d\n", sizeof(&a[0] + 1));//4/8 数组第二个元素的地址
字符数组
char arr[] = { 'a','b','c','d','e','f' };//[a b c d e f]
printf("%d\n", strlen(arr));//随机值,arr数组中没有\0,所以strlen函数会继续往后找\0,统计\0之前出现的字符个数
printf("%d\n", strlen(arr + 0));//随机值,arr+0还是数组首元素的地址
//printf("%d\n", strlen(*arr));//err - arr是数组首元素的地址,*arr是数组的首元素,‘a’-97
//printf("%d\n", strlen(arr[1]));//err -'b' - 98
printf("%d\n", strlen(&arr));//随机值
printf("%d\n", strlen(&arr + 1));//随机值
printf("%d\n", strlen(&arr[0] + 1));//随机值
printf("%llu\n", sizeof(arr));//6
printf("%llu\n", sizeof(arr + 0));//4/8 arr + 0是数组首元素的地址
printf("%llu\n", sizeof(*arr));//1 - *arr是首元素,首元素是一个字符,大小是一个字节
printf("%llu\n", sizeof(arr[1]));//1 - arr[1]是数组的第二个元素,大小是1个字节
printf("%llu\n", sizeof(&arr));//4/8 &arr是数组的地址
printf("%llu\n", sizeof(&arr + 1));//4/8 &arr + 1是从数组地址开始向后跳过了整个数组产生的一个地址
printf("%llu\n", sizeof(&arr[0] + 1));//4/8 &arr[0] + 1 是数组第二个元素的地址
return 0;
}
//sizeof是一个操作符
//sizeof 计算的是对象所占内存的大小-单位是字节,size_t
//不在乎内存中存放的是什么,只在乎内存大小
//
//strlen 库函数
//求字符串长度,从给定的地址向后访问字符,统计\0之前出现的字符个数
int main()
{
char arr[] = { 'a', 'b', 'c','d', 'e', 'f' };
char arr[] = "abcdef";
//[a b c d e f \0]
printf("%d\n", strlen(arr));//6
printf("%d\n", strlen(arr + 0));//6
//printf("%d\n", strlen(*arr));//err
//printf("%d\n", strlen(arr[1]));//err
printf("%d\n", strlen(&arr));//6
printf("%d\n", strlen(&arr + 1));//随机值
printf("%d\n", strlen(&arr[0] + 1));//5
printf("%d\n", sizeof(arr));//7
printf("%d\n", sizeof(arr + 0));//4/8 arr+0是数组首元素的地址
printf("%d\n", sizeof(*arr));//1 - *arr 数组的首元素
printf("%d\n", sizeof(arr[1]));//1 arr[1]数组的第二个元素
printf("%d\n", sizeof(&arr));//4/8 - &arr数组的地址,但是数组的地址依然是地址,是地址大小就是4/8
printf("%d\n", sizeof(&arr + 1));//4/8 - &arr + 1是\0后边的这个地址
printf("%d\n", sizeof(&arr[0] + 1));//4/8 - &arr[0] + 1是数组第二个元素的地址
char* p = "abcdef";
printf("%d\n", strlen(p));//6
printf("%d\n", strlen(p + 1));//5 从b的位置开始向后数字符
//printf("%d\n", strlen(*p)); //err
//printf("%d\n", strlen(p[0]));//err
printf("%d\n", strlen(&p));//随机值
printf("%d\n", strlen(&p + 1));//随机值
printf("%d\n", strlen(&p[0] + 1));//5 从b的位置开始向后数字符
printf("%d\n", sizeof(p));//4/8 p是指针变量,计算的是指针变量的大小
printf("%d\n", sizeof(p + 1));//4/8 p+1是'b'的地址
printf("%d\n", sizeof(*p)); //1 - *p 其实就是'a'
printf("%d\n", sizeof(p[0]));//1 - p[0]-> *(p+0)-> *p
printf("%d\n", sizeof(&p));//4/8 &p 是指针变量p在内存中的地址
printf("%d\n", sizeof(&p + 1));//4/8 - &p+1是跳过p之后的地址
printf("%d\n", sizeof(&p[0] + 1));//4/8 &p[0]是‘a’的地址,&p[0]+1就是b的地址
二维数组
int a[3][4] = { 0 };
printf("%d\n", sizeof(a));//计算的是整个数组的大小,单位是字节3*4*4 = 48
printf("%d\n", sizeof(a[0][0]));//4 第1行第一个元素的大小
printf("%d\n", sizeof(a[0]));//16 - a[0]是第一行的数组名,sizeof(a[0])就是第一行的数组名单独放在sizeof内部,计算的是第一行的大小
printf("%d\n", sizeof(a[0] + 1));//4/8 a[0]作为第一行的数组名,并没有单独放在sizeof内部,也没有被取地址
所以a[0]就是数组首元素的地址,就是第一行第一个元素的地址,a[0]+1就是第一行第二个元素的地址
printf("%d\n", sizeof(*(a[0] + 1)));//4 - *(a[0] + 1))表示的是第一行第二个元素
printf("%d\n", sizeof(a + 1));//4/8 - a表示首元素的地址,a是二维数组,首元素的地址就是第一行的地址
所以a表示的是二维数组第一行的地址,a+1就是第二行的地址
printf("%d\n", sizeof(*(a + 1)));//16 对第二行的地址解引用访问到就是第二行
*(a+1) -> a[1]
sizeof(a[1])
printf("%d\n", sizeof(&a[0] + 1));//4/8 - a[0]是第一行的数组名,&a[0]取出的就是第一行的地址
&a[0] + 1 就是第二行的地址
printf("%d\n", sizeof(*(&a[0] + 1)));//16 - 对第二行的地址解引用访问到就是第二行
printf("%d\n", sizeof(*a));//16 - a就是首元素的地址,就是第一行的地址,*a就是第一行
*a - > *(a+0) -> a[0]
printf("%d\n", sizeof(a[3]));//16 int [4]
int a = 10;
printf("%d\n", sizeof(a));//4
printf("%d\n", sizeof(int));//4
return 0;
}
本文解析了五个指针相关面试题,涉及数组指针运算、结构体指针、数组与指针算术、动态数组与指针、以及数组名和指针的特殊用法。通过实例演示,帮助理解指针在内存中的行为和操作技巧。

1235

被折叠的 条评论
为什么被折叠?



