一维数组的定义和引用
看完你能说清数组在内存里长什么样,并亲手算出任意元素的地址
按 空格/→ 演示下一步
全部页面点击任意一页,跳回舞台从这页播放
一维数组的定义和引用
看完你能说清数组在内存里长什么样,并亲手算出任意元素的地址
为什么需要数组
期末考完,要存全班30个人的成绩。用变量一个个写:score1、score2……写到 score30,光是声明就崩溃了,改个名字更是噩梦。程序语言早就为这种「一批同类型数据」准备好了容器——数组。
储物柜的名字对应数组名,柜子编号对应下标
数组的定义语法
上一页说了:要把一串同类型的数据打包管理,就得先有个「盒子」。C 语言里声明这个盒子有一套固定写法,每个位置各管一件事,写错位置编译器就不认。
柜子大小对应类型名、柜子标签对应数组名、格子总数写在方括号里
定义数组的合法与非法写法
四组对比示例:哪些数组定义合法、哪些直接编译报错,一眼看清长度必须是常量。
长度必须是编译期确定的常量表达式:字面量、宏可以,const 变量在 C 里不算;长度省略、为 0、用普通变量定义都会报错。
数组在内存中的样子
从左往右是内存地址增长方向:每个格子存一个元素,格子上方数字是下标,注意从0开始。
数组的初始化
上一页看到,刚定义的数组里装的是内存里的垃圾值。那能不能在定义时就顺手把格子填好?可以——这就是初始化,C 语言给三种写法。
完全登记=满房名单,只登记一间=其余房间自动清零
初始化 vs 先定义后赋值
两种给值方式常被混为一谈:有人以为效果等价,其实时机、写法、限制都不同,边界用法最容易踩坑。
- int a[3]={1,2,3}; 定义当场给值
- 未写满的元素自动补 0
- 可省长度:int a[]={1,2,3};
- int a[3]; a[0]=1; a[1]=2; 定义时是随机值
- 不会自动补 0,遗漏的元素是垃圾值
- 长度必须写死,不能靠赋值列表推算
定义与初始化自测
已有定义 int a[5] = {1, 2};,关于数组 a 的当前状态,下列说法正确的是?
数组元素的引用
上一页我们把数组比作一排储物柜,定义时只是分配好了格子。可柜子是用来放东西、取东西的——这一页就看怎么准确打开其中某一格,而且打开之后既能读也能写。
房间号对应下标,入住退房对应写入读取
循环遍历数组
用 for 循环把下标 0~4 依次算出来,逐个访问数组元素——这是遍历数组的固定套路。
循环变量 i 正是下标:从 0 开始、止于 4(< 5),a[i] 随 i 逐个落到每个元素上,一行 printf 完成全部访问。
数组的输入与输出
用循环配合 scanf/printf 逐个读写元素,看 C 为什么不能一次输出整个数组。
scanf 和 printf 都只认单个元素,必须由循环驱动下标 i 逐个访问;整体读写在 C 里不存在,直接打印数组名只会得到首元素地址。
越界访问:最常见的坑
从写错边界到程序崩溃,逐步看越界是怎么发生的。
输入输出自测
int a[5]; 下面的输入输出写法,哪一项是正确的?
成绩排序问题
前面我们学会了把成绩存进数组、逐个输出。但老师真正要的是排名:把一组成绩从大到小排出来。这个「排」的动作,就是排序算法要解决的经典问题。
每次比较相邻两张牌的大小,对应数组元素的依次比较
选择法的核心思想
选择排序的每一轮:扫一遍数组,把最大值换到它该在的位置。
选择法逐轮演示
从左往右看:每个格子是一轮结束后的数组状态,箭头上写这轮做了什么。
选择法排序代码
把选择法思想落到代码:外层定位置,内层找最值,最后交换。
外层 i 锁定要填的位置,内层 j 扫描未排序区记录最小值下标 min,一轮结束后才交换一次——这正是选择法比冒泡交换次数少的关键。
排序自测
用选择法将 {5,3,8,1} 升序排序(每轮选最小值放到最前),第 1 轮结束后数组是?
Fibonacci数列是什么
上一页我们用循环和下标操作数组已经很熟了。现在来个经典问题:一对兔子每月生一对小兔,小兔长大后又接着生,各月兔子总数是多少?这个数列藏着一个简单的规律。
每一棒的成绩都在前面几棒成绩的基础上累加得来
用数组存Fibonacci数列
用数组把Fibonacci递推公式直接翻译成代码,下标即递推关系。
f[i]=f[i-1]+f[i-2]一行就是Fibonacci定义本身:数组下标i把『第i项』变成可循环的变量,递推关系无需写死20遍。
递推过程图解
从左往右看:数组格子逐个填入,每格都由前两格相加得到,对照循环变量的推进。
数组应用小结
- ✓数组把多个同类型变量打包,靠下标统一管理
- ✓初始化只在定义时一次写全,之后只能逐个赋值
- ✓下标从 0 开始,最大到长度减 1,越界不报错但后果难料
- ✓遍历靠 for 循环,输入输出和排序都建立在它之上
- ✓排序与 Fibonacci 的共性:用下标表达元素间的关系
课后思考
先自己想一会儿,再看下面的参考答案——想不出来也没关系,带着问题翻回去看看。
参考答案不一定报错。C 语言不做边界检查,a[5] 只是算地址就写进去,踩到谁的内存就破坏谁。编译器不吭声是为了效率——运行时逐次检查太慢,代价转嫁给了程序员。
参考答案初始化时剩余元素自动补 0,不是垃圾值;但如果只定义 int a[5] 再逐个赋值,没赋到的就是内存里原来的垃圾。区别在于初始化列表触发了整体清零语义。
参考答案随机访问:下标就是地址,O(1) 直接读写任意元素。排序靠它比较交换任意位置,递推靠它按序回取前面的结果。链表两样都做不了这么快。