【C语言】16.指针(6)进阶篇目——数组与指针笔试题目详解

🎉个人主页: 缘三水的博客
❄专栏传送门:C语言专栏(新手向)
🎀人生格言:行动治愈迷茫的良药


🚀个人介绍:


往篇回顾

【C语言】指针(1)

【C语言】指针(2)

【C语言】指针 (3)

【C语言】指针 (4)

【C语言】指针 (5)


文章目录


前言

前面我们学习了指针的基础知识,下面我们就来练习一下指针相关的笔试题


正文开始

(一)sizeof与strlen

1.1 sizeof

定义

sizeof是单目操作符

sizeof可以求变量表达式类型 的长度,即所占内存空间的大小

单位是字节

得出的结果是无符号整型,可以用 %zu 表示

示例

注意

sizeof括号内是变量或表达式 时,括号可以 省略

sizeof括号内是数据类型 时,括号不可以省略

表达式情形

结果为什么是8呢?
解释

a是int类型

3.14是double类型

两个操作数类型不同,要发生算术转换

int类型转换成double类型

因此,最后计算结果是double类型的长度,即是8

1.2 strlen

函数原型

c 复制代码
size_t strlen(char* str);

定义

strlen是C语言中的库函数

作用是求字符串的长度

统计的是从首字符的地址str 开始到 \0 之前的字符个数

返回值类型是size_t

参数是一个字符型指针

需要包含头文件

c 复制代码
#include <string.h>

示例

特点

strlen会一直向后找到\0为止

因此,如果strlen计算的不是字符串,则可能会出现越界访问

1.3 两者对比

(二)数组和指针笔试题

数组名的意义

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

2.1 一维数组

题目
下列代码会打印出什么?

c 复制代码
int main()
{
	int a[] = { 1,2,3,4 };
	printf("%zu\n", sizeof(a));
	printf("%zu\n", sizeof(a + 0));
	printf("%zu\n", sizeof(*a));
	printf("%zu\n", sizeof(a + 1));
	printf("%zu\n", sizeof(a[1]));
	printf("%zu\n", sizeof(&a));
	printf("%zu\n", sizeof(*&a));
	printf("%zu\n", sizeof(&a + 1));
	printf("%zu\n", sizeof(&a[0]));
	printf("%zu\n", sizeof(&a[0] + 1));
	return 0;
}

结果

解释

c 复制代码
int main()
{
	int a[] = { 1,2,3,4 };
	printf("%zu\n", sizeof(a));//16
	//数组名单独放在sizeof里面,计算整个数组的大小
	printf("%zu\n", sizeof(a + 0));// 4/8
	//数组名a并没有单独放在sizeof里,所以代表的是首元素地址,计算的大小就是首元素地址的大小,由环境决定,我的环境是x64,因此计算出8
	printf("%zu\n", sizeof(*a));//4
	//数组名a并没有单独放在sizeof里,所以代表的是首元素地址,计算*a就是首元素的大小,整型4个字节
	printf("%zu\n", sizeof(a + 1));// 4/8
	//a代表首元素地址,计算的a+1就代表第二个元素的地址
	printf("%zu\n", sizeof(a[1]));// 4
	//a[1],是下标为1的元素,计算的是第二个元素的大小,4个字节
	printf("%zu\n", sizeof(&a));// 4/8
	//&a是取出整个数组地址,数组的地址也是地址,地址大小不由类型决定,因此也是4/8
	printf("%zu\n", sizeof(*&a));//16
	// *&a == a,计算整个数组的大小
	printf("%zu\n", sizeof(&a + 1));// 4/8
	//&a是取出数组的地址,&a+1是取出数组后一个同样大小的地址,地址大小由环境决定,仍然为4/8
	printf("%zu\n", sizeof(&a[0]));// 4/8
	//取出了首元素的地址,地址的大小由环境决定
	printf("%zu\n", sizeof(&a[0] + 1));// 4/8
	//取出第二个元素的地址,同样地址大小由环境决定
	return 0;
}

2.2 字符数组

题目1
下列代码会打印出什么?

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

