经典指针与数组笔试题——C语言

news2024/11/25 3:00:41

   学习这片文章中的知识点,可以加深大家对指针应用的理解,让大家更能轻松知道指针在各种情况下指向那个内存地址。
   文章开始之前 ,我们先来介绍一下一些必要的知识点 📢 :

···以下代码都是在64位编译器下测试的

经典指针与数组笔试题——C语言

  • 数组名表示的意思
  • 1.一维数组
  • 2.字符数组
  • 3.二维数组
  • 笔试题
    • 第一道
    • 第二道
    • 第三道
    • 第四道
    • 第五道
    • 第六道
    • 第七道题
    • 第八道题

数组名表示的意思

提个问题📢 :
int arr[5] = {1,2,3,4,5};
我们知道数组名arr是指针,指向一块地址,只有在一下两种情况下,arr表示整个数组:

  • sizeof(arr); 当出现sizeof的时候arr代表整个数组,计算的是整个数组的大小,所以sizeof(arr);运行的结果是20.
  • &arr; 这里的arr表示的也是整个数组,所以取出来的是整个数组的地址,当出现&arr+1的时候,表示跳过整个数组。如下图:
    图一

1.一维数组

#include <string.h>
#include <stdio.h>

int main()
{
	int arr[] = { 1,2,3,4,5 };
	printf("%d\n", sizeof(arr));//前面已经提过,此时表示整个数组的大小,int类型占4个字节,5个元素,所以结果为20
	printf("%d\n", sizeof(arr+0));//数组名指向第一个元素的地址,加0还是表示第一个元素的地址,既然是地址,那么大小只能是4/8,所以结果为8
	printf("%d\n", sizeof(*arr));//*arr解引用表示第一个元素‘1’,元素类型为int,所以大小为4
	printf("%d\n", sizeof(arr+1));//arr的类型为(int *),加1后就跳过了1个(int *)的大小,此时指针指向元素为2的地址处。因为依旧是指针,所以结果为8
	printf("%d\n", sizeof(arr[1]));//这个就表示第一个元素,因为类型是int,所以结果显然为4
	printf("%d\n", sizeof(&arr));//虽然&arr表示整个数组的大小,但是这里万不能过度理解,&arr依旧是一个地址,所以结果为8
	printf("%d\n", sizeof(*&arr));//*&arr也就等于arr,所以这个跟sizeof(arr),所以结果为20
	printf("%d\n", sizeof(&arr+1));//&arr表示整个数组的地址,加1以后也就跳过了一个数组,但依旧为指针,所以结果为8
	printf("%d\n", sizeof(&arr[0]));//这个表示第一个元素的地址,结果为8
	printf("%d\n", sizeof(&arr[0]+1));//&arr[0],表示第一个元素的地址,加1以后指针指向元素2的地址,依旧为指针,所以结果为8
	return 0;
}

2.字符数组

  学习之前,我们需要知道strlen函数返回的是字符串的长度,也就是字符串开头和终止空字符(‘\0’)之间的字符数。

