前言:必备知识回忆
1.数组名的意义
i.sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小
ii.&数组名,这里的数组名表示整个数组,取出的是整个数组的地址
iii.除此之外,所有的数组名都表示首元素的地址。
2.数组与指针的关系
对于数组和指针操作来说,a[i] 和 *(a+i) 是等效的,也就是说,a[i] 其实就是 *(a+i) 的简写形式。因此,p[0] 实际上就相当于 *(p+0),它表示 p 所指向的数组的第一个元素的值。
常见的数组和指针用法如下:
- a[i]:表示数组 a 中的第 i 个元素的值。
- *(a+i):表示 a+i 所指向的地址中存储的值(也就是 a 数组中第 i 个元素的值)。
- p[i]:表示指针变量 p 所指向的数组中的第 i 个元素的值。
- *(p+i):表示 p+i 所指向的地址中存储的值(也就是指针 p 所指向的数组中第 i 个元素的值)。
因此,p[0] 表示的就是指针变量 p 所指向的数组中的第一个元素的值,也就是 *(p+0)。
question one
#include<stdio.h>
int main()
{
int a[5] = { 1,2,3,4,5 };
int* ptr = (int*)(&a + 1);
printf("%d,%d", *(a + 1), *(ptr - 1));
return 0;
}
打印结果:2 5
分析:&a取出的是整个数组的地址,&a+1指向了数组后面的地址,(int *)代表强制类型转换,把本身访问权限为20个字节强制转换为只能访问4个字节,因此后面的ptr-1指针指向了5的地址,解引用后得到的结果为5。对于*(a+1),a并没有与&操作符结合,也没有单独放在sizeof内部,因此只是代表首元素的地址,所以解引用后得到2。
question two
#include<stdio.h>
//由于还没学习结构体,这里告知结构体的大小是20个字节
struct Test
{
int Num;
char* pcName;
short sDate;
char chap[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);
}
打印结果:0x100014
0x100001 (在 32 位系统中)
0x100004
分析: 对于表达式p+0x1,指针p 加上0x1
后,会将指针向前移动 20 个字节,即一个Test 类型的大小。因此,表达式的值为 0x100014
。对于表达式 (unsigned long)+0x1,指针 p
首先会强制转换为unsigned long类型,然后再加上 0x1
,即将指针地址加上 1。因为unsigned long类型大小为 4 字节或 8 字节(取决于系统),所以结果可能有所不同。在 32 位系统中,该表达式的值为 0x100001
。对于表达式(unsigned int *)p+ 0x1
,指针 p
先转换成unsigned int *类型的指针,然后再加上 0x1
,即将指针地址加上 4 个字节(因为unsigned int 类型的大小为 4 个字节)。因此,表达式的值为 0x100004
。
question three
#include<stdio.h>
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);//注:%x是以十六进制的形式打印
return 0;
}
打印结果:4,2000000
分析: ptr1[-1]可以转化为*(ptr1-1),&a+1取出的是整个数组的地址加一跳过了这个数组,前面的(int*)把这个数组指针强制类型转化为整形指针,所以ptr[-1]后退了四个字节,指针指向了4的前面,解引用得到4。 地址a前面加上了(int)从一个指针强制转化为了整形,加1跳过了一个字节,而一个整形占四个字节,所以我们要在内存中展开再进行计算。最终得到结果0x02000000。
question four
#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
分析:这里其实存在陷阱,在初始化该二维数组时,花括号里面采用了逗号表达式进行初始化,而不是直接赋值,因此第一行代码可以转化为:int a[3][2]={ 1 , 3 , 5 }; 题目中a[0]代表数组第一行的数组名,既没有&,也没有直接放在sizeof内部,因此这里就代表首元素1的地址,即把1的地址交给了指针变量p。p[0]可以转换为*(p+0),因此结果为1。
question five
#include<stdio.h>
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;
}
运行结果:FFFFFFFC,-4
分析:题目中把二维数组a的首元素地址放在了指针数组p中,也就是第一行的地址a[0],但是第一行如果完整存储在指针数组中需要的是int (*p)[5],虽然虽然类型有差异,但是还是可以赋过去,地址都是指向数组的起始位置。它的访问权限是16个字节,所以p+1跳过4个整形,以此类推……
p[4]可以转化为*(p+4),p[4][2]可以转化为*(*(p+4)+2).
由前面指针相减的知识可以知道,指针减指针结果为指针之间元素的个数,通过画图我们清晰的可以看到,两指针之间相差四个元素。又因为随着数组下标增长,地址由低到高变化,题目中是低地址减高地址,所以结果为负,得到-4,所以通过%d打印得到-4. -4在内存中以补码的形式存储,二进制位表示为:11111111111111111111111111111100,以%p形式打印时为十六进制形式,转化为十六进制就是:FFFFFFFC
question six
#include<stdio.h>
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;
}
运行结果:10,5
分析:&arr取出的是整个数组的地址,加一就是跳过了整个数组,通过(int *)强制类型转换,使其只能访问四个字节,所以*(ptr1-1)结果为10。 *(aa+1)可以看作aa[1],也就是第二行数组的数组名,数组名既没有直接&,又没有放在sizeof内部,所以代表数组首元素的地址,就算没有前面的强制类型转换,其也是(int *)类型,所以*(ptr2-1)最终结果为5。
question seven
#include<stdio.h>
int main()
{
char* a[] = { "work","at","alibaba" };
char** pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
运行结果:at
分析:数组a是一个指针数组,数组的每个元素都是char* ,这里pa = a ;把数组a首元素的地址(即“work”的首字母w)放在了二级指针pa中,pa++后,指针指向了数组a的第二个元素(即“at”的首字母a)的地址,最后通过解引用pa,找到了a的地址,当使用 printf("%s", *pa)
打印 *pa 的内容时,它会从a的地址开始输出字符,直到遇到字符串结束符\0。因此最终结果为“at”。
question eight
#include<stdio.h>
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;
}
运行结果:POINT
ER
ST
EW
分析:解决这道题的方法就是画图,通过画图梳理表达式之间的关系,关系理清了,注意不要混淆,答案也就出来了。 ++操作符的优先级比*高,所以++cpp后,cpp就指向cp的第二个元素通过两次解引用,得到单词“POINT”的首字母P的地址,通过打印可以直接打印出来POINT,第一问得解。 第二问:++cpp后,cpp指向了数组cp的第三个元素,通过解引用找到了指向字符"NEW"首字母的指针,又通过--操作,该指针又指向了"ENTER"首字符的地址,解引用后得到了“ENTER”首字符的地址,然后又+3,指针往后跳了三个字符,得到打印结果:ER。 后面两问也是同样的道理,依次计算即可。
通过这部分的指针练习,你对指针的理解肯定更加深刻了,快去做个思维导图总结一下指针的内容吧,指针是数据结构的重要基础,一定不能草草放过去~