【C语言】深度解析——指针和数组笔试题、指针笔试题

前言

本篇给大家介绍并且分析指针和数组的笔试题,感兴趣的小伙伴可以点赞和收藏哦~
————————————————————

一.指针和数组笔试题

下面的题目大多与数组名相关,所以这里在复习下对数组名的理解
数组名是数组首元素的地址,但是有两个例外。
1.sizeof(数组名) ,这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节;
2.&数组名,这里的数组名表示整个数组,取出的是整个数组的地址

1.一维数组(整型)

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/8
	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", sizeof(&a[0]));// 4/8
	printf("%d\n", sizeof(&a[0] + 1));// 4/8
	return 0;
}

分析:
1.a是数组名,放在sizeof里面,表示的是整个数组,计算的是整个数组的大小,a数组有4个元素,每个元素大小为4个字节,所以总共是16个字节。
2.a+0不是单独的数组名,不是例外的第一种情况,所以a是数组首元素的地址,首元素的地址加0还是首元素的地址,是地址,结果就为4/8
3. * a不是例外的两种情况,所以是数组首元素的地址,是地址,结果就为4/8
4.数组名a是数组首元素的地址,a+1是第二个元素的地址,是地址,结果就为4/8
5.表示数组第二个元素的大小,为4个字节
6.&a是数组的地址,数组的地址也是地址,是地址,结果就为4/8
7.这里有两种方式解答:
(1): * &a ==> a,所以sizeof(a)为16
(2):&a的类型为int (*) [4]数组指针,对它进行解引用操作,访问的是数组指针所指向的内容,即数组a
8.&a+1相对于&a跳过的是整个数组,但依然是地址,是地址,结果就为4/8
9.取的是数组首元素的地址,大小为4/8
10.&a[0] + 1是数组第二个元素的地址,是地址,结果就为4/8
在这里插入图片描述

2.字符数组

1.sizeof

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
	return 0;
}

分析:
1.数组名arr单独放在sizeof内部,表示整个数组,计算的是整个数组的大小,单位是字节,结果为6
2.arr表示数组首元素的地址,arr+0还是数组首元素的地址,是地址,结果就为4/8
3…arr表示数组首元素的地址,* arr就是首元素,大小是1个字节
4.是数组的第二个元素,大小是1个字节
5.&arr是数组的地址,数组的地址也是地址,是地址,结果就为4/8
6.&arr+1是跳过整个数组后的地址,是地址,结果就为4/8
7.是数组的第二个元素的地址,是地址,结果就为4/8

2.strlen

int main()
{
    
    
	char arr[] = {
    
     'a','b','c','d','e','f' };
	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;
}

分析:
1.strlen字符串长度,统计的是在字符串中\0之前字符的个数,没找到\0就一直往后找。而这个字符数组arr中没有\0,所以会一直往后找,结果是随机值
2.arr+0是首元素地址,结果与上题同,是随机值
3.arr是数组首元素的地址,* arr就是数组首元素,得到字符‘ a’-97,就会将97作为地址传参,所以strlen就会从97开始统计字符串个数,非法访问err
4.传的是数组的第二个元素,与上题同,非法访问err
5.&arr是数组的地址,数组的地址和数组首元素的地址值是一样的,传给strlen后还是从数组的第一个元素的位置开始往后统计,为随机值
6.跳过整个数组,但还是没有\0,所以是随机值
7.&arr[0] + 1是第二个元素的地址,从这个位置开始往后统计,结果也是随机值

3.sizeof

int main()
{
    
    
	char arr[] = "abcdef";// [a b c d e f \0] 自带斜杆0
	printf("%d\n", sizeof(arr));// 7
	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
	return 0;
}

分析:
1.数组名单独放在sizeof内部,计算的是整个数组的大小,单位是字节,假设斜杠0结果为7
2.arr+0是首元素的地址,是地址,结果就为4/8
3.arr是首元素地址,* arr就是首元素,结果为1(* arr --> *(arr+0) --> arr[0] )
4.是数组的第二个元素,结果为1
5.&arr是数组的地址,是地址,结果就为4/8
6.&arr + 1是跳过整个数组后的地址,是地址,结果就为4/8
7.是第二个元素的地址,是地址,结果就为4/8

4.strlen

int main()
{
    
    
	char arr[] = "abcdef";
	printf("%d\n", strlen(arr));// 6
	printf("%d\n", strlen(arr + 0));// 6
	printf("%d\n", strlen(*arr));// err
	printf("%d\n", strlen(arr[1]));// err
	printf("%d\n", strlen(&arr));// 6
	printf("%d\n", strlen(&arr + 1));// 随机值
	printf("%d\n", strlen(&arr[0] + 1));// 5
	return 0;
}

分析:
1.strlen统计的是\0之前出现的字符的个数,结果为6
2.arr是首元素的地址,arr+0还是首元素的地址,从这个位置开始往后统计\0前出现的字符个数,结果为6
3.*arr就是数组首元素,非法访问
4.是数组的第二个元素,结果同上
5.&arr是数组的地址,数组的地址和数组首元素的地址值是一样的,结果为6
6.&arr+1跳过整个数组,包括\0,后面是未知的,所以是随机值
7.&arr[0] + 1从第二个元素的位置开始往后统计,遇到斜杠0停下,结果为5

3.指针数组

1.sizeof

int main()
{
    
    
	char* p = "abcdef";//这里是把字符串的首元素地址传给p
	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
	return 0;
}

分析:
1.p是一个指针变量,大小就是4/8个字节
2.p+1指向的是字符b的地址,大小是4/8个字节
3.*p是首元素‘a’,结果为1个字节
4. p[0] --> * (p+0) --> * p,所以结果同上
5. &p取的是地址,是地址,结果就为4/8(&p --> char **)
6. 取出的是存放地址的地址,是地址,结果就为4/8
7. &p[0] + 1是字符b的地址,是地址,结果就为4/8
在这里插入图片描述
2.strlen

int main()
{
    
    
	char* p = "abcdef";
	printf("%d\n", strlen(p));// 6
	printf("%d\n", strlen(p + 1));// 5
	printf("%d\n", strlen(*p));// err
	printf("%d\n", strlen(p[0]));// err
	printf("%d\n", strlen(&p));// 随机值
	printf("%d\n", strlen(&p + 1));// 随机值
	printf("%d\n", strlen(&p[0] + 1));// 5
	return 0;
}

1.p存放的是字符a的地址,从a开始往后统计直到斜杠0停下,结果为6
2.p+1从字符b的位置开始数,结果为5
3.*p就是字符a,不是地址,非法访问
4.p[0]就是字符a,同上
5.&p是p这个指针变量里面开始往后数,没有斜杠0,所以是随机值
6.&p+1跳过p指针变量的整个地址,还是找不到斜杠0,所以是随机值
7.&p[0] + 1从字符b的位置开始数,结果为5

4.二维数组

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

1.数组名单独放在sizeof里面,计算的是整个数组的大小,3 * 4 * 4 = 48个字节
2.a[0][0]表示的数组第一行第一列的元素,大小为4个字节
3.之前学习过二维数组其实是一维数组的数组,把每一行当作一个元素,所以a[0]表示的是二维数组的第一行,第一行中有4个元素,大小为16个字节(a[0]是第一行这个一维数组的数组名,数组名单独放在sizeof里面,计算的是整个数组的大小)
4.a[0] 可以作为第一行的数组名,没有&,表示的是数组首元素的地址,等价于a[0][0],所以a[0] + 1是第一行第二个元素的地址,是地址,结果就为4/8
5.*(a[0] + 1)计算的是第一行第二个元素的大小,为4个字节
6.a是数组首元素的地址,即第一行的地址,a+1就是第二行的地址,是地址,结果就为4/8
7.(a + 1)表示的是第二行的地址,对它进行解引用得到的是第二行整体的元素,大小为16个字节
8.&a[0]是第一行的地址,+1得到第二行的地址,是地址,结果就为4/8
9.对第二行的地址解引用,计算的是第二行的大小,是16个字节
10.a是数组首元素的地址,即第一行的地址, * a就是第一行,计算的是第一行的大小,为16个字节
11.a[3]看似是越界访问,但是sizeof只关注类型,根据类型来确定大小,(也是行)结果为16个字节

5.补充:

以下面代码为例:

int main()
{
    
    
	int a = 3;
	short s = 4;
	printf("%d\n", sizeof(s = a + 1));
	printf("%d\n", s);
	return 0;
}

打印结果:
在这里插入图片描述
s = a + 1这个表达式的结果为short类型,sizeof(short)类型的大小为2。
一段代码的处理过程

编译 + 链接 --> 可执行程序 --> 运行 --> 结果
sizeof s=a+1

整个过程先处理sizeof,所以sizeof只关注类型
表达式有两种属性:
1.值属性 3+1=4
2.类型属性 s=a+1 --> sizeof
上面代码放在sizeof内部时类型属性已经确定值的大小了

二.指针笔试题

题1:

int main()
{
    
    
	int a[5] = {
    
     1, 2, 3, 4, 5 };
	int* ptr = (int*)(&a + 1);
	printf("%d,%d", *(a + 1), *(ptr - 1));
	return 0;
}

分析:
&a是数组首元素的地址,&a+1跳过整个数组,指向最后一个元素后的地址,因为ptr是int * 类型,所以要强制转换成int * 类型,此时ptr指向的是最后一个元素后的地址,ptr-1指向最后一个元素的地址,对它进行解引用操作,得到数字5;a是数字首元素的地址,a+1是第二个元素的地址,解引用得到数字2,所以答案为2,5
在这里插入图片描述

题2:

struct Test
{
    
    
	int Num;
	char* pcName;
	short sDate;
	char cha[2];
	short sBa[4];
}*p = (struct Test*)0x100000;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
int main()
{
    
    
	printf("%p\n", p + 0x1);
	printf("%p\n", (unsigned long)p + 0x1);
	printf("%p\n", (unsigned int*)p + 0x1);
	return 0;
}

结果:
在这里插入图片描述
分析:
p是结构体类型的指针,是指针,+1跳过的是一个结构体(已知结构体的大小为20个字节),所以0x100000+20 --> 0x100014(20的16进制数是14);将p转换成无符号长整型,+1就是整型+1,所以为0x100001;将p转换成无符号整型指针,是指针,+1跳过的是一个整型,大小为4个字节,所以结果为0x100004

题3:

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

结果:
在这里插入图片描述
分析:
数组里面存放着4个元素,每个元素的大小为4个字节。这里我们再回忆下小端存储,把数据的低位存放在低地址上,数据的高位存放在高地址上。(比如数字1:16进制为0x 00 00 00 01,存储到小端时为01 00 00 00(从左往右,地址由低到高变化))&a是数组首元素的地址,&a+1是跳过整个数组后的地址,这两个都是数组指针类型,因为ptr1是整型指针类型,所以(&a + 1)要进行强制类型转换成整型指针类型。ptr1[-1]相当于 * (ptr1-1),因为它是整型指针类型,所以向前移动4个字节,对整型指针解引用,从这个位置开始向后读取4个字节,所以 ptr1[-1]的结果为4。前面a已经被转换成整型指针类型了,(int)a把它转换成整型,(int)a + 1向后跳过1个字节,因为ptr2是整型指针类型,所以又把(int)a + 1转换成整型指针类型,对它进行解引用,从这个位置开始向后读取4个字节,得到00 00 00 02,因为是小端存储,所以还原回来是02 00 00 00,打印出来02前面的0可省略。
在这里插入图片描述

题4:

