03.数组与循环结构

数组与循环结构

本章介绍数组与循环结构。内存存储运行数据,数组管理同类型数据集合,占用连续内存空间。定义数组通过指定类型和大小,支持多维数组。循环包括forwhiledo...while,用于重复执行代码块,支持嵌套及控制语句breakcontinue。实例展示如何应用这些概念解决编程问题,如分糖果游戏和质数口袋问题,强调基础知识的重要性。核心技能涵盖变量作用域、内存管理和逻辑控制。

内存

内存就像计算机的"临时便签纸",程序运行时把当前要用的数据和指令快速写在这张纸上(高速存取),关机时便签内容会被全部撕掉(断电清空),而硬盘才是长期存档的"笔记本"。

开一个变量int a;,会占据32bitbit是0101....这样的一个二进制位的单元)的内存,内存通常是以8个bit位单位处理的,所以8个比特称为“字节”,int就占据4个字节。

数组

管理一连串相同类型的数据的结构,占据内存连续的一段地址。

  • 定义数组:类型 数组名[数组大小]
  • 赋值:数组名[在内存中偏移开始地址的偏移量] = 要赋的值
1
2
3
4
int a[11];
double b[11];
a[5] = 3;
b[0] = 0.5;

a[10]在内存里开辟了连续的 10 个int大小的存储空间,取值和赋值能用的偏移量是0~9

数组可以多个维度,从而表示“几行几列”这样的概念,甚至更高维度,“几页几行几列”/“几层几行几列”等。

1
2
int a[6][6][6];
char c[11][11];

在这个例子中,c[0] 表示偏移为0(也就是第1个)的一个长度为11的数组,把长度为11的数组当作单位数组的话,c[1]对准了偏移了1个数组的内存地址,或者说偏移了11char的地址,c[2]就是偏移了2个数组,或者说偏移了22char的地址……

循环

循环就像工厂的流水线传送带,让相同的处理流程反复执行。当需要处理大量同类数据(比如数组元素)时,循环能避免写重复代码,通过"模式复用"实现高效操作。

for循环

1
2
3
for(初始化; 继续进行的条件; 更新操作) {
循环的内容
}

执行顺序是: 初始化->循环的内容->更新操作->继续进行的条件(true)->循环的内容->更新操作->继续进行的条件(true)->循环的内容……

直到某一步“继续进行的条件”是false,则循环结束。

例如为数组int a[10]0~9位置顺次输入数据

1
2
3
for(int i = 0; i < 10; i ++) {
scanf("%d", &a[i]);
}

while循环

先判断是否继续,再执行,如果一开始继续进行的条件就是false,则循环的内容完全不会被执行

1
2
3
while(继续进行的条件) {
循环的内容
}

do...while循环

先执行,再判断是否继续,与 while 循环的主要区别是循环的内容至少一开始就会执行一次

1
2
3
do {
循环的内容
} while(继续进行的条件);

循环可以嵌套

“循环的内容”可以是任何常规代码,循环套循环可以处理二维数组

1
2
3
4
5
for(int i = 0; i < 11; i ++) {
for(int j = 0; j < 11; j ++) {
printf("%c ", c[i][j]);
}
}

循环的提前结束

有时候需要在达到特定目的时让循环“戛然而止”,而不是等到下一次“继续进行的条件”,用break,执行 break 时,循环整个结束,会继续循环之后的代码。

1
2
3
4
5
6
7
8
int x = 0;
for(int i = 0; i < 11; i ++) {
x += 5;
if(x > 15) {
break;
}
x *= 3;
}

循环的跳过

有时候在循环的重复操作中,想跳过部分特殊情况,用continue,遇到continue时,for循环直接跳到下一次“更新操作”,while循环直接跳到下一次“继续进行的条件”。

1
2
3
4
5
6
7
int x = 0;
for(int i = 0; i < 11; i ++) {
if(i % 2 == 0) { // % 是取模,对 2 取模余数为 0,即 i 是偶数的时候
continue; // 遇到continue,跳到循环体中的“i++”位置
}
x += 2; // 如果刚刚执行了continue,则这里 x += 2 被跳过不会执行
}

例题

例:分糖果

有 5 位小朋友编号依次为 1,2,3,4,5 他们按照自己的编号顺序围坐在一张圆桌旁。他们身上有若干糖果,现在他们玩一个分糖果游戏。从 1 号小朋友开始,将自己的糖果均分成 3 份(如果有多余的糖果,就自己立即吃掉),自己留一份,其余两份分给和他相邻的两个小朋友。接着 2,3,4,5 号小朋友也这样做。问一轮结束后,每个小朋友手上分别有多少糖果。

输入:

1
8 9 10 11 12

输出

1
2
11 7 9 11 6
6

建议的竞赛习惯:

  1. 数组开在 main 函数外面作为全局变量,因为函数内能用的内存(称为栈内存)比较少
  2. 数组大小比需要的开多一些,减少失误读写到数组外面的情况
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include<stdio.h>
int a[11];
int main() {
for(int i = 0; i < 5; i ++) {
// 用数组的 0~4 下标代替题目的 1~5 编号
scanf("%d", &a[i]);
}
int eatnum = 0;
for(int i = 0; i < 5; i ++) {
// 会发现这样 0 号能找到正确的左边的 4,4 号也能找到正确的右边的 0
int left = (i - 1 + 5) % 5;
int right = (i + 1) % 5;
eatnum += a[i] % 3;
a[i] /= 3;
a[left] += a[i];
a[right] += a[i];
}
for(int i = 0; i < 5; i ++) {
if(i != 0) printf(" ");
printf("%d", a[i]);
}
printf("\n%d\n", eatnum); // 上一行没打 \n,这里补上前一个\n

return 0;
}

例:质数口袋

小 A 有一个质数口袋,里面可以装各个质数。他从 2 开始,依次判断各个自然数是不是质数,如果是质数就会把这个数字装入口袋。

口袋的负载量就是口袋里的所有数字之和。

但是口袋的承重量有限,装的质数的和不能超过 L。请问口袋里能装下几个质数?将这些质数从小往大输出,然后输出最多能装下的质数的个数,数字之间用换行隔开。

输入

1
5

输出

1
2
3
2
3
2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include<stdio.h>
#include<math.h>
int main() {
int L, cnt = 0;
scanf("%d", &L);
for(int i = 2; L > 0; i ++) {
int j = sqrt(i); // j 在 for 循环之后还要用,考虑作用域,j的定义不能放在for里面
for( ; j > 1; j --) {
if(i % j == 0) {
break;
}
}
if(j > 1) {
// 说明 i 不是质数
continue;
}
// 没continue,执行到了这里,说明 i 是质数
if(L < i) {
break; // 已经无法再装了
}
if(L >= i) {
L -= i;
cnt ++;
printf("%d\n", i);
}
}
printf("%d\n", cnt);
}

知识点:在C语言中,变量的作用域(Scope)指的是变量在程序中可以被访问的有效范围。作用域决定了变量的可见性,即在代码的哪些部分可以引用该变量。