指针和数组笔试题解析
第一题(一维数组)
关于数组名:
数组名是首元素的地址
但是有两个例外:
1.sizeof
(数组名)-数组名表示的是整个数组,计算的是整个数组的大小,单位是字节
2.&数组名数组名也表示整个数组,取出的是整个数组的地址
除了以上两个例外之外所有的数组名都是首元素的地址
#include<stdio.h> int main() { int a[] = {1,2,3,4}; printf("%d\n",sizeof(a));//16 printf("%d\n",sizeof(a + 0));//4/8 printf("%d\n",sizeof(*a));//4 printf("%d\n",sizeof(a + 1));//4/8 printf("%d\n",sizeof(a[1]));//4 printf("%d\n",sizeof(&a));//4/8 printf("%d\n",sizeof(*&a));//16 printf("%d\n",sizeof(&a + 1));//4/8 printf("%d\n",&a[0]);//4/8 printf("%d\n",&a[0] + 1);//4/8 return 0; }
一个一个理解:
sizeof(a),a作为一个数组名单独放在sizeof内部,计算的是数组的大小,单位是字节,16
sizeof(a+0),a并非单独放在sizeof内部,也没有&,所以说数组名a就是首元素的地址,a+0还是首元素的地址,是地址大小就是4/8
sizeof(*a) a是首元素的地址,*a就是首元素,sizeof(*a)就算的是首元素的大小,4
sizeof(a+1),a是首元素的大小,(a+1)是第二个元素的地址,sizeof(a+1)计算的是指针的大小4/8
sizeof(a[1]),a[1]就是数组的第二个元素,sizeof计算的大小是4.
sizeof(&a),&a取出的是数组的地址,数组的地址,也是地址,sizeof(&a)就是4/8
sizeof(*&a),&a是数组的地址,是数组指针类型,*&a是对数组解引用,访问的是一个数组的大小,16
sizeof(&a+1),&a是数组的地址,&a+1 是跳过整个数组,&a+1还是地址,是4/8
sizeof(&a[0]),a[0],是数组第一个元素的地址,&a[0]是第一个元素的地址,是4/8
sizeof(&a[0]+1),&a[0]是第一个元素的地址,&a[0]+1就是第二个元素的地址,是4/8
第二题(字符数组)
#include <stdio.h> #include <string.h> //字符数组 int main() { char arr[] = {'a','b','c','d','e','f'}; printf("%d\n",sizeof(arr));//6 printf("%d\n",sizeof(arr+0));//4/8 printf("%d\n",sizeof(*arr));//1 printf("%d\n",sizeof(arr[1]));//1 printf("%d\n",sizeof(&arr));//4/8 printf("%d\n",sizeof(&arr + 1));//4/8 printf("%d\n",sizeof(&arr[0] + 1));//4/8 printf("%d\n",strlen(arr));//随机值 printf("%d\n",strlen(arr + 0));//随机值 printf("%d\n",strlen(*arr));//err printf("%d\n",strlen(arr[1]));//err printf("%d\n",strlen(&arr));//随机值 printf("%d\n",strlen(&arr + 1));//随机值 printf("%d\n",strlen(&arr[0] + 1));//随机值 return 0; }
还是一个一个理解:
sizeof是计算对象或者类型创建的对象所占内存空间的大小,单位是字节
sizeof 是一个操作符,不是函数
sizeof(arr), arr 是数组名,并且是单独放在sizeof内部,计算的是数组的总大小,单位是字节,6
sizeof(arr+0),arr是数组名,并非单独放在sizeof内部,arr表示首元素的地址,arr+0还是首元素的地址, 是地址大小就是4/8
sizeof(*arr),arr是首元素的地址,*arr就是首元素,sizeof计算的是首元素的大小,1
sizeof(arr[1]),arr[1]是数组的第二个元素,sizeof(arr[1]),计算的是第二个元素的大小, 1
sizeof(&arr),&arr-取出的是数组的地址,sizeof(&arr)计算的是数组的地址的大小,是地址就是4/8
sizeof(&arr+1), &arr是数组的地址,&arr+1跳过整个数组,指向’f’的后边,本质还是一个地址,是地址就是4/8
sizeof(&arr[0]+1),&arr[0]是’a’的地址,&arr[0]+1是’b’的地址,是地址就是 4/8
接下来是strlen
的理解:
strlen求字符串长度的,计算的是字符串中\0之前出现的字符的个数,统计到\0为止,如果没有\0,会继续往后找
strlen 是一个库函数
strlen(arr),arr是数组名,但是没有放在sizeof内部,也没有取地址;arr就是首元素的地址,strlen得到arr后,从arr数组首元素的地方开始计算字符串的长度,直到\0,但是arr数组中没有\0,arr内存的后边是否有\0,在什么位置是不确定的,所以\0之前出现了多少个字符是随机的. 随机的
strlen(arr+0),arr是首元素的地址,arr+0还是首元素的地址, 随机值
strlen(*arr),arr 是首元素的地址,*arr是首元素’a’,97,strlen会把’a’的ASCII值97当成了地址,err会非法访问内存 err
strlen(arr[1]),arr[1]- ‘b’ - 98 err和上一个情况一样 err
strlen(&arr),&arr是数组的地址,数组的地址也是指向数组起始位置,和第一个一样 随机值
strlen(&arr+1),&arr+1是跳过整个数组,只是起始位置变了,也是随机值 随机值
strlen(&arr[0]+1), &arr[0]是取出第一个地址,然后加1跳过一个元素,也是随机值 随机值
第三题(字符串)
#include <stdio.h> #include <cstring> //字符串 int main() { char p[] = "abcdef"; printf("%d\n",sizeof(p));//7 printf("%d\n",sizeof(p+1));//4/8 printf("%d\n",sizeof(*p));//1 printf("%d\n",sizeof(p[1]));//1 printf("%d\n",sizeof(&p));//4/8 printf("%d\n",sizeof(&p+1));//4/8 printf("%d\n",sizeof(&p[0]+1));//4/8 printf("%d\n",strlen(p));//6 printf("%d\n",strlen(p+0));//6 printf("%d\n",strlen(*p));//err printf("%d\n",strlen(p[1]));//err printf("%d\n",strlen(&p));//6 printf("%d\n",strlen(&p+1));//随机值 printf("%d\n",strlen(&p[0]+1));//5 return 0; }
还是一个一个的理解:
对于sizeof的理解
sizeof(p),p是数组名,直接放在sizeof中,这里计算的是整个数组的大小,单位是字节 7
sizeof(p+1),这里的p表示首元素的地址,p+1表示第二个元素的地址,所以是4/8
sizeof(*p),*p是第一个元素,所以此处的大小是 1
sizeof(p[1]),p[1]访问的是第二个元素,此处表示具体的第二个元素是’b’这个字符 1
sizeof(&p),&p还是整个数组的地址,其实也就是第一个元素的地址,是地址就是4/8
sizeof(&p+1),这里和也是一个地址,是地址就是 4/8
sizeof(&[0]+1),这里还是一个地址,是地址就是 4/8
对于strlen的理解
strlen(p), p表示首元素的地址,所以从第一个元素可是找,找到\0为止,所以结果是 6
strlen(p+0),这个和第一个一样的情况6
strlen(*p),*p表示的是具体的元素, strlen接收的是地址,所以这个地方 err
strlen(p[1]),这个地方穿进去的也是具体的元素,所以还是 err
strlen(&p),取出的是整个数组的地址,也就是第一个元素的地址所以是 6
strlen(&p+1),这个地方是直接跳过整个数组,所以还是随机值
strlen(&p[0]+1),这个地方是将第二个元素的地址放进去,所以是 5
第四题
#include <stdio.h> #include <cstring> int main() { char *p = "abcdef"; printf("%d\n",sizeof(p));//4/8 printf("%d\n",sizeof(p+1));//4/8 printf("%d\n",sizeof(*p));//1 printf("%d\n",sizeof(p[0]));//1 printf("%d\n",sizeof(&p));//4/8 printf("%d\n",sizeof(&p+1));//4/8 printf("%d\n",sizeof(&p[0]+1));//4/8 printf("%d\n",strlen(p));//6 printf("%d\n",strlen(p+0));//6 printf("%d\n",strlen(*p));//1 printf("%d\n",strlen(p[0]));//1 printf("%d\n",strlen(&p));//随机值 printf("%d\n",strlen(&p+1));//随机值 printf("%d\n",strlen(&p[0]+1));//5 return 0; }
还是一个一个理解,先理解sizeof
sizeof(p),这里的p是存的是第一个元素的地址,是地址就是4/8
sizeof(p+1),这里的p+1指向的是第二个元素的地址,是地址就是4/8
sizeof(*p),*p表示的是第一个元素,是char类型的,所以是 1
sizeof(p[0]),p[0]还是表示第一个元素,所以是 1
sizeof(&p),这里的&p是取出p的地址p中本来存的就是一个地址,所以这是一个二级指针,也是一个地址,是地址就是4/8
sizeof(&p+1),这里存的还是一个二级指针,是一个地址,地址就是4/8
sizeof(&p[0]+1),p[0]表示第一个元素,&p[0]表示的还是第一个元素的地址,加1之后表示的是第二个元素的地址
对于strlen的理解:
strlen(p), 这里是将首元素的地址存进去,所以是 6
strlen(p+0),这个和第一个一样,也是6
strlen(*p),p本来表示首元素的地址,这个地方*p表示的是第一个元素,所以 err
strlen(p[0]),和上一个的情况一样
strlen(&p),p本来就是一个指针变量,这里有&,所以这是一个二级指针,也是一个地址,但是这个地址具体从哪里开始的就不知道了,所以这是一个随机值
strlen(&p+1),这个地方由于我们不知道&p–>&p+1,之间有无\0,所以这也是一个随机值
`strlen(&p[0]+1),&p[0],表示的是第一个元素的地址,加1之后表示的是第二个元素的地址,所以这里的结果是 5
第五题(二维数组)
#include <stdio.h> //二维数组 int main() { int a[3][4] = {0}; printf("%d\n",sizeof(a));//48 printf("%d\n",sizeof(a[0][0]));//4 printf("%d\n",sizeof(a[0]));//16 printf("%d\n",sizeof(a[0]+1));//4/8 printf("%d\n",sizeof(*a[0]+1));//4 printf("%d\n",sizeof(a+1));//4/8 printf("%d\n",sizeof(*(a+1));16 printf("%d\n",sizeof(&a[0]+1));4/8 printf("%d\n",sizeof(*(&a[0]+1)));16 printf("%d\n",sizeof(*a));//16 printf("%d\n",sizeof(a[3]));//16 return 0; }
sizeof(a),a是二维数组的数组名,数组名单独放在sizeof内部,计算的是数组的总大小,单位是字节 48
sizeof(a[0][0]),a[0][0]是一个整型元素,大小是四个字节, 4
sizeof(a[0]), 把二维数组的每一行看做一维数组的时候,a[0]是第一行的数组名,第一行的数组名单独放在sizeof内部计算的是第一行的总大小,单位是字节 16
sizeof(a[0]+1),a[0]虽然是第一行的数组名,但是并非单独放在sizeof内部,a[0]作为第一行的数组名并非表示整个第一行这个数组,a[0]就是第一行首元素的地址,a[0]–>&a[0][0],a[0]+1跳过一个int,是a[0][1]的地址 4/8
sizeof(*(a[0]+1)),根据上个例子可以知道,那是一个地址,所以解引用后访问的是第一行第二个元素,是 4
sizeof(a+1),a是二维数组的数组名,没单独放在sizeof内部,也没有&,所以a就是数组首元素的地址,二维数组我们把它想象成一维数组,它的第一个元素就是二维数组的第一行,a就是第一行的地址,a+1就是第二行的地址,是地址就是4/8
sizeof(*(a+1)),a+1是第二行的地址,*(a+1)找到的就是第二行,所以这个表达式计算的就是第二行的大小 16
sizeof(&a[0]+1),&a[0]是第一行的地址,&a[0]+1就是第二行的地址,计算的是第二行的数组的大小 4/8
sizeof(*(&a[0]+1)),根据上一个可知这个访问的是第二行,所以是 16
sizeof(*a),a表示首元素的地址,就是第一行的地址,访问的就是第一行,所以大小是 16
sizeof(a[3]),代码没有问题,a[3]是二维数组的第四行,虽然没有第四行,但是类型能够确定,大小就是确定的大小就是一行的大小,单位是字节 16
任何一个表达式都有两个属性:值属性+类型属性
总结:
1. sizeof计算的时候只要遇见地址就是4/8
2. sizeof(数组名)表示的是整个数组的大小
3. &arr,也表示的是整个数组的大小
4.除了2和3之外的情况所有的情况数组名表示的都是首元素的大小
4.sizeof是一个操作符
5.strlen是一个库函数,形参是一个指针,所以传进去的应该是地址,这个库函数的作用就是看改地址到\0之间有几个字符.
指针笔试题
第一题
#include <stdio.h> int main() { int a[5] = {1,2,3,4,5}; int* ptr = (int*)(&a + 1); printf("%d %d\n", *(a+1), *(ptr - 1)); return 0; }
第二题
#include <stdio.h> struct Test { int Num; char* pcName; short sDate; char cha[2]; short sBa[4]; }*p; int main() { p = (struct Test*)0x100000; printf("%p\n", p+0x1); printf("%p\n",(unsigned long)p + 0x1); printf("%d\n",(unsigned int*)p + 0x1); return 0; }
由于p里面存的是一个地址,第一个打印的时候是一个地址,如果让地址加1就直接跳过20个字节,第二个把p这个变量强转为数字,加1的话就是加1, 第三个将p强转为int*类型,加1就是跳过4个字节.
所以最后的结果是00100014, 00100001, 00100004;
第三题
#include <stdio.h> 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; }
假设下图是整个数组在内存中的存储方式:
我们可以看到最后的打印,ptr1先是&a这个是整个数组的地址,加1之后变为最后一个元素的后一个地址,然后将它强转为int类型,ptr2是现将这个地址转为为整数,然后加1最后转换为int类型造成的结果就是移动一个字节
ptr1[-1]--> *(ptr1-1)
ptr1[2]--> *(ptr1+2)
所以ptr1最后的指向就是
然后根据小端存储,结果是00 00 00 04,前面的0全部省略是4
根据理解可以知道ptr2最后的地址是如图所示:
访问4个字节就是02 00 00 00
第四题
#include <stdio.h> int main() { //数组的初始化内容有逗号表达式,实际上数组初始化的是1,3,5 int a[3][2] = {(0,1),(2,3),(4,5)}; int* p; p = a[0]; printf("%d\n",p[0]); return 0; }
这个比较简单,a[0]代表的第一行的地址,第一行的地址又和第一个元素的地址一样,所以p中存的就是第一个元素的地址,p[0]--->*(p+0)
,所以这个地方打印的就是第一个元素
注意:逗号表达式的结果,就是最后一个运算的结果
第五题
#include <stdio.h> 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[4][2]的位置在如图所示
这里a是首元素的地址,也就是第一行,将第一行的地址给p,虽然类型不匹配,但是这只是一个警告,我们无视即可,所以刚开始p指向的是第一个元素的地址,这里注意p的类型,所以p每次加1只能访问4个元素.
指针相减,求得是之间有几个元素,这里还要打印地址也就是-4,
地址在打印的时候打的是补码所以最后的结果是 FF FF FF FC,最后这里还有一个要注意的地方
第六题
#include <stdio.h> 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是整个数组的地址,加1之后是最后一个元素的后一个地址
ptr2 里面实际存的是第二行的第一个地址
第七题
#include <stdio.h> int main() { char* a[] = {"work", "at", "alibaba"}; char** pa = a; pa ++; printf("%s\n", *pa); return 0; }
a是一个字符指针数组,因为a是首元素的地址,首元素就是一个地址,又要来保存首元素,所以要用一个二级指针,pa++,之后访问的就是第二个元素的地址(*pa)就是第二个元素的首地址,打印的自然是at
第八题
#include <stdio.h> 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; }
根据前3行我们可以画出如下的图:
接着就来一行一行的理解:
根据上图可以知道,cpp刚开始指向c+3位置的地址,加1之后指向c+2位置的地址,然后第一解引用找到c+2里面的元素是一个地址,然后在解引用还是一个地址,地址指向POINT
接着是*--*++cpp+3
由于刚才的++cpp已经影响到了cpp的指向,所以到这一行的时候cpp已经指向的不是首元素了
cpp先加加,指向c+1,然后解引用拿到c+1处的元素,然后再减减,指向c处的元素
,c处的元素指向的是存放ENTER的地址的地址,然后在解引用指向的是首元素,最后加3指向的是第二个E处的地址.所以最后打印ER
接着是*cpp[-2]+3
,
像上图那样,*cpp[-2]+3 -- > **(cpp-2)+3
,cpp刚开始的指向如上图所示,然后减2 指向c+3处,然后经过两次解引用指向FIRST,接着加3 所以最后打印ST
最后就是cpp[-1][-1]+1
cpp刚开始的指向还是如上图所示,cpp[-1][-1]--> *(*(cpp-1)-1)
,cpp先减1指向的是c+2处的地址解引用,拿到的是一块地址,然后减1 变成c+1, 之后在解引用,之后在加1所以最后打印的是EW