#include <stdio.h>
#include <string.h>
int main()
{
	char arr[] = { 'a','b','c','d','e','f' };
	
	printf("%d\n", strlen(arr));//strlen计算的是截止到'\0'的字符数,由于arr数组里面没有字符'\0',
	//只能依靠数组空间后面遇到'\0',这种情况是随机的,所以结果也是一个随机值。
	
	printf("%d\n", strlen(arr + 0));//arr+0也就是第一个元素的地址,和前一个一样,结果也是随机值。
	
	//printf("%d\n", strlen(*arr));//strlen需要的参数是一个(char *)的地址,*arr也就是第一个元素'a',它的ASCII码值为97,
	//所以会将97作为地址传参进去,就会从97这个地址开始往后计算长度,就会造成非法访问空间,结果报错。
	
	//printf("%d\n", strlen(arr[1]));//原因同上,结果报错。

	printf("%d\n", strlen(&arr));//&arr是整个数组的地址,指向首元素,向后统计字符数,跟第一个情况一样,所以结果为随机值。

	printf("%d\n", strlen(&arr + 1));//&arr取出的是整个数组的地址,所以&arr+1也就是会跳过一个数组的大小,也就是6个字符,
	//结果为strlen(&arr)-6.

	printf("%d\n", strlen(&arr[0] + 1));//&arr[0]表示第一个元素的地址,&arr[0]+1也就是第二个元素的地址,结果为strlen(&arr)-1.


	printf("%d\n", sizeof(arr));//sizeof(数组名)表示整个数组的大小,也就是(数组类型大小 * 数组元素个数),结果为6.

	printf("%d\n", sizeof(arr + 0));//arr+0是数组首元素的地址,既然是地址,那么结果就位4/8,运行结果为8
	printf("%d\n", sizeof(*arr));//*arr是数组首元素'a',类型是char,所以结果为1
	printf("%d\n", sizeof(arr[1]));//arr[0] == *arr,同上 ,结果为1.
	printf("%d\n", sizeof(&arr));//&arr表示整个数组地址,指向首元素,因为是地址,所以结果为4/8,运行结果是8
	printf("%d\n", sizeof(&arr + 1));//&arr+1上面提过,指向数组最后一个元素的下一个地址,因为是地址,所以结果为4/8,运行结果是8
	printf("%d\n", sizeof(&arr[0] + 1));//&arr[0]+1表示数组的第二个元素,因为是地址,所以结果为4/8,运行结果是8

	return 0;
}

总结:
strlen计算的时候注意起始位置得找对,终止字符’\0’也要确定在那里。
sizeof只要遇到指针,只需要考虑是在哪一个环境下的编译器以此来判断是4还是8


下面的代码跟上面的大同小异,就不过多赘述了。

#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

	printf("%d\n", sizeof(arr));//会将末尾没有显示出来的'\0'也统计进去,结果为7
	printf("%d\n", sizeof(arr + 0));//arr + 0是首元素的地址,是地址就是4/8个字节
	printf("%d\n", sizeof(*arr));//*arr其实就是首元素,1个字节
	printf("%d\n", sizeof(arr[1]));//arr[1]是第二个元素,1个字节
	printf("%d\n", sizeof(&arr));//&arr是数组的地址,是地址就是4/8个字节
	printf("%d\n", sizeof(&arr + 1));//&arr + 1是跳过一个数组的地址,是地址就是4/8个字节
	printf("%d\n", sizeof(&arr[0] + 1));//&arr[0] + 1是第二个元素的地址,是地址就是4/8个字节

	return 0;
}

3.二维数组

#include <stdio.h>
#include <string.h>
int main()
{
	int a[3][4] = { 0 };

	printf("%d\n", sizeof(a));//表示整个数组的大小,3*4*4=48,所以结果为48

	printf("%d\n", sizeof(a[0][0]));// 表示第一个元素,int类型,结果为4

	printf("%d\n", sizeof(a[0]));//数组a三行四列,a[0]表示第一行的地址,所以是算第一行的大小,4*4=16,结果为16

	printf("%d\n", sizeof(a[0] + 1));//a[0]+1表示第一行的第二个元素的地址,所以是4/8,运行结果为8

	printf("%d\n", sizeof(*(a[0] + 1)));//解引用后表示第一行的第二个元素,类型是int,所以结果是4

	printf("%d\n", sizeof(a + 1));//a是数组首元素的地址,是第一行的地址,类型为int(*)[4],
	//a+1就是第二行的地址,所以是4/8,运行结果为8

	printf("%d\n", sizeof(*(a + 1)));//解引用以后表示计算第二行的大小,也就是4*4=16,所以结果为16

	printf("%d\n", sizeof(&a[0] + 1));//&a[0]是第一行的地址,类型是int(*)[4],加1就会跳过相同类型大小的空间,
	//也就是第二行,因为是地址,所以结果为8

	printf("%d\n", sizeof(*(&a[0] + 1)));//根据上一行可以知道,&a[0] + 1表示第二行,
	//解引用以后就是计算第二行的大小,4*4=16,所以结果为16

	printf("%d\n", sizeof(*a));//a表示第一行的地址,解引用就表示第一行,也就是16

	printf("%d\n", sizeof(a[3]));//a[3]虽然已经越界了,但是仍然是指向一块空间的指针,a的类型是int(*) [4],所以结果为16.
	return 0;
}

