指针和数组笔试题深度解析(上)

目录

  • 前言
    • 核心要点
    • 一维数组
    • 字符数组
      • 类型一
      • 类型二
      • 类型三
    • 二维数组
    • 总结

前言

本文带大家详细了解 sizeof 与 strlen 在指针和数组方面的应用,让大家不再傻傻分不清楚,提高大家对其的理解。(注:以下代码均在x86 (32位) 环境下运行)

核心要点

数组名的意义:

1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。
2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
3. 除此之外所有的数组名都表示首元素的地址。

sizeof 与 strlen 的作用:

sizeof 是计算对象或者类型创建的对象所占内存空间的大小,单位是字节
sizeof 是操作符,不是函数, sizeof 不关注 \0
strlen 求字符串长度的,从给定的地址出发计算字符串中 \0 之前出现的字符的个数,统计到 \0 为止,如果没有看到 \0,会继续往后找
strlen 是库函数,使用需要包含头文件

指针加一跳多长?

char *p;        //一级指针   *p里的*说明p是指针,指向的类型是char型
char* *pp=&p;   //二级指针   *pp里的*说明pp是指针,指向的类型是char*型

p+1跳过的是一个char型对象
pp+1跳过的是一个char*型对象

指针加一跳多长,取决于指针指向对象的类型

一维数组

下面一段代码大家可以先试着做一做,想一想

#include
int main()
{
	int a[] = { 1,2,3,4 };
	printf("%d\n", sizeof(a));
	printf("%d\n", sizeof(a + 0));
	printf("%d\n", sizeof(*a));
	printf("%d\n", sizeof(a + 1));
	printf("%d\n", sizeof(a[1]));
	printf("%d\n", sizeof(&a));
	printf("%d\n", sizeof(*&a));
	printf("%d\n", sizeof(&a + 1));
	printf("%d\n", sizeof(&a[0]));
	printf("%d\n", sizeof(&a[0] + 1));
	return 0;
}

输出:
指针和数组笔试题深度解析(上)_第1张图片
下面我们来逐个分析:

	int a[] = {1,2,3,4};       //数组a有4个元素,每个元素占4个字节,
	printf("%d\n",sizeof(a));      // 16
    //a作为数组名单独放在sizeof内部,计算的是数组的总大小,单位是字节
	printf("%d\n",sizeof(a+0));   // 4/8
	//a并非单独放在sizeof内部,也没有&,所以数组名a就是数组首元素的地址
	//a+0还是数组首元素的地址,是地址其大小就是 4/8 个字节(x86是4字节,x64是8字节)
	printf("%d\n",sizeof(*a));    // 4
	//a是首元素的地址,*a就是首元素,sizeof(*a)算的就是首元素的大小 
	printf("%d\n",sizeof(a+1));   // 4/8
	//a是首元素的地址,a+1是第二个元素的地址,sizeof(a+1)计算的是指针也就是地址大小
	printf("%d\n",sizeof(a[1]));  // 4
	//a[1]就是数组的第二个元素,sizeof(a[1])的大小 - 4个字节
	printf("%d\n",sizeof(&a));   // 4/8
	//&a取出的整个数组的地址,数组的地址,也是地址呀,sizeof(&a)就是 4/8 个字节
	printf("%d\n",sizeof(*&a));  // 16
	//&a是数组的地址,是数组指针类型,*&a是数组指针解引用,访问一个数组的大小
	//sizeof(*&a) ==> sizeof(a)  =16
	printf("%d\n",sizeof(&a+1));  // 4/8
	//&a数组的地址,&a+1跳过整个数组,&a+1还是地址,是 4/8 个字节
	printf("%d\n",sizeof(&a[0]));  // 4/8
	//a[0]是数组的第一个元素,&a[0]是第一个元素的地址,是 4/8 个字节
	printf("%d\n",sizeof(&a[0]+1));  // 4/8
	//&a[0]是第一个元素的地址,&a[0]+1就是第二个元素的地址,是 4/8 个字节

字符数组

类型一

有了以上讲解相信大家对下面代码应该会得心应手

#include
int main()
{
	char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", sizeof(arr));
	printf("%d\n", sizeof(arr + 0));
	printf("%d\n", sizeof(*arr));
	printf("%d\n", sizeof(arr[1]));
	printf("%d\n", sizeof(&arr));
	printf("%d\n", sizeof(&arr + 1));
	printf("%d\n", sizeof(&arr[0] + 1));
	return 0;
}

