【C】(笔试题)指针与数组,指针

在这里插入图片描述
博客主页:心辛向荣
系列专栏:【从0到1,C语言学习】
一句短话:你若盛开,蝴蝶自来!
博客说明:尽己所能,把每一篇博客写好,帮助自己熟悉所学知识,也希望自己的这些内容可以帮助到一些在学习路上的伙伴,文章中如果发现错误及不足之处,还望在评论区留言,我们一起交流进步!😊

前言

🍁这篇博客的重点是对于指针以及数组的理解与运用,掌握这些题目,对于指针与数组的理解与认识会有很大的提高!

一.关于指针和数组的笔试题

  • 数组名的意义:
  1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。
  2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
  3. 除此之外所有的数组名都表示首元素的地址。
  • strlen和sizeof的区别:
  1. strlen是求字符串长度的,关注的是字符串中的\0,计算的是\0之前出现的字符的个数

​ strlen是库函数,只针对字符串

  1. sizeof只关注占用内存空间的大小,不在乎内存中放的是什么

​ sizeof是操作符

  • 正确理解题目:

对于这部分的理解,sizeof(&…)计算的是一个指针类型的大小,也就是地址,地址的大小在32位环境(x86)下是4字节,在64位环境下(x64)是8字节;但对应下面的例题,不能只是单单理解打印结果,更重要的的是理解到这个地址到底是谁的地址;同样在理解strlen求字符串长度时,也要对地址有正确的认识和理解!

1.一维数组

#include<stdio.h>
int main()
{
	int a[] = { 1,2,3,4 };
	printf("%d\n", sizeof(&a + 1));//4/8
	//&a取出的是数组的地址
	//&a对应的指针类型是int(*)[4]
	//&a+1 是从数组a的地址向后跳过了一个(4个整型元素)数组的大小
	//&a+1还是地址,是地址就是4/8字节
	printf("%d\n", sizeof(&a[0]));//4/8
	//&a[0]就是第一个元素的地址
	//计算的是地址的大小
	printf("%d\n", sizeof(&a[0] + 1));//4/8
	//&a[0]+1是第二个元素的地址
	//大小是4/8个字节
	//&a[0]+1 ---> &a[1]
	printf("%d\n", sizeof(a));//16
	//sizeof(数组名),数组名表示整个数组,计算的是整个数组的大小,单位是字节
	printf("%d\n", sizeof(a + 0));//4
	//a不是单独放在sizeof内部,也没有取地址,
	//所以a就是首元素的地址,a+0还是首元素的地址
	//是地址,大小就是4/8个字节
	printf("%d\n", sizeof(*a));//4
	//*a中的a是数组首元素的地址,*a就是对首元素的地址解引用,找到的就是首元素
	//首元素类型是整形,大小是4个字节
	printf("%d\n", sizeof(a + 1));
	//这里的a是数组首元素的地址
	//a+1是第二个元素的地址
	//sizeof(a+1)就是地址的大小
	printf("%d\n", sizeof(a[1]));//4
	//计算的是第二个元素的大小
	printf("%d\n", sizeof(&a));//4/8
	//&a取出的数组的地址,数组的地址,也是个地址
	printf("%d\n", sizeof(*&a));//16
	//第1种理解方法
	//&a----> int(*)[4]
	//&a拿到的是数组名的地址,类型是 int(*)[4],是一种数组指针
	//数组指针解引用找到的是数组
	//*&a ---> a
	//
	//第2种理解方法
	//&和*抵消了
	//*&a ---> a
	return 0;
}

运行结果:

img

2.字符数组

#include<stdio.h>
int main()
{
	char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", sizeof(arr));//6
	//sizeof(数组名),计算整个数组的大小
	printf("%d\n", sizeof(arr + 0));//4/8
	//arr + 0 是数组首元素的地址
	printf("%d\n", sizeof(*arr));//1
	//*arr就是数组的首元素,大小是1字节
	//*arr --> arr[0]
	//*(arr+0) --> arr[0]
	printf("%d\n", sizeof(arr[1]));//1
	//计算数组第二个元素的大小
	printf("%d\n", sizeof(&arr));//4/8
	//&arr是数组的地址,是地址就是4/8个字节
	printf("%d\n", sizeof(&arr + 1));//4/8
	//&arr + 1是数组后的地址
	printf("%d\n", sizeof(&arr[0] + 1));//4/8
	//&arr[0] + 1是第二个元素的地址
	return 0;
}

运行结果:

img

#include<stdio.h>
#include <string.h>
int main()
{
	char arr[] = { 'a','b','c','d','e','f' };

	printf("%d\n", strlen(arr));//随机值
	//没有'\0'作为结束标志
	printf("%d\n", strlen(arr + 0));//随机值

    //printf("%d\n", strlen(*arr));//--> strlen('a');-->strlen(97);//野指针
    // 	   程序会报错
    //printf("%d\n", strlen(arr[1]));//-->strlen('b')-->strlen(98);

	printf("%d\n", strlen(&arr));//随机值
	printf("%d\n", strlen(&arr + 1));//随机值-6
	printf("%d\n", strlen(&arr[0] + 1));//随机值-1

	return 0;
}

运行结果:

img

#include<stdio.h>
#include<string.h>
int main()
{
	char arr[] = "abcdef";
	
	//[a b c d e f \0]
	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;
}

运行结果:

img

#include<stdio.h>
int main()
{
	char arr[] = "abcdef";

	//[a b c d e f \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;
}

运行结果:

img

#include<stdio.h>
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

	return 0;
}

运行结果:

img

#include<stdio.h>
#include<string.h>
int main()
{
	char* p = "abcdef";
	printf("%d\n", strlen(p));//6
	printf("%d\n", strlen(p + 1));//5
	//printf("%d\n", strlen(*p));//野指针,报错
	//printf("%d\n", strlen(p[0]));//野指针,报错
	printf("%d\n", strlen(&p));//随机值
	printf("%d\n", strlen(&p + 1));//随机值
	//由于p的地址占4/8格字节,无法确定'\0'会不会在其中出现
	//所以推断不出这俩个随机值之间的关系
	//这俩个随机值之间相差的值也是一个随机值
	printf("%d\n", strlen(&p[0] + 1));//5
	//p[0]-->*(p+0)
	return 0;
}

运行结果:

img

3.二维数组

#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
	//a[0]是第一行这个一维数组的数组名,
	//单独放在sizeof内部,a[0]表示第一个整个这个一维数组;
	//sizeof(a[0])计算的就是第一行的大小
	printf("%d\n", sizeof(a[0] + 1));//4/8
	//a[0]并没有单独放在sizeof内部,也没取地址,a[0]就表示首元素的地址
	//就是第一行这个一维数组的第一个元素的地址,
	//a[0] + 1就是第一行第二个元素的地址
	printf("%d\n", sizeof(*(a[0] + 1)));//4
	//a[0] + 1就是第一行第二个元素的地址
	//*(a[0] + 1))就是第一行第二个元素
	printf("%d\n", sizeof(a + 1));//4/8
	//a虽然是二维数组的地址,但是并没有单独放在sizeof内部,也没取地址
	//a表示首元素的地址,二维数组的首元素是它的第一行,a就是第一行的地址
	//a+1就是跳过第一行,表示第二行的地址
	printf("%d\n", sizeof(*(a + 1)));//16
	//*(a + 1)是对第二行地址的解引用,拿到的是第二行
	//*(a+1)-->a[1]
	//sizeof(*(a+1))-->sizeof(a[1])
	printf("%d\n", sizeof(&a[0] + 1));//4/8
	//&a[0] - 对第一行的数组名取地址,拿出的是第一行的地址
	//&a[0]+1 - 得到的是第二行的地址
	printf("%d\n", sizeof(*(&a[0] + 1)));//16
	printf("%d\n", sizeof(*a));//16
	//a表示首元素的地址,就是第一行的地址
	//*a就是对第一行地址的解引用,拿到的就是第一行
	printf("%d\n", sizeof(a[3]));//16
	//这里并不会造成越界访问,sizeof在计算时关心的的是a[3]的类型
	//并不会真的去访问a[3],计算它和a[0]是一样的
	printf("%d\n", sizeof(a[0]));//16

	return 0;
}

运行结果:

img

二.关于指针的笔试题

题1:

#include <stdio.h>
int main()
{
	int a[5] = { 1, 2, 3, 4, 5 };
	int* ptr = (int*)(&a + 1);

	printf("%d,%d", *(a + 1), *(ptr - 1));
	return 0;
}

运行结果:

img

解析:

&a+1得到的是数组之后的地址,将这个地址强制类型转换为int * 类型,并赋值给int * 类型的prt,此时 * prt只能访问一个整形元素的空间;

img

*(a+1)中的a是数组首元素地址,a + 1是数组第二个元素的地址,由于prt只有一个整形空间的访问权限,prt - 1得到数组最后一个元素的地址。

题2:

#include <stdio.h>
//已知,在32位环境下(x86)
//结构体Test类型的变量大小是20个字节
struct Test
{
	int Num;
	char* pcName;
	short sDate;
	char cha[2];
	short sBa[4];
}* p = (struct Test*)0x100000;
//假设p 的值为0x100000;如下表表达式的值分别为多少?
int main()
{
	printf("%p\n", p + 0x1);
	//0x100000+20-->0x100014
	printf("%p\n", (unsigned long)p + 0x1);
	//1,048,576+1 --> 1,048,577
	//0x100001
	printf("%p\n", (unsigned int*)p + 0x1);
	//0x100000+4-->0x100004
	return 0;
}

运行结果:

img

解析:

p的类型为struct Test * ,p+1跳过一个结构体(20字节)空间的大小,20转化成16进制就是14;

unigned int 是一个无符号整形,进行整形运算即可;

将p由结构体指针类型强制类型转化为unigned int* 类型,此时p+1跳过4个字节

题3:

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

运行结果:

img

解析:

prt1是数组后的地址,类型为int * ,prt1[-1]–>*(prt-1),prt-1向前跳过四个字节;

将数组首元素地址强制类型转换为一个整形,此时 + 1进行的是整形运算;再将 + 1后的结果强制类型转换为int 类型赋值给prt2,此时prt2是a向后跳过一个字节的地址,* prt2访问4个字节的内容,

而在vs环境下,数据采用的是小端存储模式,是倒着存的,看下图理解:

img

%x以16进制的形式打印。

题4:

#include <stdio.h>
int main()
{
	int a[3][2] = { (0, 1), (2, 3), (4, 5) };
	int* p;
	p = a[0];
	printf("%d", p[0]);
	return 0;
}

运行结果:

img

解析:

这道题有一个忽悠人的点,题中的初始化用的是( )而不是 { } ;而题目( )中的是逗号表达式;

所以数组当中的内容实际上相当于int a[3][2] = {1,2,3};

题5:

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

运行结果:

img

解析:

a是二维数组首元素地址,也就是二维数组第一行的地址,其类型为int ( * ) [5],具有5个字节的访问权限;将赋值给p,p的类型为int ( * )[4],具有4个字节的访问权限;

指针 - 指针计算出的俩指针之间元素的个数,高地址 - 低地址得到的结果是整数,反之是负数;参照下图理解:

img

从图中可以观察到,俩个地址之间的元素个数为4,低地址 - 高地址,所以应该是 - 4;

将结果以%p和%d打印;%d打印出的结果为 - 4;

将 - 4以地址的形式打印;%p可以理解为一个无符号数,求出 - 4的补码转换为16进制即可;

原码:10000000 00000000 00000000 00000100

反码:11111111 11111111 11111111 11111011

补码:11111111 11111111 11111111 11111100

补码的十六进制:ff ff ff fc

题6:

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

运行结果:

img

解析:

&aa+1得到的是数组后的地址,将其强制类型转换为int * 类型,赋值给prt1,此时prt1具有4个字节的访问权限;所以prt1-1得到数组最后一个元素的地址;

aa+1(相当于&arr[1])得到二维数组第二行的地址,* *(aa+1)得到第二行(相当于 * &aa[1]–>aa[1]);此时 ** (aa + 1 )并没有加&或者放在sizeof中,所以此时它相当于第二行数组首元素地址,题目这里进行强制类型转换属于迷惑行为,有没有都一样;将其赋值给prt2,prt2-1就是第一行最后一个元素的地址了!

题7:

#include <stdio.h>
int main()
{
	char* a[] = { "work","at","alibaba" };
	char** pa = a;

	pa++;
	printf("%s\n", *pa);
	return 0;
}

运行结果:

img

解析:

看图更直观,如下,

img

题8:

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

运行结果:

img

解析:

要注意,如:++cpp,会将cpp自身的值改变;而cpp+1不会改变cpp本身;要注意区分,防止做题时混淆,具体理解如下图:

img

结语:

各位小伙伴,看到这里就是缘分嘛,希望我的这些内容可以给你带来那么一丝丝帮助,可以的话三连支持一下呗😁!!! 感谢每一位走到这里的小伙伴,我们可以一起学习交流,一起进步😉!!!加油🏃!!!

img

  • 79
    点赞
  • 67
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 68
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 68
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

韵秋梧桐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值