编程中的无限数组、模块划分与古老编译器
1. 使用无限数组生成直方图
直方图程序
hist
旨在使用无限数组包。直方图是数据项重复频率的图形化表示。该程序以一个文件作为参数,文件中包含 0 到 99 的数字列表,可包含任意数量的条目。程序会打印出直方图,展示每个数字出现的次数。
典型的输出行如下:
5 ( 6): ***************************
第一个数字(5)是行索引,在示例数据中,值为 5 的条目有 6 个,星号行以图形方式表示这 6 个条目。
有些数据超出范围,不会在直方图中表示,这些数据会被计数并列在打印输出的末尾。示例打印输出如下:
1 ( 9): ************************
2 ( 15): ****************************************
3 ( 9): ************************
4 ( 19):
***************************************************
5 ( 13): ***********************************
6 ( 14): **************************************
7 ( 14): **************************************
8 ( 14): **************************************
9 ( 20):
******************************************************
10 ( 13): ***********************************
11 ( 14): **************************************
12 ( 9): ************************
13 ( 13): ***********************************
14 ( 12): ********************************
15 ( 14): **************************************
16 ( 16): *******************************************
17 ( 9): ************************
18 ( 13): ***********************************
19 ( 15): ****************************************
20 ( 11): ******************************
21 ( 22):
********************************************************
****
22 ( 14): **************************************
23 ( 9): ************************
24 ( 10): ***************************
25 ( 15): ****************************************
26 ( 10): ***************************
27 ( 12): ********************************
28 ( 14): **************************************
29 ( 15): ****************************************
30 ( 9): ************************
104 items out of range
程序使用库函数
memset
来初始化计数器数组,该函数能高效地将数组的所有值设置为 0。代码如下:
memset(counters, '\0', sizeof(counters));
sizeof(counters)
确保整个数组都被清零。
以下是
hist.c
的完整代码:
/*******************************************************
*
* hist -- Generates a histogram of an array of
numbers.*
*
*
* Usage
*
* hist <file>
*
*
*
* Where
*
* file is the name of the file to work on.
*
********************************************************
/
#include "ia.h"
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
/*
* Define the number of lines in the histogram
*/
#define NUMBER_OF_LINES 30 /* Number of lines in
the histogram */
const int DATA_MIN = 1; /* Number of the
smallest item */
const int DATA_MAX = 30; /* Number of the largest
item */
/*
* WARNING: The number of items from DATA_MIN to
DATA_MAX (inclusive)
* must match the number of lines.
*/
/* number of characters wide to make the histogram */
const int WIDTH = 60;
static struct infinite_array data_array;
static int data_items;
int main(int argc, char *argv[])
{
/* Function to read data */
void read_data(const char name[]);
/* Function to print the histogram */
void print_histogram(void);
if (argc != 2) {
fprintf(stderr, "Error:Wrong number of
arguments\n");
fprintf(stderr, "Usage is:\n");
fprintf(stderr, " hist <data-file>\n");
exit(8);
}
ia_init(&data_array);
data_items = 0;
read_data(argv[1]);
print_histogram();
return (0);
}
/*******************************************************
*
* read_data -- Reads data from the input file into
*
* the data_array.
*
*
*
* Parameters
*
* name -- The name of the file to read.
*
********************************************************
/
void read_data(const char name[])
{
char line[100]; /* line from input file */
FILE *in_file; /* input file */
int data; /* data from input */
in_file = fopen(name, "r");
if (in_file == NULL) {
fprintf(stderr, "Error:Unable to open %s\n",
name);
exit(8);
}
while (1) {
if (fgets(line, sizeof(line), in_file) == NULL)
break;
if (sscanf(line, "%d", &data) != 1) {
fprintf(stderr,
"Error: Input data not integer number\n");
fprintf(stderr, "Line:%s", line);
}
ia_store(&data_array, data_items, data);
++data_items;
}
fclose(in_file);
}
/*******************************************************
*
* print_histogram -- Prints the histogram output.
*
********************************************************
/
void print_histogram(void)
{
/* upper bound for printout */
int counters[NUMBER_OF_LINES];
int out_of_range = 0;/* number of items out of
bounds */
int max_count = 0;/* biggest counter */
float scale; /* scale for outputting dots */
int index; /* index into the data */
memset(counters, '\0', sizeof(counters));
for (index = 0; index < data_items; ++index) {
int data;/* data for this point */
data = ia_get(&data_array, index);
if ((data < DATA_MIN) || (data > DATA_MAX))
++out_of_range;
else {
++counters[data - DATA_MIN];
if (counters[data - DATA_MIN] > max_count)
max_count = counters[data - DATA_MIN];
}
}
scale = ((float) max_count) / ((float) WIDTH);
for (index = 0; index < NUMBER_OF_LINES; ++index) {
/* index for outputting the dots */
int char_index;
int number_of_dots; /* number of * to output
*/
printf("%2d (%4d): ", index + DATA_MIN,
counters[index]);
number_of_dots = (int) (((float)
counters[index]) / scale);
for (char_index = 0;
char_index < number_of_dots;
++char_index) {
printf("*");
}
printf("\n");
}
printf("%d items out of range\n", out_of_range);
}
不同编译器的
Makefile
如下:
-
UNIX Generic C
:
#-----------------------------------------------#
# Makefile for UNIX systems #
# using a GNU C compiler. #
#-----------------------------------------------#
CC=cc
CFLAGS=-g
#
# Compiler flags:
# -g -- Enable debugging
ia: ia.c
$(CC) $(CFLAGS) -o ia ia.c
clean:
rm -f ia
- Free Software Foundation’s gcc :
[File: ia/makefile.gcc]
#-----------------------------------------------#
# Makefile for UNIX systems #
# using a GNU C compiler. #
#-----------------------------------------------#
CC=gcc
CFLAGS=-g -Wall -D__USE_FIXED_PROTOTYPES__ -ansi
all: hist
hist: hist.o ia.o
$(CC) $(CFLAGS) -o hist hist.o ia.o
hist.o: hist.c ia.h
ia.o: ia.c ia.h
clean:
rm -f hist hist.o ia.o
- Turbo C++ :
[File: ia/makefile.tcc]
#-----------------------------------------------#
# Makefile for DOS systems #
# using a Turbo C++ compiler. #
#-----------------------------------------------#
CC=tcc
CFLAGS=-v -w -ml
all: hist.exe
hist.exe: hist.obj ia.obj ia.h
$(CC) $(CFLAGS) -ehist hist.obj ia.obj
hist.obj: hist.c ia.h
$(CC) $(CFLAGS) -c hist.c
ia.obj: ia.c ia.h
$(CC) $(CFLAGS) -c ia.c
clean:
del hist.exe hist.obj ia.obj
- Borland C++ :
[File: ia/makefile.bcc]
#-----------------------------------------------#
# Makefile for DOS systems #
# using a Borland C++ compiler. #
#-----------------------------------------------#
CC=bcc
CFLAGS=-v -w -ml
all: hist.exe
hist.exe: hist.obj ia.obj ia.h
$(CC) $(CFLAGS) -ehist hist.obj ia.obj
hist.obj: hist.c ia.h
$(CC) $(CFLAGS) -c hist.c
ia.obj: ia.c ia.h
$(CC) $(CFLAGS) -c ia.c
clean:
del hist.exe
del hist.obj
del ia.obj
- Microsoft Visual C++ :
[File: ia/makefile.msc]
#-----------------------------------------------#
# Makefile for DOS systems #
# Microsoft Visual C++ Compiler. #
#-----------------------------------------------#
#
CC=cl
#
# Flags
# AL -- Compile for large model
# Zi -- Enable debugging
# W1 -- Turn on warnings
#
CFLAGS=/AL /Zi /W1
SRC=hist.c ia.cpp
OBJ=hist.obj ia.obj
all: hist.exe
hist.exe: $(OBJ)
$(CC) $(CFLAGS) $(OBJ)
hist.obj: ia.h hist.c
$(CC) $(CFLAGS) -c hist.c
ia.obj: ia.h ia.c
$(CC) $(CFLAGS) -c ia.c
clean:
erase hist.exe io.obj hist.obj
2. 任务模块划分
计算机编程更像是一门艺术而非科学,没有严格的规则来指导如何将任务划分为模块。判断一个模块好坏需要经验和实践。
信息是任何程序的关键部分,设计程序的关键在于确定使用的信息以及要对其进行的处理。在设计开始前,应分析信息流程。
模块设计应尽量减少模块间传递的信息量。例如,军队组织被划分为步兵、炮兵、坦克部队等模块,模块间的信息传递量被最小化。一个步兵中士想让炮兵轰炸敌方阵地,只需告知炮兵指挥部“Y - 94 位置有一个碉堡,摧毁它”,炮兵指挥官会处理具体细节。
程序也应如此组织,信息隐藏是良好编程的关键。一个模块应只公开完成任务所需的最少函数和数据,接口越小越简单,越容易使用,且风险更低、更不易出错。小而简单的接口也更易于设计、测试和维护,数据隐藏和良好的接口设计是打造优秀模块的关键。
3. 模块划分示例
- 文本编辑器 :文本编辑器允许用户显示和更改文本文件,大多数编辑器以显示为导向,持续在屏幕上显示当前文件的约 25 行。它还需解释用户输入的命令,这些命令信息需被解析,以便计算机理解并执行相应操作。各个命令功能相似,为命令执行模块施加标准结构可提高可读性和可靠性。
文本编辑器的不同模块间通信极少。显示管理器只需知道光标位置和文件当前状态;文件处理程序只需读取文件、写入文件并跟踪更改,所有编辑命令都可分解为一系列插入和删除操作;命令模块需将复杂的用户命令转换为文件处理程序能处理的简单插入和删除操作。实际上,命令解码器和显示管理器之间没有信息传递。文字处理器可看作高级文本编辑器,简单编辑器只需处理 ASCII 字符(一种字体、一种大小),而文字处理器需处理多种不同的字体和大小。
其模块关系可以用以下 mermaid 流程图表示:
graph LR;
A[用户] -- 输入命令 --> B[命令模块];
B -- 插入/删除操作 --> C[文件处理程序];
C -- 文件状态 --> D[显示管理器];
D -- 显示 --> A;
C -- 读取/写入文件 --> E[文件];
-
编译器
:编译器处理的信息是 C 代码,其任务是将 C 源代码转换为依赖于机器的目标代码。这个过程包括多个阶段:
- 预处理:代码通过预处理器扩展宏、处理条件编译并读取包含文件。
- 词法分析:处理后的文件传递给词法分析器,词法分析器将字符流作为输入,返回一系列标记(单词或运算符)。例如,对于英文命令 “Open the door.”,词法分析会识别出三个单词和一个句号。
- 语法分析:标记传递给解析器,解析器将它们组装成句子,并生成符号表,以便了解程序中使用的变量。
-
优化(可选):优化器分析指令,尝试使其更高效,除非在命令行指定
-O标志,否则此步骤会被省略。 - 代码生成:代码生成器将高级语句转换为特定于机器的汇编代码,汇编语言中的每个语句对应一条机器指令。
- 汇编:汇编器将汇编语言转换为机器可执行的二进制代码。
C 语言受欢迎的一个因素是易于为新机器创建 C 编译器。自由软件基金会分发的 C 编译器(gcc)源代码采用模块化编写,通过更改代码生成器和编写新的汇编器,就可以将其移植到新机器上,这两个任务相对简单。词法分析和语法分析在许多程序中都很常见,
lex
工具可根据程序使用的标记描述生成词法分析器模块,
yacc
工具可用于生成解析器模块。
编译器的模块信息流程如下 mermaid 流程图所示:
graph LR;
A[C 源代码] --> B[预处理器];
B --> C[词法分析器];
C --> D[解析器];
D --> E{是否优化};
E -- 是 --> F[优化器];
E -- 否 --> G[代码生成器];
F --> G;
G --> H[汇编器];
H --> I[目标代码];
D --> J[符号表];
- 电子表格 :简单的电子表格处理数字矩阵和方程式,并将结果显示在屏幕上。其核心是一组方程式,要将方程式转换为数字,需要像编译器一样进行词法分析和语法分析,但不同的是,电子表格不生成机器代码,而是解释方程式并计算结果。这些结果传递给显示管理器进行屏幕显示,再加上一个允许用户编辑和更改方程式的输入模块,就构成了一个电子表格。
电子表格的模块关系可以用以下 mermaid 流程图表示:
graph LR;
A[用户] -- 编辑方程式 --> B[输入模块];
B -- 方程式 --> C[词法分析/语法分析];
C -- 计算结果 --> D[显示管理器];
D -- 显示 --> A;
4. 模块设计准则
虽然没有严格的规则来规划程序的模块,但有一些通用准则:
- 模块中的公共函数数量应较少。
- 模块间传递的信息应有限。
- 模块中的所有函数应执行相关的任务。
5. 编程练习
-
练习 18 - 1
:编写一个处理页面格式的模块,包含以下函数:
-
open_file(char *name):打开打印文件。 -
define_header(char *heading):定义标题文本。 -
print_line(char *line):将行发送到文件。 -
page(void):开始新页面。 -
close_file(void):关闭打印机文件。
-
-
练习 18 - 2
:编写一个名为
search_open的模块,该模块接收一个文件名数组,搜索直到找到一个存在的文件,然后打开该文件。 -
练习 18 - 3
:编写一个符号表程序,包含以下函数:
-
void enter(char *name):将名称输入到符号表中。 -
int lookup(char *name):如果名称在表中返回 1,否则返回 0。 -
void delete(char *name):从符号表中删除名称。
-
-
练习 18 - 4
:将第 17 章的
words程序与无限数组模块结合,创建一个交叉引用程序。(额外奖励:让它识别 C 注释和字符串,创建一个 C 交叉引用器。)
6. 古老的编译器
C 语言多年来不断发展。最初,它是由 Brian Kernigham 和 Dennis Ritchie 等黑客拼凑而成,以便在地下室使用计算机。后来,C 编译器被改进并发布为“可移植 C 编译器”,其主要优点是可以在不同机器间移植,只需编写设备配置,尽管编写配置非常困难,但比从头编写编译器容易得多。
可移植 C 编译器广泛分发,很快成为最常用的 C 编译器。由于当时没有官方标准,可移植 C 编译器能编译的内容就成了“官方”标准。
可移植 C 编译器没有后来 ANSI 标准中定义的许多特性,许多新特性的添加是为了使 C 程序更安全、更可靠。用 ANSI C 编程已经很困难,而用旧的可移植 C 编程就像蒙着眼睛走钢丝且没有安全网。
7. K&R 风格的函数
K&R 风格的 C 编译器使用较旧的函数声明方式。例如,ANSI C 的函数声明:
int process(int size, float data[], char *how)
在 K&R C 中会是:
int process(size, data, how)
int size;
float data[ ];
char *how;
{
/* Rest of the function */
严格来说,不需要声明
int size
,因为所有参数类型默认自动为
int
,但显式声明所有内容是个好主意。
8. 函数原型
在 K&R 风格的 C 中,函数原型不是必需的,可以省略。例如,使用
draw
函数而不声明原型:
draw(1, 8, 2, 20);
C 会自动将此函数定义为返回
int
且参数数量和类型未知的函数,显然无法进行参数类型检查。因此,完全可能编写像下面这样的程序:
示例 19 - 1. area/area.c
#include <stdio.h>
float area(width, height)
int width;
float height;
{
return (width * height);
}
int main()
{
float size = area(3.0, 2);
printf("Area is %f\n", size);
return (0);
}
问题 19 - 1:运行示例 19 - 1 中的程序会输出什么?为什么?
K&R 风格的 C 允许使用函数原型,但只能声明返回类型,参数列表必须为
()
。例如:
extern float atof();
这里的
()
表示该函数接受数量和类型未知的参数。
还有以下几个示例程序及相关问题:
-
示例 19 - 2. ret/ret.c
#include <stdio.h>
int main()
{
/* Get the square of a number */
int i = square(5);
printf("i is %d\n", i);
return (0);
}
float square(s)
int s;
{
return (s * s);
}
问题 19 - 2:示例 19 - 2 会打印什么?为什么?
- 示例 19 - 3. sum/sum.c
#include <stdio.h>
int sum(i1, i2, i3)
{
int i1;
int i2;
int i3;
return (i1 + i2 + i3);
}
int main()
{
printf("Sum is %d\n", sum(1, 2, 3));
return (0);
}
问题 19 - 3:示例 19 - 3 会打印什么?为什么?
- 示例 19 - 4. scat/scat.c
#include <stdio.h>
#include <string.h>
char first[100]; /* First name of person */
char last[100]; /* Last name of person */
/* First and last name combined */
char full[100];
int main() {
strcpy(first, "John");
strcpy(last, "Doe");
strcpy(full, first);
strcat(full, ' ');
strcat(full, last);
printf("The name is %s\n", full);
return (0);
}
问题 19 - 4:示例 19 - 4 打印出
John'=(3
而不是
John Doe
,为什么?(结果可能因环境而异)
原型是 C 编译器非常有价值的诊断工具,没有它们,程序员可能在不知情的情况下出现各种错误。因此,原型从 C++ 引入到了 C 语言中。
8. 函数原型问题深入剖析
8.1 示例 19 - 1 分析
在示例 19 - 1 中,代码如下:
#include <stdio.h>
float area(width, height)
int width;
float height;
{
return (width * height);
}
int main()
{
float size = area(3.0, 2);
printf("Area is %f\n", size);
return (0);
}
在 K&R 风格的 C 语言中,当调用
area(3.0, 2)
时,由于没有函数原型,C 编译器会默认
area
函数返回
int
类型,并且参数类型也不会进行严格的检查。在调用时,实参
3.0
是
double
类型,而函数定义中
width
是
int
类型,会发生隐式类型转换,
3.0
被截断为
3
;实参
2
是
int
类型,与函数定义中
height
的
float
类型也会发生隐式类型转换,
2
被转换为
2.0f
。最终计算结果
3 * 2.0
得到
6.0
,但由于默认返回
int
类型,结果被截断为
6
,再赋值给
float
类型的
size
时,输出
6.000000
。
8.2 示例 19 - 2 分析
示例 19 - 2 的代码为:
#include <stdio.h>
int main()
{
/* Get the square of a number */
int i = square(5);
printf("i is %d\n", i);
return (0);
}
float square(s)
int s;
{
return (s * s);
}
同样在 K&R 风格下,调用
square(5)
时,没有函数原型,编译器默认
square
函数返回
int
类型。函数内部计算
s * s
结果为
25
,类型为
int
。但函数定义返回类型是
float
,这里发生了隐式类型转换,将
25
转换为
25.0f
。然而,由于调用处默认返回
int
类型,在赋值给
int
类型的
i
时,会将
25.0f
截断为
25
,所以最终输出
i is 25
。
8.3 示例 19 - 3 分析
示例 19 - 3 的代码如下:
#include <stdio.h>
int sum(i1, i2, i3)
{
int i1;
int i2;
int i3;
return (i1 + i2 + i3);
}
int main()
{
printf("Sum is %d\n", sum(1, 2, 3));
return (0);
}
调用
sum(1, 2, 3)
时,因为没有函数原型,编译器默认
sum
函数返回
int
类型。函数内部将三个
int
类型的参数相加,结果为
6
,返回值类型也是
int
,所以最终输出
Sum is 6
。
8.4 示例 19 - 4 分析
示例 19 - 4 的代码为:
#include <stdio.h>
#include <string.h>
char first[100]; /* First name of person */
char last[100]; /* Last name of person */
/* First and last name combined */
char full[100];
int main() {
strcpy(first, "John");
strcpy(last, "Doe");
strcpy(full, first);
strcat(full, ' ');
strcat(full, last);
printf("The name is %s\n", full);
return (0);
}
在调用
strcat(full, ' ')
时出现了问题。
strcat
函数的第二个参数要求是
const char*
类型,而这里传入的是字符常量
' '
,字符常量在 C 中是
int
类型,这会导致未定义行为。编译器可能会将这个
int
值当作地址来处理,从而在拼接字符串时出现错误,最终打印出错误的结果
John'=(3
。正确的做法应该是使用字符串
" "
,即
strcat(full, " ");
。
9. 不同编译器对 K&R 风格和 ANSI 风格的支持对比
| 编译器类型 | K&R 风格支持情况 | ANSI 风格支持情况 | 特点 |
|---|---|---|---|
| 可移植 C 编译器 | 完全支持,是其主要的函数声明和使用方式 | 支持部分特性,但不如后来的 ANSI 标准全面 | 可在不同机器间移植,早期广泛使用 |
| ANSI C 编译器 | 部分支持,但会有警告提示,不推荐使用 | 全面支持,遵循严格的类型检查和函数原型规则 | 提高了程序的安全性和可靠性 |
| GCC | 支持 K&R 风格,但建议使用 ANSI 风格,可通过编译选项控制 | 全面支持 ANSI 标准,并且有很多扩展特性 | 开源、功能强大,广泛应用于各种开发场景 |
| Visual C++ | 对 K&R 风格支持有限,更倾向于 ANSI 及后续标准 | 全面支持 ANSI 及更新的 C 标准,结合了微软的开发环境和工具 | 适用于 Windows 平台的开发 |
10. 编程练习解答思路
10.1 练习 18 - 1
要编写一个处理页面格式的模块,包含
open_file
、
define_header
、
print_line
、
page
和
close_file
函数。可以使用文件操作函数来实现这些功能。以下是一个简单的示例代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
FILE *print_file;
char header[100];
void open_file(char *name) {
print_file = fopen(name, "w");
if (print_file == NULL) {
fprintf(stderr, "Error: Unable to open file %s\n", name);
exit(8);
}
}
void define_header(char *heading) {
strcpy(header, heading);
fprintf(print_file, "%s\n", header);
}
void print_line(char *line) {
fprintf(print_file, "%s\n", line);
}
void page(void) {
fprintf(print_file, "\n\n");
fprintf(print_file, "%s\n", header);
}
void close_file(void) {
fclose(print_file);
}
10.2 练习 18 - 2
编写
search_open
模块,接收一个文件名数组,搜索直到找到一个存在的文件并打开它。可以使用
fopen
函数来检查文件是否存在。示例代码如下:
#include <stdio.h>
#include <stdlib.h>
FILE* search_open(char *filenames[], int num_files) {
for (int i = 0; i < num_files; i++) {
FILE *file = fopen(filenames[i], "r");
if (file != NULL) {
return file;
}
}
fprintf(stderr, "Error: No file found in the list.\n");
exit(8);
}
10.3 练习 18 - 3
编写符号表程序,包含
enter
、
lookup
和
delete
函数。可以使用链表或数组来实现符号表。以下是使用数组的简单示例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_SYMBOLS 100
char symbols[MAX_SYMBOLS][100];
int symbol_count = 0;
void enter(char *name) {
if (symbol_count < MAX_SYMBOLS) {
strcpy(symbols[symbol_count], name);
symbol_count++;
} else {
fprintf(stderr, "Error: Symbol table is full.\n");
}
}
int lookup(char *name) {
for (int i = 0; i < symbol_count; i++) {
if (strcmp(symbols[i], name) == 0) {
return 1;
}
}
return 0;
}
void delete(char *name) {
for (int i = 0; i < symbol_count; i++) {
if (strcmp(symbols[i], name) == 0) {
for (int j = i; j < symbol_count - 1; j++) {
strcpy(symbols[j], symbols[j + 1]);
}
symbol_count--;
return;
}
}
fprintf(stderr, "Error: Symbol %s not found in the table.\n", name);
}
10.4 练习 18 - 4
将第 17 章的
words
程序与无限数组模块结合,创建一个交叉引用程序。可以参考以下步骤:
1. 读取文件内容,使用
words
程序的逻辑将文本拆分成单词。
2. 使用无限数组模块来记录每个单词出现的行号。
3. 遍历无限数组,输出每个单词及其出现的行号。
如果要让它识别 C 注释和字符串,可以在读取文件内容时添加相应的逻辑,跳过注释和字符串部分。例如,当遇到
/*
时,跳过直到遇到
*/
;当遇到
"
时,跳过直到遇到下一个
"
。
11. 总结
本文介绍了编程中的多个重要概念,包括使用无限数组生成直方图、任务模块划分、古老的编译器以及相关的编程练习。在使用无限数组生成直方图时,通过
hist
程序展示了如何处理数据并输出可视化的结果,同时给出了不同编译器的
Makefile
示例。在任务模块划分方面,强调了信息隐藏和简单接口的重要性,并通过文本编辑器、编译器和电子表格的例子说明了如何进行模块划分。对于古老的 K&R 风格 C 编译器,详细分析了其函数声明和函数原型的特点,以及可能出现的问题。最后,给出了编程练习的解答思路,帮助读者加深对这些概念的理解和应用。在实际编程中,应该尽量采用现代的编程风格,如 ANSI C 标准,以提高代码的安全性和可维护性。
超级会员免费看
856

被折叠的 条评论
为什么被折叠?



