发布时间:2026/8/26 15:33:57
C语言指针(二)-- 一维、二维数组与指针的关系 文章目录从纠正一句常见说法开始1、从 arr[i] 开始数组下标如何定位元素2、数组名何时转换为首元素地址何时保留数组身份2.1 常见表达式array 表现为 array[0]2.2 sizeof(array)数组不会转换2.3 array取到的是“整个数组”的地址3、一维数组传参传的是首元素地址不是整个数组副本3.1 为什么函数内部不能用 sizeof(arr) 求长度3.2 两种遍历方式下标和首尾指针4、字符数组、字符指针和字符串都能打印不代表可随便修改4.1 字符串为什么需要 \04.2 字符数组数据属于数组可以修改4.3 字符指针通常指向只读字符串字面量4.4 用指针理解字符串长度5、指针数组数组元素保存的是多个地址6、指针数组和数组指针只差括号含义完全不同6.1 int *p[3]先看 p[3]7.2 int (*p)[3]括号先把 *p 绑在一起7、二维数组传参为何第一维可省列数却要保留8、练习对照内存关系仔细思考8.1 用指针求一维数组的最大值8.2 判断两个声明并说明 1 的含义8.3 编写二维数组每列求和函数总结从纠正一句常见说法开始你可能听过数组名就是指针。这句话便于入门却不够准确。它会让人误以为下面两者完全相同intarray[5];int*pointerarray;实际上array是包含 5 个int的数组对象pointer是一个保存地址的指针变量。数组有固定的元素个数和完整大小指针只保存一个地址。两者在很多表达式中表现相似是因为数组名经常转换成首元素地址而不是因为数组变成了指针变量。1、从arr[i]开始数组下标如何定位元素intscores[4]{60,75,88,92};printf(%d\n,scores[2]);printf(%d\n,*(scores2));两行都会输出88。在大多数表达式中scores会转换为scores[0]也就是首元素地址再向后移动 2 个int元素最后解引用得到第三个元素。因此可以写成scores[i]*(scoresi)这不是说下标形式和指针形式在所有语境下都能随意替换而是说明数组访问的底层定位方式。对初学者来说下标形式通常更直观当你需要理解传参、二维数组或指针遍历时*(scores i)才更有价值。#includestdio.hintmain(){intscores[4]{60,75,88,92};int*pscores;printf(scores[1] %d\n,scores[1]);printf(*(p 1) %d\n,*(p1));printf(p[1] %d\n,p[1]);return0;}运行结果三种写法都定位到第二个元素 75。p[1]能成立是因为下标运算本质上也会按“地址加偏移再解引用”的方式理解。2、数组名何时转换为首元素地址何时保留数组身份2.1 常见表达式array表现为array[0]intdata[3]{1,2,3};int*pdata;// 等价于 int *p data[0];printf(%d\n,*data);// 1printf(%d\n,*(data1));// 2传给普通函数、赋给指针、参与data i时数组名通常会转换为首元素地址。正是这条规则让数组可以自然地传给接收int *的函数。2.2sizeof(array)数组不会转换#includestdio.hintmain(){intdata[3]{1,2,3};printf(整个数组占 %zu 字节\n,sizeof(data));printf(元素个数是 %zu\n,sizeof(data)/sizeof(data[0]));return0;}运行结果此处sizeof(data)得到整个数组所占字节数而不是指针大小。sizeof(data) / sizeof(data[0])是在数组定义所在作用域计算元素数目的常见方法。但把数组传进函数后这个办法就失效了函数参数中的数组会调整为指针sizeof(arr)得到的只是指针变量大小。2.3array取到的是“整个数组”的地址intdata[3]{1,2,3};int*element_ptrdata;int(*array_ptr)[3]data;element_ptr指向一个intarray_ptr指向一个含 3 个int的完整数组。它们在打印时可能显示相同的起始位置但类型和移动步长不同element_ptr1;// 移到 data[1]array_ptr1;// 跳过整个 data[3] 数组3、一维数组传参传的是首元素地址不是整个数组副本下面两种函数声明在参数位置是等价的voidPrintA(intarr[],size_tcount);voidPrintB(int*arr,size_tcount);arr[]写法在函数参数里更像是在告诉读者“这里希望传数组”编译器会把它调整为int *arr。因此函数能访问并修改调用者的数组元素voidSetFirstTo100(intarr[],size_tcount){if(arr!NULLcount0){arr[0]100;}}intmain(){intdata[]{1,2,3};SetFirstTo100(data,3);printf(%d\n,data[0]);// 100return0;}函数没有复制数组它拿到首元素地址后仍然在访问调用者的那一块内存。若函数只需要读取数据应该用const把意图写出来intSum(constint*arr,size_tcount){size_ti;inttotal0;if(arrNULL){return0;}for(i0;icount;i){totalarr[i];}returntotal;}const int *arr说明函数不能通过arr修改元素。它并不能自动检查调用者是否传对了长度所以“地址 长度”必须作为一组契约理解arr指向开头count表示可访问元素数。3.1 为什么函数内部不能用sizeof(arr)求长度voidShowSize(intarr[]){printf(%zu\n,sizeof(arr));}这里arr已经是int *所以输出的是指针大小不是调用者数组的大小。C 语言不会在数组首地址里偷偷记录长度一个地址本身无法知道后面有多少个元素。正确的调用方式是由调用者计算真实数组长度再传给函数intdata[]{3,1,4,1,5};intcountsizeof(data)/sizeof(data[0]);PrintA(data,count);请只对真正的数组使用sizeof(data) / sizeof(data[0])。如果data已经是函数参数或普通指针该表达式就不再代表元素个数。3.2 两种遍历方式下标和首尾指针#includestdio.hvoidPrintByPointer(constint*arr,size_tcount){constint*cur;constint*end;if(arrNULL){return;}curarr;endarrcount;while(cur!end){printf(%d ,*cur);cur;}}cur是当前元素位置end是尾后位置。循环只在cur ! end时解引用所以不会读取尾后位置。若count为 0cur一开始就等于end循环自然不执行。在写代码时使用下标还是指针遍历可以按可读性选择。重要的是始终掌握边界下标要小于count指针要在[arr, arr count)这个范围内移动。4、字符数组、字符指针和字符串都能打印不代表可随便修改4.1 字符串为什么需要\0charword[]hello;这不是 5 个字符而是 6 个h、e、l、l、o和结尾的空字符\0。C 的字符串函数通过\0判断字符串在哪里结束。printf(%zu\n,sizeof(word));// 6strlen(word)的结果则是 5因为它统计可见字符不把结束符算进长度。4.2 字符数组数据属于数组可以修改charword[]hello;word[0]H;printf(%s\n,word);// Hello数组有自己的一块可写空间所以在不越界的前提下可以修改元素。注意替换一个字符不会自动增加容量若数组只有 6 个位置就不能再向里面塞入更长的字符串。4.3 字符指针通常指向只读字符串字面量constchar*messagehello;printf(%s\n,message);message[0]H;//不要这样做字符串字面量的内容不应被修改因此推荐使用const char *。即使*message没有加const有些开发环境在你运行代码后也会提示写入访问的权限冲突而暂停运行。下面的两句看起来类似存储和修改权限却不同charname[]Ada;// 数组拥有一份可修改的字符constchar*titleAda;// 指针指向只读字面量4.4 用指针理解字符串长度#includestdio.hsize_tMyStrlen(constchar*str){constchar*pstr;if(strNULL){return0;}while(*p!\0){p;}return(size_t)(p-str);}p从首字符开始一直移动到\0所在位置p - str得到跨过的字符数。这个函数隐含一个重要前提str必须指向一段以\0结束的有效字符序列。若调用者给出未结束的字符数组循环会越界寻找结束符。5、指针数组数组元素保存的是多个地址#includestdio.hintmain(){constchar*colors[]{red,green,blue};for(inti0;i3;i){printf(%s\n,colors[i]);}return0;}colors是一个数组但每个元素不是字符而是const char *每个格子都保存一个字符串的首地址。因此三段字符串可以有不同长度。不要把它和二维字符数组混淆了constchar*colors[]{red,green,blue};charlabels[][8]{red,green,blue};前者存的是多个地址字符串本身可以分散后者是一块连续的二维数组每一行固定有 8 个字符位置。两种结构都能写colors[i]或labels[i]但内存布局和可修改性不同。6、指针数组和数组指针只差括号含义完全不同6.1int *p[3]先看p[3]int*p[3];[]比*结合优先级更高所以先得到p[3]p是一个有 3 个元素的数组再看元素类型每个元素是int *。这叫指针数组。7.2int (*p)[3]括号先把*p绑在一起int(*p)[3];括号使*p先结合因此p是一个指针它指向“有 3 个int的数组”。这叫数组指针常用于二维数组的行指针。intmatrix[2][3]{{1,2,3},{4,5,6}};int(*row)[3]matrix;​printf(%d\n,row[1][2]);// 6row 1会跳到下一整行而普通int *的 1只会跳到下一个整数。7、二维数组传参为何第一维可省列数却要保留#includestdio.hvoidPrintMatrix(intmatrix[][3],introws){for(inti0;irows;i){for(intj0;j3;j){printf(%d ,matrix[i][j]);}printf(\n);}}参数int matrix[][3]在函数中会调整为int(*matrix)[3]它表示matrix指向一行每行恰好有 3 个int。行数作为rows单独传入列数写在类型中用来计算每次移动一行的步长。8、练习对照内存关系仔细思考8.1 用指针求一维数组的最大值要求函数不能修改数组当参数为空或长度为 0 时返回 0。intMax(constint*arr,intcount);思路arr指向第一个元素。先用第一个元素初始化最大值再从第二个元素开始比较没有元素时不能访问arr[0]。参考答案:#includestdio.hintMax(constint*arr,intcount){intmax;if(arrNULL||count0){return0;}maxarr[0];for(inti1;icount;i){if(arr[i]max){maxarr[i];}}returnmax;}8.2 判断两个声明并说明 1的含义int*a[4];int(*b)[4];参考答案a是含 4 个int *的指针数组a 1移到下一个指针元素b是指向“含 4 个int的数组”的指针b 1跨过整整 4 个整数。8.3 编写二维数组每列求和函数要求处理 3 列整数矩阵结果写入长度为 3 的sum数组。参考答案#includestdio.hvoidSumColumns(constintmatrix[][3],introws,intsum[3]){if(matrixNULL||sumNULL){return;}//首先确保sum数组内初始值为0for(intj0;j3;j){sum[j]0;}for(inti0;irows;i){for(intj0;j3;j){sum[j]matrix[i][j];}}}验证方式对{{1, 2, 3}, {4, 5, 6}}调用后sum应为{5, 7, 9}。参数声明中的[3]不能随意删除因为函数需要知道一行有几列。总结一维数组让我们看到数组名如何转换为首元素地址字符串让我们看到地址之外还需要结束符和容量二维数组则进一步说明指针移动必须知道目标类型的大小和结构。理解了“每一行也是一个数组”int (*p)[3]和二维数组传参就不再只是奇怪的语法。

