
继上文【C语言】指针笔试题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;
}输出

解析:
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
int* ptr = (int*)(&a + 1);
//&a为数组指针,&a + 1则跳过整个数组,
//即此时指向数组最后一个元素(5)的后面
//(int*)(&a + 1)又将它强制类型转化为int*的指针
printf("%d,%d", *(a + 1), *(ptr - 1));
//a数组名首元素地址,a + 1则为的第二个元素地址,
// *(a + 1)解引用则为2;
//ptr为int*的指针,指向数组最后一个元素(5)的后面,
//ptr - 1则指针向后退4个字节(因为指针为int*),指向数组最后一个元素,
//*解引用则为5;
return 0;
}代码:
//这里直接告诉大家结构体Test类型的变量大小是20个字节
struct Test
{
int Num;
char* pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;
//假设p 的值为0x0。 如下表表达式的值分别为多少?
int main()
{
printf("%p\n", p + 0x1);
printf("%p\n", (unsigned long)p + 0x1);
printf("%p\n", (unsigned int*)p + 0x1);
return 0;
}输出(都为16进制):

解释:
int main()
{
printf("%p\n", p + 0x1);
//p + 0x1,跳过一个结构体大小,故为0x14(16进制);
printf("%p\n", (unsigned long)p + 0x1);
//(unsigned long)p,将p强制类型转化为无符号长整形,
//(unsigned long)p + 0x1则为0x1;
printf("%p\n", (unsigned int*)p + 0x1);
//(unsigned int*)p将结构体指针强制类型转换为无符号整型指针,
//且unsigned int为4个字节,整数运算:
//所以(unsigned int*)p + 0x1则跳过四个字节,故为0x4
return 0;
}看代码:
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;
}输出:

解释:
int main()
{
int a[4] = { 1, 2, 3, 4 };
int* ptr1 = (int*)(&a + 1);
//&a数组地址,
//&a + 1数组指针跳过整个数组,即指向数组最后一个元素的后面
//(int*)(&a + 1)强制类型转换,将数组指针转化为(int*)类型的指针;
//即ptr1为指向数组最后一个元素后面int*指针
int* ptr2 = (int*)((int)a + 1);//下面画图解释
printf("%x\n", ptr1[-1]);
printf("%x\n", *ptr2); //下面画图解释
//ptr1[-1]可以转换为*(ptr-1),
//ptr - 1,ptr为int*指针,-1则为向后退4个字节,指向数组最后一个元素;
//故输出为4;
return 0;
}咱们主要来分析一下这两行代码(干货哦!!!):
int* ptr2 = (int*)((int)a + 1);
printf("%x\n", *ptr2); 在此之前,咱们要对大小端字节序了解,可以看我关于整形在内存中的存储的介绍。 假设咱们是小端字节序,则数组中元素存储是这样的:

了解了这个,来分析一下代码:
(int)a:a是数组首元素地址,(int)a是将a强制类型转化为int型的数据;
((int)a + 1):假设a是0x1000,(int)a + 1此时为整数运算则为0x1001,
(int*)((int)a + 1):又将(int)a + 1强制类型转化为int型的指针,那它此时指向哪里呢,看图
a指针指向示意图:

(int)((int)a + 1)指针指向示意图:

那对于*ptr2怎么理解呢?
因为ptr2是int型的指针,所以解引用访问四个字节,有因为咱们是小端字节序,所以它是从从ptr2指针指向的位置起(0x1001)向后访问4个字节(int)内容,即为*ptr2
图示:

16进制则为0x02000000; 当然,咱们也可以试试大端字节序的情况下是什么样的。
看代码:
#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;
}输出:

解释:
#include <stdio.h>
int main()
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
//重点在这:
//(0, 1)逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果。
//即数组可写为int a[3][2] = { 1, 3, 5 };后面默认为0;
int* p;
p = a[0];
//a[0]为第一行的数组名
//数组名为首元素地址
//即p=&a[0][0]
printf("%d", p[0]);
//p[0]可以这样看*(p+0);
//故为1;
return 0;
}看代码:
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;
}输出:

解释:
int main()
{
int a[5][5];
int(*p)[4];
//一维数组指针
p = a;
//首先,咱们先对p与a的类型进行分析:
// p是int(*)[4];a是int(*p)[5];这个是要明白的,然后分析代码;
// 对于a与p他俩的区别是什么呢:
//a + 1 会跳过 5 * sizeof(int) 字节(通常 20 字节)
//p + 1 会跳过 4 * sizeof(int) 字节(通常 16 字节)
printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
return 0;
}首先,先来看数组a的示意图:

咱们先来看a[4][2]所在的位置,很简单:

那来看看p[4][2]:

将两者放一起:

那么对于&p[4][2] - &a[4][2],即指针相减,得到的是两个指针之间的元素,为-4;
对于%d形式的输出,就为-4;
但对于%p形式的输出呢?
解释:
代码:
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;
}输出:

解释:
int main()
{
int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int* ptr1 = (int*)(&aa + 1);
//&aa取出整个数组的地址
//&aa+1跳过整个数组,指向数组最后一个元素的后面
//(int*)(&aa + 1)将数组指针强制类型转化为int*类型的
//即ptr1是一个int*的指针,且指向数组最后一个元素的后面
int* ptr2 = (int*)(*(aa + 1));
//aa二维数组的数组名是第一行的地址,类型为int(*)[5]
//aa + 1第二行地址,类型为int(*)[5]
//*(aa + 1),可以这样写aa[1]
//即数组第二行元素,为一维数组
//一维数组数组名是首元素地址
//首元素地址类型为int*
//(int*)(),多余
//即ptr2指向数组第二行第一个元素
printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));
//*(ptr1 - 1)数组最后一个元素10;
//*(ptr2 - 1)数组第一行最后一个元素5
return 0;
}看代码:
#include <stdio.h>
int main()
{
char* a[] = { "work","at","alibaba" };
char** pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}输出:

解释:
#include <stdio.h>
int main()
{
char* a[] = { "work","at","alibaba" };
//对字符指针数组进行解释:
//它是将三个字符串的首元素地址存储在数组中
char** pa = a;
//a 是数组名,在大多数表达式中会退化为指向首元素的指针,即 pa 为 &a[0](类型是 char**)。
//pa 存储的是 a[0] 的地址(即 &a[0])
pa++;
//pa 原本指向 a[0],pa++ 后指向 a[1](即 &a[1])
printf("%s\n", *pa);
//*pa 得到 a[1],即 'at' 的首地址,printf 打印这个字符串";
return 0;
}这道题需要考虑的点较多,仔细点哦!!! 代码:
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* c[] = { "ENTER","NEW","POINT","FIRST" };
//数组 c 的每个元素(c[0]、c[1] 等)存储的是指向对应字符串常量的指针:
//c[0] → 指向 "ENTER" 的首地址(即字符 'E' 的地址)。
//c[1] → 指向 "NEW" 的首地址。
//c[2] → 指向 "POINT" 的首地址。
//c[3] → 指向 "FIRST" 的首地址。
char** cp[] = { c + 3,c + 2,c + 1,c };
//c数组名为首元素地址,即&c[0];
//c + 3 → 指向c[3] cp[0]
//c + 2 → 指向c[2] cp[1]
//c + 1 → 指向c[1] cp[2]
//c → 指向c[0] cp[3]
char*** cpp = cp;
//cp数组名为首元素地址,即&cp[0];
//cpp → 指向cp[0]
//搞清楚上面这部分,就很简单了
//注意:++cpp会产生副作用,会使得cpp改变
printf("%s\n", **++cpp);
//++cpp → 指向cp[1];
//*++cpp 为cp[1]是c + 2 → 指向c[2];
//**++cpp 为c[2];
//则输出为"POINT"
printf("%s\n", *-- * ++cpp + 3);
//注意操作符优先级
//++cpp → 指向cp[2];
//* ++cpp为cp[2] → 指向c[1]
//-- * ++cpp → 指向c[0]
//*-- * ++cpp 为c[0] → 指向 "ENTER" 的首地址
//*-- * ++cpp + 3 → 指向 "ENTER" 的'E'的地址
//则输出为"ER"
printf("%s\n", *cpp[-2] + 3);
//由于此时cpp → 指向cp[2]
//cpp[-2]等效为*(cpp-2)
//cpp-2 → 指向cp[0]
//*(cpp-2)即为cp[0];
//*(cp[0])即为c[3] → 指向 "FIRST" 的首地址。
//+ 3 → 指向 "FIRST" 的'S'的地址
//则输出为"ST"
printf("%s\n", cpp[-1][-1] + 1);
//cpp[-1][-1]+1等效为*(*(cpp-1)-1)+1;
//先看cpp-1 → 指向cp[1];
//*(cpp-1)则为cp[1] → 指向c[2];
//*(cpp-1)-1 → 指向c[1];
//*(*(cpp-1)-1)则为c[1] → 指向 "NEW" 的首地址。
//+1 → 指向 "NEW" 的'E'的地址;
//则输出为"EW"
return 0;
}指针到这就结束了,但我们的学习尚未结束,希望大家对指针都有各自的理解。 谢谢学习至此!!!