int main()
{
    
    
	int a[3][2] = {
    
     (0, 1), (2, 3), (4, 5) };
	int* p;
	p = a[0];
	printf("%d", p[0]);//结果为1
	return 0;
}

分析:
第一眼看到这个题目时,我相信很多人跟我一样想当然的以为结果为0。但是如果稍微细心一点,就发现题目有坑。
错误的认为题目的二维数组是:
在这里插入图片描述
但是请注意!!!:
{ (0, 1), (2, 3), (4, 5) }里面的括号是逗号表达式,逗号表达式的结果是最后一项,所以花括号里面应该是 { 1, 3 , 5 },其余的补0。
正确的二维数组:
在这里插入图片描述
p[0]相当于 * (p+0) --> * p,即第一行第一个的元素,所以结果为1

题5:

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是5行5列的二维数组,p是数组指针,将a赋给p,p的首元素地址跟a一样。&p[4][2] - &a[4][2]两个地址相减相当于两个指针相减,指针减指针得到的是两个指针之间的元素个数,又因为随着数组下标的增长,地址是由低到高变化的,所以低地址-高地址结果是-4。
在内存中-4的形式是补码:

10000000000000000000000000000100 原码
11111111111111111111111111111011 反码
11111111111111111111111111111100 补码
1111 1111 1111 1111 1111 1111 1111 1100
F F F F F F F C
%p打印出来的结果是:0xFFFFFFFC

在这里插入图片描述

题6:

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));
	          //结果为   10             5
	return 0;
}

分析:
&aa是数组首元素的地址,&aa + 1是跳过整个数组后的地址,强转后赋给ptr1,ptr1也是指向这个地址,ptr1-1向前移动一个地址,解引用得到元素10。aa是数组名也是数组首元素的地址,aa + 1跳到第二行首元素的地址,解引用得到数组名,再强转赋给ptr2,ptr2-1是前一个元素的地址,解引用得到元素5。
在这里插入图片描述

题7:

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

分析:
a是指针数组,把3个字符串存放在数组a里面,每个元素的类型是char * 。pa是一个指针变量,类型是char **,存放的是a的地址,刚开始指向a的首元素地址。pa++跳过一个地址,解引用得到 at
在这里插入图片描述

题8:

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

结果:
在这里插入图片描述
分析:
数组c是指针数组,里面每个元素的类型是char * ,存放着"ENTER",“NEW”,“POINT”,"FIRST"这4个对应的字符串;cp数组的每个元素的类型是char ** ,存放的元素依次是c+3,c+2,c+1,c;c是数组名,指向首元素的地址(图);cpp的类型是char * * *,数组名是首元素的地址,即指向cp的的首元素的地址。
在这里插入图片描述
1.**++cpp中的++优先级大于 * *,所以cpp+1指向下一个元素的地址,解引用得到char * 的地址,再解引用得到POINT。
在这里插入图片描述
2. * – * ++cpp + 3中++的优先级最高,cpp在前面的基础上再++,指向下一个元素的地址,解引用得到下标为1的char * ,然后–,转变成下标为0的地址,再解引用,找到ENTER,+3ENTER的下标从E开始,所以最后打印ER。
在这里插入图片描述
3.*cpp[-2] + 3中cpp-2回到原来的地址,指向的char * *(c+3),解引用得到数组c最后元素的地址,再解引用得到FIRST,+3FIRST的下标从S开始,所以最后打印ST。
在这里插入图片描述
4.cpp[-1][-1] + 1等价于 * ( * (cpp-1) -1) +1,cpp由于前面的++操作,还是指向下标为2的char * *。cpp-1指向下标为1的char * * ,第一次解引用后得到下标为2的char * ,再减1,地址变为下标为1的char *,第二次解引用,找到NEW,+1NEW的下标从E开始打印,结果为EW。
在这里插入图片描述
结语:分享到此结束,感谢大家的观看!
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/2301_77459845/article/details/131763648