青江的个人站

“保持热爱,奔赴星海”

  • 主页
  • 目录
  • 图床
  • 留言板
  • -关于我-
友链 搜索文章 >>

青江的个人站

“保持热爱,奔赴星海”

  • 主页
  • 目录
  • 图床
  • 留言板
  • -关于我-

【C语言学习笔记】六、数组


阅读数: 0次    2025-07-25
字数:6.3k字 | 预计阅读时长:27分钟

1. 数组的作用

如果一个程序中需要同时定义一系列数据,单独分开定义显然是不合适的,这时候就需要用到数组。

编程语言设计需要考虑多个数据和变量如何处理,从下面三个方面来入手:

  • 数据组织
  • 资源管理
  • 性能优化

其中最简单的一种方式就是数组。

2. 数组的初步使用

数组的定义就是在变量后面加一个方括号,里面填写数组的大小,例如:

1
uint32_t numbers[10];

这样就定义了一个大小为10的数组,里面有十个数,每个数都是uint32_t类型。

数组的初始化是使用一个大括号,里面分别填写数组中的每个值,用逗号隔开:

1
uint32_t numbers[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

数组中的元素的取用,需要用到下标,数组的下标是从0开始的,因此如果要取数组中的第五个数打印输出,就需要打印输出numbers[4]:

1
printf("numbers[4] = %" PRIu32 "\n", numbers[4]);	// numbers[4] = 5

数组(Array)中特定下标(Index)中存放的值称为元素(Element)

数组的内存分布(Memory Layout)是连续不断的一片内存区域,中间不能有截断

如果要将数组中的所有元素都输出,需要用到for循环:

1
2
3
for (uint8_t index = 0; index < 10; index++) {
printf("numbers[%" PRIu8 "] = %" PRIu32 "\n", index, numbers[index]);
}

如果要对数组中的某些元素进行修改,直接通过指定下标方式修改即可:

1
numbers[4] = 666;

3. 数组定义时下标需要常量

C语言程序在编译的时候,就要求此时数组的长度为一个确定的已知的常量。

因此定义数组时需要注意,数组的长度必须为常量,将使用scanf_s()取到的一个值作为数组定义的长度值时,程序会直接报错。

1
2
3
4
// 下面的写法会报错
uint32_t index;
scanf("%" SCNu32, &index);
uint32_t numbers[index];

甚至直接将定义并初始化的一个量作为数组的长度,或是将定义的一个常量值作为数组的长度,也同样会报错。

1
2
3
4
5
6
7
8
// 下面的两种写法都会报错
// 1. 对变量做初始化
uint32_t index = 5;
uint32_t numbers[index];

// 2. 使用const定义常量
const uint32_t index = 5;
uint32_t numbers[index];

这是因为C语言程序在编译时,使用const定义的数据在编译的时候并不是常量,只是这个值会在这个局部的函数中被固定下来。

不过,定义数组时可以使用宏定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <inttypes.h>

// 数组长度宏定义
#define SIZE 5

// C语言案例
int main(void) {
uint32_t numbers[SIZE];

return 0;
}

注意,此时的数组并没有被初始化,而在程序中使用时,和变量一样,一定要保证数组被正确初始化,这样程序在使用数组时才可以拿到正确的值。

最简单的初始化是大括号内只写一个0,这样所有元素的值都会被初始化为0。

1
uint32_t numbers[10] = { 0 };

但是当大括号里的这个值为其他不为零的值,只会将数组中的第一个元素初始化为这个值,其他元素的值都为0。

因此,如果要将数组中的所有元素初始化为1,应该在大括号里面一一写出所有元素的初始化值。

4. 数组的注意事项

错误1:访问越界

如果定义数组时候的长度为n,因为数组的下标是从0开始的,则数组中下标的最大值只能为n-1,称为数组的边界(Boundary),如果访问数组的下标超过这个值,编译器会发出警告,访问到的值也会是一个随机的无意义值。

错误2:未初始化数组

定义数组时候必须初始化,访问未初始化的数组可能会得到一个随机的无意义值。

错误3:数组大小错误

定义数组时的长度必须是不为0的正整数,长度写负数或其他不符合要求的数时会报错。

警告1:部分初始化

一般来说,初始化数组要么使用{ 0 }来将数组中的所有元素初始化为0,要么就在大括号中写清楚所有元素的初始化值,一定要避免出现部分初始化的情况,部分初始化时,未被初始化的那部分元素可能是意料之外的值而导致错误。

提示:总是初始化数组

初始化数组时,要习惯性的对数组进行初始化,例如下面这种写法:

1
uint32_t numbers[10] = { 0 };

后面学到字符串时候,char*类型的数组也必须初始化:

1
char* str[10] = { "" };

5. 案例:分数

要求:

  • 已知有五个学生的成绩(85、92、98、73、87),打印出所有学生的成绩
  • 求出所有学生的总成绩、平均成绩
  • 找出最高分和最低分

代码实现:

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
29
30
31
32
33
34
35
36
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>

#define STUDENT_COUNT 5

// C语言案例:分数
int main(void) {
uint32_t grades[STUDENT_COUNT] = { 85, 92, 98, 73, 87 };
uint32_t sum = 0;
uint32_t max_grade = grades[0];
uint32_t min_grade = grades[0];
double average = 0.0;

puts("所有学生的成绩:");

for(uint32_t index = 0; index < STUDENT_COUNT; index++) {
printf("学生 %" PRIu32 " 的成绩:%" PRIu32 "\n", index + 1, grades[index]);
sum += grades[index];
if (grades[index] > max_grade) {
max_grade = grades[index];
}
if (grades[index] < min_grade) {
min_grade = grades[index];
}
}

average = (double)sum / STUDENT_COUNT;

printf("\n总成绩:%" PRIu32 "\n", sum);
printf("平均成绩:%.2f\n", average);
printf("最高分:%" PRIu32 "\n", max_grade);
printf("最低分:%" PRIu32 "\n", min_grade);

return 0;
}

6. 案例:天气

要求:

  • 给出一周七天每天的最高温度分别为:20、22、19、26、23、21、25
  • 打印一周内每天的最高温度
  • 求出这一周内最高温度的总和、平均值
  • 找出最热和最冷的一天的最高温度

代码实现:

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
29
30
31
32
33
34
35
36
37
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <inttypes.h>

#define DAYS_IN_WEEK 7

// C语言案例:天气
int main(void) {
uint32_t daily_tempratures[DAYS_IN_WEEK] = { 20, 22, 19, 26, 23, 21, 25 };
uint32_t total_temperature = 0;
int32_t max_temperature = daily_tempratures[0];
int32_t min_temperature = daily_tempratures[0];
double average_temperature = 0.0;

puts("一周内每天的最高温度:");

for (uint32_t index = 0; index < DAYS_IN_WEEK; index++) {
printf("第%" PRIu32 "天的最高温度:%" PRIu32 "℃\n", index + 1, daily_tempratures[index]);
total_temperature += daily_tempratures[index];
if (daily_tempratures[index] > max_temperature) {
max_temperature = daily_tempratures[index];
}
if (daily_tempratures[index] < min_temperature) {
min_temperature = daily_tempratures[index];
}
}

average_temperature = (double)total_temperature / DAYS_IN_WEEK;

printf("一周内最高温度的总和:%" PRIu32 "℃\n", total_temperature);
printf("一周内最高温度的平均值:%.2f℃\n", average_temperature);
printf("最热的一天的最高温度:%" PRIu32 "℃\n", max_temperature);
printf("最冷的一天的最高温度:%" PRIu32 "℃\n", min_temperature);

return 0;
}

7. 案例:账户存款取款

要求:

  • 模拟账户的存款取款操作,假设账户的金额都为整型
  • 假设一共有三个账户,其中的金额分别为15000、18000、26000
  • 第一个账户存款5000元,第二个账户取款2000元
  • 在进行存取款操作前后分别打印出每个账户的金额数和总金额数

代码实现:

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
29
30
31
32
33
34
35
36
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>

#define ACCOUNT_COUNT 3

// C语言案例:账户存款取款
int main() {
uint32_t account_balances[ACCOUNT_COUNT] = { 15000, 18000, 26000 };
uint32_t total_assets = 0;

// 打印初始账户金额
puts("初始账户金额:");
for (uint32_t index = 0; index < ACCOUNT_COUNT; index++) {
printf("账户 %" PRIu32 ": %" PRIu32 "\n", index + 1, account_balances[index]);
total_assets += account_balances[index];
}
printf("初始总金额数: %" PRIu32 "\n", total_assets);

// 模拟存取款操作
account_balances[0] += 5000;
account_balances[1] -= 2000;

// 重新计算总金额
total_assets = 0;

// 打印存取款操作后的账户金额
puts("\n存取款操作后账户金额:");
for (uint32_t index = 0; index < ACCOUNT_COUNT; index++) {
printf("账户 %" PRIu32 ": %" PRIu32 "\n", index + 1, account_balances[index]);
total_assets += account_balances[index];
}
printf("存取款操作后总金额数: %" PRIu32 "\n", total_assets);

return 0;
}

8. 案例:字母次数统计

要求:

  • 给出一句固定的话,例如:“Example text for frequency analysis.”
  • 程序自动统计这句话中每个字母出现的次数,并打印出来
  • 任意找出出现频率最高和最低的字母中的一个

代码实现:

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
#include <ctype.h> // 包含字符处理函数的头文件

#define LETTER_COUNT 26

// C语言案例:字母次数统计
int main() {
uint32_t frequency[LETTER_COUNT] = { 0 };
const char text[] = "Example text for frequency analysis.";
uint32_t max_frequency_letter = 0;
uint32_t min_frequency_letter = 0;

// 遍历文本,统计字母出现频率
for (uint32_t index = 0; text[index] != '\0'; index++) {
// 将字母转换为小写
char ch = tolower(text[index]);

// 如果是字母,则更新频率
if (ch >= 'a' && ch <= 'z') {
frequency[ch - 'a']++;
}
}

// 初始化最小频率字母为第一个有效字母
for (uint32_t index = 0; index < LETTER_COUNT; index++) {
if (frequency[index] > 0) {
min_frequency_letter = index;
break;
}
}

puts("字母出现频率统计:");
for (uint32_t index = 0; index < LETTER_COUNT; index++) {
if (frequency[index] > 0) {
printf("%c: %" PRIu32 "\n", 'a' + index, frequency[index]);
}

// 找出出现频率最高和最低的字母
if (frequency[index] > frequency[max_frequency_letter]) {
max_frequency_letter = index;
}
if (frequency[index] > 0 && frequency[index] < frequency[min_frequency_letter]) {
min_frequency_letter = index;
}
}

printf("\n任一出现频率最高的字母:%c (%" PRIu32 "次)\n", 'a' + max_frequency_letter, frequency[max_frequency_letter]);
printf("任一出现频率最低的字母:%c (%" PRIu32 "次)\n", 'a' + min_frequency_letter, frequency[min_frequency_letter]);

return 0;
}

在C语言中,数组的长度可以在定义时不指定,编译器会根据初始化的元素数量自动推断长度。因此在定义数组时方括号中可以不写长度值,前提是必须对数组进行初始化,大括号中必须有数组初始化的数据。

也有动态数组这个概念,属于C语言新特性的范畴,放在最后面来介绍。

9. 二维数组

前面只有一个方括号定义的数组都称为一维数组,它其实是一种特殊的二维数组,一维数组只有一行,有n列,二维数组可以有n行,m列。

定义二维数组的方式非常简单,写两个方括号就行,需要注意的是二维数组初始化的标准写法,用于提高代码的可读性。例如定义一个五行六列的数组:

1
2
3
4
5
6
7
uint32_t numbers[5][6] = {
{ 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, 29, 30 }
};

需要注意的是,数组下标的第一个数字指的是行,第二个数字指的是列,且行和列都是从0开始计算的。

例如取出数组中第3行第5列的元素:

1
printf("numbers[2][4] = %" PRIu32 "\n", numbers[2][4]);	// 输出:17

同样的,二维数组也可以使用for循环遍历数组中的所有元素,只是此时应该使用两个for循环嵌套来实现:

1
2
3
4
5
6
for (uint32_t i = 0; i < 5; i++) {
for (uint32_t j = 0; j < 6; j++) {
printf("%" PRIu32 " ", numbers[i][j]);
}
printf("\n");
}

10. 隐式确定数组大小的初始化

对于一维数组:

数组定义时,如果不指定数组的长度,这时候的数组就是隐式定义,此时数组中所有的元素都应该被初始化,编译器会自动根据初始化的元素的个数来确定数组的长度。

因此,隐式确定数组时不可以使用{ 0 }为所有的元素初始化为0,未指定长度时编译器不知道有多少元素被初始化为0,因此使用{ 0 }隐式初始化时,编译器默认此时数组内只有一个为0的元素,访问下标为1的元素时就会出现数组越界的问题。

二维数组的情况比较特殊:

二维数组同样也可以被隐式定义,但此时可以不指定长度的下标只能为行数(第一个下标),列数(第二个下标)必须在定义时明确给出。

11. Unicode字符编码与wchar.h、locale.h

接下来将制作一个五子棋棋盘的案例,有一些扩展知识需要提前了解。

计算机中的所有字符的显示都是通过字符编码实现的,字符编码的类型有很多,常见的有UTF-8、GBK等,GBK是“汉字内码扩展规范”的简称,主要服务于汉字的编码,而UTF-8属于Unicode编码的一种,Unicode是信息技术领域的业界标准,其整理、编码了世界上大部分的文字系统,使得电脑能以通用划一的字符集来处理和显示文字,不但减轻在不同编码系统间切换和转换的困扰,更提供了一种跨平台的乱码问题解决方案。

其具体编码方式等扩展知识可以自行搜索资料了解。

Unicode - 维基百科,自由的百科全书

汉字内码扩展规范 - 维基百科,自由的百科全书

wchar.h是C标准函数库中的头文件,提供了对宽字符的支持。宽字符(Wide character)是计算机抽象术语(没有规定具体实现细节),表示比1字节还宽的数据类型。不同于Unicode。

wchar.h - 维基百科,自由的百科全书

宽字符 - 维基百科,自由的百科全书

locale.h是C程序设计语言标准函数库的一个头文件,声明了C语言本地化函数。这些函数用于在处理多种自然语言的软件编程设计时,把程序调整到特定的区域设置。这些区域设置影响到C语言标准库的输入/输出函数。

例如:

不同国家和区域对于小数点的处理是不同的,有的是1.234,56,有的是1,234.56;

不同国家的货币符号也有不同,例如美元($)、人民币(¥)、欧元(€)、英镑(£)等;

不同地区的时间表示也不同,例如中国一般是年/月/日 00:00 24h/12h制,美国一般是MM/DD/YYYY,欧洲一般是DD/MM/YYYY;

不同国家和地区比较字符串的顺序时候的规则不同。

Locale.h - 维基百科,自由的百科全书

12. 案例:五子棋棋盘绘制

要求:

  • 程序默认输出一个正方形的棋盘,每个位置有一个占位符,需要模拟两个人下棋的过程;
  • 第一个用户通过输入一个坐标来下棋,判断输入的坐标合理并下棋成功后,在棋盘的对应位置将占位符换成一种棋子;
  • 第二个用户还是通过输入一个坐标来下棋,不过这次下棋的棋子和第一次有所不同;
  • 每次下棋后都会询问用户是否结束棋局;
  • 只需要模拟棋盘和下棋的过程,不需要有判定输赢的逻辑。

代码实现:

一种比较基础的实现方式为:

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <inttypes.h>

#define BOARD_SIZE 15 // 棋盘大小

// C语言案例:五子棋棋盘绘制
int main(void) {
char board[BOARD_SIZE][BOARD_SIZE] = { 0 }; // 棋盘数组,初始化为0
uint8_t x, y; // 用户输入的坐标

char player1_piece = 'X'; // 玩家1的棋子
char player2_piece = 'O'; // 玩家2的棋子

// 初始化棋盘
for (uint8_t i = 0; i < BOARD_SIZE; i++) {
for (uint8_t j = 0; j < BOARD_SIZE; j++) {
board[i][j] = '.'; // 使用 '.' 作为占位符
}
}

char continue_game = 'y'; // 是否继续游戏
while (continue_game == 'y' || continue_game == 'Y') {
// 打印棋盘
for (uint8_t i = 0; i < BOARD_SIZE; i++) {
for (uint8_t j = 0; j < BOARD_SIZE; j++) {
printf("%c ", board[i][j]);
}
printf("\n");
}

// 玩家1下棋
while (true) {
printf("玩家1,请输入下棋坐标 (x y): ");
scanf_s("%" SCNu8 " %" SCNu8, &x, &y);
if (x < BOARD_SIZE && y < BOARD_SIZE && board[x][y] == '.') {
board[x][y] = player1_piece;
break; // 下棋成功,跳出循环
}
else {
printf("无效的坐标或位置已被占用!请重新输入。\n");
}
}

// 打印棋盘
for (uint8_t i = 0; i < BOARD_SIZE; i++) {
for (uint8_t j = 0; j < BOARD_SIZE; j++) {
printf("%c ", board[i][j]);
}
printf("\n");
}

// 玩家2下棋
while (true) {
printf("玩家2,请输入下棋坐标 (x y): ");
scanf_s("%" SCNu8 " %" SCNu8, &x, &y);
if (x < BOARD_SIZE && y < BOARD_SIZE && board[x][y] == '.') {
board[x][y] = player2_piece;
break; // 下棋成功,跳出循环
}
else {
printf("无效的坐标或位置已被占用!请重新输入。\n");
}
}

// 打印棋盘
for (uint8_t i = 0; i < BOARD_SIZE; i++) {
for (uint8_t j = 0; j < BOARD_SIZE; j++) {
printf("%c ", board[i][j]);
}
printf("\n");
}

printf("是否继续游戏?(y/n): ");

// 空格前的空格是为了避免读取到换行符
scanf_s(" %c", &continue_game, 1); // 第三个参数表示读取的字符的最大长度为1
}

puts("游戏结束,谢谢参与!");

return 0;
}

这种实现方式较为简单,但是有几个问题:

  • 每次打印棋盘都会使控制台输出向下滚动,可以在每次打印棋盘前使用头文件Windows.h中的system("cls");进行清屏操作;
  • 玩家1下棋和玩家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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <inttypes.h>
#include <Windows.h> // Windows特有的头文件

#define BOARD_SIZE 15 // 棋盘大小

// C语言案例:五子棋棋盘绘制
int main(void) {
char board[BOARD_SIZE][BOARD_SIZE] = { 0 }; // 棋盘数组,初始化为0
uint8_t x, y; // 用户输入的坐标

char player1_piece = 'X'; // 玩家1的棋子
char player2_piece = 'O'; // 玩家2的棋子
char current_piece = player1_piece; // 当前棋子

// 初始化棋盘
for (uint8_t i = 0; i < BOARD_SIZE; i++) {
for (uint8_t j = 0; j < BOARD_SIZE; j++) {
board[i][j] = '.'; // 使用 '.' 作为占位符
}
}

char continue_game = 'y'; // 是否继续游戏
while (continue_game == 'y' || continue_game == 'Y') {
// 打印棋盘
system("cls"); // 清屏
for (uint8_t i = 0; i < BOARD_SIZE; i++) {
for (uint8_t j = 0; j < BOARD_SIZE; j++) {
printf("%c ", board[i][j]);
}
printf("\n");
}

// 玩家1下棋
while (true) {
printf("玩家%d下棋,请输入坐标 (x y):", (current_piece != player1_piece) + 1);
scanf_s("%" SCNu8 " %" SCNu8, &x, &y);

// 检查坐标是否在棋盘范围内且位置未被占用
if (x < BOARD_SIZE && y < BOARD_SIZE && board[x][y] == '.') {
board[x][y] = current_piece;
current_piece = (current_piece == player1_piece) ? player2_piece : player1_piece; // 切换棋子
break; // 下棋成功,跳出循环
}
else {
printf("无效的坐标或位置已被占用!请重新输入。\n");
}
}

// 打印棋盘
system("cls"); // 清屏
for (uint8_t i = 0; i < BOARD_SIZE; i++) {
for (uint8_t j = 0; j < BOARD_SIZE; j++) {
printf("%c ", board[i][j]);
}
printf("\n");
}
printf("是否继续游戏?(y/n): ");

// 空格前的空格是为了避免读取到换行符
scanf_s(" %c", &continue_game, 1); // 第三个参数表示读取的字符的最大长度为1
}

puts("游戏结束,谢谢参与!");

return 0;
}

同样的,这个改进后的程序也存在几个问题:

  • 没有对scanf_s()的输入做检查和清空缓冲区的操作,当输入字符等非法值时,可能导致程序陷入死循环;
  • 目前的棋盘占位符和棋子都是用常规字符表示的,不太美观,可以使用前一节提到的头文件wchar.h和locale.h来将棋盘和棋子变为宽字符。

改进后的最终程序如下:

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <inttypes.h>
#include <Windows.h> // Windows特有的头文件
#include <wchar.h> // 宽字符支持
#include <locale.h> // 设置区域支持

#define BOARD_SIZE 15 // 棋盘大小

// C语言案例:五子棋棋盘绘制
int main(void) {
// 设置控制台为宽字符模式
SetConsoleOutputCP(CP_UTF8); // 设置控制台输出为UTF-8编码

// 设置区域为当前系统默认
setlocale(LC_ALL, ""); // 设置区域为当前系统默认

wchar_t board[BOARD_SIZE][BOARD_SIZE]; // 棋盘数组
uint8_t x, y; // 用户输入的坐标

wchar_t player1_piece = 0x25CF; // 玩家1的棋子是用Unicode字符表示的实心圆点
wchar_t player2_piece = 0x25CB; // 玩家2的棋子是用Unicode字符表示的空心圆点
wchar_t current_piece = player1_piece; // 当前下棋的棋子

// 初始化棋盘
for (uint8_t i = 0; i < BOARD_SIZE; i++) {
for (uint8_t j = 0; j < BOARD_SIZE; j++) {
board[i][j] = 0x00B7; // 棋盘占位符是使用Unicode字符表示的小圆点
}
}

wchar_t continue_game = L'y'; // 控制游戏是否继续的变量,L表示宽字符
while (continue_game == L'y' || continue_game == L'Y') {
// 打印棋盘
system("cls"); // 清屏
for (uint8_t i = 0; i < BOARD_SIZE; i++) {
for (uint8_t j = 0; j < BOARD_SIZE; j++) {
wprintf(L"%lc ", board[i][j]); // 使用宽字符打印棋盘
}
printf("\n");
}

// 玩家下棋
while (true) {
printf("玩家%d下棋,请输入0 - %u的坐标(x y):", (current_piece == player1_piece) ? 1 : 2, BOARD_SIZE - 1);
// 判断输入是否有效
if (scanf_s("%" SCNu8 " %" SCNu8, &x, &y) != 2) {
printf("输入无效,请重新输入。\n");
while (getchar() != '\n'); // 清空输入缓冲区
continue; // 重新开始循环
}

// 检查坐标是否在棋盘范围内且位置未被占用
if (x < BOARD_SIZE && y < BOARD_SIZE && board[x][y] == 0x00B7) {
board[x][y] = current_piece;
current_piece = (current_piece == player1_piece) ? player2_piece : player1_piece; // 切换棋子
break; // 下棋成功,跳出循环
}
else {
printf("无效的坐标或位置已被占用!请重新输入。\n");
}
}

// 打印棋盘
system("cls"); // 清屏
for (uint8_t i = 0; i < BOARD_SIZE; i++) {
for (uint8_t j = 0; j < BOARD_SIZE; j++) {
wprintf(L"%lc ", board[i][j]); // 使用宽字符打印棋盘
}
printf("\n");
}

printf("是否继续游戏?(y/n): ");

while (getchar() != '\n'); // 清空输入缓冲区
wscanf_s(L"%lc", &continue_game, 1); // 使用宽字符输入继续游戏的选项
}

puts("游戏结束,谢谢参与!");

return 0;
}

13. 案例:农场作物成长

要求:

  • 模拟一片矩形菜地,每个位置都有50%的概率有一颗未成熟的农作物;
  • 在5秒内,每隔1秒里面的每个农作物都会有30%的概率成熟;
  • 可以在初始化时候指定一些空地有石头,无法种植作物。

代码实现:

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h> // C标准库头文件
#include <time.h> // 时间相关的头文件
#include <Windows.h> // Windows特有的头文件

// 定义农场的大小和状态
#define ROWS 15 // 菜地的行数
#define COLS 25 // 菜地的列数

// 定义农场的状态
#define EMPTY 0 // 空地
#define PLANTED 1 // 已种植的作物(未成熟)
#define MATURED 2 // 已成熟的作物
#define STONE 3 // 有石头的地

// 打印农场的状态
void print_farm(uint8_t farm[ROWS][COLS]);

// C语言案例:农场作物成长
int main(void) {
uint8_t farm[ROWS][COLS] = { [3] [4] = STONE,[5][6] = STONE }; // 初始化菜地,指定一些位置有石头
srand((unsigned int)time(NULL)); // 设置随机数种子

// 初始化菜地
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLS; j++) {
if (farm[i][j] != STONE && (rand() % 2 == 0)) {
farm[i][j] = PLANTED;
}
}
}

