【C语言】指针笔试题

各位读者好,咱们现在来看几道指针笔试题,如果让你对指针有更加深刻的理解,恳请点赞加关注;如有不足,也恳请斧正,谢谢!

注意:以下代码的运行环境均处于32位(x86)系统且为小端机器下运行。

1.笔试题1

#include
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个元素的数组a,元素类型是int,元素分别是:1,2,3,4,5。有一个int*类型的指针变量ptr,存放着a[4]内存中的第四个字节后面那个字节的地址(&a代表整个数组a的地址(类型是int(*)[5]),&a+1跳过20个字节,即&a+1代表紫色字体所述的地址。随后被强转成int*类型) 如图:

【C语言】指针笔试题_第1张图片

 我们来看printf函数所打印的东西:第一个打印的应该是2;因为a代表数组首元素地址,a+1跳过4个字节(因为a的类型是int*)指向第二个元素,则*(a+1)代表第二个元素。第二个打印的应该是5;因为有上可知ptr代表紫色字体所述地址,ptr-1向低地址方向跳过4个字节(因为ptr类型是int*)指向第五个元素,则*(ptr-1)代表第五个元素。

运行结果如下:

2.笔试题2 

#include
struct Test//已知,结构体Test类型的变量大小是20个字节
{
	int Num;
	char* pcName;
	short sDate;
	char cha[2];
	short sBa[4];
}*p;
int main()
{
	p = (struct Test*)0x100000;//0x开头的数字是16进制数字
	printf("%p\n", p + 0x1);
	printf("%p\n", (unsigned long)p + 0x1);
	printf("%p\n", (unsigned int*)p + 0x1);
	return 0;
}

注意:%p是打印地址(指针地址)的,是十六进制的形式,但是会全部打完,即有多少位打印多少位。这里指针变量是4个字节,所以会打印完8位。 

我们可以看到定义了一个结构体指针变量p,并赋值成了0x100000(16进制)。

 第一个打印的应该是00100014;因为p+0x1跳过20个字节(因为p的类型是strust Test*(结构体Test是20个字节)),所以应该打印00100014(10进制的20转换成16进制是14,所以打印:00100014)。

第二个打印的应该是00100001;因为p被强转成了unsigned long型,p+0x1得到100001,又因为以%p的形式打印,所以应该打印00100001。

第三个打印的应该是00100004;因为p被强转成了unsigned int*型,p+0x1跳过4个字节(因为p的类型是unsigned int*(unsigned int是4个字节)),所以应该打印00100004。

运行结果如下:

3.笔试题3

#include
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;
}

注意:%x在C语言中是一个格式化输出控制符,用于将无符号整数以十六进制形式输出。

我们可以看到 有一个4个元素的数组a,元素类型是int,元素分别是1,2,3,4。

有一个int*类型的指针变量ptr1,存放着a[3]内存中的第四个字节后面那个字节的地址(&a代表整个数组a的地址(类型是int(*)[4]),&a+1跳过16个字节,即&a+1代表紫色字体所述的地址。随后被强转成int*类型)

有一个int*类型的指针变量ptr2,存放着a[0]内存中的第二个字节的地址(“a代表数组首元素a[0]的地址,该地址被强转成int型,a+1再强转成int*型”这一系列操作其实就是让a跳过一个字节指向了a[0]内存中的第二个字节)

方便理解,我们看到小端存储的数组a的内存布局:

【C语言】指针笔试题_第2张图片

第一个打印的应该是4;因为 ptr1[-1]可以表示成*(ptr1-1),ptr-1向低地址方向跳过4个字节指向a[3]的首个字节,*(ptr-1)向后访问4个字节(4个字节在内存中是04000000)再以%x形式打印为4。

第二个打印的应该是2000000;因为*ptr2向后访问4个字节(4个字节再内存中是00000002)再以%x形式打印为2000000。

运行结果如下:

4.笔试题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;
}

我们看到有一个二维数组,元素类型为int,元素分别为1,3,5,0,0,0(前3个元素分别用对应逗号表达式结果初始化了)。 

定义了一个int*类型的指针变量p,并赋值为a[0];我们分析a[0],a[0]表示二维数组中的首元素(可以将二维数组看出是由一维数组组成的数组)的数组名,同时又表示这个首元素(一维数组)的首元素地址,所以a[0]可以表示&a[0][0]。

这里打印的应该是1;因为有上分析指针变量p被赋值成&a[0][0],而p[0]有可以表示成*(p+0),则*(&a[0][0]+0)跳过0个字节再解引用访问的就是a[0][0],即1。

运行结果如下:

5.笔试题5

#include
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)和一个数组指针p(类型为int(*)[4]),将a(类型为int(*)[5])赋值给p(类型为int(*)[4]),即将二维数组的首元素(是一个一维数组)地址赋值给a。

这道题的关键是明白p[4][2]和a[4][2]的含义。p[4][2]可以表示成*(*(p+4)+2);*(p+4)跳过64个字节(因为p的类型是int(*)[4])再解引用访问得到一个一维数组p[4](元素类型为int),p[4]有可以表示成数组首元素的地址,所以*(p[4]+2)跳过8个字节(因为该数组元素类型为int)再解引用访问得到p[4][2],总而言之,p一共跳过了72个字节访问得到元素p[4][2];a[4][2]简单易懂,不在赘述。画图方便理解如下:

【C语言】指针笔试题_第3张图片

第二个打印的应该是-4;因为 指针减去指针得到的是两个指针内存位置之间相隔的元素个数(不是字节数),容易看出相隔了4个元素,又因为是低地址减去高地址,所以结果为-4。

第一个打印的应该是FFFFFFFC;因为由上分析可知以%d的形式打印为-4;内存中-4的二进制补码是11111111111111111111111111111100,对应的16进制数字为FFFFFFFC。

运行结果如下:

6.笔试题6

#include
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;
}

我们可以看到有一个二维数组;一个int*型的指针变量ptr1,ptr1存放着内存中a[1][4]第四个字节后面那个字节的地址(&aa表示整个二维数组的地址(类型是int(*)[2][5]),&aa+1跳过40个字节,即&aa+1表示紫色字体所述地址,随后被强转成int*类型) 一个int*型的指针变量ptr2,ptr2存放着a[1][0]的地址(aa表示二维数组首元素(一个一维数组)的地址,*(aa+1)跳过20个字节(因为aa的类型为int(*)[5])再解引用得到整个一维数组(即二维数组第二个元素),同时得到的这个数组名也可以代表数组首元素的地址,即aa[1][0]的地址)。画图方便理解:

【C语言】指针笔试题_第4张图片

第一个打印的应该是10;因为*(ptr1-1)向低地址方向跳过4个字节(因为ptr1类型为int*) 再解引用访问a[1][4],即10。

第二个打印的应该是5;因为*(ptr2-1)向低地址方向跳过4个字节(因为ptr2类型为int*)再解引用访问a[0][4],即5。

运行结果如下:

7.笔试题7 

#include
int main()
{
	char* a[] = { "work","at","alibaba" };
	char** pa = a;
	pa++;
	printf("%s\n", *pa);
	return 0;
}

我们可以看到定义了一个有3个元素的指针数组a,元素类型为char*,元素分别为"work"、"at"、"alibaba"的首元素地址。

定义了一个类型为char**的二级指针变量pa,存放了指针数组首元素的地址a(a的类型为char**)。pa++跳过4个字节(因为pa的类型为char**(char*类型数据占4个字节))指向“at”首元素。画图方便理解:

【C语言】指针笔试题_第5张图片这里打印的应该是“at”;因为pa++后指向了"at"首元素,即‘a’的地址,再调用printf函数用%s的形式打印就打印出了“at”。注意:%s在C语言中代表字符串型格式符,用来输出一串字符串,遇到‘\0’(其实就是0)停止。

8.笔试题8 

#include
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;
}

我们画图分析执行完第三条语句后(没有执行任何printf函数)的内存布局: 

【C语言】指针笔试题_第6张图片

1.printf("%s\n",**++cpp); 

由于操作符++的优先级高于操作符*,所以++cpp先自加跳过4个字节指向cp[1],内存布局变为:

【C语言】指针笔试题_第7张图片

再执行两个操作符**:先解引用访问cp[1] 的内容(就是c[2]的地址),再解引用访问c[2]的内容(就是"POINT"首元素‘P’的地址);所以这里应该打印"POINT"。

2.printf("%s\n",*--*++cpp+3);

 由于操作符++的优先级高于操作符+,所以cpp先自加跳过4个字节指向cp[2];

又因为操作符*优先级高于操作符+,所以解引用访问cp[2]的内容(就是c[1]的地址);

又因为操作符--的优先级高于操作符+,所以cp[2]向低地址方向跳过4个字节指向c[0];

又因为操作符*优先级高于操作符+,所以解引用访问c[0]的内容(就是“ENTER”首元素'E'的地址);

最后指向操作符+,从‘E’的地址向后跳过3个字节得到"ENTER"第4个元素'E'的地址。所以这里应该打印"ER"。

执行完上述指令后内存布局为:
【C语言】指针笔试题_第8张图片

3.printf("%s\n",*cpp[-2] + 3) ;

因为操作符[]优先级高于操作符*,又因为cpp[-2]可以表示成*(cpp-2),所以cpp向低地址方向跳过8个字节指向cp[0],再解引用访问cp[0]的内容(就是c[3]的地址);

又因为操作符*优先级高于操作符+,所以解引用访问c[3]的内容(就是"FIRST"首元素'F'的地址);

最后执行操作符+,c[3]跳过3个字节指向"FIRST"第4个元素'S',即得到‘S’的地址。所以这里打印的应该是"ST"。

执行完以上指令内存布局没有变化。

4.printf("%s\n", cpp[-1][-1] + 1);

先执行两个操作符[],再执行操作符+;cpp[-1]可以表示成*(cpp-1),cpp先向低地址方向跳过4个字节指向cp[1],再解引用访问cp[1]的内容(就是c[2]的地址);另一个操作符同样思想执行完访问c[1]的内容(就是"NEW"第一个元素‘N’的地址);最后执行操作符+,c[1]跳过1个字节指向"NEW"第二个元素'E',即得到'E'的地址。所以这里打印的应该是"EW"。

执行完以上指令内存布局没有变化。

运行结果如下:

ending:

感谢阅读,跪求点赞,谢谢! 

你可能感兴趣的:(C语言,c语言)