结果

解释

c 复制代码
//地址的大小不由指针类型决定,而是由环境决定,x64下指针大小为8,x86下指针大小为4
int main()
{
	char arr[] = { 'a','b','c','d','e','f' };
	printf("%zu\n", sizeof(arr));//6
	//arr放在sizeof里面,计算整个数组的大小
	printf("%zu\n", sizeof(arr + 0));// 4/8
	//arr并没有单独放在sizeof里,arr代表首元素地址,地址大小由环境决定
	printf("%zu\n", sizeof(*arr));//1
	//arr并没有单独放在sizeof里,arr代表首元素地址,*arr就是首元素,计算首元素的大小
	printf("%zu\n", sizeof(arr[1]));//1
	//计算数组的下标为1,也就是第二个元素的大小
	printf("%zu\n", sizeof(&arr));// 4/8
	//取出整个数组的地址,但是数组的地址也是地址,因此由环境决定
	printf("%zu\n", sizeof(&arr + 1));// 4/8
	//取出数组后一个数组的地址,同样大小由环境决定
	printf("%zu\n", sizeof(&arr[0] + 1));// 4/8
	//取出第二个元素地址,同样大小由环境决定
	return 0;
}

题目2
下列代码会打印出什么?

c 复制代码
#include <string.h>
int main()
{
	char arr[] = { 'a','b','c','d','e','f' };
	printf("%zu\n", strlen(arr));
	printf("%zu\n", strlen(arr + 0));
	printf("%zu\n", strlen(*arr));
	printf("%zu\n", strlen(arr[1]));
	printf("%zu\n", strlen(&arr));
	printf("%zu\n", strlen(&arr + 1));
	printf("%zu\n", strlen(&arr[0] + 1));
	return 0;
}

结果

到第三行程序崩溃

解释

c 复制代码
#include <string.h>
int main()
{
	char arr[] = { 'a','b','c','d','e','f' };
	printf("%zu\n", strlen(arr));//随机值
	//arr不是两个特殊情况中的,所以代表首元素的地址,传给strlen函数,向后找\0,由于没有\0,结果为随机值
	
	printf("%zu\n", strlen(arr + 0));//随机值
	//arr代表首元素地址,向后找\0,由于没有\0,结果为随机值,
	
	printf("%zu\n", strlen(*arr));//非法访问,程序崩溃
	//*arr是首元素,而strlen的参数要求是一个地址,因此就把*arr == 'a' == ASCII码值为97,将97作为地址传给strlen函数,因此造成非法访问,程序崩溃
	
	printf("%zu\n", strlen(arr[1]));//非法访问,程序崩溃
	//arr[1] == 'b' == ASCII码值为98,将98作为地址传给strlen函数,非法访问,程序崩溃
	
	printf("%zu\n", strlen(&arr));//随机值
	//取出arr的地址传给strlen,由于arr的地址与首元素地址在值上相同,向后找\0,但是数组没有\0,因此为随机值
	
	printf("%zu\n", strlen(&arr + 1));//随机值
	//&arr+1是跳过一个数组后的地址,传给strlen函数,同样没有\0,因此为随机值,且小了6
	
	printf("%zu\n", strlen(&arr[0] + 1));//随机值
	//&arr[0]+1是第二个元素的地址,从此开始找向后\0,同样为随机值,且小了1
	return 0;
}

如果我们屏蔽 掉会让程序崩溃的第三行和第四行,结果为

随机值之间的差异和我们解释的一模一样

题目3
下列代码会打印出什么?

c 复制代码
int main()
{
	char arr[] = "abcdef";
	printf("%zu\n", sizeof(arr));
	printf("%zu\n", sizeof(arr + 0));
	printf("%zu\n", sizeof(*arr));
	printf("%zu\n", sizeof(arr[1]));
	printf("%zu\n", sizeof(&arr));
	printf("%zu\n", sizeof(&arr + 1));
	printf("%zu\n", sizeof(&arr[0] + 1));
	return 0;
}

结果

题目4
下列代码会打印出什么?