输出:
指针和数组笔试题深度解析(上)_第2张图片
分析:

	char arr[] = { 'a','b','c','d','e','f' };  //每个 char 型字符占一个字节,整个数组占6个字节
	printf("%d\n", sizeof(arr));      // 6
	//arr是数组名,并且是单独放在sizeof内部,计算的是数组总大小,单位是字节 - 6
	printf("%d\n", sizeof(arr + 0));  // 4/8
	//arr是数组名,并非单独放在sizeof内部,arr表示首元素的地址,arr+0还是首元素的地址
	printf("%d\n", sizeof(*arr));     // 1
	//arr是首元素的地址,*arr就是首元素,sizeof计算的是首元素的大小,是1字节
	printf("%d\n", sizeof(arr[1]));   // 1
	printf("%d\n", sizeof(&arr));     // 4/8   
	//&arr-取出的是数组的地址,sizeof(&arr)计算的是数组的地址的大小,是地址就是4/8字节
	printf("%d\n", sizeof(&arr + 1));  // 4/8
	//&arr是数组的地址,&arr+1跳过整个数组,指向'f'的后边,&arr+1的本质还是地址
	printf("%d\n", sizeof(&arr[0] + 1)); // 4/8
	//&arr[0]是‘a’的地址,&arr[0]+1是'b'的地址,是地址就是4/8字节

我们再来看看 strlen 函数的使用:

#include
#include
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));
	printf("%d\n", strlen(arr[1]));
	printf("%d\n", strlen(&arr));
	printf("%d\n", strlen(&arr + 1));
	printf("%d\n", strlen(&arr[0] + 1));
	return 0;
}

输出:
指针和数组笔试题深度解析(上)_第3张图片
为什么会出现这种情况呢?strlen(*arr) 有什么问题呢?

arr是数组首元素的地址,*arr 是首元素 - ‘a’ - 97
strlen就把 ‘a’ 的ASCII码值 97 当成了地址
导致非法访问内存,也就是 err(注:像这种地址都是不能访问的)

它下面的一段代码是 arr[1] ,也就是 ‘b’ - 98,也是不能访问的。
我们将它俩屏蔽再来看看结果:
指针和数组笔试题深度解析(上)_第4张图片
不知道大家有没有发现好像两次输出的结果有点小差异,第一次输出是:23 23,这与19 19不同,为什么呢?
我们来分析一下:

	char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", strlen(arr));    //随机值
	//arr是数组名,但是没有放在sizeof内部,也没&,arr就是首元素的地址
	//strlen得到arr后,从arr数组首元素的地方开始计算字符串的长度,直到直到\0,但是arr数组中没有\0,arr内存的后边是否有\0,在什么位置是不确定的,所以\0之前出现了多少个字符是随机的。
	printf("%d\n", strlen(arr + 0));  //随机值
	//arr是数组首元素的地址,arr+0还是首元素的地址
	  //printf("%d\n", strlen(*arr));
	  //printf("%d\n", strlen(arr[1]));
	printf("%d\n", strlen(&arr));  //随机值
	//&arr是数组的地址,数组的地址也是指向数组起始位置,和第一个案例一样
	printf("%d\n", strlen(&arr + 1));   //随机值-6
	//跳过整个数组,整个数组大小为6个字节,所以在随机值的基础上-6
	printf("%d\n", strlen(&arr[0] + 1));  //随机值-1
	//&arr[0] + 1是第二个元素地址,所以是随机值-1

类型二

废话不多说上代码:

#include
int main()
{
	char arr[] = "abcdef";        //看清楚是 sizeof  
	printf("%d\n", sizeof(arr));
	printf("%d\n", sizeof(arr + 0));
	printf("%d\n", sizeof(*arr));
	printf("%d\n", sizeof(arr[1]));
	printf("%d\n", sizeof(&arr));
	printf("%d\n", sizeof(&arr + 1));
	printf("%d\n", sizeof(&arr[0] + 1));
	return 0;
}

输出:
指针和数组笔试题深度解析(上)_第5张图片
相信大家应该挺疑惑的吧,为什么会是 7 不是 6 呢?

首先我们得知道,"abcdef"里面存了啥?
其实在这个字符串最后面有一个 \0 没有写出来
写成大括号的形式就是{‘a’,‘b’,‘c’,‘d’,‘e’,‘f’,‘\0’}
我们再看 sizeof 的用法:strlen 求字符串长度的,计算的是字符串中 \0 之前出现的字符的个数,统计到 \0 为止,如果没有看到 \0,会继续往后找
如此想必大家都明白了吧。