// 模拟作物成长
for (int time = 0; time < 5; time++) {
system("cls"); // 清屏
printf("时间: %d秒\n", time + 1);
print_farm(farm); // 打印当前农场状态
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLS; j++) {
if (farm[i][j] == PLANTED && (rand() % 10 < 3)) { // 30% 的概率成熟
farm[i][j] = MATURED;
}
}
}
Sleep(1000); // 等待1秒
}

puts("时间到,农作物成长完成!");

return 0;
}

// 打印农场的状态
void print_farm(uint8_t farm[ROWS][COLS]) {
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLS; j++) {
switch (farm[i][j]) {
case EMPTY:
printf(". ");
break;
case PLANTED:
printf("* ");
break;
case MATURED:
printf("# ");
break;
case STONE:
printf("0 ");
break;
}
}
printf("\n");
}
}

14. 第六章结束语

后面学函数和指针时也会用到数组。

对数组的操作无非就是和数据库类似的CRUD(增删改查)还有排序。

变量命名一定要养成规范的习惯。

多写!多练习!

本文来源: 青江的个人站
本文链接: https://hanqingjiang.com/2025/07/25/20250725_C_array/
版权声明: 本作品采用 CC BY-NC-SA 4.0 进行许可。转载请注明出处!
知识共享许可协议
赏

