C语言数组和字符串

概述

本文档详细介绍C语言中数组和字符串的使用方法,包括一维数组、二维数组、字符数组、字符串处理函数等内容。数组和字符串是C语言中常用的数据结构,掌握它们的使用对于编写高效、可靠的C程序至关重要。

一维数组

数组的声明与初始化

数组是一种存储相同类型元素的集合,一维数组的声明和初始化语法如下:

1
2
3
4
5
6
7
8
9
10
11
// 声明数组
类型 数组名[数组大小];

// 初始化数组
类型 数组名[数组大小] = {元素1, 元素2, ..., 元素n};

// 自动推断数组大小
类型 数组名[] = {元素1, 元素2, ..., 元素n};

// 部分初始化,其余元素为0
类型 数组名[数组大小] = {元素1, 元素2, ...};

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
// 数组声明和初始化
int numbers[5] = {1, 2, 3, 4, 5}; // 显式初始化
int numbers2[] = {6, 7, 8, 9, 10}; // 自动推断大小
int numbers3[5] = {11, 12}; // 部分初始化,其余为0

// 访问数组元素
printf("numbers[0] = %d\n", numbers[0]); // 第一个元素
printf("numbers[4] = %d\n", numbers[4]); // 最后一个元素

// 修改数组元素
numbers[2] = 100;
printf("修改后 numbers[2] = %d\n", numbers[2]);

数组的遍历

遍历数组是指依次访问数组中的每个元素,通常使用for循环实现:

1
2
3
4
5
6
7
8
9
10
11
// 打印数组
void print_array(int arr[], int size) {
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}

// 调用打印函数
printf("numbers数组: ");
print_array(numbers, 5);

补充示例:数组的冒泡排序

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
// 冒泡排序函数
void bubble_sort(int arr[], int size) {
for (int i = 0; i < size - 1; i++) {
for (int j = 0; j < size - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
// 交换元素
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}

// 使用冒泡排序
int unsorted[] = {5, 2, 9, 1, 5, 6};
int size = sizeof(unsorted) / sizeof(unsorted[0]);

printf("排序前: ");
print_array(unsorted, size);

bubble_sort(unsorted, size);

printf("排序后: ");
print_array(unsorted, size);

二维数组

二维数组的声明与初始化

二维数组是一种特殊的数组,它的元素是一维数组。二维数组的声明和初始化语法如下:

1
2
3
4
5
6
7
8
9
// 声明二维数组
类型 数组名[行数][列数];

// 初始化二维数组
类型 数组名[行数][列数] = {
{元素1, 元素2, ...},
{元素1, 元素2, ...},
...
};

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
// 二维数组声明和初始化
int matrix[2][3] = {
{1, 2, 3},
{4, 5, 6}
};

// 访问二维数组元素
printf("matrix[0][0] = %d\n", matrix[0][0]); // 第一行第一列
printf("matrix[1][2] = %d\n", matrix[1][2]); // 第二行第三列

// 修改二维数组元素
matrix[0][1] = 20;
printf("修改后 matrix[0][1] = %d\n", matrix[0][1]);

二维数组的遍历

遍历二维数组通常使用嵌套的for循环:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 打印二维数组
void print_2d_array(int arr[][3], int rows) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < 3; j++) {
printf("%d ", arr[i][j]);
}
printf("\n");
}
}

// 调用打印函数
printf("二维数组matrix: \n");
print_2d_array(matrix, 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
// 二维数组转置
void transpose_matrix(int matrix[][3], int transposed[][2], int rows, int cols) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
transposed[j][i] = matrix[i][j];
}
}
}

// 使用转置函数
int original[2][3] = {{1, 2, 3}, {4, 5, 6}};
int transposed[3][2];

printf("原始矩阵:\n");
print_2d_array(original, 2);

transpose_matrix(original, transposed, 2, 3);

printf("转置矩阵:\n");
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 2; j++) {
printf("%d ", transposed[i][j]);
}
printf("\n");
}

字符数组和字符串

字符数组的初始化

在C语言中,字符串是由字符组成的数组,以空字符'\0'结尾。字符数组的初始化方法如下:

1
2
3
4
// 字符数组初始化
char char_array[5] = {'H', 'e', 'l', 'l', 'o'}; // 普通字符数组,没有结束符
char string1[] = "Hello"; // 字符串,自动添加结束符
char string2[20] = "World"; // 指定大小的字符串

字符串的输出

字符串可以使用printf函数的%s格式说明符输出:

1
2
3
4
5
6
7
8
9
10
// 字符数组输出
printf("char_array: ");
for (int i = 0; i < 5; i++) {
printf("%c", char_array[i]);
}
printf("\n");

// 字符串输出
printf("string1: %s\n", string1);
printf("string2: %s\n", string2);

字符串长度

可以使用strlen函数获取字符串的长度(不包括结束符'\0'):

1
2
3
4
5
#include <string.h>

// 字符串长度
printf("string1的长度: %zu\n", strlen(string1));
printf("string2的长度: %zu\n", strlen(string2));

字符串处理函数

C语言提供了丰富的字符串处理函数,定义在<string.h>头文件中。

常用字符串处理函数

函数 功能 示例
strcpy(dest, src) 复制字符串 strcpy(dest, "Hello")
strcat(dest, src) 连接字符串 strcat(dest, " World")
strcmp(s1, s2) 比较字符串 strcmp(s1, s2)
strlen(s) 获取字符串长度 strlen(s)
strncpy(dest, src, n) 安全复制指定长度的字符串 strncpy(dest, src, sizeof(dest)-1)
strncat(dest, src, n) 安全连接指定长度的字符串 strncat(dest, src, sizeof(dest)-strlen(dest)-1)
strchr(s, c) 查找字符首次出现的位置 strchr(s, 'a')
strstr(s, sub) 查找子串首次出现的位置 strstr(s, "abc")

示例代码

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
// strcpy: 复制字符串
char dest[50];
char src[] = "Hello, ";
strcpy(dest, src);
printf("strcpy(dest, src): %s\n", dest);

// strcat: 连接字符串
char append[] = "World!";
strcat(dest, append);
printf("strcat(dest, append): %s\n", dest);

// strcmp: 比较字符串
char str1[] = "apple";
char str2[] = "banana";
char str3[] = "apple";

printf("strcmp(str1, str2): %d\n", strcmp(str1, str2)); // 返回负数
printf("strcmp(str1, str3): %d\n", strcmp(str1, str3)); // 返回0
printf("strcmp(str2, str1): %d\n", strcmp(str2, str1)); // 返回正数

// strncpy: 安全地复制指定长度的字符串
char safe_dest[20];
strncpy(safe_dest, "This is a very long string", sizeof(safe_dest) - 1);
safe_dest[sizeof(safe_dest) - 1] = '\0'; // 确保字符串结束
printf("strncpy安全复制: %s\n", safe_dest);

// strncat: 安全地连接指定长度的字符串
strncat(safe_dest, " appended", 10);
printf("strncat安全连接: %s\n", safe_dest);

// strchr: 查找字符首次出现的位置
char *pos = strchr("Hello, World!", 'W');
if (pos != NULL) {
printf("'W'首次出现的位置: %ld\n", pos - "Hello, World!");
}

// strstr: 查找子串首次出现的位置
char *sub_pos = strstr("Hello, World!", "World");
if (sub_pos != NULL) {
printf("'World'首次出现的位置: %ld\n", sub_pos - "Hello, World!");
}

补充示例:字符串分割

1
2
3
4
5
6
7
8
9
10
11
12
13
// 字符串分割函数
void split_string(char *str, const char *delimiter) {
char *token = strtok(str, delimiter);
while (token != NULL) {
printf("%s\n", token);
token = strtok(NULL, delimiter);
}
}

// 使用字符串分割函数
char sentence[] = "Hello,World,How,Are,You";
printf("分割字符串 \"%s\":\n", sentence);
split_string(sentence, ",");

字符串输入输出

使用scanf输入字符串

scanf函数可以用于输入字符串,但它会在空格处停止读取:

1
2
3
4
char name[50];
printf("请输入你的名字: ");
scanf("%s", name); // 注意:scanf会在空格处停止
printf("你好, %s!\n", name);

使用fgets输入带空格的字符串

fgets函数可以读取带空格的字符串,它会读取直到换行符或指定长度:

1
2
3
4
5
6
char full_name[50];
printf("请输入你的全名: ");
fflush(stdin); // 清空输入缓冲区
fgets(full_name, sizeof(full_name), stdin);
full_name[strcspn(full_name, "\n")] = '\0'; // 移除换行符
printf("你好, %s!\n", full_name);

补充示例:安全的字符串输入函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 安全的字符串输入函数
void safe_gets(char *buffer, size_t size) {
fgets(buffer, size, stdin);
// 移除换行符
size_t len = strlen(buffer);
if (len > 0 && buffer[len-1] == '\n') {
buffer[len-1] = '\0';
}
}

// 使用安全的字符串输入函数
char input[100];
printf("请输入一段文本: ");
safe_gets(input, sizeof(input));
printf("你输入的是: %s\n", input);

字符串操作示例

反转字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 反转字符串
void reverse_string(char str[]) {
int len = strlen(str);
for (int i = 0; i < len / 2; i++) {
char temp = str[i];
str[i] = str[len - 1 - i];
str[len - 1 - i] = temp;
}
}

// 使用反转字符串函数
char test_str[] = "Hello, World!";
printf("原始字符串: %s\n", test_str);
reverse_string(test_str);
printf("反转后: %s\n", test_str);

统计元音字母个数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 统计字符串中的元音字母个数
int count_vowels(const char str[]) {
int count = 0;
for (int i = 0; str[i]; i++) {
char ch = tolower(str[i]);
if (ch == 'a' || ch == 'e' || ch == 'i' || ch == 'o' || ch == 'u') {
count++;
}
}
return count;
}

// 使用统计元音字母函数
char test_str[] = "Hello, World!";
int vowel_count = count_vowels(test_str);
printf("元音字母个数: %d\n", vowel_count);

字符串大小写转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 字符串转大写
char upper_str[50];
strcpy(upper_str, test_str);
for (int i = 0; upper_str[i]; i++) {
upper_str[i] = toupper(upper_str[i]);
}
printf("大写: %s\n", upper_str);

// 字符串转小写
char lower_str[50];
strcpy(lower_str, test_str);
for (int i = 0; lower_str[i]; i++) {
lower_str[i] = tolower(lower_str[i]);
}
printf("小写: %s\n", lower_str);

补充示例:字符串替换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 字符串替换函数
void replace_char(char *str, char old_char, char new_char) {
for (int i = 0; str[i]; i++) {
if (str[i] == old_char) {
str[i] = new_char;
}
}
}

// 使用字符串替换函数
char text[] = "Hello, World!";
printf("原始字符串: %s\n", text);
replace_char(text, 'o', '0');
printf("替换后: %s\n", text);

数组和指针关系

数组名作为指针

在C语言中,数组名可以看作是指向数组首元素的指针:

1
2
3
4
5
6
int arr[] = {10, 20, 30, 40, 50};
int *ptr = arr; // 数组名是指向首元素的指针

printf("arr[0] = %d, *ptr = %d\n", arr[0], *ptr);
printf("arr[1] = %d, *(ptr+1) = %d\n", arr[1], *(ptr+1));
printf("arr[2] = %d, ptr[2] = %d\n", arr[2], ptr[2]);

指针算术

指针可以进行算术运算,如加、减操作:

1
2
3
4
5
6
7
8
9
// 指针算术
ptr++;
printf("ptr++后, *ptr = %d\n", *ptr);

ptr += 2;
printf("ptr += 2后, *ptr = %d\n", *ptr);

ptr -= 1;
printf("ptr -= 1后, *ptr = %d\n", *ptr);

数组参数传递

当数组作为函数参数传递时,实际上传递的是数组首元素的地址:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 数组参数传递
void modify_array(int arr[], int size) {
for (int i = 0; i < size; i++) {
arr[i] *= 2; // 修改数组元素
}
}

// 使用修改数组函数
int numbers[] = {1, 2, 3, 4, 5};
printf("修改前: ");
print_array(numbers, 5);

modify_array(numbers, 5);

printf("修改后: ");
print_array(numbers, 5);

动态数组

使用malloc和free

可以使用动态内存分配函数创建动态数组:

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
#include <stdlib.h>

// 动态数组示例
int *create_array(int size) {
int *arr = (int *)malloc(size * sizeof(int));
if (arr == NULL) {
printf("内存分配失败\n");
exit(1);
}
return arr;
}

// 使用动态数组
int size = 5;
int *dynamic_arr = create_array(size);

// 初始化动态数组
for (int i = 0; i < size; i++) {
dynamic_arr[i] = i + 1;
}

printf("动态数组: ");
print_array(dynamic_arr, size);

// 释放动态数组
free(dynamic_arr);

补充示例:二维动态数组

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
// 创建二维动态数组
int **create_2d_array(int rows, int cols) {
int **arr = (int **)malloc(rows * sizeof(int *));
if (arr == NULL) {
printf("内存分配失败\n");
exit(1);
}

for (int i = 0; i < rows; i++) {
arr[i] = (int *)malloc(cols * sizeof(int));
if (arr[i] == NULL) {
printf("内存分配失败\n");
exit(1);
}
}

return arr;
}

// 释放二维动态数组
void free_2d_array(int **arr, int rows) {
for (int i = 0; i < rows; i++) {
free(arr[i]);
}
free(arr);
}

// 使用二维动态数组
int rows = 2, cols = 3;
int **dynamic_2d_arr = create_2d_array(rows, cols);

// 初始化二维动态数组
int count = 1;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
dynamic_2d_arr[i][j] = count++;
}
}

// 打印二维动态数组
printf("二维动态数组:\n");
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%d ", dynamic_2d_arr[i][j]);
}
printf("\n");
}

// 释放二维动态数组
free_2d_array(dynamic_2d_arr, rows);

综合示例

示例1:数组元素查找

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 线性查找函数
int linear_search(int arr[], int size, int target) {
for (int i = 0; i < size; i++) {
if (arr[i] == target) {
return i; // 找到目标元素,返回索引
}
}
return -1; // 未找到目标元素
}

// 使用线性查找函数
int numbers[] = {10, 20, 30, 40, 50};
int size = sizeof(numbers) / sizeof(numbers[0]);
int target = 30;

int index = linear_search(numbers, size, target);
if (index != -1) {
printf("元素 %d 在数组中的索引位置: %d\n", target, index);
} else {
printf("元素 %d 不在数组中\n", target);
}

示例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
// 字符串处理综合示例
void process_string(char *str) {
printf("原始字符串: %s\n", str);

// 计算字符串长度
int len = strlen(str);
printf("字符串长度: %d\n", len);

// 统计单词个数
int word_count = 0;
int in_word = 0;

for (int i = 0; str[i]; i++) {
if (isspace(str[i])) {
in_word = 0;
} else if (!in_word) {
in_word = 1;
word_count++;
}
}

printf("单词个数: %d\n", word_count);

// 转换为大写并打印
char upper_str[100];
strcpy(upper_str, str);
for (int i = 0; upper_str[i]; i++) {
upper_str[i] = toupper(upper_str[i]);
}

printf("大写字符串: %s\n", upper_str);
}

// 使用字符串处理函数
char text[] = "Hello, how are you today?";
process_string(text);

数组和字符串的常见问题

常见问题及解决方案

  1. 数组越界:访问超出数组范围的元素

    • 解决方案:确保数组索引在有效范围内,使用循环时检查边界条件
  2. 字符串没有结束符:导致字符串处理函数出错

    • 解决方案:确保字符串以'\0'结尾,使用strncpy等安全函数
  3. 内存泄漏:动态分配的内存没有释放

    • 解决方案:使用free函数释放动态分配的内存,养成良好的内存管理习惯
  4. 缓冲区溢出:输入数据超过缓冲区大小

    • 解决方案:使用fgetsstrncpy等安全函数,指定最大读取长度
  5. 指针未初始化:使用未初始化的指针

    • 解决方案:总是初始化指针,使用前检查指针是否为NULL

最佳实践

  1. 使用有意义的变量名:数组和字符串变量名应清晰表达其用途
  2. 初始化数组:声明数组时进行初始化,避免使用未初始化的数组
  3. 使用常量定义数组大小:使用#defineconst定义数组大小,提高代码可维护性
  4. 优先使用安全的字符串函数:如strncpystrncat等,避免缓冲区溢出
  5. 检查函数返回值:如fgetsmalloc等函数的返回值
  6. 释放动态内存:使用free函数释放动态分配的内存
  7. 使用适当的数据结构:根据实际需求选择合适的数据结构,如数组、链表等
  8. 编写模块化代码:将数组和字符串操作封装为函数,提高代码复用性
  9. 添加适当的注释:解释复杂的数组和字符串操作
  10. 测试边界情况:测试空数组、空字符串、最大长度等边界情况

总结

C语言中的数组和字符串是基础但重要的数据结构,通过本文档的学习,我们了解了:

  1. 一维数组:声明、初始化、访问、修改和遍历
  2. 二维数组:声明、初始化、访问、修改和遍历
  3. 字符数组和字符串:初始化、输出、长度计算
  4. 字符串处理函数strcpystrcatstrcmp等常用函数的使用
  5. 字符串输入输出scanffgets的使用方法
  6. 字符串操作:反转、统计、大小写转换等
  7. 数组和指针关系:数组名作为指针、指针算术、数组参数传递
  8. 动态数组:使用mallocfree创建和释放动态数组
  9. 常见问题及解决方案:数组越界、字符串没有结束符等问题的解决方法
  10. 最佳实践:编写高质量数组和字符串代码的建议

掌握数组和字符串的使用对于编写高效、可靠的C程序至关重要,希望本文档能帮助您更好地理解和使用C语言中的数组和字符串。

arrays_strings.c

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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
#include <stdio.h>
#include <string.h>
#include <ctype.h>

/**
* @brief 数组和字符串操作示例
* @details 这个程序演示了C语言中数组和字符串的各种用法,包括
* 一维数组、二维数组、字符数组、字符串处理函数等。
* @return 0 表示程序成功执行
*/

// =========================
// 函数原型声明
// =========================
void print_array(int arr[], int size); // 打印一维数组
void print_2d_array(int arr[][3], int rows); // 打印二维数组
void reverse_string(char str[]); // 反转字符串
int count_vowels(const char str[]); // 统计元音字母个数

/**
* @brief 主函数
* @return 0 表示程序成功执行
*/
int main() {
// =========================
// 一维数组
// =========================
printf("=== 一维数组 ===\n");

// 数组声明和初始化
int numbers[5] = {1, 2, 3, 4, 5}; // 显式初始化
int numbers2[] = {6, 7, 8, 9, 10}; // 自动推断大小
int numbers3[5] = {11, 12}; // 部分初始化,其余为0

// 访问数组元素
printf("numbers[0] = %d\n", numbers[0]); // 第一个元素
printf("numbers[4] = %d\n", numbers[4]); // 最后一个元素

// 修改数组元素
numbers[2] = 100;
printf("修改后 numbers[2] = %d\n", numbers[2]);

// 打印数组
printf("numbers数组: ");
print_array(numbers, 5);

printf("numbers2数组: ");
print_array(numbers2, 5);

printf("numbers3数组: ");
print_array(numbers3, 5);

printf("\n");

// =========================
// 二维数组
// =========================
printf("=== 二维数组 ===\n");

// 二维数组声明和初始化
int matrix[2][3] = {
{1, 2, 3},
{4, 5, 6}
};

// 访问二维数组元素
printf("matrix[0][0] = %d\n", matrix[0][0]); // 第一行第一列
printf("matrix[1][2] = %d\n", matrix[1][2]); // 第二行第三列

// 修改二维数组元素
matrix[0][1] = 20;
printf("修改后 matrix[0][1] = %d\n", matrix[0][1]);

// 打印二维数组
printf("二维数组matrix: \n");
print_2d_array(matrix, 2);

printf("\n");

// =========================
// 字符数组和字符串
// =========================
printf("=== 字符数组和字符串 ===\n");

// 字符数组初始化
char char_array[5] = {'H', 'e', 'l', 'l', 'o'};
char string1[] = "Hello";
char string2[20] = "World";

// 字符串输出
printf("char_array: ");
for (int i = 0; i < 5; i++) {
printf("%c", char_array[i]);
}
printf("\n");

printf("string1: %s\n", string1);
printf("string2: %s\n", string2);

// 字符串长度
printf("string1的长度: %zu\n", strlen(string1));
printf("string2的长度: %zu\n", strlen(string2));

printf("\n");

// =========================
// 字符串处理函数
// =========================
printf("=== 字符串处理函数 ===\n");

char dest[50];
char src[] = "Hello, ";
char append[] = "World!";

// strcpy: 复制字符串
strcpy(dest, src);
printf("strcpy(dest, src): %s\n", dest);

// strcat: 连接字符串
strcat(dest, append);
printf("strcat(dest, append): %s\n", dest);

// strcmp: 比较字符串
char str1[] = "apple";
char str2[] = "banana";
char str3[] = "apple";

printf("strcmp(str1, str2): %d\n", strcmp(str1, str2)); // 返回负数
printf("strcmp(str1, str3): %d\n", strcmp(str1, str3)); // 返回0
printf("strcmp(str2, str1): %d\n", strcmp(str2, str1)); // 返回正数

// strncpy: 安全地复制指定长度的字符串
char safe_dest[20];
strncpy(safe_dest, "This is a very long string", sizeof(safe_dest) - 1);
safe_dest[sizeof(safe_dest) - 1] = '\0'; // 确保字符串结束
printf("strncpy安全复制: %s\n", safe_dest);

// strncat: 安全地连接指定长度的字符串
strncat(safe_dest, " appended", 10);
printf("strncat安全连接: %s\n", safe_dest);

// strchr: 查找字符首次出现的位置
char *pos = strchr("Hello, World!", 'W');
if (pos != NULL) {
printf("'W'首次出现的位置: %ld\n", pos - "Hello, World!");
}

// strstr: 查找子串首次出现的位置
char *sub_pos = strstr("Hello, World!", "World");
if (sub_pos != NULL) {
printf("'World'首次出现的位置: %ld\n", sub_pos - "Hello, World!");
}

printf("\n");

// =========================
// 字符串输入输出
// =========================
printf("=== 字符串输入输出 ===\n");

char name[50];
char full_name[50];

printf("请输入你的名字: ");
scanf("%s", name); // 注意:scanf会在空格处停止
printf("你好, %s!\n", name);

// 使用fgets读取带空格的字符串
printf("请输入你的全名: ");
fflush(stdin); // 清空输入缓冲区
fgets(full_name, sizeof(full_name), stdin);
full_name[strcspn(full_name, "\n")] = '\0'; // 移除换行符
printf("你好, %s!\n", full_name);

printf("\n");

// =========================
// 字符串操作示例
// =========================
printf("=== 字符串操作示例 ===\n");

char test_str[] = "Hello, World!";
printf("原始字符串: %s\n", test_str);

// 反转字符串
reverse_string(test_str);
printf("反转后: %s\n", test_str);

// 再次反转回来
reverse_string(test_str);

// 统计元音字母个数
int vowel_count = count_vowels(test_str);
printf("元音字母个数: %d\n", vowel_count);

// 字符串转大写
char upper_str[50];
strcpy(upper_str, test_str);
for (int i = 0; upper_str[i]; i++) {
upper_str[i] = toupper(upper_str[i]);
}
printf("大写: %s\n", upper_str);

// 字符串转小写
char lower_str[50];
strcpy(lower_str, test_str);
for (int i = 0; lower_str[i]; i++) {
lower_str[i] = tolower(lower_str[i]);
}
printf("小写: %s\n", lower_str);

printf("\n");

// =========================
// 数组和指针关系
// =========================
printf("=== 数组和指针关系 ===\n");

int arr[] = {10, 20, 30, 40, 50};
int *ptr = arr; // 数组名是指向首元素的指针

printf("arr[0] = %d, *ptr = %d\n", arr[0], *ptr);
printf("arr[1] = %d, *(ptr+1) = %d\n", arr[1], *(ptr+1));
printf("arr[2] = %d, ptr[2] = %d\n", arr[2], ptr[2]);

// 指针算术
ptr++;
printf("ptr++后, *ptr = %d\n", *ptr);

return 0;
}

