在我们学习完指针的有关知识之后,我们为了对指针有一个更为深入的理解,我们今天来学习几道指针笔试题,来看看指针是如何考察的。
数组名一般情况下为:
数组首元素的地址
两个例外,此时数组名代表整个数组:
sizeof(数组名)
&(数组名)
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,数组有五个元素,a为数组名,随后,我们发现&a,此时a为数组名且直接跟在&后边,所以此时代表整个数组的地址,整个数组地址加1,跨越整个数组,此时&a+1的类型为int (*) [5],为一个数组指针,强制类型转换为(int *),赋给ptr指针变量。
最后进行打印,先打印*(a+1),a为数组名,且不属于两个例外,说明在此处代表数组首元素的地址,整型指针进行加1,跨越一个整型元素,解引用,此时就是a[1],为数组第二个元素,ptr指向数组的末尾,且此时为int*,所以进行减1,向前跨越一个整型元素,解引用,即为a[4],为数组的第五个元素。
所以结果就是2和5。
我们来看结果:
struct Test
{
int Num;
char *pcName;
short sDate;
char cha[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);
return 0;
}
首先我们看到定义了一个结构体指针p,类型为struct Test*,而Test类型的变量大小为20个字节,这里我们先不多讲,后面会对结构体内存对齐有详细的讲解。
p+x1 :p是结构体指针,也就是一个地址,地址为0x100000,加上0x1,即就是跨越一个结构体,加上20个字节,由于为16进制所以得到0x100014,用%p打印,打印四个字节,打印结果为0x00100014。
(unsigned long)p + 0x1 :p为一个指针,存储结构体Test的地址,为0x100000,此时强制类型转换成一个无符号的长整型,整型进行加1,即就是直接加1,同时用%p打印,打印结果为0x00100001。
(unsigned int*)p + 0x1 : p为0x100000,为结构体指针,强制类型转换成整型指针,整型指针加1,即就是跨越一个整型元素,即加上4个字节,打印结果为0x00100004。
最后我们来看结果验证一下:
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,数组有四个元素。
ptr1: a为数组的数组名,由于是&a,所以属于两个例外中的一个,此时a代表整个数组,&a代表整个数组的地址,该地址加1,跨越整个数组,此时数据类型为int (*) [4],强转为int* 赋给ptr1。
ptr2 : a此时不是特殊情况,即a为数组首元素的地址,类型为int*,强转为int型,然后加1,再次强转为int*,此时的地址就是向后跨越了一个字节。
ptr1[-1]: ptr为一个整型指针,ptr[-1]相当*(ptr-1),ptr指向数组最后一个元素的后边,减1向前跨越一个整型元素,所以打印的结果是4。
*ptr2:ptr2指向首元素地址的后边的一个字节,由于在vs中是小端存储,所以数据的低位字节位于低地址处,高位字节位于高地址处。
我们通过一张图来解释:
所以此时得到的值为0x20000000,%x十六进制打印为20000000。
最后我们来看看结果:
#include
int main()
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
int *p;
p = a[0];
printf( "%d", p[0]);
return 0;
}
首先我们来定义一个二维数组,行为3,列为2,这个题给我们埋下了一个坑,二维数组初始化括号内部是逗号表达式,而不是常规的初始化,逗号表达式从左向右依次计算,值为最右边表达式的值,所以这个数组的初始化为不完全初识化,只初始化了三个元素。
定义一个指针变量p,p指向a[0],这里的a[0]也是一个数组名,他就是二维数组第一行的数组名,此时,不是两种特殊情况,这个就是数组首元素的地址,即就是第一行第一个元素的地址,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;
}
定义一个五行五列的二维数组a,同时定义一个有四个元素的数组指针p,此时将a赋给p。
我们通过一个图来讲解:
由于数组指针,每个数组只有四个元素,而二维数组的列为5,所以p[4][2]的地址小于a[4][2]的地址,所以%d打印的结果就是-4,而用%p打印,打印一个地址,不能打印出一个负数,此时就要考虑数据在内存中的存储。
-4的原码:1000 0000 0000 0000 0000 0000 0000 0100
-4的反码:1111 1111 1111 1111 1111 1111 1111 1011
-4的补码:1111 1111 1111 1111 1111 1111 1111 1100
此时用%p打印,所以认为这个数是无符号的,将-4的补码直接转换为十六进制打印
所以打印结果为:
ff ff ff fc
我们来验证一下结果:
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。
ptr1:对&aa,aa为二维数组的数组名,&aa是特殊情况,代表整个数组,&aa代表整个数组的地址,对&aa加1,跨越整个数组,然后强转为int*。
ptr2:aa为一般情况,为二维数组的数组名为二维数组第一行的地址,对第一行的地址加1,跨越一行,指向数组的第二行,对该指针解引用,得到aa[1],即就是第二行的数组名,也是第二行第一个元素的地址,此时类型就是int* ,强转并没有什么意义。
#include
int main()
{
char *a[] = {"work","at","alibaba"};
char**pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
定义一个char*类型的指针数组,数组中每个元素为char*类型,再定义一个二级指针指向这个数组的首元素。
pa++,pa的类型为char**,说明pa指向的元素类型为char*,pa加1,跨域一个char*的元素,pa本来指向work的首元素,pa++之后指向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;
}
首先定义一个指针数组,数组中每一个元素都是字符串首字符的地址,然后定义一个二级指针数组,再定义一个三级指针变量指向二级指针数组的首元素,即就是二级指针数组首元素的地址。
我们来通过图片来理解一下他们的关系:
注意:
c是一个char*类型的指针数组,数组中的元素并不是字符串,而是字符串首字母的地址。
我们来验证一下: