【C语言进阶】指针常见笔试题详解

前言

在我们学习完指针的有关知识之后,我们为了对指针有一个更为深入的理解,我们今天来学习几道指针笔试题,来看看指针是如何考察的。

预备知识:

数组名一般情况下为

数组首元素的地址

两个例外,此时数组名代表整个数组:

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。

我们来看结果:

【C语言进阶】指针常见笔试题详解_第1张图片

第二题 

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。

最后我们来看结果验证一下:

【C语言进阶】指针常见笔试题详解_第2张图片

第三题

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中是小端存储,所以数据的低位字节位于低地址处,高位字节位于高地址处。

我们通过一张图来解释:

【C语言进阶】指针常见笔试题详解_第3张图片

所以此时得到的值为0x20000000,%x十六进制打印为20000000。

最后我们来看看结果:

【C语言进阶】指针常见笔试题详解_第4张图片

第四题

#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,这个题给我们埋下了一个坑,二维数组初始化括号内部是逗号表达式,而不是常规的初始化,逗号表达式从左向右依次计算,值为最右边表达式的值,所以这个数组的初始化为不完全初识化,只初始化了三个元素。

【C语言进阶】指针常见笔试题详解_第5张图片

 定义一个指针变量p,p指向a[0],这里的a[0]也是一个数组名,他就是二维数组第一行的数组名,此时,不是两种特殊情况,这个就是数组首元素的地址,即就是第一行第一个元素的地址,p[0]相当于*(p+0),代表二维数组的首元素故打印结果为1。

我们来验证一下结果:

【C语言进阶】指针常见笔试题详解_第6张图片

 第五题

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。

我们通过一个图来讲解:

【C语言进阶】指针常见笔试题详解_第7张图片

由于数组指针,每个数组只有四个元素,而二维数组的列为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

我们来验证一下结果:

【C语言进阶】指针常见笔试题详解_第8张图片

第六题:

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* ,强转并没有什么意义。

* ( ptr1 - 1 ):ptr1指向整个数组的末尾,此时ptr1为int* 类型,所以减1向前跨越一个整型元素,再解引用,此时的结果应该是10。
*(ptr2 - 1):ptr2指向数组第二行第一个元素,也为int* 类型,所以减1向前跨越一个整型元素,再解引用,此时的结果是5。
我们来验证一下:
【C语言进阶】指针常见笔试题详解_第9张图片

 

第七题

#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。

同样我们来验证一下:

【C语言进阶】指针常见笔试题详解_第10张图片

 第八题

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语言进阶】指针常见笔试题详解_第11张图片

 注意:

c是一个char*类型的指针数组,数组中的元素并不是字符串,而是字符串首字母的地址。

**++ cpp:cpp指向cp[0],前置++后,cpp指向cp[1],解引用后为c+2,再次解引用,得到POINT首字母P的地址,输出POINT
*--*++ cpp + 3 前置++后,cpp指向cp[2],解引用后为c+1,前置--后,cp[2]被改为c,指向c[0]的地址,解引用得到ENTER的首字母E的地址,类型为char*,加3之后,跨越三个char类型的元素,得到E的地址,输出ER
* cpp [ - 2 ] + 3 cpp[-2]相当于*(cpp-2),得到c+3,类型为char**,再次解引用得到FIRST首元素F的地址,为char*类型,加3跨越3个char类型的元素,所以得到S的地址,所以输出ST
cpp [ - 1 ][ - 1 ] + 1:cpp[-1][-1] 相当于*(*(cpp-1)-1),所以cpp-1指向c+2,解引用后得到c+2,c+2-1得到c+1,指向NEW的首元素,得到N的地址,此时为char*类型,加1后得到E的地址,所以输出EW

我们来验证一下:

【C语言进阶】指针常见笔试题详解_第12张图片 

 

你可能感兴趣的:(c语言进阶,c语言,c++,开发语言)