相关新闻

2026/8/26 15:33:57

华为MetaERP # EBS → Fusion BC 预算模块【业务规则对齐实施指南】定位:迁移蓝图「业务规则映射章节」标准内容,承接主数据映射、余额迁移、预算校验逻辑迁移。> > 核心结

EBS → Fusion BC 预算模块【业务规则对齐实施指南】定位:迁移蓝图「业务规则映射章节」标准内容,承接主数据映射、余额迁移、预算校验逻辑迁移。核心结论: EBS 预算模型 总账依附型、CCID 最小粒度、三层占用模型、预算日记账驱动变更 Fusi…

2026/8/26 16:24:15

DM达梦数据库遇过的坑

最近在做信创环境的项目 ,原项目用的MySQL数据库,转到DM数据库中好多SQL都出现了问题,为方便以后开发,将遇到过的问题在这里做个记录,以后会不断更新。转换丢失警告 情形一: 出错原因:原SQL里使…

2026/8/26 16:24:15

移动软件开发实验1

移动软件开发实验1一、实验内容(一)安装开发工具(二)创建hello world小程序1.index.wxml2.index.wxss3.index.js(三)运行结果二、问题总结与体会(一)问题总结(二&#xf…

2026/8/26 16:19:12

OUC26夏移动软件开发-实验1

OUC26夏移动软件开发-实验1 姓名:马一诺 学号:项目内容姓名和学号马一诺本实验属于哪门课程中国海洋大学26夏《移动软件开发》实验名称实验1:热身运动GitHub源码链接https://github.com/1-qaz-2-wsx/ouc26-mobile-dev/tree/main/lab1博客链接…

2026/8/26 9:13:28

[光学原理与应用-521]:对光的错误理解与纠偏

首先光是一种能量的载体和形态,宏观上观察到的光是由无数个微观的光量子组成的,每个光子在产生的瞬间,其在真空的空间中以确定不变的速度沿着一个初始的方向一直向前,在微观层面,每个光量子的运动轨迹是以波函数所展现…

2026/8/25 11:48:27

SIP通话转接原理与REFER方法实战解析

1. 通话转接不是“挂断再拨号”,而是SIP会话的动态重定向你有没有遇到过这样的场景:客服坐席A正在和客户通电话,突然需要把这通对话无缝转给专家坐席B,客户完全感知不到中间的断连——既没听到忙音,也没被要求重新拨号…

2026/8/25 16:56:43

Kolla-ansible单节点OpenStack部署实战:从环境准备到排坑指南

1. 为什么选择Kolla-ansible来部署单节点OpenStack?如果你正在寻找一种能把OpenStack从“概念”快速变成“可用的实验环境”的方法,那么Kolla-ansible几乎是当前最主流、最省心的选择。我见过太多人卡在手动编译依赖、配置服务、处理版本冲突的泥潭里&am…

2026/8/26 0:04:32

Python random 模块常用函数详解:从入门到实战

目录 1. 引言2. 准备工作3. 基础随机函数4. 序列相关函数5. 随机种子与复现6. 实战案例7. 注意事项8. 常见问题与排查9. 总结 1. 引言 摘要: 本文系统介绍 Python 标准库 random 模块中最常用的随机数生成函数。内容涵盖基础随机函数(random()、unifor…

2026/8/26 1:19:35

JSON总结

JSON概念 JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,主要用于跟服务器进行交换数据。它基于ECMAScript的一个子集。 JSON采用完全独立于语言的文本格式,但是也使用了类似于C语言家族的习惯(包括C、C、C#、Java、JavaScr…

2026/8/26 1:19:35

保存连接sse 是什么原理,为什么不会一直请求

“保持连接”用的是 SSE(Server-Sent Events),本质是一个没有马上结束的 HTTP 请求。 过程是: 拷贝机发送一次请求: GET /api/code-sync/events服务器返回: Content-Type: text/event-stream但不关闭响应&…

2026/8/24 13:42:17

实测才敢推 AI论文网站 2026最新测评与推荐

2026年真正好用的AI论文网站,核心看生成的论文质量、低AI味、格式正确、学术适配四大指标。综合实测,千笔AI、ThouPen、豆包、DeepSeek、Grammarly 是当前最值得推荐的梯队,覆盖从免费到付费、从中文到英文、从文科到理工的全场景需求。一、综…

2026/8/24 18:13:48

2026必备!AI论文网站测评:最新推荐与深度对比

2026年真正好用的AI论文网站,核心看生成的论文质量、低AI味、格式正确、学术适配四大指标。综合实测,千笔AI、ThouPen、豆包、DeepSeek、Grammarly 是当前最值得推荐的梯队,覆盖从免费到付费、从中文到英文、从文科到理工的全场景需求。 一、…

2026/8/25 1:08:14

摆脱论文困扰!盘点2026年全网爆红的的AI论文写作工具

一天写完毕业论文在2026年已不再是天方夜谭。2026年最炸裂、实测能大幅提速的AI论文写作工具,覆盖选题构思、文献整理、内容生成、格式排版等核心场景,真正帮你高效搞定论文难题。 一、全流程王者:一站式搞定论文全链路(一天定稿首…