分析:

	char arr[] = "abcdef";       
	printf("%d\n", sizeof(arr));      // 7
	printf("%d\n", sizeof(arr + 0));  // 4/8
	//首元素地址
	printf("%d\n", sizeof(*arr));    // 1
	//arr[0]的大小
	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

再来看关于 strlen 的:

#include
#include
int main()
{
	char arr[] = "abcdef";
	printf("%d\n", strlen(arr));
	printf("%d\n", strlen(arr + 0));
	//printf("%d\n", strlen(*arr));  //这两行代码会导致非法访问,屏蔽
	//printf("%d\n", strlen(arr[1]));
	printf("%d\n", strlen(&arr));
	printf("%d\n", strlen(&arr + 1));
	printf("%d\n", strlen(&arr[0] + 1));
	return 0;
}

看输出:
指针和数组笔试题深度解析(上)_第6张图片
分析:

	printf("%d\n", strlen(arr));      // 6        arr是整个数组地址
	printf("%d\n", strlen(arr + 0));  // 6        arr+0是首元素地址
	printf("%d\n", strlen(&arr));     // 6        &arr是整个数组地址
	printf("%d\n", strlen(&arr + 1));  //随机值   &arr+1是跳过整个数组后的地址
	printf("%d\n", strlen(&arr[0] + 1));  //5     &arr[0] + 1是第二个元素的地址

类型三

#include
int main()
{
	const char* p = "abcdef";          //p存放的是字符串首元素 —‘a’的地址,可以通过该地址找到后面的元素
	printf("%d\n", sizeof(p));
	printf("%d\n", sizeof(p + 1));
	printf("%d\n", sizeof(*p));
	printf("%d\n", sizeof(p[0]));
	printf("%d\n", sizeof(&p));
	printf("%d\n", sizeof(&p + 1));
	printf("%d\n", sizeof(&p[0] + 1));
	return 0;
}

输出:
指针和数组笔试题深度解析(上)_第7张图片
分析:

	printf("%d\n", sizeof(p));       // 4/8
	//p存放的是字符串首元素 —‘a’的地址,地址大小就是 4/8
	printf("%d\n", sizeof(p + 1));   // 4/8    ‘b’的地址
	printf("%d\n", sizeof(*p));      // 1      首元素‘a’的大小为一字节
	printf("%d\n", sizeof(p[0]));    // 1      首元素‘a’的大小为一字节
	printf("%d\n", sizeof(&p));      // 4/8    取出首元素地址
	printf("%d\n", sizeof(&p + 1));  // 4/8    ‘b’的地址
	printf("%d\n", sizeof(&p[0] + 1));  // 4/8   ‘b’的地址

再上strlen:

#include
#include
int main()
{
	const char* p = "abcdef";  
	printf("%d\n", strlen(p));
	printf("%d\n", strlen(p + 1));
	//printf("%d\n", strlen(*p));     //老规矩,非法访问,先屏蔽
	//printf("%d\n", strlen(p[0]));
	printf("%d\n", strlen(&p));
	printf("%d\n", strlen(&p + 1));
	printf("%d\n", strlen(&p[0] + 1));
	return 0;
}

输出:
指针和数组笔试题深度解析(上)_第8张图片
是不是没有想到呢,我们来分析一下:

	const char* p = "abcdef";  
	printf("%d\n", strlen(p));    //6   首元素地址
	printf("%d\n", strlen(p + 1));   //5   第二个元素地址
	printf("%d\n", strlen(&p));     //随机值
	//p是首元素地址,那么&p就是把 p 的地址取出来,strlen从 p 的地址处开始找\0,见下图
	printf("%d\n", strlen(&p + 1));   //随机值
	printf("%d\n", strlen(&p[0] + 1));  //5  第二个元素地址

假设‘a’的地址是 0x0012ff40 ,那么p里存放的就是该地址,如下图:
指针和数组笔试题深度解析(上)_第9张图片
strlen(&p)就是把p的地址放进去,然后从p的地址处开始找 \0 ,如图
指针和数组笔试题深度解析(上)_第10张图片

图中标出&p的位置,从该处往后找 \0 ,地址是占4个或8个字节,我们这里用x86,占4个字节,那里面的数是怎么样的呢?(假设我们是小端存储,如有不懂请看往期)
指针和数组笔试题深度解析(上)_第11张图片
我们可以看到第四个字节位置是00,也就是 \0 ,strlen应该是读到这里为止,那么strlen(&p)应该为3,这是在‘a’的地址确定的情况下,问题是我们不知道‘a’的地址是多少,在每次测试时都有可能不同,所以就是随机值了。strlen(&p + 1)也是同样的道理。