// =========================
// 函数定义
// =========================

/**
* @brief 打印一维数组
* @param arr 要打印的数组
* @param size 数组的大小
*/
void print_array(int arr[], int size) {
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}

/**
* @brief 打印二维数组
* @param arr 要打印的二维数组
* @param rows 数组的行数
*/
void print_2d_array(int arr[][3], int rows) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < 3; j++) {
printf("%d ", arr[i][j]);
}
printf("\n");
}
}

/**
* @brief 反转字符串
* @param str 要反转的字符串
*/
void reverse_string(char str[]) {
int len = strlen(str);
for (int i = 0; i < len / 2; i++) {
char temp = str[i];
str[i] = str[len - 1 - i];
str[len - 1 - i] = temp;
}
}

/**
* @brief 统计字符串中的元音字母个数
* @param str 要统计的字符串
* @return 元音字母的个数
*/
int count_vowels(const char str[]) {
int count = 0;
for (int i = 0; str[i]; i++) {
char ch = tolower(str[i]);
if (ch == 'a' || ch == 'e' || ch == 'i' || ch == 'o' || ch == 'u') {
count++;
}
}
return count;
}

C语言函数

概述

本文档详细介绍C语言中函数的使用方法,包括函数的定义、声明、调用、参数传递、返回值、递归函数、静态函数等内容。函数是C语言中组织代码的基本单位,合理使用函数可以提高代码的可读性、可维护性和复用性。

函数的基本概念

函数是一段完成特定任务的代码块,它可以接收输入参数,执行特定操作,并返回结果(或不返回结果)。在C语言中,函数的使用包括以下几个步骤:

  1. 函数声明:告诉编译器函数的名称、参数类型和返回类型
  2. 函数定义:实现函数的具体功能
  3. 函数调用:在程序中使用函数

函数的声明与定义

函数声明

函数声明(也称为函数原型)的基本语法如下:

1
返回类型 函数名(参数类型1 参数名1, 参数类型2 参数名2, ...);

函数定义

函数定义的基本语法如下:

1
2
3
4
返回类型 函数名(参数类型1 参数名1, 参数类型2 参数名2, ...) {
// 函数体:实现具体功能的代码
return 返回值; // 如果返回类型不是void,则需要返回值
}

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 函数原型声明
void print_hello(); // 无参数无返回值的函数
void print_number(int num); // 带参数无返回值的函数
int add(int a, int b); // 带参数带返回值的函数

// 函数定义
void print_hello() {
printf("Hello from function!\n");
}

void print_number(int num) {
printf("你传入的数字是: %d\n", num);
}

int add(int a, int b) {
return a + b;
}

函数的调用

函数调用的基本语法

1
函数名(参数1, 参数2, ...);

如果函数有返回值,可以将其赋值给变量:

1
返回类型 变量名 = 函数名(参数1, 参数2, ...);

示例代码

1
2
3
4
5
6
7
8
9
// 调用无参数无返回值的函数
print_hello();

// 调用带参数无返回值的函数
print_number(123);

// 调用带参数带返回值的函数
int result = add(10, 20);
printf("10 + 20 = %d\n", result);

参数传递方式

C语言中函数参数的传递方式有两种:值传递和指针传递。

值传递

值传递是指将实参的值复制一份传递给形参,函数内部对形参的修改不会影响实参的值。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 值传递示例
void swap_by_value(int x, int y) {
int temp = x;
x = y;
y = temp;
// 这里交换的只是形参的值,实参不会改变
printf("函数内部交换后: x = %d, y = %d\n", x, y);
}

// 调用值传递函数
int x = 10, y = 20;
printf("交换前: x = %d, y = %d\n", x, y);
swap_by_value(x, y);
printf("值传递交换后: x = %d, y = %d\n", x, y); // 实参值不变

指针传递

指针传递是指将实参的地址传递给形参,函数内部通过指针可以修改实参的值。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
// 指针传递示例
void swap_by_reference(int *x, int *y) {
int temp = *x;
*x = *y;
*y = temp;
// 这里通过指针修改了实参的值
}

// 调用指针传递函数
int x = 10, y = 20;
printf("交换前: x = %d, y = %d\n", x, y);
swap_by_reference(&x, &y);
printf("指针传递交换后: x = %d, y = %d\n", x, y); // 实参值被修改

数组作为参数

数组作为参数传递时,实际上传递的是数组首元素的地址,因此函数内部对数组元素的修改会影响原数组。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 数组作为参数示例
void modify_array(int arr[], int size) {
for (int i = 0; i < size; i++) {
arr[i] *= 2; // 修改数组元素
}
}

// 调用修改数组的函数
int numbers[] = {1, 2, 3, 4, 5};
int size = sizeof(numbers) / sizeof(numbers[0]);

printf("修改前的数组: ");
for (int i = 0; i < size; i++) {
printf("%d ", numbers[i]);
}
printf("\n");

modify_array(numbers, size);

printf("修改后的数组: ");
for (int i = 0; i < size; i++) {
printf("%d ", numbers[i]);
}
printf("\n");

递归函数

递归函数是指在函数内部调用自身的函数。递归函数通常用于解决可以分解为相同问题的子问题的情况,如阶乘计算、斐波那契数列等。

递归函数的基本结构

1
2
3
4
5
6
7
返回类型 函数名(参数) {
if (基本情况) {
return 基本情况的值;
} else {
return 递归调用 + 其他操作;
}
}

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
// 计算阶乘的递归函数
int factorial(int n) {
if (n == 0 || n == 1) {
return 1; // 递归终止条件
} else {
return n * factorial(n - 1); // 递归调用
}
}

// 调用递归函数
int n = 5;
printf("%d的阶乘是: %d\n", n, factorial(n));

补充示例:斐波那契数列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 计算斐波那契数列的递归函数
int fibonacci(int n) {
if (n <= 1) {
return n; // 递归终止条件
} else {
return fibonacci(n - 1) + fibonacci(n - 2); // 递归调用
}
}

// 调用递归函数
printf("斐波那契数列的前10项: ");
for (int i = 0; i < 10; i++) {
printf("%d ", fibonacci(i));
}
printf("\n");

递归的优缺点

优点

  • 代码简洁,逻辑清晰
  • 适合解决分治问题

缺点

  • 可能导致栈溢出(递归深度过大)
  • 可能存在重复计算(如斐波那契数列)
  • 效率通常低于迭代方法

静态函数

静态函数是指使用static关键字修饰的函数,它只能在当前文件中被访问,不能被其他文件调用。

示例代码

1
2
3
4
5
6
7
8
9
10
// 静态函数示例
static void static_function() {
static int count = 0; // 静态变量,只会初始化一次
count++;
printf("静态函数被调用了 %d 次\n", count);
}

// 调用静态函数
static_function();
static_function(); // 静态函数的局部变量会保持上次调用的值

静态变量

在函数内部使用static关键字修饰的变量称为静态局部变量,它的特点是:

  • 只会初始化一次
  • 函数调用结束后不会被销毁
  • 下次调用函数时会保留上次的值

内联函数

内联函数是指使用inline关键字修饰的函数,编译器会尝试将内联函数的代码直接嵌入到调用处,而不是通过函数调用的方式执行,从而提高程序的执行效率。

示例代码

1
2
3
4
5
6
7
8
// 内联函数示例
inline int max(int a, int b) {
return (a > b) ? a : b;
}

// 调用内联函数
printf("max(15, 25) = %d\n", max(15, 25));
printf("max(-5, 0) = %d\n", max(-5, 0));

注意事项

  • 内联函数适用于代码简短、调用频繁的函数
  • 内联函数的定义通常放在头文件中
  • 编译器可能会忽略inline关键字的请求(例如对于复杂的函数)

函数的作用域和生命周期

作用域

  • 函数作用域:函数中定义的变量只在函数内部可见
  • 文件作用域:在函数外部定义的变量和函数在整个文件中可见
  • 全局作用域:在所有函数外部定义的变量和非静态函数在整个程序中可见

生命周期

  • 自动变量:函数内部定义的普通变量,生命周期从函数调用开始到函数返回结束
  • 静态变量:使用static修饰的变量,生命周期从程序开始到程序结束
  • 全局变量:在函数外部定义的变量,生命周期从程序开始到程序结束

函数的高级特性

函数指针

函数指针是指向函数的指针变量,它可以存储函数的地址,并通过指针调用函数。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 函数定义
int add(int a, int b) {
return a + b;
}

int subtract(int a, int b) {
return a - b;
}

// 函数指针的使用
int (*operation)(int, int); // 声明函数指针

operation = add; // 指向add函数
printf("10 + 5 = %d\n", operation(10, 5));

operation = subtract; // 指向subtract函数
printf("10 - 5 = %d\n", operation(10, 5));

回调函数

回调函数是指作为参数传递给另一个函数的函数,它通常在特定事件发生时被调用。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 回调函数类型定义
typedef void (*Callback)(int);

// 回调函数实现
void print_result(int result) {
printf("计算结果: %d\n", result);
}

// 接受回调函数的函数
void calculate(int a, int b, Callback callback) {
int result = a + b;
callback(result); // 调用回调函数
}

// 使用回调函数
calculate(10, 20, print_result);

可变参数函数

可变参数函数是指参数数量可变的函数,例如printf函数。在C语言中,可以使用<stdarg.h>头文件中的工具来实现可变参数函数。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdarg.h>

// 计算可变参数的和
int sum(int count, ...) {
va_list args; // 定义参数列表
int total = 0;

va_start(args, count); // 初始化参数列表

for (int i = 0; i < count; i++) {
total += va_arg(args, int); // 获取下一个参数
}

va_end(args); // 清理参数列表

return total;
}

// 调用可变参数函数
printf("1 + 2 + 3 = %d\n", sum(3, 1, 2, 3));
printf("4 + 5 + 6 + 7 = %d\n", sum(4, 4, 5, 6, 7));

函数重载模拟

C语言本身不支持函数重载(即同名函数但参数列表不同),但可以通过宏和函数指针来模拟函数重载的效果。

示例代码

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
// 使用宏模拟函数重载
#include <stdio.h>

// 不同类型的函数实现
void print_int(int value) {
printf("整数: %d\n", value);
}

void print_float(float value) {
printf("浮点数: %f\n", value);
}

void print_string(const char* value) {
printf("字符串: %s\n", value);
}

// 使用宏模拟重载
#define print(value) _Generic((value), \
int: print_int, \
float: print_float, \
char*: print_string \
)(value)

// 使用模拟的重载函数
print(10); // 调用print_int
print(3.14f); // 调用print_float
print("Hello"); // 调用print_string

综合示例

示例1:计算器函数

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
// 计算器函数
float calculate(char operator, float a, float b) {
switch (operator) {
case '+':
return a + b;
case '-':
return a - b;
case '*':
return a * b;
case '/':
if (b != 0) {
return a / b;
} else {
printf("错误:除数不能为零\n");
return 0;
}
default:
printf("错误:无效的运算符\n");
return 0;
}
}