谢谢你请我喝可乐~

支付宝
微信
  • Notes
  • C

扫一扫,分享到微信

微信分享二维码
【保姆级教程】Git客户端Fork的基础使用
无Node.js环境时使用PicList上传图片到MinIO图床
  1. 1. 1. 数组的作用
  2. 2. 2. 数组的初步使用
  3. 3. 3. 数组定义时下标需要常量
  4. 4. 4. 数组的注意事项
    1. 4.1. 错误1:访问越界
    2. 4.2. 错误2:未初始化数组
    3. 4.3. 错误3:数组大小错误
    4. 4.4. 警告1:部分初始化
    5. 4.5. 提示:总是初始化数组
  5. 5. 5. 案例:分数
  6. 6. 6. 案例:天气
  7. 7. 7. 案例:账户存款取款
  8. 8. 8. 案例:字母次数统计
  9. 9. 9. 二维数组
  10. 10. 10. 隐式确定数组大小的初始化
  11. 11. 11. Unicode字符编码与wchar.h、locale.h
  12. 12. 12. 案例:五子棋棋盘绘制
  13. 13. 13. 案例:农场作物成长
  14. 14. 14. 第六章结束语
© 2021-2025 青江的个人站
晋ICP备2024051277号-1
powered by Hexo & Yilia
  • 友链
  • 搜索文章 >>

tag:

  • 新年快乐!
  • 生日快乐🎂
  • 小技巧
  • Linux
  • 命令
  • 语录
  • Blog
  • Notes
  • 复刻
  • Android
  • FPGA
  • Homework
  • MATLAB
  • C
  • Server
  • Vivado
  • Git

  • 引路人-稚晖
  • Bilibili-稚晖君
  • 超有趣讲师-Frank
  • Bilibili-Frank