二维数组

首先来补充一点:(假设有个二维数组 int arr[3][4])

arr 也可以看做另类的一维数组,我们可以把每行当成一个元素,这样第一行就可以用 a[0]来表示,第二行用 a[1]来表示,以此类推(a[0]、a[1]若是单独放在sizeof里面,或者前面加了个&,则a[0]、a[1]的类型才是int(*)[4],才能代表一行元素的地址,否则只能代表一行的首元素地址。如有疑惑看往期—指针进阶),那么每一行里的具体元素怎么表示呢?我们可以再加个括号即可,如:第一行第二个元素,我们可以写成:a[0][1]也可以写成 *(a[0]+1) 还可以写成 * ( *(a+0)+1)来表示,为什么呢?
我们知道 *(a+1)==a[1] ,那么二维数组第一行 a[0] 也可以写成 *(a+0) ,同样的 a[0][1]也就可以写成 *(a[0]+1)

下面我们来练练二维数组:

#include
int main()
{
	int a[3][4] = { 0 };
	printf("%d\n", sizeof(a));
	printf("%d\n", sizeof(a[0][0]));
	printf("%d\n", sizeof(a[0]));
	printf("%d\n", sizeof(a[0] + 1));
	printf("%d\n", sizeof(*(a[0] + 1)));
	printf("%d\n", sizeof(a + 1));
	printf("%d\n", sizeof(*(a + 1)));
	printf("%d\n", sizeof(&a[0] + 1));
	printf("%d\n", sizeof(*(&a[0] + 1)));
	printf("%d\n", sizeof(*a));
	printf("%d\n", sizeof(a[3]));
	return 0;
}

输出:
指针和数组笔试题深度解析(上)_第12张图片
不知道大家准确率咋样,我们直接开始分析了:

	int a[3][4] = { 0 };
	printf("%d\n", sizeof(a));   // 48
	//a是二维数组的数组名,数组名单独放在sizeof内部,计算的是数组的总大小,单位是字节
	printf("%d\n", sizeof(a[0][0]));  // 4
	//a[0][0]是一个整型元素,大小是4个字节
	printf("%d\n", sizeof(a[0]));   // 16
	//a[0]是第一行的数组名,第一行的数组名单独放在sizeof内部,计算的是第一行的总大小,单位是字节 - 16
	printf("%d\n", sizeof(a[0] + 1));  // 4/8
	//a[0]虽然是第一行的数组名,但是并非单独放在sizeof内部
	//a[0]作为第一行的数组名并非表示整个第一行这个数组,a[0]就是第一行首元素的地址,a[0]--> &a[0][0] - int*
	//a[0]+1,跳过一个int,是a[0][1]的地址  4/8字节
	printf("%d\n", sizeof(*(a[0] + 1)));   // 4
	//a[0]+1是第一行第二个元素的地址,所以*(a[0]+1)就是a[0][1],大小是4个字节
	printf("%d\n", sizeof(a + 1));  // 4/8
	//a是二维数组的数组名,没单独放在sizeof内部,也没有&,所以a就是数组首元素的地址
	//二维数组,我们把它想象成一维数组,它的第一个元素就是二维数组的第一行
	//a就是第一行的地址,a+1 是第二行的地址,是地址,大小就是 4/8 个字节
	printf("%d\n", sizeof(*(a + 1)));   // 16
	//a+1是第二行的地址,*(a+1) 找到的就是第二行,sizeof(*(a + 1))也就是sizeof(a[1])计算的就是第二行的大小
	printf("%d\n", sizeof(&a[0] + 1));  // 4/8
	//&a[0]是第一行的地址,&a[0]+1就是第二行的地址,sizeof(&a[0] + 1)计算的第二行地址大小
	printf("%d\n", sizeof(*(&a[0] + 1)));  // 16
	//&a[0] + 1是第二行的地址,*(&a[0] + 1)拿到的就是第二行,大小就是16个字节
	printf("%d\n", sizeof(*a));  // 16
	//a表示首元素的地址,就是第一行的地址 - &a[0]
	printf("%d\n", sizeof(a[3]));  //16
	//a[3]是二维数组的第4行,虽然没有第四行,但是类型能够确定,大小就是确定的。大小就是一行的大小,单位是字节 - 16

总结

做这类题就是要抓住核心要点,明白 strlen、sizeof 的用法,再多加练习就能做到秒杀,相信大家看完本期一定收获满满吧,以后碰到就不怕了。

你可能感兴趣的:(记录学习,c#,开发语言)