// 使用计算器函数
printf("5 + 3 = %.2f\n", calculate('+', 5, 3));
printf("5 - 3 = %.2f\n", calculate('-', 5, 3));
printf("5 * 3 = %.2f\n", calculate('*', 5, 3));
printf("5 / 3 = %.2f\n", calculate('/', 5, 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
29
30
31
// 冒泡排序函数
void bubble_sort(int arr[], int size) {
for (int i = 0; i < size - 1; i++) {
for (int j = 0; j < size - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
// 交换元素
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}

// 使用排序函数
int numbers[] = {5, 2, 9, 1, 5, 6};
int size = sizeof(numbers) / sizeof(numbers[0]);

printf("排序前: ");
for (int i = 0; i < size; i++) {
printf("%d ", numbers[i]);
}
printf("\n");

bubble_sort(numbers, size);

printf("排序后: ");
for (int i = 0; i < size; i++) {
printf("%d ", numbers[i]);
}
printf("\n");

示例3:二分查找函数

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
// 二分查找函数(要求数组已排序)
int binary_search(int arr[], int size, int target) {
int left = 0;
int right = size - 1;

while (left <= right) {
int mid = left + (right - left) / 2;

if (arr[mid] == target) {
return mid; // 找到目标元素,返回索引
} else if (arr[mid] < target) {
left = mid + 1; // 目标在右半部分
} else {
right = mid - 1; // 目标在左半部分
}
}

return -1; // 未找到目标元素
}

// 使用二分查找函数
int sorted_numbers[] = {1, 2, 5, 5, 6, 9};
int size = sizeof(sorted_numbers) / sizeof(sorted_numbers[0]);
int target = 5;

int index = binary_search(sorted_numbers, size, target);
if (index != -1) {
printf("元素 %d 在数组中的索引位置: %d\n", target, index);
} else {
printf("元素 %d 不在数组中\n", target);
}

函数设计最佳实践

  1. 函数应该有单一职责:每个函数应该只做一件事情,并且做好它
  2. 函数名应该清晰表达函数的功能:使用有意义的函数名,避免使用模糊的名称
  3. 函数参数应该合理:参数数量不宜过多,通常不超过5个
  4. 函数应该有适当的注释:使用注释说明函数的功能、参数和返回值
  5. 避免函数过长:通常函数长度不应超过50-100行
  6. 合理使用返回值:函数应该返回有意义的值,便于调用者判断函数执行结果
  7. 避免全局变量:尽量使用参数传递和返回值,而不是依赖全局变量
  8. 注意参数的有效性检查:对传入的参数进行合理性检查,避免程序崩溃
  9. 合理使用递归:对于适合递归的问题使用递归,对于不适合的问题使用迭代
  10. 考虑函数的可测试性:设计函数时考虑如何方便地进行单元测试

总结

C语言中的函数是组织代码的基本单位,通过本文档的学习,我们了解了:

  1. 函数的基本概念:函数的声明、定义和调用
  2. 参数传递方式:值传递和指针传递的区别
  3. 递归函数:递归的基本原理和应用场景
  4. 静态函数:静态函数的特点和使用方法
  5. 内联函数:内联函数的作用和使用场景
  6. 函数的高级特性:函数指针、回调函数和可变参数函数
  7. 函数重载模拟:使用宏模拟函数重载的效果
  8. 函数设计最佳实践:如何设计好的函数

合理使用函数可以使代码更加模块化、可读性更高、更容易维护和扩展。在实际编程中,我们应该根据具体需求选择合适的函数设计方案,以达到最佳的代码质量和执行效率。

functions.c

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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
#include <stdio.h>
#include <math.h>

/**
* @brief 函数示例程序
* @details 这个程序演示了C语言中函数的各种用法,包括函数定义、
* 函数调用、参数传递、返回值、递归函数等。
* @return 0 表示程序成功执行
*/

// =========================
// 函数原型声明
// =========================
void print_hello(); // 无参数无返回值的函数
void print_number(int num); // 带参数无返回值的函数
int add(int a, int b); // 带参数带返回值的函数
int factorial(int n); // 递归函数
void swap_by_value(int x, int y); // 值传递示例
void swap_by_reference(int *x, int *y); // 指针传递示例
static void static_function(); // 静态函数
int max(int a, int b); // 普通函数

/**
* @brief 主函数
* @return 0 表示程序成功执行
*/
int main() {
// =========================
// 调用无参数无返回值的函数
// =========================
printf("=== 调用无参数无返回值的函数 ===\n");
print_hello();

printf("\n");

// =========================
// 调用带参数无返回值的函数
// =========================
printf("=== 调用带参数无返回值的函数 ===\n");
print_number(123);

printf("\n");

// =========================
// 调用带参数带返回值的函数
// =========================
printf("=== 调用带参数带返回值的函数 ===\n");
int result = add(10, 20);
printf("10 + 20 = %d\n", result);

printf("\n");

// =========================
// 调用递归函数
// =========================
printf("=== 调用递归函数 ===\n");
int n = 5;
printf("%d的阶乘是: %d\n", n, factorial(n));

printf("\n");

// =========================
// 值传递与指针传递比较
// =========================
printf("=== 值传递与指针传递比较 ===\n");

int x = 10, y = 20;
printf("交换前: x = %d, y = %d\n", x, y);

// 值传递 - 不会改变实参的值
swap_by_value(x, y);
printf("值传递交换后: x = %d, y = %d\n", x, y);

// 指针传递 - 会改变实参的值
swap_by_reference(&x, &y);
printf("指针传递交换后: x = %d, y = %d\n", x, y);

printf("\n");

// =========================
// 调用内联函数
// =========================
printf("=== 调用内联函数 ===\n");
printf("max(15, 25) = %d\n", max(15, 25));
printf("max(-5, 0) = %d\n", max(-5, 0));

printf("\n");

// =========================
// 调用静态函数
// =========================
printf("=== 调用静态函数 ===\n");
static_function();
static_function(); // 静态函数的局部变量会保持上次调用的值

return 0;
}

// =========================
// 函数定义
// =========================

/**
* @brief 打印"Hello from function!"
* @details 无参数无返回值的函数示例
*/
void print_hello() {
printf("Hello from function!\n");
}

/**
* @brief 打印一个整数
* @details 带参数无返回值的函数示例
* @param num 要打印的整数
*/
void print_number(int num) {
printf("你传入的数字是: %d\n", num);
}

/**
* @brief 计算两个整数的和
* @details 带参数带返回值的函数示例
* @param a 第一个整数
* @param b 第二个整数
* @return 两个整数的和
*/
int add(int a, int b) {
return a + b;
}

/**
* @brief 计算阶乘
* @details 递归函数示例
* @param n 要计算阶乘的整数
* @return n的阶乘
*/
int factorial(int n) {
if (n == 0 || n == 1) {
return 1; // 递归终止条件
} else {
return n * factorial(n - 1); // 递归调用
}
}

/**
* @brief 交换两个整数的值(值传递)
* @details 演示值传递的特点:不会改变实参的值
* @param x 第一个整数
* @param y 第二个整数
*/
void swap_by_value(int x, int y) {
int temp = x;
x = y;
y = temp;
// 这里交换的只是形参的值,实参不会改变
printf("函数内部交换后: x = %d, y = %d\n", x, y);
}

/**
* @brief 交换两个整数的值(指针传递)
* @details 演示指针传递的特点:会改变实参的值
* @param x 指向第一个整数的指针
* @param y 指向第二个整数的指针
*/
void swap_by_reference(int *x, int *y) {
int temp = *x;
*x = *y;
*y = temp;
// 这里通过指针修改了实参的值
}

/**
* @brief 静态函数示例
* @details 静态函数只能在当前文件中访问
* 静态函数内部的静态变量会保持上次调用的值
*/
static void static_function() {
static int count = 0; // 静态变量,只会初始化一次
count++;
printf("静态函数被调用了 %d 次\n", count);
}

/**
* @brief 求两个整数的最大值
* @details 内联函数示例,适合简单的函数,编译器会将其展开
* @param a 第一个整数
* @param b 第二个整数
* @return 两个整数中的最大值
*/
int max(int a, int b) {
return (a > b) ? a : b;
}

C语言控制流结构

概述

本文档详细介绍C语言中的各种控制流结构,包括条件语句、循环语句以及控制循环的特殊语句。控制流结构是程序设计的核心部分,它们允许程序根据不同的条件执行不同的代码块,或者重复执行某些代码块。

条件语句

if-else语句

if-else语句是C语言中最基本的条件语句,用于根据条件的真假执行不同的代码块。

基本语法

1
2
3
4
5
6
7
if (条件表达式) {
// 条件为真时执行的代码
} else if (另一个条件表达式) {
// 第一个条件为假,第二个条件为真时执行的代码
} else {
// 所有条件都为假时执行的代码
}

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 基本if-else示例
int number = 10;

if (number > 0) {
printf("%d 是正数\n", number);
} else if (number < 0) {
printf("%d 是负数\n", number);
} else {
printf("%d 是零\n", number);
}

// 嵌套if-else示例
int age = 25;
if (age >= 18) {
printf("%d岁,已成年", age);
if (age >= 60) {
printf(",且已退休\n");
} else {
printf(",但未退休\n");
}
} else {
printf("%d岁,未成年\n", age);
}

补充示例:判断闰年

1
2
3
4
5
6
7
8
// 判断闰年的示例
int year = 2024;

if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) {
printf("%d年是闰年\n", year);
} else {
printf("%d年不是闰年\n", year);
}

switch语句

switch语句用于根据变量的值选择执行不同的代码块,适用于多分支条件判断。

基本语法

1
2
3
4
5
6
7
8
9
10
11
12
switch (表达式) {
case1:
// 表达式等于值1时执行的代码
break;
case2:
// 表达式等于值2时执行的代码
break;
// 更多case语句...
default:
// 表达式不等于任何case值时执行的代码
break;
}

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// switch语句示例
char grade = 'B';
switch (grade) {
case 'A':
printf("优秀\n");
break;
case 'B':
printf("良好\n");
break;
case 'C':
printf("中等\n");
break;
case 'D':
printf("及格\n");
break;
case 'F':
printf("不及格\n");
break;
default:
printf("无效等级\n");
break;
}

补充示例:使用switch判断月份天数

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
// 使用switch判断月份天数
int month = 2;
int year = 2024;
int days;

switch (month) {
case 1: case 3: case 5: case 7: case 8: case 10: case 12:
days = 31;
break;
case 4: case 6: case 9: case 11:
days = 30;
break;
case 2:
// 判断是否为闰年
if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) {
days = 29;
} else {
days = 28;
}
break;
default:
days = 0;
printf("无效的月份\n");
break;
}

if (days > 0) {
printf("%d年%d月有%d天\n", year, month, days);
}

循环语句

for循环

for循环是一种常用的循环语句,特别适合已知循环次数的情况。

基本语法

1
2
3
for (初始化表达式; 条件表达式; 更新表达式) {
// 循环体代码
}

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 基本for循环
printf("1到5的数字:");
for (int i = 1; i <= 5; i++) {
printf("%d ", i);
}
printf("\n");

// 倒序循环
printf("5到1的数字:");
for (int i = 5; i >= 1; i--) {
printf("%d ", i);
}
printf("\n");

// 步长为2的循环
printf("1到10的奇数:");
for (int i = 1; i <= 10; i += 2) {
printf("%d ", i);
}
printf("\n");

补充示例:计算阶乘

1
2
3
4
5
6
7
8
9
// 计算阶乘
int n = 5;
long long factorial = 1;

for (int i = 1; i <= n; i++) {
factorial *= i;
}

printf("%d的阶乘是:%lld\n", n, factorial);

while循环

while循环在条件为真时重复执行循环体,适合不确定循环次数的情况。

基本语法

1
2
3
4
while (条件表达式) {
// 循环体代码
// 通常需要在循环体中修改条件表达式的值,以避免无限循环
}

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 基本while循环
int count = 1;
printf("1到5的数字:");
while (count <= 5) {
printf("%d ", count);
count++;
}
printf("\n");

// 计算1到100的和
int sum = 0;
int num = 1;
while (num <= 100) {
sum += num;
num++;
}
printf("1到100的和:%d\n", sum);

补充示例:猜数字游戏

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
// 简单的猜数字游戏
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main() {
int secret_number, guess, attempts = 0;

// 生成1-100之间的随机数
srand(time(0));
secret_number = rand() % 100 + 1;

printf("猜数字游戏:请猜一个1-100之间的数字\n");

while (1) {
printf("请输入你的猜测:");
scanf("%d", &guess);
attempts++;

if (guess < secret_number) {
printf("太小了!\n");
} else if (guess > secret_number) {
printf("太大了!\n");
} else {
printf("恭喜你猜对了!你用了%d次尝试\n", attempts);
break;
}
}

return 0;
}

do-while循环

do-while循环与while循环类似,但它会先执行一次循环体,然后再检查条件是否为真。

基本语法

1
2
3
do {
// 循环体代码
} while (条件表达式);

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 基本do-while循环
int i = 1;
printf("1到5的数字:");
do {
printf("%d ", i);
i++;
} while (i <= 5);
printf("\n");

// do-while 保证至少执行一次
int j = 10;
printf("即使条件一开始就不满足,do-while也会执行一次:");
do {
printf("%d ", j);
j++;
} while (j <= 5);
printf("\n");

补充示例:菜单选择

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
// 简单的菜单选择示例
int choice;

do {
printf("\n菜单:\n");
printf("1. 选项一\n");
printf("2. 选项二\n");
printf("3. 选项三\n");
printf("4. 退出\n");
printf("请选择(1-4):");
scanf("%d", &choice);

switch (choice) {
case 1:
printf("你选择了选项一\n");
break;
case 2:
printf("你选择了选项二\n");
break;
case 3:
printf("你选择了选项三\n");
break;
case 4:
printf("退出程序\n");
break;
default:
printf("无效的选择,请重新输入\n");
break;
}
} while (choice != 4);

嵌套循环

嵌套循环是指在一个循环内部包含另一个循环,常用于处理二维数组、矩阵等二维结构。

示例代码

1
2
3
4
5
6
7
8
// 打印九九乘法表(前5行)
printf("九九乘法表(前5行):\n");
for (int row = 1; row <= 5; row++) {
for (int col = 1; col <= row; col++) {
printf("%d*%d=%-2d ", col, row, col * row);
}
printf("\n");
}

补充示例:打印金字塔

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 打印金字塔
int height = 5;

for (int i = 1; i <= height; i++) {
// 打印空格
for (int j = 1; j <= height - i; j++) {
printf(" ");
}
// 打印星号
for (int k = 1; k <= 2 * i - 1; k++) {
printf("*");
}
printf("\n");
}

控制循环的特殊语句

break语句

break语句用于跳出当前循环或switch语句,执行循环或switch语句之后的代码。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 使用break跳出循环
printf("使用break,当i=3时跳出循环:");
for (int i = 1; i <= 5; i++) {
if (i == 3) {
break; // 跳出循环
}
printf("%d ", i);
}
printf("\n");

// 在while循环中使用break
printf("在while循环中使用break,当count=4时跳出:");
int count_break = 1;
while (count_break <= 5) {
if (count_break == 4) {
break;
}
printf("%d ", count_break);
count_break++;
}
printf("\n");

continue语句

continue语句用于跳过当前循环的剩余部分,直接开始下一次循环迭代。

示例代码

1
2
3
4
5
6
7
8
9
// 使用continue跳过当前循环
printf("使用continue,跳过i=3:");
for (int i = 1; i <= 5; i++) {
if (i == 3) {
continue; // 跳过当前迭代
}
printf("%d ", i);
}
printf("\n");

补充示例:使用continue跳过偶数

1
2
3
4
5
6
7
8
9
// 使用continue跳过偶数
printf("1到10之间的奇数:");
for (int i = 1; i <= 10; i++) {
if (i % 2 == 0) {
continue; // 跳过偶数
}
printf("%d ", i);
}
printf("\n");

goto语句

goto语句用于无条件跳转到程序中的指定标签位置,应谨慎使用,以免破坏程序的结构。

基本语法

1
2
3
4
goto 标签;
// ...
标签:
// 跳转到这里执行

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 使用goto语句跳出多层循环
int i, j;

for (i = 1; i <= 3; i++) {
for (j = 1; j <= 3; j++) {
printf("i=%d, j=%d\n", i, j);
if (i == 2 && j == 2) {
goto end_loop; // 跳转到标签end_loop
}
}
}

end_loop:
printf("跳出循环\n");

综合示例

示例1:判断素数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 判断素数
int num = 17;
int is_prime = 1; // 假设是素数

if (num <= 1) {
is_prime = 0;
} else {
for (int i = 2; i * i <= num; i++) {
if (num % i == 0) {
is_prime = 0;
break;
}
}
}

if (is_prime) {
printf("%d是素数\n", num);
} else {
printf("%d不是素数\n", num);
}

示例2:斐波那契数列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 打印斐波那契数列的前10项
int n = 10;
int a = 0, b = 1, c;

printf("斐波那契数列的前%d项:\n", n);
printf("%d %d ", a, b);

for (int i = 2; i < n; i++) {
c = a + b;
printf("%d ", c);
a = b;
b = c;
}
printf("\n");

总结

C语言提供了丰富的控制流结构,包括:

  1. 条件语句

    • if-else语句:用于基本的条件判断
    • switch语句:用于多分支条件判断
  2. 循环语句

    • for循环:适合已知循环次数的情况
    • while循环:适合不确定循环次数的情况
    • do-while循环:至少执行一次循环体
    • 嵌套循环:处理二维结构
  3. 控制循环的特殊语句

    • break:跳出当前循环或switch语句
    • continue:跳过当前循环的剩余部分
    • goto:无条件跳转到指定标签(应谨慎使用)

掌握这些控制流结构是编写高效、灵活的C程序的基础。在实际编程中,应根据具体情况选择合适的控制流结构,以提高代码的可读性和执行效率。

control_flow.c

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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
#include <stdio.h>

/**
* @brief 控制流示例程序
* @details 这个程序演示了C语言中各种控制流结构的使用,
* 包括if-else语句、switch语句、for循环、while循环
* 和do-while循环,以及break和continue语句的使用。
* @return 0 表示程序成功执行
*/
int main() {
// =========================
// if-else 语句
// =========================
printf("=== if-else 语句 ===\n");

int number = 10;

if (number > 0) {
printf("%d 是正数\n", number);
} else if (number < 0) {
printf("%d 是负数\n", number);
} else {
printf("%d 是零\n", number);
}

// 嵌套if-else
int age = 25;
if (age >= 18) {
printf("%d岁,已成年", age);
if (age >= 60) {
printf(",且已退休\n");
} else {
printf(",但未退休\n");
}
} else {
printf("%d岁,未成年\n", age);
}

printf("\n");

// =========================
// switch 语句
// =========================
printf("=== switch 语句 ===\n");

char grade = 'B';
switch (grade) {
case 'A':
printf("优秀\n");
break;
case 'B':
printf("良好\n");
break;
case 'C':
printf("中等\n");
break;
case 'D':
printf("及格\n");
break;
case 'F':
printf("不及格\n");
break;
default:
printf("无效等级\n");
break;
}

printf("\n");

// =========================
// for 循环
// =========================
printf("=== for 循环 ===\n");

// 基本for循环
printf("1到5的数字:");
for (int i = 1; i <= 5; i++) {
printf("%d ", i);
}
printf("\n");

// 倒序循环
printf("5到1的数字:");
for (int i = 5; i >= 1; i--) {
printf("%d ", i);
}
printf("\n");

// 步长为2的循环
printf("1到10的奇数:");
for (int i = 1; i <= 10; i += 2) {
printf("%d ", i);
}
printf("\n\n");

// =========================
// while 循环
// =========================
printf("=== while 循环 ===\n");

int count = 1;
printf("1到5的数字:");
while (count <= 5) {
printf("%d ", count);
count++;
}
printf("\n");

// 计算1到100的和
int sum = 0;
int num = 1;
while (num <= 100) {
sum += num;
num++;
}
printf("1到100的和:%d\n\n", sum);

// =========================
// do-while 循环
// =========================
printf("=== do-while 循环 ===\n");

int i = 1;
printf("1到5的数字:");
do {
printf("%d ", i);
i++;
} while (i <= 5);
printf("\n");

// do-while 保证至少执行一次
int j = 10;
printf("即使条件一开始就不满足,do-while也会执行一次:");
do {
printf("%d ", j);
j++;
} while (j <= 5);
printf("\n\n");

// =========================
// 嵌套循环
// =========================
printf("=== 嵌套循环 ===\n");

// 打印九九乘法表(前5行)
printf("九九乘法表(前5行):\n");
for (int row = 1; row <= 5; row++) {
for (int col = 1; col <= row; col++) {
printf("%d*%d=%-2d ", col, row, col * row);
}
printf("\n");
}

printf("\n");

// =========================
// break 和 continue 语句
// =========================
printf("=== break 和 continue 语句 ===\n");

// 使用break跳出循环
printf("使用break,当i=3时跳出循环:");
for (int i = 1; i <= 5; i++) {
if (i == 3) {
break; // 跳出循环
}
printf("%d ", i);
}
printf("\n");

// 使用continue跳过当前循环
printf("使用continue,跳过i=3:");
for (int i = 1; i <= 5; i++) {
if (i == 3) {
continue; // 跳过当前迭代
}
printf("%d ", i);
}
printf("\n");

// 在while循环中使用break
printf("在while循环中使用break,当count=4时跳出:");
int count_break = 1;
while (count_break <= 5) {
if (count_break == 4) {
break;
}
printf("%d ", count_break);
count_break++;
}
printf("\n");

return 0;
}

C语言变量与数据类型

概述

本文档详细介绍C语言中的各种基本数据类型,包括整型、浮点型、字符型和布尔型,以及它们的声明、初始化、大小范围和基本操作。

整数类型

基本整数类型

类型 大小 范围 说明
int 通常4字节 -2,147,483,648 到 2,147,483,647 最常用的整数类型
short 通常2字节 -32,768 到 32,767 短整型,占用空间较小
long 通常4字节或8字节 取决于编译器和系统 长整型,可表示更大的数值
long long 通常8字节 -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 长长整型,可表示非常大的数值

无符号整数类型

类型 大小 范围 说明
unsigned int 通常4字节 0 到 4,294,967,295 无符号整型,只能表示非负数
unsigned short 通常2字节 0 到 65,535 无符号短整型
unsigned long 通常4字节或8字节 取决于编译器和系统 无符号长整型
unsigned long long 通常8字节 0 到 18,446,744,073,709,551,615 无符号长长整型

整数类型示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 基本整数类型
int age = 25; // 整型,通常4字节
short short_num = 32767; // 短整型,通常2字节
long long_num = 1234567890; // 长整型,通常4字节或8字节
long long big_num = 123456789012345LL; // 长长整型,通常8字节

// 无符号整数类型
unsigned int positive_num = 4294967295U; // 无符号整型
unsigned short ushort_num = 65535U; // 无符号短整型

// 输出整数类型的值
printf("int age = %d\n", age);
printf("short short_num = %d\n", short_num);
printf("long long_num = %ld\n", long_num);
printf("long long big_num = %lld\n", big_num);
printf("unsigned int positive_num = %u\n", positive_num);
printf("unsigned short ushort_num = %u\n", ushort_num);

整数类型的大小和范围

1
2
3
4
5
6
7
8
#include <limits.h>

// 显示整数类型的大小和范围
printf("int 大小: %zu 字节, 范围: %d 到 %d\n", sizeof(int), INT_MIN, INT_MAX);
printf("short 大小: %zu 字节, 范围: %d 到 %d\n", sizeof(short), SHRT_MIN, SHRT_MAX);
printf("long 大小: %zu 字节\n", sizeof(long));
printf("long long 大小: %zu 字节\n", sizeof(long long));
printf("unsigned int 大小: %zu 字节, 最大值: %u\n", sizeof(unsigned int), UINT_MAX);

浮点类型

浮点类型概述

类型 大小 精度 说明
float 4字节 约6-7位有效数字 单精度浮点数
double 8字节 约15-17位有效数字 双精度浮点数(默认)
long double 8字节或16字节 约18-19位有效数字 长双精度浮点数

浮点类型示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 浮点类型
float pi_float = 3.14159f; // 单精度浮点数,4字节
double pi_double = 3.141592653589793; // 双精度浮点数,8字节
long double pi_long_double = 3.141592653589793238L; // 长双精度浮点数

// 输出浮点类型的值
printf("float pi_float = %.5f\n", pi_float);
printf("double pi_double = %.15lf\n", pi_double);
printf("long double pi_long_double = %.20Lf\n", pi_long_double);

// 显示浮点类型的大小
printf("float 大小: %zu 字节\n", sizeof(float));
printf("double 大小: %zu 字节\n", sizeof(double));
printf("long double 大小: %zu 字节\n", sizeof(long double));

浮点类型的特殊值

1
2
3
4
5
6
7
8
#include <math.h>

// 浮点类型的特殊值
double infinity = INFINITY; // 无穷大
double not_a_number = NAN; // 非数字

printf("无穷大: %f\n", infinity);
printf("非数字: %f\n", not_a_number);

字符类型

字符类型概述

类型 大小 范围 说明
char 1字节 通常-128到127或0到255 字符类型,可表示ASCII字符
unsigned char 1字节 0到255 无符号字符类型
signed char 1字节 -128到127 有符号字符类型

字符类型示例

1
2
3
4
5
6
7
8
9
// 字符类型
char letter = 'A'; // 字符类型,1字节
char symbol = '$';
char newline = '\n';

// 输出字符类型的值
printf("char letter = %c (ASCII值: %d)\n", letter, letter);
printf("char symbol = %c\n", symbol);
printf("换行符演示: 第一行%c第二行\n", newline);

转义序列

转义序列 说明
\n 换行符
\t 制表符
\r 回车符
\' 单引号
\" 双引号
\\ 反斜杠
\0 空字符

布尔类型

布尔类型概述

类型 大小 说明
bool 通常1字节 true (1) 或 false (0) 布尔类型,需要包含头文件<stdbool.h>

布尔类型示例

1
2
3
4
5
6
7
8
9
#include <stdbool.h>

// 布尔类型
bool is_active = true; // 布尔类型,需要#include <stdbool.h>
bool is_empty = false;

// 布尔类型在printf中通常用%d输出,true为1,false为0
printf("bool is_active = %d\n", is_active);
printf("bool is_empty = %d\n", is_empty);

变量的基本操作

算术运算符

运算符 描述 示例 结果
+ 加法 a + b a和b的和
- 减法 a - b a和b的差
* 乘法 a * b a和b的积
/ 除法 a / b a除以b的商
% 取余 a % b a除以b的余数

算术运算示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 变量的基本操作
int a = 10;
int b = 5;
int sum = a + b;
int difference = a - b;
int product = a * b;
int quotient = a / b;
int remainder = a % b;

printf("a = %d, b = %d\n", a, b);
printf("a + b = %d\n", sum);
printf("a - b = %d\n", difference);
printf("a * b = %d\n", product);
printf("a / b = %d\n", quotient);
printf("a %% b = %d\n", remainder);

自增和自减运算符

运算符 描述 示例 结果
++ 自增 a++++a a的值加1
-- 自减 a----a a的值减1

自增自减示例

1
2
3
4
5
6
7
8
9
10
11
// 自增和自减操作
int a = 10;
int b = 5;

a++; // 后置自增,先使用a的值,再自增
b--; // 后置自减,先使用b的值,再自减
printf("a++ = %d, b-- = %d\n", a, b);

++a; // 前置自增,先自增,再使用a的值
--b; // 前置自减,先自减,再使用b的值
printf("++a = %d, --b = %d\n", a, b);

类型转换

隐式类型转换

当不同类型的操作数进行运算时,C语言会自动进行类型转换,通常是将较小的类型转换为较大的类型,以避免数据丢失。

1
2
3
4
5
6
7
8
// 隐式类型转换示例
int int_num = 100;
float float_num = 3.14;
double result = int_num + float_num; // int转换为float,结果为double

printf("int_num = %d\n", int_num);
printf("float_num = %f\n", float_num);
printf("result = %lf\n", result);

显式类型转换

可以使用强制类型转换运算符来显式地转换类型。

1
2
3
4
5
6
7
8
9
10
11
// 显式类型转换示例
double pi = 3.1415926535;
int int_pi = (int)pi; // 强制转换为int,小数部分被截断

printf("pi = %lf\n", pi);
printf("int_pi = %d\n", int_pi);

// 另一个示例:计算圆的面积
int radius = 5;
double area = 3.14159 * radius * radius;
printf("半径为%d的圆的面积: %lf\n", radius, area);

常量

字面常量

类型 示例 说明
整型常量 100, 0x64 (十六进制), 0144 (八进制) 整数字面量
浮点常量 3.14, 6.02e23 (科学记数法) 浮点数字面量
字符常量 'A', '\n' 字符字面量
字符串常量 "Hello, World!" 字符串字面量

const修饰符

使用const关键字可以定义常量,常量的值在程序执行过程中不能被修改。

1
2
3
4
5
6
7
8
// const修饰符示例
const int MAX_AGE = 100;
const float PI = 3.14159;
const char NEWLINE = '\n';

printf("MAX_AGE = %d\n", MAX_AGE);
printf("PI = %f\n", PI);
printf("使用换行符: 第一行%c第二行\n", NEWLINE);

#define预处理器指令

使用#define可以定义宏常量,在编译前会被替换为对应的值。

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

// #define预处理器指令示例
#define MAX_AGE 100
#define PI 3.14159
#define NEWLINE '\n'

int main() {
printf("MAX_AGE = %d\n", MAX_AGE);
printf("PI = %f\n", PI);
printf("使用换行符: 第一行%c第二行\n", NEWLINE);

return 0;
}

变量命名规则

  1. 变量名只能包含字母、数字和下划线
  2. 变量名不能以数字开头
  3. 变量名区分大小写
  4. 变量名不能是C语言的关键字
  5. 变量名应具有描述性,便于理解代码

变量命名示例

1
2
3
4
5
6
7
8
9
10
11
// 好的变量命名
int student_age;
float average_score;
bool is_student_active;

// 不好的变量命名
int a; // 过于简单,不具描述性
float s; // 过于简单,不具描述性
bool b; // 过于简单,不具描述性
int 123abc; // 不能以数字开头
int for; // 不能使用关键字

总结

C语言提供了丰富的数据类型,包括:

  1. 整数类型int, short, long, long long及其无符号版本
  2. 浮点类型float, double, long double
  3. 字符类型char, unsigned char, signed char
  4. 布尔类型bool (需要包含<stdbool.h>)

选择合适的数据类型对于编写高效、可靠的C程序非常重要。应根据实际需求选择能够满足范围和精度要求的最小数据类型,以节省内存空间并提高程序性能。

同时,良好的变量命名和常量使用习惯可以提高代码的可读性和可维护性。

variables.c

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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
#include <stdio.h>
#include <stdbool.h>
#include <limits.h>
#include <float.h>

/**
* @brief 变量和数据类型示例
* @details 这个程序演示了C语言中各种基本数据类型的使用,
* 包括整型、浮点型、字符型和布尔型,并展示了它们的
* 声明、初始化和基本操作。
* @return 0 表示程序成功执行
*/
int main() {
// =========================
// 整数类型
// =========================
printf("=== 整数类型 ===\n");

// 基本整数类型
int age = 25; // 整型,通常4字节
short short_num = 32767; // 短整型,通常2字节
long long_num = 1234567890; // 长整型,通常4字节或8字节
long long big_num = 123456789012345LL; // 长长整型,通常8字节

// 无符号整数类型
unsigned int positive_num = 4294967295U; // 无符号整型
unsigned short ushort_num = 65535U; // 无符号短整型

printf("int age = %d\n", age);
printf("short short_num = %d\n", short_num);
printf("long long_num = %ld\n", long_num);
printf("long long big_num = %lld\n", big_num);
printf("unsigned int positive_num = %u\n", positive_num);
printf("unsigned short ushort_num = %u\n\n", ushort_num);

// 显示整数类型的大小和范围
printf("int 大小: %zu 字节, 范围: %d 到 %d\n", sizeof(int), INT_MIN, INT_MAX);
printf("short 大小: %zu 字节, 范围: %d 到 %d\n", sizeof(short), SHRT_MIN, SHRT_MAX);
printf("long 大小: %zu 字节\n", sizeof(long));
printf("long long 大小: %zu 字节\n\n", sizeof(long long));

// =========================
// 浮点类型
// =========================
printf("=== 浮点类型 ===\n");

float pi_float = 3.14159f; // 单精度浮点数,4字节
double pi_double = 3.141592653589793; // 双精度浮点数,8字节
long double pi_long_double = 3.141592653589793238L; // 长双精度浮点数

printf("float pi_float = %.5f\n", pi_float);
printf("double pi_double = %.15lf\n", pi_double);
printf("long double pi_long_double = %.20Lf\n\n", pi_long_double);

// 显示浮点类型的大小
printf("float 大小: %zu 字节\n", sizeof(float));
printf("double 大小: %zu 字节\n", sizeof(double));
printf("long double 大小: %zu 字节\n\n", sizeof(long double));

// =========================
// 字符类型
// =========================
printf("=== 字符类型 ===\n");

char letter = 'A'; // 字符类型,1字节
char symbol = '$';
char newline = '\n';

printf("char letter = %c (ASCII值: %d)\n", letter, letter);
printf("char symbol = %c\n", symbol);
printf("换行符演示: 第一行%c第二行\n\n", newline);

// =========================
// 布尔类型
// =========================
printf("=== 布尔类型 ===\n");

bool is_active = true; // 布尔类型,需要#include <stdbool.h>
bool is_empty = false;

// 布尔类型在printf中通常用%d输出,true为1,false为0
printf("bool is_active = %d\n", is_active);
printf("bool is_empty = %d\n\n", is_empty);

// =========================
// 变量的基本操作
// =========================
printf("=== 变量操作 ===\n");

int a = 10;
int b = 5;
int sum = a + b;
int difference = a - b;
int product = a * b;
int quotient = a / b;
int remainder = a % b;

printf("a = %d, b = %d\n", a, b);
printf("a + b = %d\n", sum);
printf("a - b = %d\n", difference);
printf("a * b = %d\n", product);
printf("a / b = %d\n", quotient);
printf("a %% b = %d\n", remainder);

// 自增和自减操作
a++;
b--;
printf("a++ = %d, b-- = %d\n", a, b);

return 0;
}

C语言Hello World程序

概述

本文档详细介绍C语言中的Hello World程序,这是学习任何编程语言时的第一个程序,也是了解C语言基本结构的起点。通过分析Hello World程序,我们可以掌握C语言的基本语法、程序结构和标准输出函数的使用方法。

Hello World程序分析

基本代码

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

/**
* @brief 基础的Hello World程序
* @details 这是一个最简单的C程序,用于演示C语言的基本结构
* 和标准输出函数的使用。
* @return 0 表示程序成功执行
*/
int main() {
// 使用printf函数输出字符串到控制台
printf("Hello, World!\n");

// 返回0表示程序正常结束
return 0;
}

代码结构分析

  1. 预处理指令

    1
    #include <stdio.h>

    这是一个预处理指令,用于包含标准输入输出库(stdio.h),该库中定义了printf等标准输入输出函数。

  2. 注释

    1
    2
    3
    4
    5
    6
    /**
    * @brief 基础的Hello World程序
    * @details 这是一个最简单的C程序,用于演示C语言的基本结构
    * 和标准输出函数的使用。
    * @return 0 表示程序成功执行
    */

    这是一个文档注释,用于说明程序的功能、细节和返回值。在C语言中,注释分为单行注释(//)和多行注释(/* */)。

  3. 主函数

    1
    2
    3
    4
    int main() {
    // 函数体
    return 0;
    }

    main函数是C程序的入口点,每个C程序都必须有且只有一个main函数。int表示函数返回值类型为整型,()表示函数不接受参数。

  4. 函数体

    1
    printf("Hello, World!\n");

    这是一条语句,使用printf函数输出字符串”Hello, World!”到控制台。\n是换行符,用于在输出后换行。

  5. 返回语句

    1
    return 0;

    表示函数执行完毕,返回值为0,通常表示程序正常结束。在main函数中,返回值0表示程序成功执行,非0值表示程序异常结束。

C程序的基本结构

一个完整的C程序通常包含以下部分:

  1. 预处理指令:以#开头的指令,用于包含头文件、定义宏等。
  2. 全局声明:全局变量、函数原型等的声明。
  3. 主函数main函数,程序的入口点。
  4. 其他函数:用户定义的函数。

示例:带自定义函数的C程序

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

// 函数原型声明
void print_hello();

int main() {
// 调用自定义函数
print_hello();
return 0;
}

// 函数定义
void print_hello() {
printf("Hello, World!\n");
}

标准输出函数printf

printf函数是C语言中最常用的输出函数,用于将格式化的数据输出到标准输出设备(通常是控制台)。

基本语法

1
int printf(const char *format, ...);

格式说明符

printf函数使用格式说明符来指定输出数据的类型和格式,常用的格式说明符包括:

格式说明符 数据类型 示例
%d 整型 printf("%d", 100);
%f 浮点型 printf("%f", 3.14);
%c 字符型 printf("%c", 'A');
%s 字符串 printf("%s", "Hello");
%p 指针 printf("%p", &x);
%% 百分号 printf("%%");

示例:使用不同格式说明符

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

int main() {
int age = 25;
float height = 175.5;
char grade = 'A';
char name[] = "张三";

printf("姓名:%s\n", name);
printf("年龄:%d\n", age);
printf("身高:%.1f\n", height);
printf("成绩:%c\n", grade);

return 0;
}

补充示例代码

示例1:带变量的Hello World

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>

int main() {
// 定义变量
char name[] = "C语言";

// 使用变量输出
printf("Hello, %s!\n", name);
printf("Welcome to the world of %s programming!\n", name);

return 0;
}

示例2:多语言Hello World

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

int main() {
printf("Hello, World! (English)\n");
printf("你好,世界!(Chinese)\n");
printf("こんにちは、世界!(Japanese)\n");
printf("안녕하세요, 세계!(Korean)\n");
printf("Bonjour, le monde! (French)\n");
printf("Hola, mundo! (Spanish)\n");
printf("Hallo, Welt! (German)\n");

return 0;
}

示例3:交互式Hello World

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

int main() {
char name[50];

// 提示用户输入姓名
printf("请输入你的姓名:");
// 读取用户输入
scanf("%s", name);
// 输出个性化问候
printf("Hello, %s!\n", name);

return 0;
}

示例4:带计算的Hello World

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

int main() {
int a = 10;
int b = 20;
int sum = a + b;

printf("Hello, World!\n");
printf("让我们做一个简单的计算:\n");
printf("%d + %d = %d\n", a, b, sum);

return 0;
}

编译和运行C程序

Windows系统

  1. 使用MinGW编译器

    1
    2
    gcc hello_world.c -o hello_world.exe
    hello_world.exe
  2. 使用Visual Studio

    • 创建新的C++项目(C++项目可以编译C代码)
    • 添加hello_world.c文件
    • 编译并运行项目

Linux系统

  1. 使用GCC编译器
    1
    2
    gcc hello_world.c -o hello_world
    ./hello_world

macOS系统

  1. 使用Clang编译器
    1
    2
    clang hello_world.c -o hello_world
    ./hello_world

常见问题和解决方案

1. 编译错误:undefined reference to printf

原因:没有正确包含stdio.h头文件,或者没有链接C标准库。

解决方案

  • 确保在代码开头添加了#include <stdio.h>
  • 编译时使用正确的命令,如gcc hello_world.c -o hello_world

2. 运行错误:程序没有输出

原因:可能是输出被缓冲区拦截,或者程序没有正确执行到输出语句。

解决方案

  • 确保使用了\n换行符,它会刷新输出缓冲区
  • 或者使用fflush(stdout)显式刷新输出缓冲区

3. 中文输出乱码

原因:编码格式不匹配,通常是因为源文件编码与终端编码不一致。

解决方案

  • 在Windows系统中,使用-fexec-charset=gbk编译选项:
    1
    gcc hello_world.c -o hello_world.exe -fexec-charset=gbk
  • 或者在代码中使用UTF-8编码,并确保终端支持UTF-8

4. 编译错误:expected ‘;’ before ‘}’ token

原因:语句末尾缺少分号。

解决方案

  • 检查代码中每个语句的末尾是否都有分号,特别是printf语句和return语句。

C语言的特点和优势

  1. 简洁高效:C语言语法简洁,生成的机器代码高效,适合系统编程和性能要求高的应用。

  2. 可移植性:C语言编写的程序可以在不同的平台上编译运行,只需少量修改。

  3. 强大的表达能力:C语言提供了丰富的运算符和数据类型,可以表达复杂的逻辑和算法。

  4. 底层访问能力:C语言允许直接访问内存和硬件,适合系统编程和嵌入式开发。

  5. 广泛的应用:C语言被广泛应用于操作系统、编译器、数据库、游戏引擎等领域。

  6. 作为其他语言的基础:许多现代编程语言(如C++、Java、Python等)都借鉴了C语言的语法和设计思想。

学习C语言的建议

  1. 从基础开始:掌握C语言的基本语法、数据类型、运算符等基础知识。

  2. 多写代码:通过编写各种小程序来巩固所学知识,培养编程思维。

  3. 阅读优秀代码:阅读开源项目或经典教材中的代码,学习良好的编程风格和技巧。

  4. 理解内存管理:C语言需要手动管理内存,理解内存分配和释放的原理对编写高质量的C程序至关重要。

  5. 学习调试技巧:掌握使用调试器(如GDB)来查找和修复程序中的错误。

  6. 参与项目:通过参与实际项目来提高自己的编程能力,积累实战经验。

总结

Hello World程序是学习C语言的起点,它虽然简单,但包含了C程序的基本结构和要素。通过分析Hello World程序,我们了解了:

  1. C程序的基本结构:预处理指令、函数定义、语句等。
  2. 标准输出函数printf函数的使用方法和格式说明符。
  3. 编译和运行C程序:在不同操作系统上的编译和运行方法。
  4. 常见问题和解决方案:编译错误、运行错误等问题的解决方法。

掌握这些基础知识后,我们可以开始学习C语言的更多高级特性,如控制流、函数、数组、指针、结构体等,逐步提高自己的编程能力。

C语言是一种强大而灵活的编程语言,它不仅是学习其他编程语言的基础,也是系统编程、嵌入式开发等领域的重要工具。通过不断学习和实践,我们可以熟练掌握C语言,编写出高效、可靠的程序。

hello_world.c

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

/**
* @brief 基础的Hello World程序
* @details 这是一个最简单的C程序,用于演示C语言的基本结构
* 和标准输出函数的使用。
* @return 0 表示程序成功执行
*/
int main() {
// 使用printf函数输出字符串到控制台
printf("Hello, World!\n");

// 返回0表示程序正常结束
return 0;
}

Lua 标准库指南

简介

本指南详细介绍了 Lua 中的标准库,包括 string、math、os、io、table、debug、coroutine 和 package 等库的使用方法。通过本指南,您将学习如何在 Lua 中有效地使用这些标准库,提高代码的效率和可读性。

目录

  1. string 库(字符串操作)
  2. math 库(数学函数)
  3. os 库(操作系统交互)
  4. io 库(文件操作)
  5. table 库(表操作)
  6. debug 库(调试功能)
  7. coroutine 库(协程)
  8. package 库(模块管理)
  9. 实用工具函数
  10. 标准库最佳实践
  11. 常见问题与解决方案

1. string 库(字符串操作)

string 库提供了丰富的字符串操作函数,包括字符串长度计算、大小写转换、截取、查找、替换、格式化等功能。

示例代码

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
local str = "Hello, Lua Programming!"
print("Original string:", str)

-- 1.1 字符串长度
print("Length:", string.len(str))
print("Length (using #):", #str)

-- 1.2 字符串大小写转换
print("Uppercase:", string.upper(str))
print("Lowercase:", string.lower(str))

-- 1.3 字符串截取
print("Substring (1-5):", string.sub(str, 1, 5))
print("Substring (8-end):", string.sub(str, 8))
print("Substring (from end):", string.sub(str, -11))

-- 1.4 字符串查找
local start_pos, end_pos = string.find(str, "Lua")
print("Find 'Lua':", start_pos, end_pos)

-- 1.5 字符串替换
local new_str = string.gsub(str, "Lua", "LUA")
print("Replace 'Lua' with 'LUA':", new_str)

-- 1.6 字符串格式化
local formatted = string.format("%s is %d years old and has %.2f GPA", "Alice", 25, 3.85)
print("Formatted string:", formatted)

-- 1.7 字符串重复
print("Repeat 'Hi ' 3 times:", string.rep("Hi ", 3))

-- 1.8 字符串匹配(正则表达式)
local email = "user@example.com"
local username = string.match(email, "(%a+)@")
local domain = string.match(email, "@(%a+%.%a+)")
print("Email parts - username:", username, "domain:", domain)

-- 1.9 字符串分割(使用gmatch)
local csv = "apple,banana,orange,grape"
local fruits = {}
for fruit in string.gmatch(csv, "[^,]+") do
table.insert(fruits, fruit)
end
print("Split CSV:", table.concat(fruits, " | "))

常用函数表

函数 描述 示例
string.len(s) 计算字符串长度 string.len(“hello”) → 5
string.upper(s) 转换为大写 string.upper(“hello”) → “HELLO”
string.lower(s) 转换为小写 string.lower(“HELLO”) → “hello”
string.sub(s, i, j) 截取字符串 string.sub(“hello”, 1, 3) → “hel”
string.find(s, pattern) 查找子串 string.find(“hello”, “ll”) → 3, 4
string.gsub(s, pattern, replacement) 替换子串 string.gsub(“hello”, “l”, “x”) → “hexxo”
string.format(format, …) 格式化字符串 string.format(“%.2f”, 3.14159) → “3.14”
string.rep(s, n) 重复字符串 string.rep(“hi”, 3) → “hihihi”
string.match(s, pattern) 匹配字符串 string.match(“user@example.com“, “(%a+)@”) → “user”
string.gmatch(s, pattern) 迭代匹配 for word in string.gmatch(“hello world”, “%a+”) do … end

2. math 库(数学函数)

math 库提供了各种数学函数,包括基本数学运算、三角函数、指数和对数、随机数生成等功能。

示例代码

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
-- 2.1 基本数学运算
print("math.abs(-10):", math.abs(-10))
print("math.floor(3.7):", math.floor(3.7))
print("math.ceil(3.2):", math.ceil(3.2))
print("math.sqrt(16):", math.sqrt(16))

-- 2.2 三角函数
print("math.sin(math.pi/2):", math.sin(math.pi/2))
print("math.cos(math.pi):", math.cos(math.pi))
print("math.tan(math.pi/4):", math.tan(math.pi/4))
print("math.asin(1):", math.asin(1))

-- 2.3 指数和对数
print("math.exp(1):", math.exp(1))
print("math.log(math.exp(1)):", math.log(math.exp(1)))
print("math.log10(100):", math.log10(100))
print("math.pow(2, 10):", math.pow(2, 10))

-- 2.4 最大最小值
print("math.max(10, 5, 20, 15):", math.max(10, 5, 20, 15))
print("math.min(10, 5, 20, 15):", math.min(10, 5, 20, 15))

-- 2.5 随机数
math.randomseed(os.time()) -- 设置随机种子
print("Random (0-1):", math.random())
print("Random (1-10):", math.random(10))
print("Random (5-15):", math.random(5, 15))

-- 2.6 常量
print("math.pi:", math.pi)
print("math.huge:", math.huge)

常用函数表

函数 描述 示例
math.abs(x) 绝对值 math.abs(-10) → 10
math.floor(x) 向下取整 math.floor(3.7) → 3
math.ceil(x) 向上取整 math.ceil(3.2) → 4
math.sqrt(x) 平方根 math.sqrt(16) → 4
math.sin(x) 正弦 math.sin(math.pi/2) → 1
math.cos(x) 余弦 math.cos(math.pi) → -1
math.tan(x) 正切 math.tan(math.pi/4) → 1
math.exp(x) 指数 math.exp(1) → 2.71828…
math.log(x) 自然对数 math.log(math.exp(1)) → 1
math.log10(x) 常用对数 math.log10(100) → 2
math.pow(x, y) 幂运算 math.pow(2, 10) → 1024
math.max(…) 最大值 math.max(1, 5, 3) → 5
math.min(…) 最小值 math.min(1, 5, 3) → 1
math.random(…) 随机数 math.random(1, 10) → 5 (随机)
math.randomseed(x) 设置随机种子 math.randomseed(os.time())

常量

常量 描述
math.pi 圆周率 3.141592653589793
math.huge 无穷大 1.#INF

3. os 库(操作系统交互)

os 库提供了与操作系统交互的功能,包括时间函数、执行系统命令、获取环境变量等功能。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
-- 3.1 时间函数
print("os.time():", os.time())
print("os.date():", os.date())
print("os.date('%Y-%m-%d'):", os.date("%Y-%m-%d"))
print("os.date('%H:%M:%S'):", os.date("%H:%M:%S"))

-- 3.2 执行系统命令
print("\nExecute system command 'echo Hello':")
local handle = io.popen("echo Hello")
if handle then
local result = handle:read("*a")
print("Result:", result)
handle:close()
end

-- 3.3 获取环境变量
print("\nos.getenv('PATH'):", os.getenv("PATH"))

-- 3.4 其他os函数
print("os.clock():", os.clock())
print("os.difftime(os.time(), os.time() - 3600):", os.difftime(os.time(), os.time() - 3600))

常用函数表

函数 描述 示例
os.time([table]) 获取当前时间或转换时间表 os.time() → 1620000000
os.date([format [, time]]) 格式化时间 os.date(“%Y-%m-%d”) → “2021-05-03”
os.clock() 程序运行时间 os.clock() → 0.123
os.difftime(t2, t1) 时间差 os.difftime(os.time(), os.time()-3600) → 3600
os.execute([command]) 执行系统命令 os.execute(“dir”)
os.getenv(varname) 获取环境变量 os.getenv(“PATH”)
os.remove(filename) 删除文件 os.remove(“test.txt”)
os.rename(oldname, newname) 重命名文件 os.rename(“old.txt”, “new.txt”)
os.tmpname() 生成临时文件名 os.tmpname() → “tmp0000000001”

时间格式化字符串

格式 描述 示例
%Y 四位年份 2021
%m 两位月份 05
%d 两位日期 03
%H 24小时制小时 14
%M 分钟 30
%S 45
%a 缩写星期名 Sun
%A 完整星期名 Sunday
%b 缩写月份名 May
%B 完整月份名 May
%c 本地日期和时间 05/03/21 14:30:45
%x 本地日期 05/03/21
%X 本地时间 14:30:45

4. io 库(文件操作)

io 库提供了文件操作的功能,包括打开文件、读写文件、关闭文件等功能。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
-- 4.1 标准输入输出
print("\nStandard I/O:")
-- io.write("Enter your name: ")
-- local name = io.read()
-- print("Hello, " .. name)

-- 4.2 临时文件
local tmp = io.tmpfile()
tmp:write("Temporary data\n")
tmp:seek("set")
print("Temporary file content:", tmp:read("*l"))
tmp:close()

常用函数表

函数 描述 示例
io.open(filename [, mode]) 打开文件 io.open(“test.txt”, “r”)
io.input([file]) 设置或获取标准输入 io.input(“input.txt”)
io.output([file]) 设置或获取标准输出 io.output(“output.txt”)
io.read([format]) 从标准输入读取 io.read(“*l”)
io.write(…) 写入标准输出 io.write(“hello”)
io.tmpfile() 创建临时文件 local tmp = io.tmpfile()
io.type(obj) 检查是否为文件句柄 io.type(file) → “file”
io.flush() 刷新输出缓冲区 io.flush()
io.close([file]) 关闭文件 io.close(file)

文件操作模式

模式 描述
r 只读模式,文件必须存在
w 只写模式,覆盖现有文件或创建新文件
a 追加模式,在文件末尾添加内容或创建新文件
r+ 读写模式,文件必须存在
w+ 读写模式,覆盖现有文件或创建新文件
a+ 读写模式,在文件末尾添加内容或创建新文件
b 二进制模式(与其他模式组合使用)

读取格式

格式 描述
“*n” 读取一个数字
“*a” 读取整个文件
“*l” 读取一行(默认)
number 读取指定数量的字符

5. table 库(表操作)

table 库提供了表操作的功能,包括连接表元素、插入和删除元素、排序表、解包表等功能。

示例代码

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
-- 5.1 table.concat(连接表元素)
local colors = {"red", "green", "blue"}
print("table.concat(colors, ', '):", table.concat(colors, ", "))

-- 5.2 table.insert 和 table.remove
local numbers = {1, 2, 3, 4, 5}
table.insert(numbers, 3, 100) -- 在索引3插入100
print("After table.insert(numbers, 3, 100):", table.concat(numbers, ", "))

local removed = table.remove(numbers, 3) -- 删除索引3的元素
print("After table.remove(numbers, 3):", table.concat(numbers, ", "))
print("Removed element:", removed)

-- 5.3 table.sort
local unsorted = {5, 2, 8, 1, 9, 3}
table.sort(unsorted)
print("table.sort(unsorted):", table.concat(unsorted, ", "))

-- 5.4 table.unpack
local coords = {10, 20, 30}
local x, y, z = unpack(coords)
print("unpack(coords):", x, y, z)

-- 5.5 表的遍历(ipairs和pairs)
local mixed = {"a", "b", name = "mixed", value = 100, "c"}
print("\nUsing ipairs (array part only):")
for i, v in ipairs(mixed) do
print(i, v)
end

print("\nUsing pairs (all elements):")
for k, v in pairs(mixed) do
print(k, v)
end

常用函数表

函数 描述 示例
table.concat(table [, sep [, i [, j]]]) 连接表元素 table.concat({“a”, “b”, “c”}, “, “) → “a, b, c”
table.insert(table, [pos,] value) 插入元素 table.insert({1, 2, 3}, 2, 10) → {1, 10, 2, 3}
table.remove(table [, pos]) 删除元素 table.remove({1, 10, 2, 3}, 2) → {1, 2, 3}
table.sort(table [, comp]) 排序表 table.sort({3, 1, 4, 2}) → {1, 2, 3, 4}
table.pack(…) 打包参数为表 table.pack(1, 2, 3) → {1, 2, 3, n=3}
unpack(table [, i [, j]]) 解包表为参数 unpack({1, 2, 3}) → 1, 2, 3

表的遍历

函数 描述 适用场景
ipairs(table) 遍历表的数组部分 只包含数字索引的表
pairs(table) 遍历表的所有元素 包含混合索引的表

6. debug 库(调试功能)

debug 库提供了调试功能,包括获取函数信息、查看全局环境、设置钩子等功能。

示例代码

1
2
3
4
5
6
7
8
9
10
-- 6.1 获取当前函数名
function test_debug()
local info = debug.getinfo(1)
print("Current function name:", info.name)
print("Line number:", info.currentline)
end
test_debug()

-- 6.2 查看全局环境
print("\ndebug.getmetatable(_G):", debug.getmetatable(_G))

常用函数表

函数 描述 示例
debug.getinfo([thread,] level) 获取函数信息 debug.getinfo(1)
debug.getlocal([thread,] level, local) 获取局部变量 debug.getlocal(1, 1)
debug.setlocal([thread,] level, local, value) 设置局部变量 debug.setlocal(1, 1, 10)
debug.getupvalue(func, up) 获取上值 debug.getupvalue(func, 1)
debug.setupvalue(func, up, value) 设置上值 debug.setupvalue(func, 1, 10)
debug.getmetatable(value) 获取元表 debug.getmetatable(_G)
debug.setmetatable(value, table) 设置元表 debug.setmetatable({}, {})
debug.traceback([thread [, message [, level]]]) 获取堆栈跟踪 debug.traceback()
debug.sethook([thread,] hook, mask [, count]) 设置钩子 debug.sethook(print, “c”)
debug.gethook([thread]) 获取钩子 debug.gethook()

7. coroutine 库(协程)

coroutine 库提供了协程功能,包括创建协程、运行协程、挂起协程、检查协程状态等功能。

示例代码

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
-- 创建协程
local co = coroutine.create(function()
for i = 1, 3 do
print("Coroutine yielding:", i)
coroutine.yield(i)
end
return "Coroutine finished"
end)

-- 运行协程
print("Coroutine status:", coroutine.status(co))

local status, result = coroutine.resume(co)
print("Resume 1 - Status:", status, "Result:", result)
print("Coroutine status:", coroutine.status(co))

status, result = coroutine.resume(co)
print("Resume 2 - Status:", status, "Result:", result)

status, result = coroutine.resume(co)
print("Resume 3 - Status:", status, "Result:", result)

status, result = coroutine.resume(co)
print("Resume 4 - Status:", status, "Result:", result)
print("Coroutine status:", coroutine.status(co))

常用函数表

函数 描述 示例
coroutine.create(f) 创建协程 coroutine.create(function() end)
coroutine.resume(co [, …]) 运行或恢复协程 coroutine.resume(co)
coroutine.yield(…) 挂起协程 coroutine.yield(“value”)
coroutine.status(co) 检查协程状态 coroutine.status(co) → “suspended”
coroutine.wrap(f) 创建协程并返回函数 local co = coroutine.wrap(function() end)
coroutine.running() 获取当前运行的协程 coroutine.running()
coroutine.isyieldable() 检查是否可以挂起 coroutine.isyieldable()

协程状态

状态 描述
suspended 协程已创建但尚未运行,或已运行但被挂起
running 协程正在运行
dead 协程已执行完毕,或因错误而终止
normal 协程正在运行,但不是当前正在执行的协程

8. package 库(模块管理)

package 库提供了模块管理功能,包括模块的加载、搜索路径的设置等功能。

示例代码

1
2
3
print("package.path:", package.path)
print("package.cpath:", package.cpath)
print("package.loaded:", next(package.loaded))

常用函数表

函数 描述 示例
package.loaded 已加载的模块表 package.loaded.string
package.preload 预加载模块表 package.preload[“mymodule”] = function() end
package.path Lua模块搜索路径 package.path = package.path .. “;./?.lua”
package.cpath C模块搜索路径 package.cpath = package.cpath .. “;./?.dll”
package.loadlib(libname, funcname) 加载C库 package.loadlib(“mylib.dll”, “luaopen_mylib”)
require(modname) 加载模块 require(“mymodule”)

模块搜索路径

  • package.path:Lua 模块的搜索路径,格式为多个路径模板,用分号分隔
  • package.cpath:C 模块的搜索路径,格式与 package.path 类似

9. 实用工具函数

除了标准库提供的函数外,我们还可以创建一些实用的工具函数,来简化常见的操作。

9.1 字符串分割函数

1
2
3
4
5
6
7
8
9
10
11
function split(str, sep)
local result = {}
for part in string.gmatch(str, "[^" .. sep .. "]+") do
table.insert(result, part)
end
return result
end

local csv_str = "apple,banana,orange,grape"
local parts = split(csv_str, ",")
print("Split CSV:", table.concat(parts, " | "))

9.2 深拷贝函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function deepcopy(orig)
local orig_type = type(orig)
local copy
if orig_type == 'table' then
copy = {}
for orig_key, orig_value in next, orig, nil do
copy[deepcopy(orig_key)] = deepcopy(orig_value)
end
setmetatable(copy, deepcopy(getmetatable(orig)))
else
copy = orig
end
return copy
end

local original = {a = 1, b = {c = 2}}
local copied = deepcopy(original)
copied.b.c = 100
print("Original.b.c:", original.b.c)
print("Copied.b.c:", copied.b.c)

9.3 表的长度(处理混合表)

1
2
3
4
5
6
7
8
9
function tablelength(T)
local count = 0
for _ in pairs(T) do count = count + 1 end
return count
end

local mixed_table = {"a", "b", name = "test", value = 42}
print("#mixed_table:", #mixed_table) -- 只计算数组部分
print("tablelength(mixed_table):", tablelength(mixed_table)) -- 计算所有元素

10. 标准库最佳实践

10.1 字符串操作

  1. 使用 # 运算符获取字符串长度#strstring.len(str) 更简洁
  2. 合理使用字符串格式化string.format() 可以使字符串拼接更清晰
  3. 避免频繁字符串连接:对于大量字符串连接,使用表和 table.concat() 更高效
  4. 使用正则表达式时要注意性能:复杂的正则表达式可能会影响性能

10.2 数学运算

  1. 设置随机种子:在使用 math.random() 前,应该使用 math.randomseed(os.time()) 设置随机种子
  2. 注意浮点数精度:Lua 中的数字都是浮点数,要注意精度问题
  3. 使用合适的数学函数:根据需要选择合适的数学函数,如 math.floor()math.ceil()

10.3 文件操作

  1. 总是检查文件是否打开成功local file, err = io.open(filename, mode)
  2. 使用后关闭文件:文件操作完成后,应该调用 file:close() 关闭文件
  3. 使用 xpcall 确保资源清理:在处理需要使用资源的操作时,使用 xpcall 确保即使发生错误,资源也能被正确清理

10.4 表操作

  1. 使用 ipairs 和 pairs 进行遍历:根据表的类型选择合适的遍历函数
  2. 合理使用表的方法:根据需要选择合适的表操作函数,如 table.insert()table.remove()
  3. 注意表的内存使用:对于大型表,要注意内存使用情况

10.5 协程使用

  1. 合理使用协程:协程适用于需要暂停和恢复执行的场景,如 IO 操作
  2. 注意协程状态:在使用协程前,应该检查其状态
  3. 避免嵌套过深:嵌套过深的协程会使代码难以理解和维护

11. 常见问题与解决方案

11.1 字符串操作

问题:字符串连接效率低
解决方案:使用表和 table.concat() 进行大量字符串连接

1
2
3
4
5
6
7
8
9
10
11
12
-- 低效的方式
local result = ""
for i = 1, 1000 do
result = result .. i
end

-- 高效的方式
local parts = {}
for i = 1, 1000 do
parts[#parts + 1] = i
end
local result = table.concat(parts)

11.2 数学运算

问题:随机数每次运行都相同
解决方案:使用 math.randomseed(os.time()) 设置随机种子

1
2
math.randomseed(os.time())  -- 设置随机种子
print("Random number:", math.random())

11.3 文件操作

问题:文件打开失败
解决方案:检查文件是否存在,或使用 pcall() 捕获错误

1
2
3
4
5
6
7
local file, err = io.open("test.txt", "r")
if not file then
print("Error opening file:", err)
return
end
-- 文件操作...
file:close()

11.4 表操作

问题:表的长度计算不准确
解决方案:对于混合表,使用自定义函数计算长度

1
2
3
4
5
6
7
8
function tablelength(T)
local count = 0
for _ in pairs(T) do count = count + 1 end
return count
end

local mixed_table = {"a", "b", name = "test", value = 42}
print("Table length:", tablelength(mixed_table))

11.5 模块管理

问题:模块找不到
解决方案:检查 package.pathpackage.cpath 是否包含模块所在的路径

1
2
3
print("package.path:", package.path)
-- 如果需要,添加路径
package.path = package.path .. ";./?.lua"

总结

本指南介绍了 Lua 中的标准库,包括 string、math、os、io、table、debug、coroutine 和 package 等库的使用方法。通过学习本指南,您应该已经掌握了 Lua 中标准库的基本使用技巧和最佳实践。

Lua 的标准库提供了丰富的功能,可以满足大多数编程需求。在实际应用中,您可以根据具体情况选择合适的库和函数,提高代码的效率和可读性。

希望本指南对您有所帮助,祝您在 Lua 编程中取得成功!

standard_lib.lua

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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
-- Lua标准库示例
-- print语句使用英语,注释使用中文

print("=== Lua Standard Library Example ===")

-- 1. string库(字符串操作)
print("\n1. string Library (String Operations)")

local str = "Hello, Lua Programming!"
print("Original string:", str)

-- 1.1 字符串长度
print("Length:", string.len(str))
print("Length (using #):", #str)

-- 1.2 字符串大小写转换
print("Uppercase:", string.upper(str))
print("Lowercase:", string.lower(str))

-- 1.3 字符串截取
print("Substring (1-5):", string.sub(str, 1, 5))
print("Substring (8-end):", string.sub(str, 8))
print("Substring (from end):", string.sub(str, -11))

-- 1.4 字符串查找
local start_pos, end_pos = string.find(str, "Lua")
print("Find 'Lua':", start_pos, end_pos)

-- 1.5 字符串替换
local new_str = string.gsub(str, "Lua", "LUA")
print("Replace 'Lua' with 'LUA':", new_str)

-- 1.6 字符串格式化
local formatted = string.format("%s is %d years old and has %.2f GPA", "Alice", 25, 3.85)
print("Formatted string:", formatted)

-- 1.7 字符串重复
print("Repeat 'Hi ' 3 times:", string.rep("Hi ", 3))

-- 1.8 字符串匹配(正则表达式)
local email = "user@example.com"
local username = string.match(email, "(%a+)@")
local domain = string.match(email, "@(%a+%.%a+)")
print("Email parts - username:", username, "domain:", domain)

-- 1.9 字符串分割(使用gmatch)
local csv = "apple,banana,orange,grape"
local fruits = {}
for fruit in string.gmatch(csv, "[^,]+") do
table.insert(fruits, fruit)
end
print("Split CSV:", table.concat(fruits, " | "))

-- 2. math库(数学函数)
print("\n2. math Library (Mathematical Functions)")

-- 2.1 基本数学运算
print("math.abs(-10):", math.abs(-10))
print("math.floor(3.7):", math.floor(3.7))
print("math.ceil(3.2):", math.ceil(3.2))
print("math.sqrt(16):", math.sqrt(16))

-- 2.2 三角函数
print("math.sin(math.pi/2):", math.sin(math.pi/2))
print("math.cos(math.pi):", math.cos(math.pi))
print("math.tan(math.pi/4):", math.tan(math.pi/4))
print("math.asin(1):", math.asin(1))

-- 2.3 指数和对数
print("math.exp(1):", math.exp(1))
print("math.log(math.exp(1)):", math.log(math.exp(1)))
print("math.log10(100):", math.log10(100))
print("math.pow(2, 10):", math.pow(2, 10))

-- 2.4 最大最小值
print("math.max(10, 5, 20, 15):", math.max(10, 5, 20, 15))
print("math.min(10, 5, 20, 15):", math.min(10, 5, 20, 15))

-- 2.5 随机数
math.randomseed(os.time()) -- 设置随机种子
print("Random (0-1):", math.random())
print("Random (1-10):", math.random(10))
print("Random (5-15):", math.random(5, 15))

-- 2.6 常量
print("math.pi:", math.pi)
print("math.huge:", math.huge)

-- 3. os库(操作系统交互)
print("\n3. os Library (Operating System Interaction)")

-- 3.1 时间函数
print("os.time():", os.time())
print("os.date():", os.date())
print("os.date('%Y-%m-%d'):", os.date("%Y-%m-%d"))
print("os.date('%H:%M:%S'):", os.date("%H:%M:%S"))

-- 3.2 执行系统命令
print("\nExecute system command 'echo Hello':")
local handle = io.popen("echo Hello")
if handle then
local result = handle:read("*a")
print("Result:", result)
handle:close()
end

-- 3.3 获取环境变量
print("\nos.getenv('PATH'):", os.getenv("PATH"))

-- 3.4 其他os函数
print("os.clock():", os.clock())
print("os.difftime(os.time(), os.time() - 3600):", os.difftime(os.time(), os.time() - 3600))

-- 4. io库(文件操作补充)
print("\n4. io Library (File Operations)")

-- 4.1 标准输入输出
print("\nStandard I/O:")
-- io.write("Enter your name: ")
-- local name = io.read()
-- print("Hello, " .. name)

-- 4.2 临时文件
local tmp = io.tmpfile()
tmp:write("Temporary data\n")
tmp:seek("set")
print("Temporary file content:", tmp:read("*l"))
tmp:close()

-- 5. table库(表操作补充)
print("\n5. table Library (Table Operations)")

-- 5.1 table.concat(连接表元素)
local colors = {"red", "green", "blue"}
print("table.concat(colors, ', '):", table.concat(colors, ", "))

-- 5.2 table.insert 和 table.remove
local numbers = {1, 2, 3, 4, 5}
table.insert(numbers, 3, 100) -- 在索引3插入100
print("After table.insert(numbers, 3, 100):", table.concat(numbers, ", "))

local removed = table.remove(numbers, 3) -- 删除索引3的元素
print("After table.remove(numbers, 3):", table.concat(numbers, ", "))
print("Removed element:", removed)

-- 5.3 table.sort
local unsorted = {5, 2, 8, 1, 9, 3}
table.sort(unsorted)
print("table.sort(unsorted):", table.concat(unsorted, ", "))

-- 5.4 table.unpack
local coords = {10, 20, 30}
local x, y, z = unpack(coords)
print("unpack(coords):", x, y, z)

-- 5.5 表的遍历(ipairs和pairs)
local mixed = {"a", "b", name = "mixed", value = 100, "c"}
print("\nUsing ipairs (array part only):")
for i, v in ipairs(mixed) do
print(i, v)
end

print("\nUsing pairs (all elements):")
for k, v in pairs(mixed) do
print(k, v)
end

-- 6. debug库(调试功能)
print("\n6. debug Library (Debugging Functions)")

-- 6.1 获取当前函数名
function test_debug()
local info = debug.getinfo(1)
print("Current function name:", info.name)
print("Line number:", info.currentline)
end
test_debug()

-- 6.2 查看全局环境
print("\ndebug.getmetatable(_G):", debug.getmetatable(_G))

-- 7. coroutine库(协程)
print("\n7. coroutine Library (Coroutines)")

-- 创建协程
local co = coroutine.create(function()
for i = 1, 3 do
print("Coroutine yielding:", i)
coroutine.yield(i)
end
return "Coroutine finished"
end)

-- 运行协程
print("Coroutine status:", coroutine.status(co))

local status, result = coroutine.resume(co)
print("Resume 1 - Status:", status, "Result:", result)
print("Coroutine status:", coroutine.status(co))

status, result = coroutine.resume(co)
print("Resume 2 - Status:", status, "Result:", result)

status, result = coroutine.resume(co)
print("Resume 3 - Status:", status, "Result:", result)

status, result = coroutine.resume(co)
print("Resume 4 - Status:", status, "Result:", result)
print("Coroutine status:", coroutine.status(co))

-- 8. package库(模块管理)
print("\n8. package Library (Module Management)")

print("package.path:", package.path)
print("package.cpath:", package.cpath)
print("package.loaded:", next(package.loaded))

-- 9. 实用工具函数
print("\n9. Utility Functions")

-- 9.1 字符串分割函数
function split(str, sep)
local result = {}
for part in string.gmatch(str, "[^" .. sep .. "]+") do
table.insert(result, part)
end
return result
end

local csv_str = "apple,banana,orange,grape"
local parts = split(csv_str, ",")
print("Split CSV:", table.concat(parts, " | "))

-- 9.2 深拷贝函数
function deepcopy(orig)
local orig_type = type(orig)
local copy
if orig_type == 'table' then
copy = {}
for orig_key, orig_value in next, orig, nil do
copy[deepcopy(orig_key)] = deepcopy(orig_value)
end
setmetatable(copy, deepcopy(getmetatable(orig)))
else
copy = orig
end
return copy
end

local original = {a = 1, b = {c = 2}}
local copied = deepcopy(original)
copied.b.c = 100
print("Original.b.c:", original.b.c)
print("Copied.b.c:", copied.b.c)

-- 9.3 表的长度(处理混合表)
function tablelength(T)
local count = 0
for _ in pairs(T) do count = count + 1 end
return count
end

local mixed_table = {"a", "b", name = "test", value = 42}
print("#mixed_table:", #mixed_table) -- 只计算数组部分
print("tablelength(mixed_table):", tablelength(mixed_table)) -- 计算所有元素

print("\n=== End of Standard Library Examples ===")

Lua 协程指南

简介

本指南详细介绍了 Lua 中的协程(Coroutines)机制,包括协程的创建、运行、挂起、状态检查等基本操作,以及协程在实际应用中的使用场景。通过本指南,您将学习如何在 Lua 中有效地使用协程,提高代码的灵活性和可读性。

目录

  1. 协程的基本概念
  2. 协程的创建
  3. 协程的运行
  4. 协程的挂起
  5. 协程状态检查
  6. 向协程传递参数
  7. 协程错误处理
  8. 协程的实际应用示例
  9. 协程与多线程的比较
  10. 协程最佳实践
  11. 常见问题与解决方案

1. 协程的基本概念

协程是一种用户级的轻量级线程,它允许在程序执行过程中暂停执行,然后在适当的时候恢复执行。与多线程不同,协程的执行是由程序显式控制的,而不是由操作系统调度的。

协程的特点

  • 协作式多任务:协程的切换是由程序显式控制的,而不是由操作系统调度的
  • 轻量级:协程的创建和切换开销比线程小得多
  • 共享内存:协程之间共享同一进程的内存空间,不需要特殊的通信机制
  • 顺序执行:在单线程环境中,协程是顺序执行的,不会出现并发访问的问题

协程的状态

协程有四种状态:

  • suspended:协程已创建但尚未运行,或已运行但被挂起
  • running:协程正在运行
  • dead:协程已执行完毕,或因错误而终止
  • normal:协程正在运行,但不是当前正在执行的协程(仅在嵌套协程时出现)

2. 协程的创建

在 Lua 中,使用 coroutine.create() 函数来创建协程。

示例代码

1
2
3
4
5
6
7
8
-- 创建一个协程
local co = coroutine.create(function()
print("Coroutine started")
return "Coroutine finished"
end)

-- 检查协程状态
print("Coroutine status after creation:", coroutine.status(co))

说明

  • coroutine.create(f) 函数接收一个函数 f 作为参数,返回一个新的协程
  • 协程创建后,其状态为 suspended
  • 创建协程不会自动运行它,需要使用 coroutine.resume() 来运行

3. 协程的运行

使用 coroutine.resume() 函数来运行协程。

示例代码

1
2
3
4
-- 运行协程
local success, result = coroutine.resume(co)
print("Coroutine execution result:", success, result)
print("Coroutine status after execution:", coroutine.status(co))

说明

  • coroutine.resume(co, ...) 函数接收一个协程 co 和可选的参数,返回两个值:
    • 第一个值是布尔值,表示协程是否成功执行
    • 第二个值是协程的返回值或错误信息
  • 协程执行完毕后,其状态变为 dead
  • 对于已处于 dead 状态的协程,再次调用 coroutine.resume() 会返回错误

4. 协程的挂起

使用 coroutine.yield() 函数来挂起协程。

示例代码

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
-- 创建一个可以挂起的协程
local co_with_yield = coroutine.create(function()
print("Coroutine with yield started")

print("Coroutine yielding first value")
coroutine.yield("First yield value")

print("Coroutine resumed after first yield")

print("Coroutine yielding second value")
coroutine.yield("Second yield value")

print("Coroutine resumed after second yield")
return "Coroutine with yield finished"
end)

-- 第一次运行协程(到第一个yield)
print("Coroutine status before first resume:", coroutine.status(co_with_yield))
local success, value1 = coroutine.resume(co_with_yield)
print("First resume result:", success, value1)
print("Coroutine status after first yield:", coroutine.status(co_with_yield))

-- 第二次运行协程(到第二个yield)
local success, value2 = coroutine.resume(co_with_yield)
print("Second resume result:", success, value2)
print("Coroutine status after second yield:", coroutine.status(co_with_yield))

-- 第三次运行协程(完成)
local success, result = coroutine.resume(co_with_yield)
print("Third resume result:", success, result)
print("Coroutine status after completion:", coroutine.status(co_with_yield))

说明

  • coroutine.yield(...) 函数接收可选的参数,这些参数会作为 coroutine.resume() 的返回值
  • 协程挂起后,其状态变为 suspended
  • 可以通过再次调用 coroutine.resume() 来恢复协程的执行,从挂起的地方继续

5. 协程状态检查

使用 coroutine.status() 函数来检查协程的状态。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-- 创建一个新的协程用于状态检查
local status_check_co = coroutine.create(function()
print("Status check coroutine started")
coroutine.yield("Yielding...")
print("Status check coroutine resumed")
return "Status check coroutine finished"
end)

-- 检查各种状态
print("Status after creation:", coroutine.status(status_check_co))

coroutine.resume(status_check_co)
print("Status after first yield:", coroutine.status(status_check_co))

coroutine.resume(status_check_co)
print("Status after completion:", coroutine.status(status_check_co))

说明

  • coroutine.status(co) 函数接收一个协程 co 作为参数,返回该协程的状态
  • 协程的状态可能是 suspendedrunningdeadnormal

6. 向协程传递参数

可以在 coroutine.resume() 中向协程传递参数,这些参数会作为协程函数的参数或 coroutine.yield() 的返回值。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
local param_co = coroutine.create(function(a, b)
print("Coroutine received parameters:", a, b)
local c = a + b
print("Coroutine calculated sum:", c)

-- 从yield恢复时接收参数
local d = coroutine.yield(c)
print("Coroutine received parameter after yield:", d)
return c + d
end)

-- 第一次resume传递初始参数
local success, sum = coroutine.resume(param_co, 10, 20)
print("First resume result:", success, sum)

-- 第二次resume传递参数给yield
local success, total = coroutine.resume(param_co, 30)
print("Second resume result:", success, total)

说明

  • 第一次调用 coroutine.resume(co, ...) 时,传递的参数会作为协程函数的参数
  • 后续调用 coroutine.resume(co, ...) 时,传递的参数会作为 coroutine.yield() 的返回值
  • coroutine.yield(...) 传递的参数会作为 coroutine.resume() 的返回值
  • 协程函数的返回值会作为最后一次 coroutine.resume() 的返回值

7. 协程错误处理

协程执行过程中可能会出现错误,需要进行适当的处理。

示例代码

1
2
3
4
5
6
7
8
local error_co = coroutine.create(function()
print("Error handling coroutine started")
error("Error in coroutine")
end)

local success, err = coroutine.resume(error_co)
print("Resume result with error:", success, err)
print("Coroutine status after error:", coroutine.status(error_co))

说明

  • 当协程执行过程中出现错误时,coroutine.resume() 的第一个返回值为 false,第二个返回值为错误信息
  • 协程出错后,其状态变为 dead
  • 可以使用 pcall() 函数来捕获协程执行过程中的错误,使程序能够继续执行

8. 协程的实际应用示例

8.1 生产者-消费者模式

生产者-消费者模式是协程的一个常见应用场景,其中生产者生成数据,消费者消费数据。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
local producer = coroutine.create(function()
for i = 1, 5 do
print("Producer producing item", i)
coroutine.yield(i)
end
return "No more items"
end)

local consumer = function()
while true do
local success, item = coroutine.resume(producer)
if not success then
print("Consumer error:", item)
break
end
if coroutine.status(producer) == "dead" then
print("Consumer received:", item)
break
end
print("Consumer consuming item", item)
end
end

consumer()

8.2 自定义迭代器

协程可以用于创建自定义迭代器,使迭代过程更加灵活。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- 使用协程创建一个自定义迭代器
function custom_iterator(start, stop, step)
return coroutine.wrap(function()
for i = start, stop, step do
coroutine.yield(i)
end
end)
end

-- 使用自定义迭代器
print("Using custom iterator:")
for value in custom_iterator(1, 10, 2) do
print("Iterator value:", value)
end

8.3 其他应用场景

  • 状态机:使用协程来实现复杂的状态机,使状态转换更加清晰
  • 异步操作:使用协程来模拟异步操作,使代码看起来像同步代码
  • 游戏开发:使用协程来实现游戏中的AI行为、动画效果等
  • 解析器:使用协程来实现各种解析器,如JSON解析器、XML解析器等

示例代码:简单的状态机

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
-- 使用协程实现一个简单的状态机
local function state_machine()
return coroutine.wrap(function()
while true do
print("State: Idle")
local event = coroutine.yield("Idle")

if event == "start" then
print("State: Running")
event = coroutine.yield("Running")

if event == "pause" then
print("State: Paused")
event = coroutine.yield("Paused")
end

if event == "stop" then
print("State: Stopped")
event = coroutine.yield("Stopped")
end
end
end
end)
end

-- 使用状态机
local sm = state_machine()
print("Current state:", sm())
print("Current state:", sm("start"))
print("Current state:", sm("pause"))
print("Current state:", sm("start"))
print("Current state:", sm("stop"))

9. 协程与多线程的比较

特性 协程 多线程
调度方式 协作式(由程序控制) 抢占式(由操作系统调度)
内存共享 共享同一进程的内存空间 可能共享内存,也可能不共享
并发访问 不会出现并发访问问题(单线程) 可能出现并发访问问题,需要同步机制
创建和切换开销
编程复杂度 低(不需要处理同步问题) 高(需要处理同步问题)
适用场景 高IO、低计算的任务 高计算的任务

10. 协程最佳实践

10.1 基本原则

  1. 合理使用协程:协程适用于需要暂停和恢复执行的场景,如IO操作、状态机等
  2. 避免嵌套过深:嵌套过深的协程会使代码难以理解和维护
  3. 正确处理错误:使用 pcall() 来捕获协程执行过程中的错误
  4. 及时清理资源:协程执行完毕后,相关的资源应该被及时清理
  5. 避免长时间阻塞:协程中应该避免长时间阻塞的操作,以免影响其他协程的执行

10.2 代码风格

  1. 命名规范:协程相关的变量和函数应该有清晰的命名,表明其用途
  2. 注释说明:对于复杂的协程逻辑,应该添加适当的注释,说明其工作原理
  3. 模块化:将协程相关的代码组织成模块,提高代码的可维护性
  4. 错误处理:为协程添加适当的错误处理机制,提高代码的健壮性

11. 常见问题与解决方案

11.1 常见问题

  1. 协程无法恢复:可能是因为协程已经处于 dead 状态,或者在协程中出现了错误
  2. 协程挂起后无法获取返回值:可能是因为在挂起协程时没有正确传递返回值
  3. 协程执行顺序混乱:可能是因为协程的调度逻辑有问题
  4. 内存泄漏:可能是因为协程引用了大量的资源,执行完毕后没有被及时清理

11.2 解决方案

  1. 协程无法恢复

    • 检查协程的状态,确保其处于 suspended 状态
    • 使用 pcall() 来捕获协程执行过程中的错误
  2. 协程挂起后无法获取返回值

    • 确保在 coroutine.yield() 中正确传递返回值
    • 确保在 coroutine.resume() 中正确获取返回值
  3. 协程执行顺序混乱

    • 设计清晰的协程调度逻辑
    • 使用状态机来管理协程的执行顺序
  4. 内存泄漏

    • 协程执行完毕后,及时清理相关的资源
    • 避免在协程中引用大量的资源
    • 使用弱引用表来存储协程相关的数据

总结

本指南介绍了 Lua 中的协程机制,包括协程的基本概念、创建、运行、挂起、状态检查等基本操作,以及协程在实际应用中的使用场景。通过学习本指南,您应该已经掌握了 Lua 中协程的基本使用方法和最佳实践。

协程是 Lua 中一个强大的特性,它可以使代码更加灵活和可读,特别是在处理需要暂停和恢复执行的场景时。希望本指南对您有所帮助,祝您在 Lua 编程中取得成功!

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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
-- Lua协程示例
-- print语句使用英语,注释使用中文

print("=== Lua Coroutines Example ===")

-- 1. 协程的创建(coroutine.create)
print("\n1. Coroutine Creation")

-- 创建一个协程
local co = coroutine.create(function()
print("Coroutine started")
return "Coroutine finished"
end)

-- 检查协程状态
print("Coroutine status after creation:", coroutine.status(co))

-- 2. 协程的运行(coroutine.resume)
print("\n2. Coroutine Execution")

-- 运行协程
local success, result = coroutine.resume(co)
print("Coroutine execution result:", success, result)
print("Coroutine status after execution:", coroutine.status(co))

-- 3. 协程的挂起(coroutine.yield)
print("\n3. Coroutine Suspension")

-- 创建一个可以挂起的协程
local co_with_yield = coroutine.create(function()
print("Coroutine with yield started")

print("Coroutine yielding first value")
coroutine.yield("First yield value")

print("Coroutine resumed after first yield")

print("Coroutine yielding second value")
coroutine.yield("Second yield value")

print("Coroutine resumed after second yield")
return "Coroutine with yield finished"
end)

-- 第一次运行协程(到第一个yield)
print("Coroutine status before first resume:", coroutine.status(co_with_yield))
local success, value1 = coroutine.resume(co_with_yield)
print("First resume result:", success, value1)
print("Coroutine status after first yield:", coroutine.status(co_with_yield))

-- 第二次运行协程(到第二个yield)
local success, value2 = coroutine.resume(co_with_yield)
print("Second resume result:", success, value2)
print("Coroutine status after second yield:", coroutine.status(co_with_yield))

-- 第三次运行协程(完成)
local success, result = coroutine.resume(co_with_yield)
print("Third resume result:", success, result)
print("Coroutine status after completion:", coroutine.status(co_with_yield))

-- 4. 协程状态检查
print("\n4. Coroutine Status Check")

-- 创建一个新的协程用于状态检查
local status_check_co = coroutine.create(function()
print("Status check coroutine started")
coroutine.yield("Yielding...")
print("Status check coroutine resumed")
return "Status check coroutine finished"
end)

-- 检查各种状态
print("Status after creation:", coroutine.status(status_check_co))

coroutine.resume(status_check_co)
print("Status after first yield:", coroutine.status(status_check_co))

coroutine.resume(status_check_co)
print("Status after completion:", coroutine.status(status_check_co))

-- 5. 向协程传递参数
print("\n5. Passing Parameters to Coroutine")

local param_co = coroutine.create(function(a, b)
print("Coroutine received parameters:", a, b)
local c = a + b
print("Coroutine calculated sum:", c)

-- 从yield恢复时接收参数
local d = coroutine.yield(c)
print("Coroutine received parameter after yield:", d)
return c + d
end)

-- 第一次resume传递初始参数
local success, sum = coroutine.resume(param_co, 10, 20)
print("First resume result:", success, sum)

-- 第二次resume传递参数给yield
local success, total = coroutine.resume(param_co, 30)
print("Second resume result:", success, total)

-- 6. 协程错误处理
print("\n6. Coroutine Error Handling")

local error_co = coroutine.create(function()
print("Error handling coroutine started")
error("Error in coroutine")
end)

local success, err = coroutine.resume(error_co)
print("Resume result with error:", success, err)
print("Coroutine status after error:", coroutine.status(error_co))

-- 7. 协程的实际应用示例:生产者-消费者模式
print("\n7. Practical Example: Producer-Consumer Pattern")

local producer = coroutine.create(function()
for i = 1, 5 do
print("Producer producing item", i)
coroutine.yield(i)
end
return "No more items"
end)

local consumer = function()
while true do
local success, item = coroutine.resume(producer)
if not success then
print("Consumer error:", item)
break
end
if coroutine.status(producer) == "dead" then
print("Consumer received:", item)
break
end
print("Consumer consuming item", item)
end
end

consumer()

-- 8. 协程的实际应用示例:迭代器
print("\n8. Practical Example: Custom Iterator")

-- 使用协程创建一个自定义迭代器
function custom_iterator(start, stop, step)
return coroutine.wrap(function()
for i = start, stop, step do
coroutine.yield(i)
end
end)
end

-- 使用自定义迭代器
print("Using custom iterator:")
for value in custom_iterator(1, 10, 2) do
print("Iterator value:", value)
end

print("\n=== End of Coroutines Example ===")

Lua 错误处理指南

简介

本指南详细介绍了 Lua 中的错误处理机制,包括基本错误抛出、保护调用(pcall)、扩展保护调用(xpcall)、自定义错误处理、错误对象、断言等功能。通过本指南,您将学习如何在 Lua 中有效地处理错误,提高代码的健壮性和可靠性。

目录

  1. 基本错误抛出
  2. pcall(保护调用)
  3. xpcall(扩展保护调用)
  4. 自定义错误处理
  5. 错误对象和错误信息
  6. 断言(assert)
  7. 错误处理的实际应用场景
  8. 嵌套错误处理
  9. 资源清理和错误处理
  10. 错误处理最佳实践
  11. 常见错误类型及解决方案

1. 基本错误抛出

在 Lua 中,可以使用 error() 函数来抛出错误。当错误被抛出时,如果没有被捕获,程序将会终止执行。

示例代码

1
2
3
4
5
6
7
8
9
10
-- 使用error函数抛出错误
function divide(a, b)
if b == 0 then
error("Division by zero") -- 抛出错误
end
return a / b
end

-- 直接调用会导致程序崩溃
-- print(divide(10, 0))

说明

  • error(message, level) 函数用于抛出错误
  • message 参数是错误信息,可以是字符串或其他类型的值
  • level 参数可选,指定错误发生的栈级别,默认为 1

2. pcall(保护调用)

pcall 函数用于保护调用,它会捕获函数执行过程中可能抛出的错误,使程序能够继续执行。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
-- pcall接收一个函数和可变参数,返回布尔值和结果/错误信息
local success, result = pcall(divide, 10, 2)
if success then
print("10 / 2 =", result)
else
print("Error:", result)
end

local success, result = pcall(divide, 10, 0)
if success then
print("10 / 0 =", result)
else
print("Error:", result)
end

-- 使用匿名函数作为参数
local success, result = pcall(function()
return divide(20, 4)
end)
print("20 / 4 =", result)

说明

  • pcall(func, ...) 函数接收一个函数和可变参数
  • 第一个返回值 success 是布尔值,表示函数是否成功执行
  • 第二个返回值 result 是函数的返回值或错误信息

3. xpcall(扩展保护调用)

xpcall 函数是 pcall 的扩展版本,它允许指定一个错误处理函数,用于获取更详细的错误信息,如堆栈跟踪。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- xpcall接收两个函数:要执行的函数和错误处理函数
local function error_handler(err)
return "Custom error handler: " .. err .. "\nStack trace: " .. debug.traceback()
end

local success, result = xpcall(function()
return divide(10, 0)
end, error_handler)

if success then
print("Result:", result)
else
print("Error with traceback:", result)
end

说明

  • xpcall(func, err_handler, ...) 函数接收一个要执行的函数、一个错误处理函数和可变参数
  • func 执行出错时,err_handler 会被调用,并传入错误信息
  • debug.traceback() 函数用于获取当前的堆栈跟踪信息

4. 自定义错误处理

您可以创建自定义的错误处理函数,以更灵活地处理错误情况。

示例代码

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
-- 创建一个通用的错误处理函数
function safe_call(func, ...)
local args = {...}
return xpcall(function()
return func(unpack(args))
end, function(err)
local trace = debug.traceback()
return {
success = false,
error = err,
traceback = trace,
timestamp = os.time()
}
end)
end

-- 测试自定义错误处理
local success, result = safe_call(divide, 15, 3)
if success then
print("15 / 3 =", result)
else
print("Error:", result)
end

local success, result = safe_call(divide, 10, 0)
if success then
print("Result:", result)
else
print("Error details:")
print(" Success:", result.success)
print(" Error:", result.error)
print(" Timestamp:", os.date("%Y-%m-%d %H:%M:%S", result.timestamp))
-- 只打印traceback的前几行
local trace_lines = {}
for line in string.gmatch(result.traceback, "[^\n]+") do
table.insert(trace_lines, line)
if #trace_lines >= 5 then break end
end
print(" Traceback (first 5 lines):")
for _, line in ipairs(trace_lines) do
print(" " .. line)
end
end

说明

  • safe_call 函数是一个通用的错误处理包装器
  • 它使用 xpcall 来捕获错误,并返回一个包含详细错误信息的表
  • 错误信息包括成功状态、错误消息、堆栈跟踪和时间戳

5. 错误对象和错误信息

在 Lua 中,错误信息可以是任何类型的值,不仅仅是字符串。您可以使用表来创建结构化的错误信息,包含更多的错误详情。

示例代码

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
-- 抛出带有详细信息的错误
function process_data(data)
if type(data) ~= "table" then
error({code = 1001, message = "Invalid data type", expected = "table", got = type(data)})
end
if not data.id then
error({code = 1002, message = "Missing required field", field = "id"})
end
return "Data processed successfully: " .. data.id
end

-- 处理带有错误对象的错误
local success, result = pcall(process_data, "not a table")
if not success then
if type(result) == "table" then
print("Structured error:")
print(" Code:", result.code)
print(" Message:", result.message)
print(" Expected:", result.expected)
print(" Got:", result.got)
else
print("Simple error:", result)
end
end

local success, result = pcall(process_data, {name = "test"})
if not success then
if type(result) == "table" then
print("Structured error:")
print(" Code:", result.code)
print(" Message:", result.message)
print(" Field:", result.field)
else
print("Simple error:", result)
end
end

说明

  • 错误对象可以是任何类型的值,包括表
  • 使用表作为错误对象可以包含更多的错误信息,如错误代码、错误消息、预期值等
  • 在捕获错误时,可以根据错误对象的类型进行不同的处理

6. 断言(assert)

assert 函数是一种简洁的错误处理方式,它用于检查条件是否为真,如果不为真,则抛出错误。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
-- assert(condition, message) 如果condition为false,抛出错误信息
local function safe_convert_to_number(str)
local num = tonumber(str)
assert(num ~= nil, "Invalid number format: " .. str)
return num
end

local success, result = pcall(safe_convert_to_number, "123")
print("Convert '123' to number:", result)

local success, result = pcall(safe_convert_to_number, "abc")
print("Convert 'abc' to number failed:", result)

说明

  • assert(condition, message) 函数用于检查条件是否为真
  • 如果 condition 为 false,assert 会抛出错误,错误信息为 message
  • assert 通常用于检查函数参数的有效性或函数执行的前置条件

7. 错误处理的实际应用场景

7.1 配置文件加载

在加载配置文件时,可能会遇到文件不存在、文件格式错误等问题,需要进行适当的错误处理。

示例代码

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
local function load_config(file_path)
local file, err = io.open(file_path, "r")
if not file then
error("Failed to open config file: " .. err)
end

local content = file:read("*a")
file:close()

local config, err = load(content, file_path, "t", {})
if not config then
error("Failed to parse config file: " .. err)
end

local success, result = pcall(config)
if not success then
error("Failed to execute config: " .. result)
end

return result
end

-- 测试配置文件加载(假设配置文件不存在)
local success, result = safe_call(load_config, "non_existent_config.lua")
if not success then
print("Config loading failed:", result.error)
end

7.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
local function simulate_network_request(url, timeout)
-- 模拟网络请求可能失败的情况
local random_failure = math.random(1, 5) -- 20% 失败概率
if random_failure == 1 then
error("Network timeout after " .. timeout .. "ms")
end

-- 模拟成功响应
return {
status = 200,
url = url,
data = {message = "Success", timestamp = os.time()}
}
end

-- 测试网络请求
print("\nNetwork request simulation:")
for i = 1, 3 do
local success, result = safe_call(simulate_network_request, "https://example.com/api", 5000)
if success then
print("Request " .. i .. " succeeded:", result.status)
else
print("Request " .. i .. " failed:", result.error)
end
end

8. 嵌套错误处理

在实际应用中,错误处理可能会嵌套多层,需要注意错误的传播和处理。

示例代码

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
local function outer_function()
print("Entering outer_function")

local success, result = pcall(function()
print("Entering inner function")

-- 内部函数可能抛出错误
local data = nil
data.field = "value" -- 这会导致错误

print("Exiting inner function")
return "Success"
end)

if not success then
print("Inner function error:", result)
-- 可以选择重新抛出错误
-- error("Propagating error from outer_function")
end

print("Exiting outer_function")
return "Outer success"
end

local success, result = pcall(outer_function)
print("Final result:", success and result or result)

说明

  • 嵌套错误处理是指在一个错误处理函数中又进行了可能抛出错误的操作
  • 在嵌套错误处理中,需要注意错误的传播方向和处理方式
  • 可以选择捕获并处理错误,也可以选择重新抛出错误,让上层函数处理

9. 资源清理和错误处理

在处理需要使用资源(如文件、网络连接等)的操作时,需要确保即使发生错误,资源也能被正确清理。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
local function process_with_resources()
-- 模拟打开资源
local resource = {name = "test_resource", opened = true}
print("Resource opened:", resource.name)

-- 使用xpcall确保资源被正确清理
local success, result = xpcall(function()
-- 模拟处理过程中发生错误
error("Error during resource processing")
return "Processed successfully"
end, function(err)
-- 清理资源
resource.opened = false
print("Resource cleaned up:", resource.name)
return err -- 返回原始错误
end)

return success, result
end

local success, result = process_with_resources()
print("Process result:", success and result or result)

说明

  • 在处理需要使用资源的操作时,应该使用 xpcall 来确保即使发生错误,资源也能被正确清理
  • 错误处理函数是清理资源的理想位置,因为它会在函数执行出错时被调用
  • 清理资源后,应该返回原始错误,以便上层函数知道发生了什么错误

10. 错误处理最佳实践

10.1 基本原则

  1. 尽早检测错误:在函数入口处检查参数的有效性,避免在函数执行过程中出现错误
  2. 提供明确的错误信息:错误信息应该清晰、准确,包含足够的信息,以便于调试
  3. 适当使用错误对象:对于复杂的错误情况,使用表作为错误对象,包含更多的错误信息
  4. 确保资源清理:使用 xpcall 确保即使发生错误,资源也能被正确清理
  5. 合理处理错误:根据错误的类型和严重程度,采取适当的处理措施,如重试、降级、报警等

10.2 错误处理策略

  1. 局部处理:对于可以在局部处理的错误,应该在局部处理,避免错误向上传播
  2. 向上传播:对于无法在局部处理的错误,应该向上传播,让上层函数处理
  3. 全局处理:对于一些致命错误,可以在全局层面进行处理,如记录错误日志、显示错误信息等

10.3 错误日志

在实际应用中,应该记录错误日志,以便于调试和监控。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
-- 简单的错误日志函数
function log_error(err, context)
local timestamp = os.date("%Y-%m-%d %H:%M:%S")
local log_message = string.format("[%s] ERROR: %s", timestamp, err)
if context then
log_message = log_message .. " Context: " .. context
end

-- 实际应用中,可能会写入文件或发送到日志系统
print(log_message)
end

-- 使用错误日志
local success, result = pcall(function()
error("Test error")
end)

if not success then
log_error(result, "Test context")
end

11. 常见错误类型及解决方案

11.1 常见错误类型

错误类型 错误信息示例 原因 解决方案
除以零 attempt to divide by zero 尝试除以零 在除法操作前检查除数是否为零
索引 nil 值 attempt to index local ‘x’ (a nil value) 尝试访问 nil 值的字段 在访问字段前检查值是否为 nil
调用非函数 attempt to call local ‘f’ (a nil value) 尝试调用非函数值 在调用前检查值是否为函数
文件不存在 No such file or directory 尝试打开不存在的文件 在打开文件前检查文件是否存在,或使用 pcall 捕获错误
内存不足 not enough memory 内存分配失败 减少内存使用,或使用 pcall 捕获错误

11.2 解决方案示例

除以零错误

1
2
3
4
5
6
7
8
9
10
11
12
13
function safe_divide(a, b)
if b == 0 then
return nil, "Division by zero"
end
return a / b
end

local result, err = safe_divide(10, 0)
if err then
print("Error:", err)
else
print("Result:", result)
end

索引 nil 值错误

1
2
3
4
5
6
7
8
9
10
function safe_get_field(t, field, default)
if t and t[field] then
return t[field]
end
return default
end

local data = nil
local value = safe_get_field(data, "field", "default value")
print("Value:", value)

调用非函数错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function safe_call(f, ...)
if type(f) == "function" then
return pcall(f, ...)
end
return false, "Not a function"
end

local f = nil
local success, result = safe_call(f, 1, 2, 3)
if not success then
print("Error:", result)
else
print("Result:", result)
end

总结

本指南介绍了 Lua 中的错误处理机制,包括基本错误抛出、保护调用(pcall)、扩展保护调用(xpcall)、自定义错误处理、错误对象、断言等功能。通过学习本指南,您应该已经掌握了 Lua 中错误处理的基本技巧和最佳实践。

在实际应用中,错误处理是一项非常重要的技能,它可以提高代码的健壮性和可靠性,减少程序崩溃的可能性,同时也可以提供更友好的用户体验。希望本指南对您有所帮助,祝您在 Lua 编程中取得成功!

error_handling.lua

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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
-- Lua错误处理示例
-- print语句使用英语,注释使用中文

print("=== Lua Error Handling Example ===")

-- 1. 基本错误抛出
print("\n1. Basic Error Throwing")

-- 使用error函数抛出错误
function divide(a, b)
if b == 0 then
error("Division by zero") -- 抛出错误
end
return a / b
end

-- 直接调用会导致程序崩溃
-- print(divide(10, 0))

-- 2. pcall(保护调用)
print("\n2. pcall (Protected Call)")

-- pcall接收一个函数和可变参数,返回布尔值和结果/错误信息
local success, result = pcall(divide, 10, 2)
if success then
print("10 / 2 =", result)
else
print("Error:", result)
end

local success, result = pcall(divide, 10, 0)
if success then
print("10 / 0 =", result)
else
print("Error:", result)
end

-- 使用匿名函数作为参数
local success, result = pcall(function()
return divide(20, 4)
end)
print("20 / 4 =", result)

-- 3. xpcall(扩展保护调用)
print("\n3. xpcall (Extended Protected Call)")

-- xpcall接收两个函数:要执行的函数和错误处理函数
local function error_handler(err)
return "Custom error handler: " .. err .. "\nStack trace: " .. debug.traceback()
end

local success, result = xpcall(function()
return divide(10, 0)
end, error_handler)

if success then
print("Result:", result)
else
print("Error with traceback:", result)
end

-- 4. 自定义错误处理
print("\n4. Custom Error Handling")

-- 创建一个通用的错误处理函数
function safe_call(func, ...)
local args = {...}
return xpcall(function()
return func(unpack(args))
end, function(err)
local trace = debug.traceback()
return {
success = false,
error = err,
traceback = trace,
timestamp = os.time()
}
end)
end

-- 测试自定义错误处理
local success, result = safe_call(divide, 15, 3)
if success then
print("15 / 3 =", result)
else
print("Error:", result)
end

local success, result = safe_call(divide, 10, 0)
if success then
print("Result:", result)
else
print("Error details:")
print(" Success:", result.success)
print(" Error:", result.error)
print(" Timestamp:", os.date("%Y-%m-%d %H:%M:%S", result.timestamp))
-- 只打印traceback的前几行
local trace_lines = {}
for line in string.gmatch(result.traceback, "[^\n]+") do
table.insert(trace_lines, line)
if #trace_lines >= 5 then break end
end
print(" Traceback (first 5 lines):")
for _, line in ipairs(trace_lines) do
print(" " .. line)
end
end

-- 5. 错误对象和错误信息
print("\n5. Error Objects and Messages")

-- 抛出带有详细信息的错误
function process_data(data)
if type(data) ~= "table" then
error({code = 1001, message = "Invalid data type", expected = "table", got = type(data)})
end
if not data.id then
error({code = 1002, message = "Missing required field", field = "id"})
end
return "Data processed successfully: " .. data.id
end

-- 处理带有错误对象的错误
local success, result = pcall(process_data, "not a table")
if not success then
if type(result) == "table" then
print("Structured error:")
print(" Code:", result.code)
print(" Message:", result.message)
print(" Expected:", result.expected)
print(" Got:", result.got)
else
print("Simple error:", result)
end
end

local success, result = pcall(process_data, {name = "test"})
if not success then
if type(result) == "table" then
print("Structured error:")
print(" Code:", result.code)
print(" Message:", result.message)
print(" Field:", result.field)
else
print("Simple error:", result)
end
end

-- 6. 断言(assert)
print("\n6. Assertions")

-- assert(condition, message) 如果condition为false,抛出错误信息
local function safe_convert_to_number(str)
local num = tonumber(str)
assert(num ~= nil, "Invalid number format: " .. str)
return num
end

local success, result = pcall(safe_convert_to_number, "123")
print("Convert '123' to number:", result)

local success, result = pcall(safe_convert_to_number, "abc")
print("Convert 'abc' to number failed:", result)

-- 7. 错误处理的实际应用场景
print("\n7. Practical Error Handling Scenarios")

-- 7.1 配置文件加载
local function load_config(file_path)
local file, err = io.open(file_path, "r")
if not file then
error("Failed to open config file: " .. err)
end

local content = file:read("*a")
file:close()

local config, err = load(content, file_path, "t", {})
if not config then
error("Failed to parse config file: " .. err)
end

local success, result = pcall(config)
if not success then
error("Failed to execute config: " .. result)
end

return result
end

-- 测试配置文件加载(假设配置文件不存在)
local success, result = safe_call(load_config, "non_existent_config.lua")
if not success then
print("Config loading failed:", result.error)
end

-- 7.2 网络请求模拟
local function simulate_network_request(url, timeout)
-- 模拟网络请求可能失败的情况
local random_failure = math.random(1, 5) -- 20% 失败概率
if random_failure == 1 then
error("Network timeout after " .. timeout .. "ms")
end

-- 模拟成功响应
return {
status = 200,
url = url,
data = {message = "Success", timestamp = os.time()}
}
end

-- 测试网络请求
print("\nNetwork request simulation:")
for i = 1, 3 do
local success, result = safe_call(simulate_network_request, "https://example.com/api", 5000)
if success then
print("Request " .. i .. " succeeded:", result.status)
else
print("Request " .. i .. " failed:", result.error)
end
end

-- 8. 嵌套错误处理
print("\n8. Nested Error Handling")

local function outer_function()
print("Entering outer_function")

local success, result = pcall(function()
print("Entering inner function")

-- 内部函数可能抛出错误
local data = nil
data.field = "value" -- 这会导致错误

print("Exiting inner function")
return "Success"
end)

if not success then
print("Inner function error:", result)
-- 可以选择重新抛出错误
-- error("Propagating error from outer_function")
end

print("Exiting outer_function")
return "Outer success"
end

local success, result = pcall(outer_function)
print("Final result:", success and result or result)

-- 9. 资源清理和错误处理
print("\n9. Resource Cleanup and Error Handling")

local function process_with_resources()
-- 模拟打开资源
local resource = {name = "test_resource", opened = true}
print("Resource opened:", resource.name)

-- 使用xpcall确保资源被正确清理
local success, result = xpcall(function()
-- 模拟处理过程中发生错误
error("Error during resource processing")
return "Processed successfully"
end, function(err)
-- 清理资源
resource.opened = false
print("Resource cleaned up:", resource.name)
return err -- 返回原始错误
end)

return success, result
end

local success, result = process_with_resources()
print("Process result:", success and result or result)

print("\n=== End of Error Handling Examples ===")

Lua 文件操作指南

简介

本指南详细介绍了 Lua 中的文件 I/O 操作,包括文件的读写、追加、二进制操作、文件指针操作等功能。通过本指南,您将学习如何在 Lua 中有效地进行文件操作,以及如何处理可能出现的错误。

目录

  1. 写入文件
  2. 读取文件
  3. 追加内容到文件
  4. 文件指针操作
  5. 二进制文件操作
  6. 错误处理
  7. 临时文件
  8. 文件元信息
  9. 批量文件操作
  10. 清理测试文件
  11. 文件操作模式
  12. 常见问题与解决方案

1. 写入文件

在 Lua 中,写入文件的基本步骤是:打开文件、写入内容、关闭文件。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-- 1.1 使用io.open打开文件,模式为写入(w)
local file, err = io.open("test.txt", "w")
if not file then
print("Error opening file for writing:", err)
return
end

-- 1.2 写入内容
file:write("Hello, Lua file I/O!\n")
file:write("This is a test file.\n")
file:write("Line 3: Number = ", 42, "\n")
file:write(string.format("Line 4: Formatted number = %.2f\n", 3.14159))

-- 1.3 关闭文件
file:close()
print("File written successfully.")

说明

  • io.open(filename, mode) 用于打开文件,返回文件句柄和错误信息
  • 模式 "w" 表示写入模式,如果文件不存在则创建,如果存在则覆盖
  • file:write() 用于写入内容,可以接受多个参数
  • string.format() 用于格式化字符串
  • 操作完成后必须使用 file:close() 关闭文件,以释放资源

2. 读取文件

Lua 提供了多种读取文件的方式,包括一次性读取整个文件、逐行读取和按指定格式读取。

示例代码

2.1 一次性读取整个文件

1
2
3
4
5
6
7
8
9
10
local file = io.open("test.txt", "r")
if not file then
print("Error opening file for reading")
return
end

local content = file:read("*a") -- *a 表示读取整个文件
print("Reading entire file:")
print(content)
file:close()

2.2 逐行读取文件

1
2
3
4
5
6
7
8
print("\nReading file line by line:")
local file = io.open("test.txt", "r")
local line_number = 1
for line in file:lines() do
print("Line " .. line_number .. ":", line)
line_number = line_number + 1
end
file:close()

2.3 按指定格式读取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
print("\nReading with specific format:")
local file = io.open("test.txt", "r")
-- 读取前两行
local line1 = file:read("*l") -- *l 表示读取一行
local line2 = file:read("*l")
print("First line:", line1)
print("Second line:", line2)

-- 读取一个数字
local line3 = file:read("*l")
local num = tonumber(string.match(line3, "Number = (%d+)"))
print("Extracted number:", num)

file:close()

说明

  • 模式 "r" 表示读取模式
  • file:read("*a") 读取整个文件内容
  • file:lines() 返回一个迭代器,用于逐行读取文件
  • file:read("*l") 读取一行内容
  • file:read(n) 读取 n 个字符

3. 追加内容到文件

追加内容到文件使用 "a" 模式打开文件。

示例代码

1
2
3
4
5
6
7
8
9
10
local file = io.open("test.txt", "a")  -- a 模式表示追加
file:write("\nAppended line 1.\n")
file:write("Appended line 2.\n")
file:close()

-- 验证追加结果
local file = io.open("test.txt", "r")
print("File content after appending:")
print(file:read("*a"))
file:close()

说明

  • 模式 "a" 表示追加模式,写入的内容会添加到文件末尾
  • 其他操作与写入模式类似

4. 文件指针操作

文件指针操作允许您在文件中移动指针位置,以便从特定位置读取或写入内容。

示例代码

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
local file = io.open("test.txt", "r")

-- 获取当前文件指针位置
local pos = file:seek()
print("Initial file position:", pos)

-- 读取5个字符
local chars = file:read(5)
print("Read 5 characters:", chars)

-- 获取当前位置
pos = file:seek()
print("Position after reading 5 chars:", pos)

-- 移动到文件开头
pos = file:seek("set", 0)
print("Position after seeking to start:", pos)

-- 读取10个字符
chars = file:read(10)
print("Read 10 characters from start:", chars)

-- 移动到文件末尾
pos = file:seek("end")
print("Position at end of file:", pos)

-- 向前移动20个字符
pos = file:seek("cur", -20)
print("Position 20 chars before end:", pos)

-- 读取从当前位置到文件末尾的内容
local remaining = file:read("*a")
print("Remaining content:", remaining)

file:close()

说明

  • file:seek() 返回当前文件指针位置
  • file:seek("set", offset) 从文件开头移动 offset 个字节
  • file:seek("cur", offset) 从当前位置移动 offset 个字节
  • file:seek("end", offset) 从文件末尾移动 offset 个字节

5. 二进制文件操作

二进制文件操作使用 "wb""rb" 模式打开文件。

示例代码

5.1 写入二进制文件

1
2
3
4
5
6
7
8
9
local bin_file = io.open("binary.dat", "wb")
if bin_file then
-- 写入一些二进制数据
bin_file:write("\x48\x65\x6C\x6C\x6F") -- "Hello" in hex
bin_file:write(string.char(0x57, 0x6F, 0x72, 0x6C, 0x64)) -- "World"
bin_file:write("\x00") -- null terminator
bin_file:close()
print("Binary file written successfully.")
end

5.2 读取二进制文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
local bin_file = io.open("binary.dat", "rb")
if bin_file then
local bin_content = bin_file:read("*a")
print("Binary file content (length:", #bin_content, "):")

-- 以十六进制形式打印内容
for i = 1, #bin_content do
local byte = string.byte(bin_content, i)
io.write(string.format("%02X ", byte))
end
print()

-- 转换为字符串
local str_content = bin_content
print("String representation:", str_content)

bin_file:close()
end

说明

  • 模式 "wb" 表示二进制写入模式
  • 模式 "rb" 表示二进制读取模式
  • string.char() 用于将数值转换为字符
  • string.byte() 用于将字符转换为数值

6. 错误处理

在文件操作中,错误处理是非常重要的。Lua 提供了 pcall 函数来捕获和处理错误。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
-- 使用pcall保护文件操作
local success, result = pcall(function()
local file = io.open("non_existent_file.txt", "r")
if not file then
error("File not found")
end
file:close()
end)

if not success then
print("Error caught:", result)
end

说明

  • pcall 函数用于保护调用,捕获可能出现的错误
  • 第一个返回值表示操作是否成功
  • 第二个返回值表示操作结果或错误信息

7. 临时文件

临时文件是一种特殊的文件,在关闭后会自动删除。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- 创建临时文件
local tmp_file = io.tmpfile()
if tmp_file then
tmp_file:write("This is a temporary file.\n")
tmp_file:write("It will be deleted automatically when closed.\n")

-- 移动到文件开头并读取内容
tmp_file:seek("set", 0)
print("Temporary file content:")
print(tmp_file:read("*a"))

tmp_file:close()
print("Temporary file closed and deleted.")
end

说明

  • io.tmpfile() 创建一个临时文件,返回文件句柄
  • 临时文件在关闭后会自动删除
  • 临时文件的内容存储在内存中,适合存储临时数据

8. 文件元信息

在 Lua 中,可以通过执行系统命令来获取文件的元信息。

示例代码

1
2
3
4
5
6
7
8
9
10
-- 使用io.popen执行系统命令获取文件信息
-- 注意:这依赖于操作系统,此处使用Windows命令
local cmd = "dir test.txt | findstr test.txt"
local handle = io.popen(cmd)
if handle then
local result = handle:read("*a")
print("File information (from system command):")
print(result)
handle:close()
end

说明

  • io.popen() 用于执行系统命令,返回一个文件句柄
  • 通过读取这个文件句柄,可以获取命令的输出结果
  • 不同操作系统的命令可能不同,需要根据实际情况调整

9. 批量文件操作

批量文件操作是指一次性处理多个文件的操作,例如创建多个文件、读取多个文件等。

示例代码

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
-- 创建多个测试文件
for i = 1, 3 do
local filename = string.format("batch_file_%d.txt", i)
local file = io.open(filename, "w")
if file then
file:write(string.format("This is batch file #%d\n", i))
file:close()
print("Created file:", filename)
end
end

-- 读取并显示所有批量文件
print("\nReading all batch files:")
for i = 1, 3 do
local filename = string.format("batch_file_%d.txt", i)
local file = io.open(filename, "r")
if file then
print("\n" .. filename .. ":")
print(file:read("*a"))
file:close()

-- 删除创建的文件
os.remove(filename)
print("Deleted file:", filename)
end
end

说明

  • 使用循环来处理多个文件
  • string.format() 用于生成文件名
  • os.remove() 用于删除文件

10. 清理测试文件

在测试完成后,清理创建的测试文件是一个好习惯。

示例代码

1
2
3
4
5
6
7
8
9
10
-- 删除之前创建的文件
local files_to_delete = {"test.txt", "binary.dat"}
for _, filename in ipairs(files_to_delete) do
local success, err = os.remove(filename)
if success then
print("Deleted file:", filename)
else
print("Error deleting file", filename, ":", err)
end
end

说明

  • os.remove() 用于删除文件,返回操作是否成功
  • 可以通过遍历文件列表来删除多个文件

11. 文件操作模式

Lua 支持多种文件操作模式,如下表所示:

模式 描述
“r” 读取模式(默认)
“w” 写入模式,覆盖现有文件
“a” 追加模式,在文件末尾添加内容
“r+” 读写模式,从文件开头开始
“w+” 读写模式,覆盖现有文件
“a+” 读写模式,在文件末尾添加内容
“rb” 二进制读取模式
“wb” 二进制写入模式
“ab” 二进制追加模式
“r+b” 二进制读写模式
“w+b” 二进制读写模式,覆盖现有文件
“a+b” 二进制读写模式,在文件末尾添加内容

12. 常见问题与解决方案

问题:文件打开失败

解决方案:使用 io.open() 的第二个返回值来获取错误信息,并进行适当的处理。

1
2
3
4
5
6
7
local file, err = io.open("filename.txt", "r")
if not file then
print("Error opening file:", err)
return
end
-- 文件操作...
file:close()

问题:文件权限不足

解决方案:确保您有足够的权限访问文件,或者尝试以管理员身份运行程序。

问题:文件路径错误

解决方案:使用绝对路径或相对路径来指定文件位置,确保路径正确。

1
2
3
4
5
-- 使用绝对路径
local file = io.open("C:\\Users\\Username\\Documents\\test.txt", "r")

-- 使用相对路径
local file = io.open("./data/test.txt", "r")

问题:文件句柄未关闭

解决方案:始终在文件操作完成后调用 file:close() 来关闭文件句柄。

1
2
3
4
5
local file = io.open("filename.txt", "r")
if file then
-- 文件操作...
file:close() -- 确保关闭文件
end

总结

本指南介绍了 Lua 中的文件操作功能,包括文件的读写、追加、二进制操作、文件指针操作等。通过学习本指南,您应该已经掌握了 Lua 中文件操作的基本技巧和常见问题的解决方案。

在实际应用中,文件操作是一项非常重要的功能,它允许您的程序与外部文件进行交互,存储和读取数据。希望本指南对您有所帮助,祝您在 Lua 编程中取得成功!

file_io.lua

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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
-- Lua文件操作示例
-- print语句使用英语,注释使用中文

print("=== Lua File I/O Example ===")

-- 1. 写入文件
print("\n1. Writing to File")

-- 1.1 使用io.open打开文件,模式为写入(w)
local file, err = io.open("test.txt", "w")
if not file then
print("Error opening file for writing:", err)
return
end

-- 1.2 写入内容
file:write("Hello, Lua file I/O!\n")
file:write("This is a test file.\n")
file:write("Line 3: Number = ", 42, "\n")
file:write(string.format("Line 4: Formatted number = %.2f\n", 3.14159))

-- 1.3 关闭文件
file:close()
print("File written successfully.")

-- 2. 读取文件
print("\n2. Reading from File")

-- 2.1 一次性读取整个文件
local file = io.open("test.txt", "r")
if not file then
print("Error opening file for reading")
return
end

local content = file:read("*a") -- *a 表示读取整个文件
print("Reading entire file:")
print(content)
file:close()

-- 2.2 逐行读取文件
print("\nReading file line by line:")
local file = io.open("test.txt", "r")
local line_number = 1
for line in file:lines() do
print("Line " .. line_number .. ":", line)
line_number = line_number + 1
end
file:close()

-- 2.3 按指定格式读取
print("\nReading with specific format:")
local file = io.open("test.txt", "r")
-- 读取前两行
local line1 = file:read("*l") -- *l 表示读取一行
local line2 = file:read("*l")
print("First line:", line1)
print("Second line:", line2)

-- 读取一个数字
local line3 = file:read("*l")
local num = tonumber(string.match(line3, "Number = (%d+)"))
print("Extracted number:", num)

file:close()

-- 3. 追加内容到文件
print("\n3. Appending to File")

local file = io.open("test.txt", "a") -- a 模式表示追加
file:write("\nAppended line 1.\n")
file:write("Appended line 2.\n")
file:close()

-- 验证追加结果
local file = io.open("test.txt", "r")
print("File content after appending:")
print(file:read("*a"))
file:close()

-- 4. 文件指针操作
print("\n4. File Pointer Operations")

local file = io.open("test.txt", "r")

-- 获取当前文件指针位置
local pos = file:seek()
print("Initial file position:", pos)

-- 读取5个字符
local chars = file:read(5)
print("Read 5 characters:", chars)

-- 获取当前位置
pos = file:seek()
print("Position after reading 5 chars:", pos)

-- 移动到文件开头
pos = file:seek("set", 0)
print("Position after seeking to start:", pos)

-- 读取10个字符
chars = file:read(10)
print("Read 10 characters from start:", chars)

-- 移动到文件末尾
pos = file:seek("end")
print("Position at end of file:", pos)

-- 向前移动20个字符
pos = file:seek("cur", -20)
print("Position 20 chars before end:", pos)

-- 读取从当前位置到文件末尾的内容
local remaining = file:read("*a")
print("Remaining content:", remaining)

file:close()

-- 5. 二进制文件操作
print("\n5. Binary File Operations")

-- 写入二进制文件
local bin_file = io.open("binary.dat", "wb")
if bin_file then
-- 写入一些二进制数据
bin_file:write("\x48\x65\x6C\x6C\x6F") -- "Hello" in hex
bin_file:write(string.char(0x57, 0x6F, 0x72, 0x6C, 0x64)) -- "World"
bin_file:write("\x00") -- null terminator
bin_file:close()
print("Binary file written successfully.")
end

-- 读取二进制文件
local bin_file = io.open("binary.dat", "rb")
if bin_file then
local bin_content = bin_file:read("*a")
print("Binary file content (length:", #bin_content, "):")

-- 以十六进制形式打印内容
for i = 1, #bin_content do
local byte = string.byte(bin_content, i)
io.write(string.format("%02X ", byte))
end
print()

-- 转换为字符串
local str_content = bin_content
print("String representation:", str_content)

bin_file:close()
end

-- 6. 错误处理
print("\n6. Error Handling")

-- 使用pcall保护文件操作
local success, result = pcall(function()
local file = io.open("non_existent_file.txt", "r")
if not file then
error("File not found")
end
file:close()
end)

if not success then
print("Error caught:", result)
end

-- 7. 临时文件
print("\n7. Temporary Files")

-- 创建临时文件
local tmp_file = io.tmpfile()
if tmp_file then
tmp_file:write("This is a temporary file.\n")
tmp_file:write("It will be deleted automatically when closed.\n")

-- 移动到文件开头并读取内容
tmp_file:seek("set", 0)
print("Temporary file content:")
print(tmp_file:read("*a"))

tmp_file:close()
print("Temporary file closed and deleted.")
end

-- 8. 文件元信息
print("\n8. File Metadata")

-- 使用io.popen执行系统命令获取文件信息
-- 注意:这依赖于操作系统,此处使用Windows命令
local cmd = "dir test.txt | findstr test.txt"
local handle = io.popen(cmd)
if handle then
local result = handle:read("*a")
print("File information (from system command):")
print(result)
handle:close()
end

-- 9. 批量文件操作
print("\n9. Batch File Operations")

-- 创建多个测试文件
for i = 1, 3 do
local filename = string.format("batch_file_%d.txt", i)
local file = io.open(filename, "w")
if file then
file:write(string.format("This is batch file #%d\n", i))
file:close()
print("Created file:", filename)
end
end

-- 读取并显示所有批量文件
print("\nReading all batch files:")
for i = 1, 3 do
local filename = string.format("batch_file_%d.txt", i)
local file = io.open(filename, "r")
if file then
print("\n" .. filename .. ":")
print(file:read("*a"))
file:close()

-- 删除创建的文件
os.remove(filename)
print("Deleted file:", filename)
end
end

-- 10. 清理测试文件
print("\n10. Cleaning Up Test Files")

-- 删除之前创建的文件
local files_to_delete = {"test.txt", "binary.dat"}
for _, filename in ipairs(files_to_delete) do
local success, err = os.remove(filename)
if success then
print("Deleted file:", filename)
else
print("Error deleting file", filename, ":", err)
end
end

print("\n=== End of File Operation Examples ===")

mymodule 数学工具模块教程

本教程基于 mymodule.luamodule_example.lua 文件,详细介绍 mymodule 数学工具模块的使用方法和功能。

目录

  1. 模块概述
  2. 模块常量
  3. 模块函数
  4. 模块的导入和使用
  5. 模块函数的错误处理
  6. 模块的扩展
  7. 模块的缓存机制
  8. 示例代码
  9. 最佳实践

1. 模块概述

mymodule 是一个 Lua 数学工具模块,提供了各种数学计算功能,包括几何计算、数学函数、素数判断、斐波那契数列生成、最大公约数和最小公倍数计算、角度和弧度转换等。

该模块采用了 Lua 标准的模块编写方式,通过返回一个包含所有函数和常量的表来实现。模块内部还包含了参数验证功能,确保函数接收到有效的输入。

2. 模块常量

mymodule 模块定义了以下常量:

常量名 描述
PI 3.14159 圆周率
E 2.71828 自然对数的底
GRAVITY 9.81 重力加速度

访问模块常量

1
2
3
4
5
6
7
-- 导入模块
local math_module = require("mymodule")

-- 访问模块常量
print("PI:", math_module.PI)
print("E:", math_module.E)
print("GRAVITY:", math_module.GRAVITY)

3. 模块函数

3.1 几何计算

计算圆的面积

1
2
3
4
5
6
function mymodule.circle_area(radius)
-- 计算圆的面积
-- 参数: radius - 圆的半径(非负数)
-- 返回值: 圆的面积
-- 错误: 如果半径无效(不是数字或为负数),抛出错误
end

示例

1
2
3
local circle_radius = 5
local circle_area = math_module.circle_area(circle_radius)
print("Circle area with radius " .. circle_radius .. ":", circle_area)

计算矩形的面积

1
2
3
4
5
6
7
function mymodule.rectangle_area(width, height)
-- 计算矩形的面积
-- 参数: width - 矩形的宽度(非负数)
-- height - 矩形的高度(非负数)
-- 返回值: 矩形的面积
-- 错误: 如果参数无效(不是数字或为负数),抛出错误
end

示例

1
2
3
local rect_width, rect_height = 4, 6
local rect_area = math_module.rectangle_area(rect_width, rect_height)
print("Rectangle area (" .. rect_width .. "x" .. rect_height .. "):", rect_area)

计算三角形的面积

1
2
3
4
5
6
7
function mymodule.triangle_area(base, height)
-- 计算三角形的面积
-- 参数: base - 三角形的底(非负数)
-- height - 三角形的高(非负数)
-- 返回值: 三角形的面积
-- 错误: 如果参数无效(不是数字或为负数),抛出错误
end

示例

1
2
3
local tri_base, tri_height = 3, 7
local tri_area = math_module.triangle_area(tri_base, tri_height)
print("Triangle area (base=" .. tri_base .. ", height=" .. tri_height .. "):", tri_area)

3.2 数学函数

计算阶乘

1
2
3
4
5
6
function mymodule.factorial(n)
-- 计算阶乘
-- 参数: n - 非负整数
-- 返回值: n的阶乘
-- 错误: 如果参数无效(不是数字或为负数),抛出错误
end

示例

1
2
3
local fact_num = 5
local fact_result = math_module.factorial(fact_num)
print("Factorial of " .. fact_num .. ":", fact_result)

3.3 素数判断

1
2
3
4
5
6
function mymodule.is_prime(n)
-- 判断是否为素数
-- 参数: n - 整数
-- 返回值: 如果n是素数,返回true;否则返回false
-- 错误: 如果参数不是数字,抛出错误
end

示例

1
2
3
4
5
local test_nums = {2, 3, 4, 17, 20, 23, 100}
for _, num in ipairs(test_nums) do
local is_prime = math_module.is_prime(num)
print(num .. " is prime?", is_prime)
end

3.4 斐波那契数列

1
2
3
4
5
6
7
function mymodule.fibonacci(n)
-- 生成斐波那契数列
-- 参数: n - 数列长度(正整数)
-- 返回值: 包含前n个斐波那契数的表
-- 错误: 如果参数不是数字,抛出错误
-- 注意: 如果n <= 0,返回空表
end

示例

1
2
3
local fib_count = 10
local fib_sequence = math_module.fibonacci(fib_count)
print("First " .. fib_count .. " Fibonacci numbers:", table.concat(fib_sequence, ", "))

3.5 最大公约数和最小公倍数

计算最大公约数

1
2
3
4
5
6
function mymodule.gcd(a, b)
-- 计算两个数的最大公约数
-- 参数: a, b - 两个整数
-- 返回值: a和b的最大公约数
-- 错误: 如果参数不是数字,抛出错误
end

示例

1
2
3
local a, b = 24, 36
local gcd_result = math_module.gcd(a, b)
print("GCD of " .. a .. " and " .. b .. ":", gcd_result)

计算最小公倍数

1
2
3
4
5
6
7
function mymodule.lcm(a, b)
-- 计算两个数的最小公倍数
-- 参数: a, b - 两个整数
-- 返回值: a和b的最小公倍数
-- 错误: 如果参数不是数字,抛出错误
-- 注意: 如果a或b为0,返回0
end

示例

1
2
3
local a, b = 24, 36
local lcm_result = math_module.lcm(a, b)
print("LCM of " .. a .. " and " .. b .. ":", lcm_result)

3.6 角度和弧度转换

将角度转换为弧度

1
2
3
4
5
6
function mymodule.deg_to_rad(degrees)
-- 将角度转换为弧度
-- 参数: degrees - 角度值
-- 返回值: 对应的弧度值
-- 错误: 如果参数不是数字,抛出错误
end

示例

1
2
3
local degrees = 90
local radians = math_module.deg_to_rad(degrees)
print(degrees .. " degrees = ", radians, " radians")

将弧度转换为角度

1
2
3
4
5
6
function mymodule.rad_to_deg(radians)
-- 将弧度转换为角度
-- 参数: radians - 弧度值
-- 返回值: 对应的角度值
-- 错误: 如果参数不是数字,抛出错误
end

示例

1
2
3
local radians_val = math_module.PI / 2
local degrees_val = math_module.rad_to_deg(radians_val)
print(radians_val .. " radians = ", degrees_val, " degrees")

4. 模块的导入和使用

4.1 完整导入

最简单的方法是完整导入整个模块,这样可以访问模块的所有常量和函数。

1
2
3
4
5
6
7
8
9
-- 导入模块
local math_module = require("mymodule")

-- 使用模块常量
print("PI:", math_module.PI)

-- 使用模块函数
local area = math_module.circle_area(5)
print("Circle area:", area)

4.2 选择性导入常用函数

如果只需要使用模块中的部分函数,可以选择性地导入这些函数,这样可以减少代码中的重复前缀。

1
2
3
4
5
6
7
8
9
-- 导入模块
local math_module = require("mymodule")

-- 选择性导入常用函数
local is_prime, factorial = math_module.is_prime, math_module.factorial

-- 直接使用导入的函数
print("is_prime(17):", is_prime(17))
print("factorial(6):", factorial(6))

4.3 重命名导入的模块

如果觉得模块名称太长,可以在导入时给模块指定一个简短的别名。

1
2
3
4
5
6
-- 重命名导入的模块
local mm = require("mymodule")

-- 使用别名访问模块的常量和函数
print("mm.PI:", mm.PI)
print("mm.circle_area(3):", mm.circle_area(3))

5. 模块函数的错误处理

mymodule 模块中的函数会对输入参数进行验证,如果参数无效,会抛出错误。为了避免程序因为错误而终止,应该使用 pcall 函数来捕获和处理这些错误。

基本错误处理

1
2
3
4
5
6
7
-- 使用pcall捕获错误
local success, result = pcall(math_module.circle_area, -5)
if not success then
print("Error caught:", result)
else
print("Circle area:", result)
end

批量测试错误情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- 测试多个错误情况
local test_cases = {
{func = math_module.rectangle_area, args = {"invalid", 5}, desc = "Invalid width (string)"},
{func = math_module.triangle_area, args = {3, -2}, desc = "Negative height"},
{func = math_module.factorial, args = {-1}, desc = "Negative factorial"}
}

for _, case in ipairs(test_cases) do
local success, err = pcall(case.func, unpack(case.args))
if not success then
print(case.desc .. " error:", err)
else
print(case.desc .. " result:", err)
end
end

6. 模块的扩展

在 Lua 中,可以在导入模块后为其添加新的函数或修改现有的函数,这种扩展只在当前 Lua 环境中有效,不会修改原始模块文件。

添加新函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-- 导入模块
local math_module = require("mymodule")

-- 添加新函数:计算正方形的面积
function math_module.quadratic_area(side)
-- 验证参数
if type(side) ~= "number" or side < 0 then
error("Invalid side: must be a non-negative number")
end
return side * side
end

-- 使用新函数
local square_side = 4
local square_area = math_module.quadratic_area(square_side)
print("Square area with side " .. square_side .. ":", square_area)

修改现有函数

1
2
3
4
5
6
7
8
9
10
11
12
-- 保存原始函数
local original_circle_area = math_module.circle_area

-- 修改函数:添加日志输出
function math_module.circle_area(radius)
print("Calculating circle area with radius:", radius)
return original_circle_area(radius)
end

-- 使用修改后的函数
local area = math_module.circle_area(5)
print("Circle area:", area)

7. 模块的缓存机制

Lua 会缓存已导入的模块,多次 require 同一个模块只会返回同一个实例,不会重复加载模块文件。

1
2
3
4
5
6
7
8
-- 第一次导入模块
local module1 = require("mymodule")

-- 第二次导入同一模块(返回缓存的实例)
local module2 = require("mymodule")

-- 验证两个变量引用的是同一个实例
print("Are module1 and module2 the same instance?", module1 == module2) -- 输出: true

缓存机制的影响

  1. 优点:提高性能,避免重复加载和初始化模块
  2. 注意:如果在运行时修改了模块,所有引用该模块的地方都会受到影响

8. 示例代码

完整示例:使用 mymodule 模块进行各种计算

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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
-- Lua模块使用示例
-- print语句使用英语,注释使用中文

print("=== Lua Module Usage Example ===")

-- 1. 导入模块
print("\n1. Importing Module")
local math_module = require("mymodule")

-- 2. 访问模块常量
print("\n2. Accessing Module Constants")
print("PI:", math_module.PI)
print("E:", math_module.E)
print("GRAVITY:", math_module.GRAVITY)

-- 3. 调用模块函数
print("\n3. Calling Module Functions")

-- 3.1 几何计算
print("\n3.1 Geometric Calculations")
local circle_radius = 5
local circle_area = math_module.circle_area(circle_radius)
print("Circle area with radius " .. circle_radius .. ":", circle_area)

local rect_width, rect_height = 4, 6
local rect_area = math_module.rectangle_area(rect_width, rect_height)
print("Rectangle area (" .. rect_width .. "x" .. rect_height .. "):", rect_area)

local tri_base, tri_height = 3, 7
local tri_area = math_module.triangle_area(tri_base, tri_height)
print("Triangle area (base=" .. tri_base .. ", height=" .. tri_height .. "):", tri_area)

-- 3.2 数学函数
print("\n3.2 Mathematical Functions")
local fact_num = 5
local fact_result = math_module.factorial(fact_num)
print("Factorial of " .. fact_num .. ":", fact_result)

-- 3.3 素数判断
print("\n3.3 Prime Number Check")
local test_nums = {2, 3, 4, 17, 20, 23, 100}
for _, num in ipairs(test_nums) do
local is_prime = math_module.is_prime(num)
print(num .. " is prime?", is_prime)
end

-- 3.4 斐波那契数列
print("\n3.4 Fibonacci Sequence")
local fib_count = 10
local fib_sequence = math_module.fibonacci(fib_count)
print("First " .. fib_count .. " Fibonacci numbers:", table.concat(fib_sequence, ", "))

-- 3.5 最大公约数和最小公倍数
print("\n3.5 Greatest Common Divisor and Least Common Multiple")
local a, b = 24, 36
local gcd_result = math_module.gcd(a, b)
local lcm_result = math_module.lcm(a, b)
print("GCD of " .. a .. " and " .. b .. ":", gcd_result)
print("LCM of " .. a .. " and " .. b .. ":", lcm_result)

-- 3.6 角度和弧度转换
print("\n3.6 Degree and Radian Conversion")
local degrees = 90
local radians = math_module.deg_to_rad(degrees)
print(degrees .. " degrees = ", radians, " radians")

local radians_val = math_module.PI / 2
local degrees_val = math_module.rad_to_deg(radians_val)
print(radians_val .. " radians = ", degrees_val, " degrees")

-- 4. 不同的模块导入方式
print("\n4. Different Module Import Methods")

-- 4.1 完整导入(已演示)
-- local math_module = require("mymodule")

-- 4.2 选择性导入常用函数
print("\n4.2 Selective Function Import")
local is_prime, factorial = math_module.is_prime, math_module.factorial
print("Using selectively imported functions:")
print("is_prime(17):", is_prime(17))
print("factorial(6):", factorial(6))

-- 4.3 重命名导入的模块
print("\n4.3 Renaming Imported Module")
local mm = require("mymodule") -- 简短别名
print("Using renamed module (mm):")
print("mm.PI:", mm.PI)
print("mm.circle_area(3):", mm.circle_area(3))

-- 5. 模块函数的错误处理
print("\n5. Error Handling in Module Functions")

-- 使用pcall捕获错误
local success, result = pcall(math_module.circle_area, -5)
if not success then
print("Error caught:", result)
end

-- 测试其他错误情况
local test_cases = {
{func = math_module.rectangle_area, args = {"invalid", 5}, desc = "Invalid width (string)"},
{func = math_module.triangle_area, args = {3, -2}, desc = "Negative height"},
{func = math_module.factorial, args = {-1}, desc = "Negative factorial"}
}

for _, case in ipairs(test_cases) do
local success, err = pcall(case.func, unpack(case.args))
if not success then
print(case.desc .. " error:", err)
end
end

-- 6. 多次导入同一模块
print("\n6. Multiple Imports of the Same Module")
-- Lua会缓存已导入的模块,多次require只会返回同一个实例
local module1 = require("mymodule")
local module2 = require("mymodule")

print("Are module1 and module2 the same instance?", module1 == module2)

-- 7. 模块的扩展(在使用处添加新函数)
print("\n7. Module Extension")
-- 可以在导入后为模块添加新函数
function math_module.quadratic_area(side)
return side * side
end

local square_side = 4
local square_area = math_module.quadratic_area(square_side)
print("Square area with side " .. square_side .. ":", square_area)

-- 注意:这种扩展只在当前 Lua 环境中有效,不会修改原始模块文件

print("\n=== End of Module Usage Example ===")

9. 最佳实践

  1. 错误处理:始终使用 pcall 捕获模块函数可能抛出的错误
  2. 模块导入:根据需要选择合适的导入方式(完整导入、选择性导入或重命名导入)
  3. 参数验证:在调用模块函数前,确保提供有效的参数
  4. 模块扩展:谨慎扩展或修改模块,因为这些修改会影响所有引用该模块的代码
  5. 代码组织:将相关功能组织到模块中,提高代码的可维护性和可重用性

性能优化建议

  1. 避免重复导入:在文件顶部导入模块一次,然后在整个文件中使用
  2. 选择性导入:对于频繁使用的函数,考虑选择性导入以减少代码量
  3. 缓存模块实例:如果在多个地方使用同一模块,缓存模块实例以避免重复查找

总结

mymodule 是一个功能丰富的数学工具模块,提供了多种数学计算功能,包括几何计算、数学函数、素数判断、斐波那契数列生成、最大公约数和最小公倍数计算、角度和弧度转换等。

通过本教程的学习,您应该已经掌握了 mymodule 模块的使用方法,包括:

  • 如何导入和使用模块
  • 如何访问模块的常量和函数
  • 如何处理模块函数可能抛出的错误
  • 如何扩展模块功能
  • 模块的缓存机制及其影响

模块是 Lua 中组织代码的重要方式,合理使用模块可以提高代码的可维护性、可重用性和可读性。希望本教程对您有所帮助!

mymodule.lua

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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
-- Lua模块示例
-- print语句使用英语,注释使用中文

-- 定义一个数学工具模块
local mymodule = {}

-- 模块常量
mymodule.PI = 3.14159
mymodule.E = 2.71828
mymodule.GRAVITY = 9.81

-- 模块私有函数(以下划线开头表示私有)
local function _validate_number(num)
return type(num) == "number" and num == num -- 检查是否为有效数字(排除NaN)
end

-- 模块函数:计算圆的面积
function mymodule.circle_area(radius)
if not _validate_number(radius) or radius < 0 then
error("Invalid radius: must be a non-negative number")
end
return mymodule.PI * radius * radius
end

-- 模块函数:计算矩形的面积
function mymodule.rectangle_area(width, height)
if not _validate_number(width) or not _validate_number(height) then
error("Invalid dimensions: must be numbers")
end
if width < 0 or height < 0 then
error("Invalid dimensions: must be non-negative")
end
return width * height
end

-- 模块函数:计算三角形的面积
function mymodule.triangle_area(base, height)
if not _validate_number(base) or not _validate_number(height) then
error("Invalid dimensions: must be numbers")
end
if base < 0 or height < 0 then
error("Invalid dimensions: must be non-negative")
end
return 0.5 * base * height
end

-- 模块函数:计算阶乘
function mymodule.factorial(n)
if not _validate_number(n) then
error("Invalid input: must be a number")
end
if n < 0 then
error("Invalid input: must be non-negative")
end
if n == 0 or n == 1 then
return 1
end
local result = 1
for i = 2, n do
result = result * i
end
return result
end

-- 模块函数:判断是否为素数
function mymodule.is_prime(n)
if not _validate_number(n) then
error("Invalid input: must be a number")
end
if n <= 1 then
return false
end
if n <= 3 then
return true
end
if n % 2 == 0 or n % 3 == 0 then
return false
end
local i = 5
while i * i <= n do
if n % i == 0 or n % (i + 2) == 0 then
return false
end
i = i + 6
end
return true
end

-- 模块函数:生成斐波那契数列
function mymodule.fibonacci(n)
if not _validate_number(n) then
error("Invalid input: must be a number")
end
if n <= 0 then
return {}
end
local fib = {1}
if n > 1 then
table.insert(fib, 1)
for i = 3, n do
table.insert(fib, fib[i-1] + fib[i-2])
end
end
return fib
end

-- 模块函数:计算两个数的最大公约数
function mymodule.gcd(a, b)
if not _validate_number(a) or not _validate_number(b) then
error("Invalid input: must be numbers")
end
a = math.abs(a)
b = math.abs(b)
while b ~= 0 do
a, b = b, a % b
end
return a
end

-- 模块函数:计算两个数的最小公倍数
function mymodule.lcm(a, b)
if not _validate_number(a) or not _validate_number(b) then
error("Invalid input: must be numbers")
end
if a == 0 or b == 0 then
return 0
end
return math.abs(a * b) / mymodule.gcd(a, b)
end

-- 模块函数:将角度转换为弧度
function mymodule.deg_to_rad(degrees)
if not _validate_number(degrees) then
error("Invalid input: must be a number")
end
return degrees * mymodule.PI / 180
end

-- 模块函数:将弧度转换为角度
function mymodule.rad_to_deg(radians)
if not _validate_number(radians) then
error("Invalid input: must be a number")
end
return radians * 180 / mymodule.PI
end

-- 返回模块表
return mymodule

module_example.lua

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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
-- Lua模块使用示例
-- print语句使用英语,注释使用中文

print("=== Lua Module Usage Example ===")

-- 1. 导入模块
print("\n1. Importing Module")
local math_module = require("mymodule")

-- 2. 访问模块常量
print("\n2. Accessing Module Constants")
print("PI:", math_module.PI)
print("E:", math_module.E)
print("GRAVITY:", math_module.GRAVITY)

-- 3. 调用模块函数
print("\n3. Calling Module Functions")

-- 3.1 几何计算
print("\n3.1 Geometric Calculations")
local circle_radius = 5
local circle_area = math_module.circle_area(circle_radius)
print("Circle area with radius " .. circle_radius .. ":", circle_area)

local rect_width, rect_height = 4, 6
local rect_area = math_module.rectangle_area(rect_width, rect_height)
print("Rectangle area (" .. rect_width .. "x" .. rect_height .. "):", rect_area)

local tri_base, tri_height = 3, 7
local tri_area = math_module.triangle_area(tri_base, tri_height)
print("Triangle area (base=" .. tri_base .. ", height=" .. tri_height .. "):", tri_area)

-- 3.2 数学函数
print("\n3.2 Mathematical Functions")
local fact_num = 5
local fact_result = math_module.factorial(fact_num)
print("Factorial of " .. fact_num .. ":", fact_result)

-- 3.3 素数判断
print("\n3.3 Prime Number Check")
local test_nums = {2, 3, 4, 17, 20, 23, 100}
for _, num in ipairs(test_nums) do
local is_prime = math_module.is_prime(num)
print(num .. " is prime?", is_prime)
end

-- 3.4 斐波那契数列
print("\n3.4 Fibonacci Sequence")
local fib_count = 10
local fib_sequence = math_module.fibonacci(fib_count)
print("First " .. fib_count .. " Fibonacci numbers:", table.concat(fib_sequence, ", "))

-- 3.5 最大公约数和最小公倍数
print("\n3.5 Greatest Common Divisor and Least Common Multiple")
local a, b = 24, 36
local gcd_result = math_module.gcd(a, b)
local lcm_result = math_module.lcm(a, b)
print("GCD of " .. a .. " and " .. b .. ":", gcd_result)
print("LCM of " .. a .. " and " .. b .. ":", lcm_result)

-- 3.6 角度和弧度转换
print("\n3.6 Degree and Radian Conversion")
local degrees = 90
local radians = math_module.deg_to_rad(degrees)
print(degrees .. " degrees = ", radians, " radians")

local radians_val = math_module.PI / 2
local degrees_val = math_module.rad_to_deg(radians_val)
print(radians_val .. " radians = ", degrees_val, " degrees")

-- 4. 不同的模块导入方式
print("\n4. Different Module Import Methods")

-- 4.1 完整导入(已演示)
-- local math_module = require("mymodule")

-- 4.2 选择性导入常用函数
print("\n4.2 Selective Function Import")
local is_prime, factorial = math_module.is_prime, math_module.factorial
print("Using selectively imported functions:")
print("is_prime(17):", is_prime(17))
print("factorial(6):", factorial(6))

-- 4.3 重命名导入的模块
print("\n4.3 Renaming Imported Module")
local mm = require("mymodule") -- 简短别名
print("Using renamed module (mm):")
print("mm.PI:", mm.PI)
print("mm.circle_area(3):", mm.circle_area(3))

-- 5. 模块函数的错误处理
print("\n5. Error Handling in Module Functions")

-- 使用pcall捕获错误
local success, result = pcall(math_module.circle_area, -5)
if not success then
print("Error caught:", result)
end

-- 测试其他错误情况
local test_cases = {
{func = math_module.rectangle_area, args = {"invalid", 5}, desc = "Invalid width (string)"},
{func = math_module.triangle_area, args = {3, -2}, desc = "Negative height"},
{func = math_module.factorial, args = {-1}, desc = "Negative factorial"}
}

for _, case in ipairs(test_cases) do
local success, err = pcall(case.func, unpack(case.args))
if not success then
print(case.desc .. " error:", err)
end
end

-- 6. 多次导入同一模块
print("\n6. Multiple Imports of the Same Module")
-- Lua会缓存已导入的模块,多次require只会返回同一个实例
local module1 = require("mymodule")
local module2 = require("mymodule")

print("Are module1 and module2 the same instance?", module1 == module2)

-- 7. 模块的扩展(在使用处添加新函数)
print("\n7. Module Extension")
-- 可以在导入后为模块添加新函数
function math_module.quadratic_area(side)
return side * side
end

local square_side = 4
local square_area = math_module.quadratic_area(square_side)
print("Square area with side " .. square_side .. ":", square_area)

-- 注意:这种扩展只在当前 Lua 环境中有效,不会修改原始模块文件

print("\n=== End of Module Usage Example ===")