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