c 复制代码
#include <string.h>
int main()
{
	char arr[] = "abcdef";
	printf("%zu\n", strlen(arr));
	printf("%zu\n", strlen(arr + 0));
	printf("%zu\n", strlen(*arr));
	printf("%zu\n", strlen(arr[1]));
	printf("%zu\n", strlen(&arr));
	printf("%zu\n", strlen(&arr + 1));
	printf("%zu\n", strlen(&arr[0] + 1));
	return 0;
}

图片解释

结果

c 复制代码
#include <string.h>
int main()
{
	char arr[] = "abcdef";
	printf("%zu\n", strlen(arr));//6
	printf("%zu\n", strlen(arr + 0));//6
	printf("%zu\n", strlen(*arr));//非法访问,程序崩溃
	printf("%zu\n", strlen(arr[1]));//非法访问,程序崩溃
	printf("%zu\n", strlen(&arr));//6
	printf("%zu\n", strlen(&arr + 1));//随机值
	printf("%zu\n", strlen(&arr[0] + 1));//5
	return 0;
}

题目5

c 复制代码
int main()
{
	char* p = "abcdef";
	printf("%zu\n", sizeof(p));
	printf("%zu\n", sizeof(p + 1));
	printf("%zu\n", sizeof(*p));
	printf("%zu\n", sizeof(p[0]));
	printf("%zu\n", sizeof(&p));
	printf("%zu\n", sizeof(&p + 1));
	printf("%zu\n", sizeof(&p[0] + 1));
	return 0;
}

*注意:这里是常量字符串,char* p指针变量存放的是第一个字符的地址

常量字符串也是字符串,末尾隐藏\0

图片解释

结果

题目6

c 复制代码
int main()
{
	char* p = "abcdef";
	printf("%zu\n", strlen(p));
	printf("%zu\n", strlen(p + 1));
	printf("%zu\n", strlen(*p));
	printf("%zu\n", strlen(p[0]));
	printf("%zu\n", strlen(&p));
	printf("%zu\n", strlen(&p + 1));
	printf("%zu\n", strlen(&p[0] + 1));
	return 0;
}

结果

2.3 二维数组

题目

c 复制代码
int main()
{
	int a[3][4] = { 0 };
	printf("%zu\n", sizeof(a));
	printf("%zu\n", sizeof(a[0][0]));
	printf("%zu\n", sizeof(a[0]));
	printf("%zu\n", sizeof(a[0] + 1));
	printf("%zu\n", sizeof(*(a[0] + 1)));
	printf("%zu\n", sizeof(a + 1));
	printf("%zu\n", sizeof(*(a + 1)));
	printf("%zu\n", sizeof(&a[0] + 1));
	printf("%zu\n", sizeof(*(&a[0] + 1)));
	printf("%zu\n", sizeof(*a));
	printf("%zu\n", sizeof(a[3]));
	return 0;
}

结果

解释

理解关键
二维数组的每个元素其实是一维数组

c 复制代码
int main()
{
	int a[3][4] = { 0 };
	printf("%zu\n", sizeof(a));//48
	//a数组名单独放在sizeof内,计算整个二维数组大小
	
	printf("%zu\n", sizeof(a[0][0]));//4
	//计算第一行第一个元素大小
	
	printf("%zu\n", sizeof(a[0]));//16
	//a[0]是第一行数组名,单独放在sizeof内,计算第一行的大小
	
	printf("%zu\n", sizeof(a[0] + 1));// 4/8
	//a[0]没有单独放在sizeof内,所以表示第一行首元素地址
	//a[0]+1就是第一行第二个元素地址
	
	printf("%zu\n", sizeof(*(a[0] + 1)));//4
	//a[0]+1就是第一行第二个元素大小
	//*解引用,*(a[0] + 1)就是第一行第二个元素,所以计算的是这个元素的大小
	
	printf("%zu\n", sizeof(a + 1));// 4/8
	// a是数组名并没有单独放在sizeof内,所以代表首元素地址
	//又因为a是二维数组,元素是一维数组
	// a的类型是 int(*)[4]
	//所以a+1就是代表第二行的地址
	
	printf("%zu\n", sizeof(*(a + 1)));//16
	//a+1代表第二行的地址
	//*(a + 1)就是计算第二行这整个数组的大小
	
	printf("%zu\n", sizeof(&a[0] + 1));// 4/8
	//&a[0]代表第一行整个数组的地址
	//&a[0]+1就代表第二行整个数组的地址
	
	printf("%zu\n", sizeof(*(&a[0] + 1)));//16
	//&a[0]+1代表第二行整个数组的地址
	//*(&a[0] + 1)就代表第二行整个数组
	//计算的就是第二行整个数组的大小
	
	printf("%zu\n", sizeof(*a));//16
	//a是数组名,并没单独放在sizeof内,代表首元素(也就是第一行)地址
	//*a就是代表第一行整个数组
	//计算的就是第一行这整个数组的大小
	
	printf("%zu\n", sizeof(a[3]));//16
	//sizeof并不会真正访问括号内的内容
	//sizeof计算的原理是根据括号内的类型计算
	//因此a[3]的类型是int (*)[4],所以结果为16
	return 0;
}

对最后一行的sizeof并不会真正访问括号内的内容

这里再举一个例子

c 复制代码
int main()
{
	short s = 4;
	int a = 10;
	printf("%zu\n", sizeof(s = a + 2));
	printf("%hd\n", s);
	return 0;
}

打印结果是什么呢
结果是

对第一行

由于a是int类型,2也是int类型,两个相加的结果赋到s中

而s却是短整型short

最终结果由s的类型决定

short类型大小是2

对第二行

由于sizeof并不会访问括号内的表达式

所以,表达式是不会经过计算的

因此,最后打印的s并不是12,是原来的值4

(三)指针运算笔试题详解

题目1
下列代码打印结果是什么?

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

结果

解释

c 复制代码
a是首元素地址
a+1就是第二个元素的地址
*(a+1)就是第二个元素2

&a取出的是整个数组的地址
&a+1是跳过一个相同类型数组的后一个数组的地址
(int*)是强制类型转换,将原来的int(*)[5]的类型转换为int*
强制类型转换后的指针赋给ptr指针变量
因此,ptr-1就是向后一个int大小的地址,指向5
*(ptr-1)就是5

题目2

c 复制代码
//在X86环境下​
//假设结构体的大小是20个字节​
//程序输出的结果是啥?
struct Test
{
 int Num;
 char *pcName;
 short sDate;
 char cha[2];
 short sBa[4];
}*p = (struct Test*)0x100000;

int main()
{
 printf("%p\n", p + 0x1);
 printf("%p\n", (unsigned long)p + 0x1);
 printf("%p\n", (unsigned int*)p + 0x1);
 return 0;
}

结果

解释

c 复制代码
//在X86环境下​
//假设结构体的大小是20个字节​
//程序输出的结果是啥?
struct Test
{
 int Num;
 char *pcName;
 short sDate;
 char cha[2];
 short sBa[4];
}*p = (struct Test*)0x100000;//利用这个结构体创建一个指针变量p,将0x100000这个16进制整型强制类型转换成大小为20字节的结构体类型

//考察的是指针+-整数
int main()
{
 printf("%p\n", p + 0x1);//十六进制0x1就是1,p+1就是跳过一个20字节大小的地址,所以结果是0x100014,又因为在x86环境下,地址是32位,8字节,所以打印结果是00 10 00 14
 printf("%p\n", (unsigned long)p + 0x1);//强制类型转换成无符号长整型,所以就是数学运算,0x100001,x86下打印就是00 10 00 01
 printf("%p\n", (unsigned int*)p + 0x1);//强制类型转换成无符号整型指针,p+1就是跳过1个整型大小的地址,结果就是0x100004,x86下打印就是00 10 00 04
 return 0;
}

题目3

c 复制代码
int main()
{
 int a[3][2] = { (0, 1), (2, 3), (4, 5) };
 int *p;
 p = a[0];
 printf( "%d", p[0]);
 return 0;
}

结果

解释

c 复制代码
int main()
{
 int a[3][2] = { (0, 1), (2, 3), (4, 5) };//这里面是括号,不是大括号
 //并且是逗号表达式,真正有效的是逗号后的元素
 //因此等价于{{1,3}{5,0}{0,0}}这样的二维数组
 int *p;
 p = a[0];//a[0]是数组名,不属于两种特殊情况
 //代表第一行首元素的地址 其实就是&a[0][0]
 printf( "%d", p[0]);//p[0] == *(p+0)代表第一行第一个元素1
 return 0;
}

题目4

c 复制代码
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;
}

结果

解释

题目5

c 复制代码
//假设环境是x86环境,程序输出的结果是啥?​
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;
}

打印结果

图形

解释

c 复制代码
//假设环境是x86环境,程序输出的结果是啥?​
int main()
{
	int a[5][5];
	int(*p)[4];
	p = a;
	//a是二维数组名,表示第一行地址,类型是int(*)[5]
	//而p类型是int(*)[4]
	//类型决定了一次访问的跨度
	
	printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
	//|指针-指针|表示两个地址间的元素个数
	//&p[4][2]是绿色方块位置处
	//&a[4][2]是红色方块位置处
	//低地址减去高地址,结果为负数
	
	//%d--以十进制打印有符号整数
	//%p--打印地址
	//%p认为内存中存放的补码就是地址
	//-4补码
	//11111111 11111111 11111111 11111100
	//再以16进制打印出来
	//FF       FF        FF      FC
	return 0;
}

题目6

c 复制代码
int main()
{
 char *a[] = {"work","at","alibaba"};
 char**pa = a;
 pa++;
 printf("%s\n", *pa);
 return 0;
}

结果

图片解释

解释

c 复制代码
int main()
{
 char *a[] = {"work","at","alibaba"};
 //三个常量字符串
 //w o r k \0
 //a t \0
 //a l i b a b a \0
 //a数组里面有三个元素,存放的是三个常量字符串的首字符地址
 char**pa = a;
 //创建一个二级指针存放数组a首元素地址,即指向'w'的地址
 pa++;
 //由于类型是char*,+1跳过一个同类型大小地址,存放的地址是指向'a'的地址
 printf("%s\n", *pa);
 //解引用二级指针,将'a'地址传给printf向后打印字符串,直到遇见\0
 //因此,打印at
 return 0;
}

题目7

c 复制代码
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;
}

结果

图片解释

1 printf函数前

2 第一行printf

3 第二行printf

4 第三行printf

注意前面的++才会改变cpp的值,因为等价于cpp = cpp +1
这里cpp[-2] == *(cpp-2)
*cpp[-2] == **(cpp-2)
并不会改变cpp的值

5 第四行printf
cpp[-1] == *(cpp-1)
cpp[-1][-1] == ( (cpp-1)-1)

总结

这篇文章是指针系列的最后一篇文章,讲述了指针相关的笔试题,希望能对你有帮助

有错误的地方希望可以得到大佬的指正
最后不要吝啬你的点赞,收藏和评论!!!
感谢你的观看

相关推荐
半桶水专家3 小时前
go语言中的结构体嵌入详解
开发语言·后端·golang
在屏幕前出油3 小时前
二、Python面向对象编程基础——理解self
开发语言·python
阿方索4 小时前
python文件与数据格式化
开发语言·python
weixin_440730505 小时前
java结构语句学习
java·开发语言·学习
JIngJaneIL5 小时前
基于java+ vue医院管理系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot
量子联盟5 小时前
功能完整的PHP站点导航管理系统php程序;开源免费下载
开发语言·php
仙俊红5 小时前
在 Java 中,`==` 和 `equals()` 的区别
java·开发语言·jvm
序属秋秋秋6 小时前
《Linux系统编程之进程控制》【进程等待】
linux·c语言·c++·进程·系统编程·进程控制·进程等待
JIngJaneIL6 小时前
基于java + vue校园跑腿便利平台系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot
happybasic6 小时前
python字典中字段重复性的分析~~
开发语言·python