C 高阶系列导航
1. 前言
在 C/C++ 相关的岗位笔试中,数组和指针相关的知识点为高频考点,能够很好考察应聘者的 C/C++ 功底。因此,本文精选了数组与指针相关的笔试题,并解析解题思路,希望能够加强读者对数组和指针的理解。
在阅读本文前,强烈推荐先学习博文《【C 高阶】- 彻底理解数组与指针》,里面详细介绍了数组与指针之间的共性与关联。
2. 笔试题精选
第一题
以下程序输出的结果是?
int nums[5] = {1, 2, 3, 4, 5};
int* p = (int*)(&nums + 1);
printf("*(nums + 1) = %d\n", *(nums + 1));
printf("*(p - 1) = %d\n", *(p - 1));
- 解析
&nums + 1
等价于(size_t)&nums + 1 * sizeof(int[5])
,此时p
指向了数组nums
的下一个地址。即假设nums
的地址为 0x10000000,则p
指向的地址为 0x10000014。nums + 1
等价于(size_t)nums + 1 * sizeof(int)
,返回的nums[1]
的地址。p - 1
等价于(size_t)p - 1 * sizeof(int)
,返回指针p
所指地址向前偏移 4 个字节后的地址。由于p
指向了数组nums
的下一个地址,因此p - 1
得到的是nums[4]
的地址。
- 输出结果
*(nums + 1) = 2
*(p - 1) = 5
第二题
以下程序输出的结果是?
int* p = NULL;
printf("p = %p\n", p);
printf("p + 1 = %p\n", p + 1);
printf("(size_t)p + 1 = %lu\n", (size_t)p + 1);
printf("(short*)p + 1 = %p\n", (short*)p + 1);
printf("(long*)p + 1 = %p\n", (long*)p + 1);
- 解析
p + 1
等价于(size_t)p + 1 * sizeof(int)
,返回指针p
所指地址偏移 4 个字节后的地址。(size_t)p + 1
为p
所指的地址值直接加一。(short*)p + 1
等价于(size_t)(short*)p + 1 * sizeof(short)
,返回指针p
所指地址向后偏移 2 个字节后的地址。(long*)p + 1
等价于(size_t)(long*)p + 1 * sizeof(long)
,返回指针p
所指地址向后偏移 8 个字节后的地址。
- 输出结果
p = 0x0
p + 1 = 0x4
(size_t)p + 1 = 1
(short*)p + 1 = 0x2
(long*)p + 1 = 0x8
第三题
现有指针 p
,指针地址 &p
为 0x10000000。以下程序输出的结果是?
printf("&p + 1 = %p\n", &p + 1);
printf("(long)&p + 1 = %p\n", (void*)((long)&p + 1));
printf("(short*)&p + 1 = %p\n", (short*)&p + 1);
printf("(long*)&p + 1 = %p\n", (long*)&p + 1);
printf("(int**)&p + 1 = %p\n", (int**)&p + 1);
- 解析
&p + 1
等价于(size_t)&p + 1 * sizeof(int*)
,返回指针p
的地址偏移 8 个字节后的地址。(long)&p + 1
为p
的地址值直接加一。(short*)&p + 1
等价于(size_t)(short*)&p + 1 * sizeof(short)
,返回指针p
的地址向后偏移 2 个字节后的地址。(long*)&p + 1
等价于(size_t)(long*)&p + 1 * sizeof(long)
,返回指针p
的地址向后偏移 8 个字节后的地址。(int**)&p + 1
等价于(size_t)(int**)&p + 1 * sizeof(int*)
,返回指针p
的地址向后偏移 8 个字节后的地址。
- 输出结果
&p + 1 = 0x10000008
(long)&p + 1 = 0x10000001
(short*)&p + 1 = 0x10000002
(long*)&p + 1 = 0x10000008
(int**)&p + 1 = 0x10000008
第四题
现有数组 nums
、指针 p1
和 p2
,指针地址 &p
为 0x10000000。已知以下程序为小端存储策略,则输出的结果是?
int nums[] = {1, 2, 3, 4};
int* p1 = (int*)(&nums + 1);
int* p2 = (int*)((size_t)nums + 1);
printf("p1[-1] = %d\n", p1[-1]);
printf("*p2 = 0x%x\n", *p2);
- 解析
&nums + 1
等价于(size_t)&nums + 1 * sizeof(int[4])
,此时p1
指向了数组nums
的下一个地址。即假设nums
的地址为 0x10000000,则p1
指向的地址为 0x10000010。(size_t)nums + 1
为nums
表示的地址值直接加一,此时p2
指向了数组nums
表示的地址的下一个地址。即假设nums
的地址为 0x10000000,则p2
指向的地址为 0x10000001。p1[-1]
等价于*((size_t)p1 - 1 * sizeof(int))
,而(size_t)p1 - 1 * sizeof(int)
返回指针p1
所指地址向前偏移 4 个字节后的地址,即nums[3]
的地址。- 由于
p2
指向了数组nums
表示的地址的下一个地址,即*p
从该地址开始取 4 字节地址内存按整型解析,因此需要分析数组nums
在内存中的存储情况。由于程序为小端存储策略,因此具体内存分布如下所示。可见,*p2
从所指地址开始解析的 4 字节内存为 0x02000000。
地址:低 -> 高
| nums[0] | nums[1] | nums[2] | nums[3] |
nums: 01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00
|
p2
- 输出结果
p1[-1] = 4
*p2 = 0x2000000
第五题
以下程序输出的结果是?
int nums[5][5] = {0};
int (*p)[3] = (int(*)[3])nums;
printf("&p[3][2] - &nums[3][2] = %d\n", (int)(&p[3][2] - &nums[3][2]));
- 解析
nums
为有 5 个子数组、每个子数组有 5 个元素的二维数组,因此nums
实际有 25 个整型元素。p
为数组指针,所属的数组类型为 3 个元素。&p[3][2]
等效于((int[3])((size_t)p + 3 * sizeof(int[3])))[2]
(非标准写法),即返回p
所指地址向后偏移 11 个字节后的地址。&nums[3][2]
返回nums
中第 17 个元素的地址。
- 输出结果
&p[4][2] - &nums[4][2] = -6
第六题
以下程序输出的结果是?
int nums[2][4] = {1, 2, 3, 4, 5, 6, 7, 8};
int* p1 = (int *)(&nums + 1);
int* p2 = (int *)(*(nums + 1));
printf("*(p1 - 1) = %d\n", *(p1 - 1));
printf("*p2 = %d\n", *p2);
- 解析
&nums + 1
等价于(size_t)&nums + 1 * sizeof(int[2][4])
,此时p1
指向了数组nums
的下一个地址。即假设nums
的地址为 0x10000000,则p1
指向的地址为 0x10000020。nums + 1
等价于(int[4])((size_t)nums + 1 * sizeof(int[4]))
(非标准写法),返回nums
的地址向后偏移 16 个字节后的地址,即p2
指向了nums[4]
。p - 1
等价于(size_t)p - 1 * sizeof(int)
,返回指针p
所指地址向前偏移 4 个字节后的地址。由于p
指向了数组nums
的下一个地址,因此p - 1
得到的是nums[1][3]
的地址。
- 输出结果
*(p1 - 1) = 8
*p2 = 5
第七题
以下程序输出的结果是?
char* str[] = {"HELLO", "WORLD"};
char** p = str;
printf("*p = %s\n", *p);
printf("p[1] = %s\n", p[1]);
printf("p[0] + 1 = %s\n", p[0] + 1);
- 解析
str[1]
指向"WORLD"
,str[0]
指向"HELLO"
。p
指向str
即p = &str[0]
,*p <-> str[0]
。p[1]
等价于*((char**)((size_t)p + 1 * sizeof(char*)))
,返回p
所指地址&str[0]
向前偏移 8 个字节后的地址&str[1]
上的内容str[1]
。p[0]
等价于*((char**)((size_t)p + 0 * sizeof(char*)))
,返回p
所指地址&str[0]
上的内容str[0]
。str[0] + 1
等价于(size_t)str[0] + 1 * sizeof(char)
,返回str[0]
所指地址向前偏移 1 字节后的地址。
- 输出结果
*p = HELLO
p[1] = WORLD
p[0] + 1 = ELLO
第八题
以下程序输出的结果是?
char* str[] = {"HELLO", "WORLD"};
char** p1[] = {str + 1, str};
char*** p2 = p1;
printf("**++p2 = %s\n", **++p2);
printf("*++*p2 + 1 = %s\n", *++*p2 + 1);
printf("*p2[-1] + 3 = %s\n", *p2[-1] + 3);
printf("p2[-1][-1] + 1 = %s\n", p2[-1][-1] + 1);
- 解析
p1[0]
指向str[1]
,str[1]
指向"WORLD"
;p1[1]
指向str[0]
,str[0]
指向"HELLO"
。p2
指向了p1
即p2 = &p1[0]
,*p2 <-> p1[0]
指向了str[1]
,**p2 <-> *p1[0] <-> str[1]
指向了"WORLD"
。**++p2
,p2
先与“++”结合得p2 = p2 + 1 = (char***)((size_t)p2 + 1 * sizeof(char**))
,此时p2
所含地址值为地址&p1[0]
向后偏移 8 个字节后的地址&p1[1]
,*p2 <-> p[1]
指向了str[0]
,**p2 <-> *p[1] <-> str[0]
指向了"HELLO"
。*++*p2 + 1
,p2
先与*
结合为*p2 <-> p[1]
,可得*++(p1[1])
;p1[1]
先与“++”结合为p1[1] = p1[1] + 1 = (char**)((size_t)p1[1] + 1 * sizeof(char*))
,此时p1[1]
所含地址值为地址&str[0]
向后偏移 8 个字节后的地址&str[1]
,*p1[1] <-> str[1]
;str[1] + 1
等价于(size_t)str[1] + 1 * siezof(char)
,返回str[1]
所指地址向后偏移 1 个字节后的地址。*p2[-1]
,p2
先与“[]”结合,p2[-1]
等价于*((char***)((size_t)p2 - 1 * sizeof(char**)))
,返回p2
所含地址值&p1[1]
向前偏移 8 个字节后的地址&p[0]
上的内容即p1[0]
;*p2[-1] <-> *p1[0] <-> str[1]
;str[1] + 3
等价于(size_t)str[1] + 3 * sizeof(char)
,返回str[1]
所指地址向后偏移 3 个字节后的地址。p2[-1][-1]
,经上分析可知p2[-1] <-> p1[0]
,(p1[0])[-1]
等价于*((char**)((size_t)p1[0] - 1 * sizeof(char*)))
,则返回p1[0]
所指地址&str[1]
向前偏移 8 个字节后的地址&str[0]
上的内容即str[0]
;str[0] + 1
等价于(size_t)str[0] + 1 * sizeof(char)
,返回str[0]
所指地址向前偏移 1 个字节后的地址。
- 输出结果
**++p2 = HELLO
*++*p2 + 1 = ORLD
*p2[-1] + 3 = LD
p2[-1][-1] + 1 = ELLO