下面这段代码的执行结果是?
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
int* ptr = (int*)(&a + 1);
printf("%d,%d\n", *(a + 1), *(ptr - 1));
return 0;
}
解决这种数组与指针相结合的问题,关键在于认清代码中的数组名到底表示什么。一般情况下:
两个例外:
sizeof(数组名)
:计算的是整个数组的大小(单位是字节)&数组名
:取到的是整个数组的地址,&数组名+1
跳过一整个数组 所以上述代码中的&a
得到的就是整个数组的地址,如果用一个指针变量来存储这个地址的话,指针变量的类型是:nt (*) [5]
,&a+1
跳过一整个数组,表示紧接着a
数组后面的一个地址,这个地址还是表示一个数组的地址,然后把这个地址强制转换成整型指针,此时这个地址表示一个整型的地址。虽然&a+1
从一个数组的地址变成了一个整形的地址,但是它们在数值上却没有发生任何变化,那把一个数组地址强制转换成整型地址的意义在哪呢?意义在于:不同类型的地址访问到的内存空间大小会有所不同,比如:一个整型地址只能访问到从当前地址开始,连续的四个字节的内容(一个地址指向一个字节);一个字符的地址只能访问到当前地址所指向的一个字节的内容;一个数组的地址可以访问到整个数组所占用的字节内容。把一个数组的地址强制转换成整型的地址,就好比把酒店中的总统套房改造成一个标间,虽然它们的门牌号都相同,但由于它们的房间类型发生了,所以里面房间的大小就会发生改变,从原来200平的总统套房,变成如今只有90平的标间。对地址类型的转变,主要体现在:地址加减整数上。比如:一个整型地址+1,会跳过4个字节(这里+1就表示跳过一个整型,一个整型就对应4个字节);一个字符型地址+1,会跳过一个字节;上述代码中的数组地址+1,会跳过20个字节(数组中有5个整型原素,一个整型对应4个字节,5个整型就对应20个字节,这20个字节也就是a数组的大小)。当了解了这些知识点以后,再来看上面的代码,*(a + 1)
其中a
是数组名,表示首元素地址,也就是一个整型的地址,+1跳过一个整型,所以a+1
表示数组中第二个元素的地址,也就是2
的地址,然后解引用,就会得到2
。再看*(ptr - 1)
,此时的ptr
指向数组a
后面紧接着的地址,并且已经被强制转换成了整型指针,所以ptr-1
会跳过一个整型,指向数组中的5
,解引用就会得到5
。
所以最终这段代码会在屏幕上打印出:2 5
下面这段代码的执行结果是?
struct Test
{
int Num;
int pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
int main()
{
p = (struct Test*)0x100000;//强行让p等于0x100000
printf("%p\n", p + 0x1);
printf("%p\n", (unsigned long)p + 0x1);
printf("%p\n", (unsigned int*)p + 0x1);
return 0;
}
这里的p
本质上是一个结构体类型的指针,这个结构体的大小是20个字节,+1跳过一个结构体的大小,也就是+20个字节,其中的0x
表示十六进制,20的十六进制是14,所以这里的p+0x1
就是:0x100000+0x14=0x100014。(unsigned long)p
把指针变量p
强制类型转换成整型变量,此时p
的值0x100000
就不再表示地址了,而是表示一个整数0x100000
,(unsigned long)p + 0x1
就是我们小学就学过的整数加法,结果等于0x100001。(unsigned int*)p
把结构体类型的指针强制类型转换成整型指针,此时p
的值0x100000
还表示一个地址,但表示的是一个整型的地址,不再是结构体类型的地址,所以+1跳过一个整型,也就是+4个字节,(unsigned int*)p + 0x1
就是0x100000+0x4=0x100004。最终%p打印的是地址,所以结果为:00100014、00100001、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
拿到的是整个数组的大小,+1跳过一个数组的大小,上面已经相依讲过,这里不再赘述。本题需要注意的有:ptr[-1]
就等价于*(ptr-1)
,还有就是(int*)((int)a + 1)
这里a
本来表示数组首元素地址,这里先把其转换成整型,然后+1,这里进行的是整数加法,然后再把这个结果转换成整型指针,放到ptr2
这个指针变量里面,ptr2
的指向如下图所示。还需注意的是,在我当前的编译环境下,数据是按照小端字节序的模式存到内存里面的,所以在从内存中读数据的时候也许注意。%x
是以十六进制的形式打印。最终结果是:4,2000000
下面这段代码的执行结果是?
int main()
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
int* p;
p = a[0];
printf("%d", p[0]);
return 0;
}
本题需要注意的有:数组初始化中的逗号表达式,实际上是:int a[3][2] = { 1,3,5 };
。还需注意a[0]
是二维数组a
中第一行一维数组的数组名,数组名又表示首元素地址,所以此时p
里面存的是数组中a[0][0]
也就是元素1的地址,p[0]
就等价于*(p+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;
}
本题需要注意的点有:首先p
是一个数组指针,指向的数组类型是:int [4]
。也就是说p
指向一个整型数组,这个数组有四个元素,其次a
是二维数组名,表示首元素的地址,二维数组的首元素是a[0]
,也就是二维数组中第一行那个一维数组,所以a
表示首元素地址,就是二维数组中第一行那个一维数组的地址,它的类型是int(*)[5]
,把这个类型的地址放到类型为int(*)[4]
的指针变量里面,是可以强行放进去的。&p[4][2]
其中p[4][2]
就是&*(*(p+4)+2)
,这里需要注意p指向的数组只有四个元素,所以p+1跳过四个元素,这里p+4就会跳过16个元素。
本题最终的结果就是:FFFFFFFC、-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;
}
本题需要注意的是:&aa
拿到的是整个二维数组的地址&aa+1
跳过整个二维数组,它的类型是int [2][5]
,然后把这个地址强制转换成int*
型;aa
数组名表示首元素地址,是第一行一维数组的地址,aa+1
表示第二行一维数组的地址,解引用得到第二行的一维数组也就是aa[1]
,aa[1]
是第二行一维数组的数组名,数组名表示首元素地址,也就是aa[1][0]
的地址,aa[1][0
]就是6,这里也就是6的地址,所以这原本就是一个整形的地址,(*(aa + 1))
前面的强制类型转换就可有可无。本题最终的结果就是:10、5
下面这段代码的执行结果是?
int main()
{
char* a[] = { "work","at","alibaba" };
char** pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
本题需要注意的是:首先a
是一个字符指针数组,说明a
数组里面存放的是字符的地址,而字符串常量的值就是首字符的地址,所以a
数组里面存放的就是"work"
中'w'
的地址、"at"
中'a'
的地址、"alibaba"
中'a'
的地址,数组名a
表示首元素地址,也就是a[0]
的地址,a[0]
里面存放的是'w
’的地址,所以a[0]
的地址就是'w'
的地址的地址,也就是指针的地址,所以这用了一个二级指针变量pa
来接收。char** pa
应该这样来理解:首先* pa
中的*
告知我们pa
是一个指针变量,char*
告诉我们这个指针变量指向一个char*
类型的数据,所以对pa++
,会跳过一个char*
类型的数据,此时pa
指向a
数组中的第二个元素a[1]
,也就是说此时pa
指向'a'
的地址,所以对pa
解引用就会得到'a'
的地址,然后以%s
的格式打印,最终在屏幕上打印出: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;
}
**++cpp
中++cpp
,cpp
就指向cp[1]
, 第一次解引用得到cp[1]
这块空间里面的内容,也就是c[2]
的地址,第二次解引用得到c[2]
这块空间里面的内容,c[2]
里面存的是'P'
的地址,所以第一次打印出: POINT。*-- * ++cpp + 3
,首先++cpp
,此时cpp
指向cp[2]
,然后解引用得到cp[2]
这块空间里面的内容,也就是c[1]
的地址,然后- -,c[1]
的地址是一个char*
类型的地址。所以- -跳过一个char*
类型的数据,此时得到的就是c[0]
的地址,解引用得到c[0]
这块空间里面的内容,也就是得到'E'
的地址,这是一个字符的地址,+3跳过3个字符,此时得到"ENTER"
中第二个'E'
的地址,所以第二次打印出:ER。*cpp[-2] + 3
中的cpp[-2]
就表示*(cpp-2)
,cpp
当前指向cp[2]
,所以cpp-2
指向cp[0]
,*(cpp-2)
得到cp[0]
这块空间里面的内容,也就是得到了c[3]
的地址,所以cpp[-2]
表示的就是c[3]
的地址,*cpp[-2]
得到c[3]
这块空间里面的内容,也就是得到了'F'
的地址,这是一个char
类型(字符)的地址,所以*cpp[-2] + 3
会跳过3个字符,此时表示"FIRST"
中'S'
的地址,最终打印出来:ST。cpp[-1][-1] + 1
中的cpp[-1
]表示*(cpp-1)
,cpp
当前指向cp[2]
,所以*(cpp-1)
最终得到c[2]
的地址,cpp[-1][-1]
表示:*(*(cpp-1)-1)
,其中*(cpp-1)-1
得到c[1]
的地址,所以*(*(cpp-1)-1)
最终得到c[1]
这块空间里面的内容,也就是得到'N'
的地址,这也就说明cpp[-1][-1]
表示的就是'N'
的地址,所以cpp[-1][-1] + 1
就表示:"NEW"
中'E'
的地址,最终打印出:EW