我们需要知道在int a[3][4] = { 0 };里面:

  • a的类型是int(*)[4],所以大小时16个字节。a表示这个二维数组的首元素地址和第一行的首元素地址。

笔试题

第一道

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

s=a+2的返回结果是s的值,也就是9,因为s的类型是short,所以sizeof(s = a + 2)也就相当于sizeof(short),所以运行结果为2.

第二道

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+1)表示跳过整个数组,此时指针指向元素5的后面的地址
为什么要进行强制类型转换呢?
原因是&a的类型是int()[5], 而不是我们想象的int*,因此要进行强制类型转换
所以也容易得到指针ptr指向元素5的后面的地址,所以
(ptr-1)也就是向后移动了一个(int*)大小的位置,移动后指向元素5的地址,运行结果为:2 5

第三道

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指向数组元素4的后面一个位置,所以ptr[-1]也就是指向元素4的位置。

如图所示,ptr2移动以后指向的地址所存储的数据是00 00 00 02,由于我的机器是小端存储,所以值应该为02 00 00 00,由于打印的是十六进制的数,转换一下,得到:0x1E8480

第四道

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

先来看一下int a[3][2] = { (0, 1), (2, 3), (4, 5) };括号表达式的结果是最后一个式子的结果,也就是{1,3,5};
也就是给a初始化为a[3][2] = {{1,3,5} {0,0,0}{0,0,0}};
指针p指向的是第一行的地址,也就是元素1所在的地址,所以结果为1

第五道

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]);//-4
	//10000000000000000000000000000100
	//11111111111111111111111111111011
	//1111 1111 1111 1111 1111 1111 1111 1100
	//F    F    F    F    F    F    F    C
	//0xFFFFFFFC

	return 0;
}

在这里插入图片描述
如上图所示,因为指针-指针的意义是两个指针之间的字符数量,所以&p[4][2] 和 &a[4][2]之间相差4个字符。所以结果是-4
由于数据是由补码的形式存储的,所以要将原码-4转换为补码是:

4的二进制为:10000000000000000000000000000100
4的反码为: 11111111111111111111111111111011
4的补码为:1111 1111 1111 1111 1111 1111 1111 1100
转换成十六进制也就是:0xFFFFFFFC
所以运行结果为:0xFFFFFFFC -4

第六道

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

这道题没有新知识,结果直接给出,是:10 5

第七道题

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

pa++可以看做是pa[1],也就是第二行的元素,所以运行结果为:at

第八道题

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;
}
  • printf("%s\n", **++cpp);先算++cpp,此时cpp指针指向 数组cp的c+2的地址,然后*++cpp表示数组cp元素c+2,此时指向数组c中的"POINT"的地址,**++cpp表示数组c中的元素"POINT",所以结果为POINT

  • printf("%s\n", *-- * ++cpp + 3);运算符的优先级:++ * – * + ++cpp表示cpp指针指向 数组cp的c+1的地址(因为前一行已经自增,指向了c+2),*++cpp表示数组cp元素c+1,此时指向数组c中的"NEW"的地址,–*++cpp表示指针指向数组cp的元素c,++cpp表示指向数组c的元素"ENTER"的地址,也就是指向第一个E,++cpp+3表示指向第二个E,因为打印格式为%s,所以运行结果为ER

  • printf("%s\n", *cpp[-2] + 3);此时cpp指针经过前面两次自增,此时指向数组cp中的c+1处,cpp[-2]表示指向数组cp的c+3,*cpp[-2]表示指向数组c中的"FIRST",*cpp[-2] + 3表示指向数组c中的"FIRST"的S,所以运行结果为ST

  • printf("%s\n", cpp[-1][-1] + 1);先来看cpp[-1],此时指向数组cp的c+2处,cpp[-1][-1]表示指向数组c中的"POINT"然后再向后移动一个char*大小,也就是指向"NEW", cpp[-1][-1] + 1表示指向E,所以运行结果为EW。其实 cpp[-1][-1] + 1还可以看作*(*(cpp-1)-1)+1,运算步骤同上。

  😄 创作不易,你的点赞和关注都是对我莫大的鼓励,再次感谢您的观看。😄

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/746967.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

orcle报错:无监听程序,解决方法

orcle报错&#xff1a;无监听程序&#xff0c;解决方法 报错页面&#xff1a; 打开桌面侧边安装orcle的列表&#xff0c;找到Net Configuration Assistant&#xff0c;双击&#xff08;这个可以重新配置监听&#xff09; ![]](https://img-blog.csdnimg.cn/3ba6bd6bd0af413ca5…

nginx 开机自启

0x00 前言 简单的记录下 0x01 正文 cd /lib/systemd/system/ vim nginx.service [Unit] Descriptionnginx service Afternetwork.target [Service] Typeforking ExecStart/usr/local/nginx/sbin/nginx ExecReload/usr/local/nginx/sbin/nginx -s reload ExecStop/usr/lo…

pt18CSS

CSS 基础使用 CSS全称为&#xff1a; Cascading Style Sheets &#xff0c;意为层叠样式表 &#xff0c;与HTML相辅相成&#xff0c;实现网页的排版布局与样式美化 CSS使用方式 行内样式/内联样式 使用简单&#xff0c;但不推荐&#xff0c;大面积使用&#xff0c;很累 借…

ESP32(MicroPython) 网页显示温湿度+RGB点阵控制

本程序整合了RGB点阵可交互超声波云台网页显示温湿度程序和网页控制WS2812程序的功能&#xff0c;对一些参数进行了调整。去掉了图标以加快加载速度&#xff0c;去掉了超声波云台和按键控制以简化接线并实现RGB点阵更新周期可调&#xff0c;由于RGB点阵更新周期相对较长&#x…

vue3前端模拟https安全策略同局域网内测试方法-local-ssl-proxy

文章目录 前言建议全局安装运行安全策略模拟运行效果如果其他客户端不能访问 直接在cmd跑即可&#xff0c;不过我们应该先运行项目 前言 为什么要用https安全策略呢&#xff0c;因为http浏览器策略访问权限有限&#xff0c;不能使用navigator的激活“用户音频或视频”的方法&a…

windows上的mysql服务突然消失: 10061 Unkonwn error

问题描述 windows10 系统,今天早晨系统自己更新了下,也没啥问题,突然发现电脑上安装的mysql 服务没了… 原因分析&#xff1a; 我是安装的解压版的mysql 虽然服务没了,但是文件夹,包括数据啥的都在最重要的就是数据啦,还好都在 解决方案&#xff1a; 打开mysql 的bin所在目录…

Git撤销已合并提交的多种姿势

#Git撤销已合并提交的多种姿势 在Git中&#xff0c;合并分支是一个常见的操作&#xff0c;但有时候可能会意外地将错误的提交合并到了主分支。这时候需要撤销已合并的提交并恢复到正确的状态。本文将介绍的是如何在Git中撤销已合并的提交&#xff0c;无论这个提交记录是最新的还…

包管理工具:npm

安装Node的过程会自动安装npm工具 比如npm install dayjs后 const dayjsrequire("dayjs")console.log(dayjs()) 直接运行 生成package.json文件  方式一&#xff1a;手动从零创建项目&#xff0c;npm init –y  方式二&#xff1a;通过脚手架创建项目&#xf…

Sentinel服务器容错简介

spring gateway 详解 服务容错高并发带来的问题服务雪崩效应常见容错方案常见的容错思路1、隔离2、超时3、限流4、熔断5、降级 常见的容错组件 SentinelSentinel 具有以下特征:Sentinel概念和功能相关概念1、资源2、规则 重要功能 服务容错 高并发带来的问题 在微服务架构中&…

Vivado使用误区与进阶系列(七)用Tcl定制Vivado设计实现流程

01 基本的FPGA设计实现流程 FPGA 的设计流程简单来讲&#xff0c;就是从源代码到比特流文件的实现过程。大体上跟 IC 设计流程类似&#xff0c;可以分为前端设计和后端设计。其中前端设计是把源代码综合为对应的门级网表的过程&#xff0c;而后端设计则是把门级网表布局布线到…

orcle报错:TNS 监听程序无法为请求的服务器类型找到可用的处理程序

orcle报错&#xff1a;TNS 监听程序无法为请求的服务器类型找到可用的处理程序 方法一&#xff1a;配置文件修改 服务端的数据库是专用服务器,但是在客户端的tnsname.ora里配置中设置了连接方式为shared,这种情况下打开tnsnames.ora, 找到安装orcle的安装目录&#xff0c;点…

MSP430F5529,超声波,距离检测报警,倒车雷达,SR-04模块

文章目录 硬件连接功能实物效果代码 硬件连接 /* OLED----MSP430VCC-----3.3VGND-----GNDSCL------P3.1SDA------P3.0 */ /* 蜂鸣器----MSP430VCC-----3.3VGND-----GNDDAT------P2.4 */ /* 超声波----MSP430VCC-----3.3VGND-----GNDTRIG------P1.3ECHO------P1.2 */ /* …

模板学堂|数据关系和AntV、ECharts图表解析

DataEase开源数据可视化分析平台于2022年6月正式发布模板市场&#xff08;https://dataease.io/templates/&#xff09;。模板市场旨在为DataEase用户提供专业、美观、拿来即用的仪表板模板&#xff0c;方便用户根据自身的业务需求和使用场景选择对应的仪表板模板&#xff0c;并…

PHP 音乐欣赏网站mysql数据库web结构apache计算机软件工程网页wamp

一、源码特点 PHP音乐欣赏网站 是一套完善的web设计系统&#xff0c;对理解php编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。 代码下载 https://download.csdn.net/download/qq_41221322/88041034https://download.…

Sentry 监控 Docker 方式部署

一、简介 根据主篇 Sentry 监控部署与使用 流程&#xff0c;使用 Docker 方式 方式进行部署。 docker 方式 部署操作比较简单&#xff0c;也是 Sentry 官方 比较推崇的方式&#xff0c;直接按 Sentry On-Premise 提供的方式按部就班部署就好了。或者可直接参考 Docker 部署 Se…

关于根据文件名以及内容查找文件存放路径

1 根据文件名字查找文件存放路径 1.1 命令如下&#xff08;先切换到存放该文件的顶级父目录下&#xff09;&#xff1a; find /path/to/search -name "filename"​​ 1.2 案例如下 2 根据内容查找包含该内容的文件存放路径 2.1 命令如下&#xff08;先切换到存放该文…

《大大简化每次运行bochs的命令行》ubuntu里安装vscode + makefile文件基本编写 + shell命令

&#x1f4cd;安装vscode 启动vscode 如图打开商店&#xff0c;在搜索栏里输入visual studio code&#xff0c;安装即可 在随便一个命令行里输入code即可打开vscode &#x1f4cd;makefile文件基本编写 在实验项目文件夹里创建makefile文件&#xff08;vscode直接能快捷创…

禁止22H2版windows10出现windows11的跨版本升级提示

近期微软为了强推windows11&#xff0c;笔者所用的笔记本又出现了升级windows11的提示&#xff0c;烦人不说&#xff0c;还担心一不小心点错了&#xff0c;系统就给升了&#xff0c;赶紧禁止了跨版本升级&#xff0c;相关设置记录如下&#xff1a; 一、问题情况 系统出现了升…

[C++] C++特殊类设计 以及 单例模式:设计无法拷贝、只能在堆上创建、只能在栈上创建、不能继承的类, 单例模式以及饿汉与懒汉的场景...

特殊类 1. 不能被拷贝的类 注意, 是不能被拷贝的类, 不是不能拷贝构造的类. 思路就是 了解什么时候 会以什么途径 发生拷贝, 然后将路堵死. 拷贝发生一般发生在 拷贝构造 和 赋值重载 所以, 只要把类的这两个成员函数堵死, 此类就不能拷贝了 C98 在C11之前, 可以通过这种方…

基于Javaweb实现ATM机系统开发实战(六)开卡用户登录及其功能实现

首先输入用户名密码&#xff0c;测试一下用户登录功能&#xff0c;跳转到了UserLogin页面&#xff0c;发现404&#xff0c;是因为我们的servlet还没有编写&#xff0c;页面无法进行跳转。 还是老规矩&#xff0c;先写servlet&#xff1a; package com.atm.servlet;import com…