最近学习到了指针进阶篇章,了解了多种高级指针的用法(也可能一点也不高级)。本文就对指针的常见试题做一个解析。
理论依据
1.指针的基本概念:
1. 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
2. 指针的大小是固定的4/8个字节(32位平台/64位平台)。
3. 指针是有类型,指针的类型决定了指针的+-整数的步长,指针解引用操作的时候的权限。
2.数组各部分的含义:
例:int arr[2]={1,2}
arr代表数组首元素地址。即arr=&arr[0]。同时arr可以理解为一个指向此数组的指针。指针中存放着数组首元素的地址,解引用可得到首元素*arr=arr[0]。
[]为下标引用操作符,在调用阶段,下标内的数字决定了引用数组的元素编号。
arr[1]=*(arr+1)
int为数组元素的类型,决定了数组开辟的大小,也决定了arr的类型。
指针试题
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;
}
//程序的结果是什么?
分析:
程序第一行创建了5个int类型元素的数组。
第二行将&a+1,获得了跳过该数组的地址,并强制转换为int *类型。将&a+1赋给了ptr。
a+1中,a为首元素地址,由于是int类型,+1跳过四个字节,获得&a[1]。将其解引用后得到a[1]。即2。
ptr-1得到a[4]的地址&a[4]。解引用得到a[4]。即5
结论:
答案为:2,5。
2.
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;
}
分析:
%p是将数字按照16进制打印出来。
p结构体类型的指针,值为0x100000
此题考查不同类型的指针p加减整数的步长
第一行:
p是结构体类型指针,步长为20,因此p+1=0x100000+0x00014=0x10014。打印后结果为0x00010014
第二行:
将p强制转换为长整型,注意:此时p不再是指针型变量,在计算时不需考虑步长,直接计算即可。
第三行:
将p强制转换成unsigned int*类型,此时加减的步长为4。
结论:
答案为:0x00010014,0x00100001,0x00100004。
3.
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在空间中的存储模型(16进制):
第二行:
&a是数组指针,&a=int (*)[4]a,&a+1跳过一个数组的长度即16个字节。再将&a+1强制转换成int*类型。
第三行:
a是数组首元素地址,原本类型为int*。现在将其强行转换成int类型,此时+1则是在原有数值+1,之后将其转换为int *类型,即转换为地址。此时指针跳过一个字节。
第四行:
ptr[-1]=*(ptr-1)
结论:
答案为:0x00000004,0x20000000。
4.
nt 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]即二维元素第一行的首元素地址赋给p。
第四行:
p[0]=*(p+0)
结论:
答案为:1。
5.
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;
}
分析:
a是数组首元素地址,类型为int (*)[5]。类型为int*,p是数组指针,类型为int(*)[4]。
p=a将a的值赋给p。此时a和p属于不同类型的变量,强行赋值会导致编译器警告,但仍会将a的值放到p中,使p指向a数组的首元素。
&p[4][2]=*(p+4)+2。&a[4][2]=*(a+4)+2。其中p+4得到跳过16个整形的地址。a+4跳过20个整形的地址。
*(p+4)拿到了一个一维数组首元素的地址,*(p+4)+2从得到该元素跳过两个元素的地址
因此,*(a+4)+2-*(p+4)+2=4。
结论:
答案为:fffffffc,-4。
6.
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*类型赋给ptr1
第三行:
aa表示数组首元素地址。aa+1=aa[1],*(aa+1)得到第二行第一列的元素地址,即6的地址。
第四行:
ptr1的类型为int*,ptr1-1跳过4个字节,得到arr[1][4]的地址,解引用后得到a[1][4]。
ptr2的类型为int*,ptr2-1跳过4个字节。
结论:
答案为:10,5。
7.
int main()
{
char *a[] = {"work","at","alibaba"};
char**pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
第一行:
a[]为指针数组,指针类型为char*,此种定义方式相当于char* a[0]=char* p0="work"。即把w的地址放到p中.
第二行:
将数组的首元素地址放到二级指针pa中。
第三行:
由于pa的类型为char*,因此pa++增加一个字节。
第四行:
*pa得到数组第二个元素的地址。
结论:
答案为:at。
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;
}
第一行:
c[]中包含四个存放字符串首字母地址的指针。
第二行:
在cp中存放数组c中的元素地址。
第三行;
在cpp中存放数组cp的首元素地址。
第四行:
先给cpp++,得到&cp[1],之后经过两次解引用,第一次得到cp[1],即&c[2]。第二次解引用得到c[2],即指向“point"首字母的指针。经%s打印出POINT。
第五行:
先给cpp++,原先cpp指向cp[1],++后指向cp[2]。解引用后得到cp[2]。cp[2]=&c[1]。&c[1]-1得到&c[0]。解引用后得到c[0],即指向”ENTER"中E的指针。此时+3,指针指向第二个E。因此打印结果为:ER。
第六行:
cpp[-2]=*(cpp-2)=cp[0]。解引用后为*cp[0]。*cp[0]=c[3]。c[3]+3得到FIRST中S的地址。
打印结果为:ST。
第七行:
cpp[-1][-1]=*(*(cpp-1)-1)
cpp-1=&cp[1]
*(cpp-1)=cp[1]
cp[1]-1=&c[2]-1=&c[1]
*&c[1]=c[1]
c[1]+1得到NEW中E的地址。
打印结果为:EW。
结论:
答案为:POINT,ER,ST,EW