【指针内功修炼】深度剖析指针笔试题(三)
🌟前言
关于 指针的进阶奥秘 ,我们在上一篇文章中已经接讲过了,这篇文章重点剖析在面试过程中会遇到的 指针的笔试题
1. 指针和数组笔试题解析解析
我们再来剖析一下 指针和数组笔试题
再这之前,先回顾一下数组的重要知识点
数组名 数组首元素的地址 这里有 2 个例外:
sizeof(数组名)
,这里的数组名是表示整个数组的,计算的是整个数组的大小,单位是字节。&数组名
,这里的数组名也表示整个数组,取出的是数组的地址; 除上面 2 种特殊情况外,所有的数组名都是数组首元素的地址
🍑 一维数组
📝 代码示例
🌟 代码解析
sizeof(a)
:数组名 a 单独放在 sizeof 内部,计算的整个数组的大小,单位是字节,4*4 = 16;sizeof(a + 0)
:a 表示的首元素的地址,a+0 还是数组首元素的地址,是地址大小 4/8;sizeof(*a)
:a 表示的首元素的地址,a 就是对首元素的地址的解引用,就是首元素,大小是 4 个字节sizeof(a + 1)
:a 表示的首元素的地址,a+1 是第二个元素的地址,是地址,大小就 4/8 个字节;sizeof(a[1])
:a[1] 是数组的第二个元素,大小是 4 个字节;sizeof(&a)
:&a 表示是数组的地址,数组的地址也是地址,地址大小就是 4/8 字节;sizeof(*&a)
:可以理解为*
和&
抵消效果,*&a
相当于 a,sizeof(a) 是 16;&a
等于int(*)[4]
;还可以这样理解:&a 是数组的地址,它的类型是int(*)[4]
数组指针,如果解引用,访问的就是 4 个 int 的数组,大小是 16 个字节;sizeof(&a + 1)
:&a 是数组的地址,&a+1 跳过整个数组后的地址,是地址就是 4/8;sizeof(&a[0])
:&a[0] 取出数组第一个元素的地址,是地址就是 4/8;sizeof(&a[0] + 1)
:&a[0]+1 就是第二个元素的地址,是地址大小就是 4/8 个字节
注意:
<font color=#FF0000 >(1)sizeof 只关注占用空间的大小,单位是字节; (2)sizeof 不关注类型; (3)sizeof 是操作符;</font>
🍑 字符数组
📝 代码示例一
🌟 代码解析
sizeof(arr)
:arr 作为数组名单独放在 sizeof 内部,计算的整个数组的大小,单位是字节,6;sizeof(arr + 0)
:arr 就是首元素的地址,arr+0 还是首元素的地址,地址大小就是 4/8;sizeof(*arr)
:arr 就是首元素的地址,arr 就是首元素,是一个字符,大小是一个字节,1;sizeof(arr[1])
:arr[1] 就是数组的第二个元素,是一个字符,大小是 1 个字节;sizeof(&arr)
:&arr 取出的是数组的地址,数组的地址也是地址,地址就是 4/8 个字节;sizeof(&arr + 1)
:&arr 取出的是数组的地址,&arr+1,跳过了整个数组,&arr+1 还是地址,地址就是 4/8 个字节;sizeof(&arr[0] + 1)
:&arr[0] 是第一个元素的地址,&arr[0]+1 就是第二个元素的地址,地址就是 4/8 个字节;
📝 代码示例二
🌟 代码解析
strlen(arr)
:arr 是首元素的地址,但是 arr 数组中没有 \0,计算的时候就不知道什么时候停止,结果是:随机值;strlen(arr + 0)
:arr 是首元素的地址,arr+0 还是首元素的地址,结果是:随机值;strlen(*arr)
:strlen 需要的是一个地址,从这个地址开始向后找字符,直到 \0,统计字符的个数。但是*arr
是数组的首元素,也就是'a'
,这时传给 strlen 的就是'a'
的 ascii 码值 97,strlen 函数会把 97 作为起始地址,统计字符串,会形成内存访问冲突,,所以这里的写法就是错误的;strlen(arr[1])
:和上一个一样,内存访问冲突;strlen(&arr)
:&arr 是 arr 数组的地址,虽然类型和 strlen 的参数类型有所差异,但是传参过去后,还是从第一个字符的位置向后数字符,结果还是随机值。strlen(&arr + 1)
:&arr 就是数组的地址,&arr + 1 跳过了整个数组;随机值;如下图
strlen(&arr[0] + 1)
:&arr[0] 拿出的就是第一个元素 a 的地址,&arr[0] + 1 表示第二个元素 b 的地址,从 b 的位置往后数,也是随机值,和上图一样; 当然我们也还是打印看一下,错误的两个代码我就直接屏蔽不打印
注意:
<font color=#FF0000 >(1)strlen 关注的字符串中 \0 的为止,计算的是 \0 之前出现了多少个字符; (2)strlen 只指针对字符串; (3)strlen 是库函数;</font>
📝 代码示例三
🌟 代码解析
首先 arr 数组在内存中开辟了一块儿空间,连续存放的👇
sizeof(arr)
:arr 是数组名,单独放在 sizeof 内部,计算的是整个数组的大小,也就是 7 个字节;sizeof(arr + 0)
:arr 是数组名,这里没有 &,也不是单独放在 sizeof 内部的(因为有个加 0),所以 arr 就是首元素 a 的地址,arr + 0 还是首元素的地址,地址大小就是 4/8;如下图👇
sizeof(*arr)
:arr
表示首元素 a 的地址,对 arr 进行解引用,就是计算 a 的大小,a 是 char 类型的,占 1 个字节;其实还可以这样理解:*arr
等价于*(arr+0)
还等价于arr[0]
;sizeof(arr[1])
:表示元素 b 的大小,1 个字节;sizeof(&arr)
:&arr 表示整个数组的地址,地址大小就是 4/8;sizeof(&arr + 1)
:&arr 就是数组的地址,&arr + 1 跳过了整个数组,不管指向哪里,取出的都是地址,是地址就是 4/8 字节👇
sizeof(&arr[0] + 1)
:&arr[0] 表示 a 的地址,加 1 表示 b 的地址,地址大小就是 4/8;
📝 代码示例四
🌟 代码解析
内存图👇
strlen(arr)
:arr 表示首元素的地址,也就是第一个元素 a 的地址,strlen 从 a 开始往后数,遇到 \0 就停止,也就是长度为 6;strlen(arr + 0)
:arr 表示首元素的地址,加 0 还是表示首元素的地址,所以 strlen 从 a 开始往后数,遇到 \0 就停止,也就是长度为 6;strlen(*arr)
:arr
表示首元素的地址,对 arr 进行解引用表示数组的首元素,也就是 a,这时传给 strlen 的就是 a 的 ascii 码值 97,strlen 函数会把 97 作为起始地址,统计字符串,会形成内存访问冲突,,所以这里的写法就是错误的;strlen(arr[1])
:和上一个一样,传过去的是字符 b 的 ASCII 码值 98,内存访问冲突;strlen(&arr)
:&arr 是 arr 数组的地址,虽然类型和 strlen 的参数类型有所差异,但是传参过去后,还是从第一个字符的位置向后数字符,遇到 \0 就停止,也就是长度为 6;strlen(&arr + 1)
:&arr 就是整个数组的地址,&arr + 1 跳过了整个数组,所以是随机值;
strlen(&arr[0] + 1)
:&arr[0] 拿出的就是第一个元素 a 的地址,&arr[0] + 1 表示第二个元素 b 的地址,从 b 的位置往后数,遇到 \0 就停止,长度为 5;
📝 代码示例五
🌟 代码解析
如图👇
sizeof(p)
:p 是一个指针变量,sizeof(p) 计算的就是指针变量的大小,4/8 个字节;sizeof(p + 1)
:p 是指针变量,是存放地址的,p+1 也是地址,地址大小就是 4/8 字节;sizeof(*p)
:p 是char*
的指针,解引用访问 1 个字节,sizeof(*p)
是 1 个字节;sizeof(p[0])
:p[0]
等价于*(p+0)
还等价于*p
,也就是访问 1 个字节;sizeof(&p)
:&p 也是地址,是地址就是 4/8 字节,&p 是二级指针;sizeof(&p + 1)
:&p 是地址, 加 1 后还是地址,是地址就是 4 / 8 字节;sizeof(&p[0] + 1)
:p[0] 就是 a,&p[0] 就是 a 的地址,&p[0]+1 就是 b 的地址,是地址就是 4/8 字节;
📝 代码示例六
🌟 代码解析
如图👇
strlen(p)
:p 中存放的是 a 的地址,strlen(p) 就是从 a 的位置向后求字符串的长度,长度是 6;strlen(p + 1)
:p+1 是 b 的地址,从 b 的位置开始求字符串长度是 5;strlen(*p)
:p
表示首元素的地址,对 p 进行解引用表示数组的首元素,也就是 a,这时传给 strlen 的就是 a 的 ascii 码值 97,strlen 函数会把 97 作为起始地址,统计字符串,会形成内存访问冲突,,所以这里的写法就是错误的;传内存访问冲突;strlen(p[0])
:和上一个一样,传过去的是字符 a 的 ASCII 码值 97,内存访问冲突;p[0]
等价于*(p+0)
还等价于*p
;strlen(&p)
:&p 取出的是指针变量 p 的地址,它也是存储在内存中的,所以访问的是随机值;
strlen(&p + 1)
:&p 取出的是指针变量 p 的地址,加 1 表示跳过整个 p,所以访问的是随机值;
strlen(&p[0] + 1)
:p[0]
等价于*(p+0)
等价于*p
等价于'a'
,&p[0] 就是首字符的地址,&p[0]+1 就是第二个字符 b 的地址,从第 2 个字符的位置向后数字符串,长度是 5;
🍑 二维数组
📝 代码示例一
🌟 代码解析
如图👇
sizeof(a)
:数组名单独放在 sizeof 内部,计算的是整个数组的大小,344=48;sizeof(a[0][0])
:表示第 0 行,第 0 个元素,大小是 4;sizeof(a[0])
:a[0] 表示第一行的数组名,a[0] 作为数组名单独放在 sizeof 内部,计算的是第一行的大小,也就是 16;sizeof(a[0] + 1)
:a[0] 表示第一行的数组名,没有 &,没有单独放在 sizeof 内部,所以 a[0] 表示的就是首元素的地址,即 a[0][0] 的地址,a[0] + 1 就是第一行第二个元素的地址,也就是 4/8 字节;sizeof(*(a[0] + 1))
:a[0] + 1 就是第一行第二个元素的地址,解引用,就是第一行第二个元素,大小为 4;sizeof(a + 1)
:a 表示二维数组的数组名,没有 &,没有单独放在 sizeof 内部,所以 a 表示首元素地址,也就是第一行 a[0] 的地址,加 a + 1 第二行的地址,是类型为int(*)[4]
的数组指针,是地址也就是 4/8 字节;sizeof(*(a + 1))
:a + 1 是第二行的地址,*(a + 1)
相当于第二行的数组名,*(a + 1)
等价于a[1]
,计算的就是第二行的大小,16 字节;sizeof(&a[0] + 1)
:a[0] 是第一行的地址,&a[0] 就是第一行的地址,&a[0] + 1 就是第二行的地址,是地址也就是 4/8 字节;sizeof(*(&a[0] + 1))
:*(&a[0] + 1)
相当于第二行,也就是 a[1],大小是 16 字节;sizeof(*a)
:a 是二维数组的数组名,没有 &,没有单独放在 sizeof 内部,a 表示首元素的地址,*a
就是二维数组的首元素,也就是第一行,大小为 16 字节;*a
等价于*(a+0)
还等价于a[0]
;sizeof(a[3])
:感觉 a[3] 越界了,但是没关系,a[3] 表示第四行的数组名,它的类型是个一维数组,也就是 int[4],所以大小为 16;
🍑 总结
数组名的意义:
<font color=#FF0000 >(1)sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。 (2)&数组名,这里的数组名表示整个数组,取出的是整个数组的地址。 (3)除此之外所有的数组名都表示首元素的地址。</font>
2. 指针笔试题
🍑 笔试题一
📝 代码示例
🌟 代码解析
*(ptr - 1)
:&a + 1 的类型为int(*)[5]
,然后赋给 ptr,ptr 是一个整型指针,ptr - 1 向前挪一个整型,指向 5,解引用,相当于把 5 拿出来;
*(a + 1)
:a 表示首元素的地址,加 1 向后访问一个整型,指向 2 的地址,解引用就是 2
🌟 运行结果
🍑 笔试题二
📝 代码示例
🌟 代码解析
p + 0x1
:p 的值为0x100000
,又因为强制类型转换成了struct Test*
,并且结构体 Test 类型的变量大小是 20 个字节,所以 p+1 也就是 结构体指针 + 1,那么就要跳过一个结构体的大小,也就是跳过 20 字节,20 用十六进制表示就是 0x100014,所以答案也就是 0x100014;(unsigned long)p + 0x1
:p 强制类型转换成 unsigned long,说明此时 p 就是一个数字,整型数字加 1,就是加上一个 1;也就是 0x100000 + 1,等于 0x100001;(unsigned int*)p + 0x1
:p 强制类型转换成unsigned int*
,也就是个整型指针,整型指针加 1,就是加 4 个字节,所以就是 0x100000 + 4 等于 0x100004;
🌟 运行结果
🍑 笔试题三
📝 代码示例
🌟 代码解析
ptr1[-1]
:&a 取出的是整个数组的地址,&a + 1 跳过整个数组,然后把这个地址强制类型转换成int*
,并赋值给 ptr1;ptr1[-1]
等价于*(ptr1+(-1))
还等价于*(ptr1-1)
,所以此时就指向元素 4
*ptr2
:a 是数组名表示首元素的地址,那这个地址强制类型转换成整型,也就是一个指针,加 1,就只是指针加 1,作为地址,只是向后跳了一个字节,那么此时我们就要把内存换分成以字节为单位的顺序,也就是下面这样👇
*ptr2
解引用,就是要访问 4 个字节,也就是访问 0x00000002 一个整型的内容我是以小端放进去的,要以小端拿出来,所以
*ptr2
还原出来的数字就是:0x02000000
🌟 运行结果
🍑 笔试题四
📝 代码示例
🌟 代码解析
(0, 1), (2, 3), (4, 5)
是一个 逗号表达式,其实数组里面的内容就是a[0] 表示第一行第一个元素的地址,所以指针 p 指向就是 1;
p[0]
等价于*(p+0)
也就是等价于*p
,答案就是 1;
🌟 运行结果
🍑 笔试题五
📝 代码示例
🌟 代码解析
p 是一个数组指针, p 能够指向的数组是 4 个元素,p+1 往后跳 4 个整型,也就是 16 个字节;
p[4][2]
等价于*(*(p+4)+2)
,a 是二维数组的数组名,代表首元素的地址; 所以 p = a 表示 p 也指向首元素的地址,那么 p+1 往后跳 4 个整型;所以就是两个地址相减,小地址减去大地址,得到的就是中间元素的个数,但是这里是负数,也就是 -4
🌟 运行结果
这里解释一下 -4 的十六进制👇
🍑 笔试题六
📝 代码示例
🌟 代码解析
&aa
,取出的是整个二维数组的地址,&aa + 1
跳过整个二维数组,然后强制类型转换成 整型指针, 再赋给 ptr1,ptr1 - 1 就是向后挪一个字节,然后解引用,指向元素 10; aa 表示第一行的地址,aa+1 表示跳过第一行,来到了第二行,再解引用,相当于拿到了第二行的数组名,再解引用,相当于拿到了第二行首元素的地址,也就是 6 的地址,然后赋给 ptr2,ptr2 - 1 就是向后挪一个字节,然后解引用,指向元素 5;
🌟 运行结果
🍑 笔试题七
📝 代码示例
🌟 代码解析
内存图👇
pa 是
char**
的类型,pa++ 其实就是 pa+1,相当于跳过 1 个char*
的元素👇
*pa
对 pa 解引用,向后访问 1 个元素,这个元素里面放的是 a 的地址,打印 at
🍑 笔试题八
📝 代码示例
🌟 代码解析
内存图👇
**++cpp
:cpp 本来指向 c+3 的地址,当 ++cpp 后,跳过 1 个char**
的元素,指向 c+2 的地址,*++cpp
第一颗星解引用的时候,拿到的是 c+2 指向的元素的地址,然后再解引用**++cpp
,拿到了char*
里面的值,也就是 P 的地址,这里是 %s 打印,也就是通过 P 的地址向后打印,也就是打印 PONIT;*-- * ++cpp + 3
:先计算 ++cpp,上面已经计算过一次 ++cpp 了,所以此时 cpp 跳过 1 个char*
的元素,指向 c+1,* ++cpp
并对其进行解引用,拿到的是 c+1 里面的内容,-- * ++cpp
然后在对其进行减 1,也就是 c+1 减 1,变成 c 了,此时这里面指向的元素也发生变化了,也就是指向了数组 c 的首元素的地址,指向 ENTER 的char*
了,然后再解引用,就拿到了char*
所指向的元素 E 的地址,然后再加 3,跳过 N、T,指向 E,再以 %s 打印,输出 ER;👇
*cpp[-2] + 3
:cpp[-2] 其实就是*(cpp-2)
,所以这个表达式就等价于**(cpp-2)+3
,当 cpp-2 时,又回到了指向 c+3 的地址 ,第一个解引用,找到了里面的元素 c+3,第二个解引用,拿到了 c+3 指向的元素,也就是 F 的地址,然后再加 3,就指向 S,再以 %s 打印,输出 ST;👇
cpp[-1][-1] + 1
:cpp[-1][-1]
其实就是*(*(cpp-1)-1)
,所以这个表达式就等价于*(*(cpp-1)-1)+1
,首先 cpp 还是指向 c+1 的位置,cpp-1,指向 c+2 的地址,然后解引用,拿到这个位置的元素,也就是 c+2,然后再减 1,变成了 c+1,c 是数组首元素的地址,c+1 表示数组 c 第二个元素的地址,第二次解引用,拿到了 N 的地址,从 N 的地址加 1 指向 E,再以 %s 打印,输出 EW;👇
🌟 运行结果
版权声明: 本文为 InfoQ 作者【Albert Edison】的原创文章。
原文链接:【http://xie.infoq.cn/article/fa20d8b3ca3f86ccba13b38c1】。文章转载请联系作者。
评论