news 2026/10/21 18:45:00

C语言输入输出避坑指南:缓冲区、getchar、scanf与fgets全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C语言输入输出避坑指南:缓冲区、getchar、scanf与fgets全解析

学C语言的人,十个里有八个在“输入”这件事上栽过跟头。尤其是学到字符和字符串这一章,你会发现一个诡异的现象:代码明明照书敲的,编译也没报错,可一运行,程序像在闹情绪——要么等半天不干活,要么自动跳过输入直接往下跑,要么屏幕上多了一堆莫名其妙的空行。我当年学到这里,差点把键盘砸了。

这一章讲的就是字符与字符串的输入输出。它解决的问题非常具体:程序怎么从键盘拿到单个字符、怎么拿到一整行文字、怎么安全地存进变量、又怎么把它们规规矩矩地打出来。适合所有刚学到数组和指针、开始接触字符串处理的学习者,也适合那些“会用scanf但总出幺蛾子”的初学者回过头来补课。

先说个结论放这儿:出问题的根源,十有八九不是你的语法错了,而是没弄明白输入缓冲区是怎么运作的。这一章你如果能先把缓冲区这个概念吃透,后面那些所谓的“坑”,你会看得一清二楚。

1. 从一次卡住的输入说起:字符流与缓冲区的底层逻辑

你肯定遇到过这种情况:写了一个简单程序,用scanf读一个整数,再读一个字符,结果第二个输入根本没等你敲键盘,程序就直接拿着一个不知道是什么的值往下跑了。我当初第一次遇到,以为是电脑坏了。

要弄明白这是怎么回事,得先知道一个事实:程序不是直接从键盘上读数据的,而是从一块叫“输入缓冲区”的内存区域里读。你敲键盘的时候,字符并不是一个一个地立刻飞进程序,而是先被系统攒在一个缓冲区里,直到你按下回车键,这些字符才作为一个整体被送进缓冲区,程序再从中读取。

这就像食堂打饭。你不是站在窗口前伸手就能从锅里抓菜,而是厨师先把菜一盘一盘装好,放在出餐口,你再一盘一盘端走。输入缓冲区就是那个出餐口。

举个例子,你写了这段代码:

int num; char ch; scanf("%d", &num); scanf("%c", &ch);

运行时你输入了42然后回车。你心里想的是:我先给num输入42,然后给ch输入一个字符。但实际发生了什么?

你敲下的字符是:4、2、\n。回车符\n也被当作一个字符放进了缓冲区。第一次scanf("%d")读完42之后,它发现后面是个\n,不是数字,就停下来,但它不会把\n带走。于是缓冲区里还躺着一个孤零零的\n。第二次scanf("%c")一上场,二话不说就把这个\n读走了——因为%c的规则是“读一个字符,不管它是空格还是换行”。

所以你还没来得及输字符,ch就已经是换行符了。程序看起来就“跳过输入”了。

而这个缓冲区,就是本章所有输入陷阱的总根源。掌握了它,你就掌握了理解getchar、scanf、fgets等一堆函数行为的一把钥匙。

2. getchar与putchar:最朴素的逐字符处理

很多人觉得getchar和putchar太简单了,一个读一个写,有啥好学的?但实际上,这两个函数才是理解字符流的起点,也是处理很多复杂问题的基石。

2.1 为什么getchar的返回值是int而不是char

这是一个很多教材都提但不一定讲透的点。getchar()的声明是:

int getchar(void);

注意,它返回的是int,不是char。为什么不直接返回char?因为判断文件结束需要用到EOF,而EOF在C语言中通常被定义为-1。

如果getchar返回char类型,那么当char恰好可以表示-1时,你无法区分“读到了一个值为-1的字符”和“读到文件末尾了”。在绝大多数实现中,char要么是有符号的,要么是无符号的。如果是无符号,那-1就会被解释成255,压根对不上;如果有符号,虽然能表示-1,但你就没法区分这个-1到底是真实的字符还是EOF了。所以标准委员会干脆让它返回一个int,用4个字节来装,既能装下所有可能的字符值(0~127甚至扩展到255),又能单独用-1代表EOF。

这就是为什么你写while ((ch = getchar()) != EOF)时,ch必须声明成int而不是char。如果声明成char,某些平台下这个循环会出问题:要么永远等不到EOF,要么把一个本来合法的字符误判成了EOF。

2.2 换行符的处理:读入后别忘清场

getchar每次读一个字符,包括换行符\n。比如你想逐字符读取用户输入,直到遇到换行就停止:

int ch; while ((ch = getchar()) != '\n' && ch != EOF) { // 处理这个字符 }

这个写法很常用,它模拟了“读一整行”的效果。但注意,一旦跳出循环,意味着你已经把那个\n读走了。如果你想继续用getchar读下一行,这是好事;如果你跳完循环之后还想让其他输入函数正常工作,那你就不用额外清理了,因为换行已经被消费掉了。

很多人在这里犯迷糊,把while ((ch = getchar()) != '\n')和输入结束的判断搞混。请记住:getchar读不到数据时返回EOF(-1),读到行尾时返回\n(10),这俩不是一回事。

2.3 用putchar搭建一个简易回显器

putchar就简单多了,它把一个字符写到标准输出。如果你想把用户输入的字符原样打出来,可以这么做:

#include <stdio.h> int main(void) { int ch; printf("请输入一段文字(回车结束):\n"); while ((ch = getchar()) != '\n') { putchar(ch); } putchar('\n'); return 0; }

这个程序从缓冲区里逐个读取字符,直到遇到换行,然后原样输出。它演示了“输入一个字符、处理一个字符”的模式,后续很多文本处理程序的雏形就是这个样子——比如你想统计输入中有多少个字母a,或者想把所有大写字母转成小写,思路都一样:逐个读,逐个改,逐个输出。

3. scanf解析字符串:%s、%c的边界与空白符的恩怨

scanf是大多数人学会的第一个输入函数,但也是最容易用出“薛定谔行为”的函数。原因在于,scanf家族处理不同类型时,对空白符(空格、制表符、换行)的态度截然不同。

3.1 %s读不进带空格的字符串

scanf("%s", str)遇到空字符、换行符就会停止读取。这也就是说,你用%s读“hello world”只能得到“hello”。因为它把空格当作了一个天然的休止符。这背后的逻辑是:%s的语义是“读一个不包含空白符的字符串”,它天生就是用来读单词的,不是用来读整句的。

如果你确实想用scanf读带空格的一整行,有个写法:

scanf("%[^\n]", str);

这个写法表示“读到换行符为止”。它可以用,但我不推荐新手上来就用这种魔术一般的格式,因为它有很多细节问题(比如缓冲区溢出的风险、以及换行符残留等)。与其绕弯子,不如老老实实用后面要讲的fgets。

3.2 %c是“盲读”字符,而%d和%s会跳过空白

这大概是scanf家族中最让人迷惑的顺序差异了。%d在读取一个整数之前,会自动跳过所有的空白符(空格、换行、制表符)。%s也一样,先跳过前导空白,再读单词。唯独%c不跳,它会原封不动地读下一个字符,哪怕那个字符是空格或者换行。

这就是第一小节那个场景的根源。scanf("%d")读完数字后留下的换行符,会被紧随其后的scanf("%c")照单全收。

如果你想在%c之前跳过空白,有一个技巧:在格式串中加一个空格,写成scanf(" %c", &ch)。注意%c前面那个空格,它的作用是告诉scanf:“读取前先跳过这个空白”。这个技巧非常实用,你在混合读取不同类型时,可以用它避免换行爆炸。

3.3 格式匹配失败时的行为

scanf还有一个特性:如果格式串里的内容与输入不匹配,它会立刻失败并返回一个值(代表成功匹配的项数)。这是一种隐形的错误处理机制。

int num; int result = scanf("%d", &num); if (result != 1) { printf("输入格式不对\n"); }

很多初学者根本不知道scanf有返回值。实际上,这个返回值非常宝贵。养成检查它的习惯,能帮你避免一大堆未定义行为。我见过有同学用scanf读数字,用户输了个字母,结果变量里存了个垃圾值,程序继续跑得欢,最后输出一堆天文数字——根源就是没有检查返回值。

4. 缓冲区残留换行符:新手最常见的“输入不生效”

这一节我们要把那个最经典的坑拆开揉碎了讲清楚。你现在已经知道什么是缓冲区了,接下来看看它在实际中怎么咬人的。

4.1 场景复现:scanf后紧跟getchar/fgets

我在交流群里看过无数遍这个求助帖:“我用scanf读了一个整数,然后用fgets读字符串,结果fgets直接返回了,根本没有等待输入”。下面回复的人各有说法,但那个真正的原因,大部分新手听完还是一头雾水。

我复现一下场景:

int age; char name[50]; printf("请输入年龄:"); scanf("%d", &age); printf("请输入姓名:"); fgets(name, sizeof(name), stdin); printf("姓名:%s", name);

运行效果是:程序打印出“请输入姓名:”,然后不等你输入,直接打印出“姓名:\n”(空行),然后结束。

原因你已经猜到了:你上次输入年龄时,缓冲区里留了一个换行符。fgets有个特性:它会一直读到换行符为止,包括换行符本身。所以当fgets一进入,它发现自己要读的那一行里,第一个字符就是刚才残留的换行,于是它如获至宝,立刻返回——因为它觉得这一行已经读完了,虽然这行是空的。

4.2 清除缓冲区的常用方法

要解决这个问题,一个核心思路是:在读取整数之后、读取字符串之前,把残留的换行符“吃掉”。

方法一,用getchar:

scanf("%d", &age); getchar(); // 吃掉换行符

这个方法最直接,但有个局限:如果用户输入的时候不小心多敲了个空格,比如“42 ”,那缓冲区里残留的就是‘ ’和‘\n’,一个getchar吃不干净。

方法二,循环读取直到换行:

int ch; while ((ch = getchar()) != '\n' && ch != EOF) { // 不断读取,丢弃所有剩余字符 }

这个方法更稳健,无论缓冲区里残留多少个空格、多少个换行,都能清掉。代价是多写了三行。

方法三,在scanf格式串里加空格:

scanf("%d", &age);

如果后面紧接着读字符输入,改成scanf(" %c", &ch),让%c自动跳过空白。这个方法最优雅,但要注意它只对scanf内部有效,对fgets无能为力。

4.3 为什么“有时候能跑,有时候不能跑”

你可能发现,在某些代码里,你明明也用了scanf和fgets连用,但从来没出过问题。这是因为scanf("%d")之后,并不是每次都会残留换行符。比如:

scanf("%d", &num);

如果你的输入是42\n,那么缓冲区里除了num拿走的部分,还能剩\n,导致问题发生。但如果某些平台上的实现,或者某些IDE做了特殊处理,可能导致行为不同。更常见的情况是:你上一次不输入整数,而是输入字符串%s,它会跳过前导空白再读单词,但也不会消费末尾的换行。

所以,并不是“某段代码一定出问题,某段代码一定正常”。关键在于你上一个输入函数在缓冲区里留下了什么。这个缓冲区就像一个共享餐桌,你吃完不擦嘴,下一个人就得面对你留下的油渍。理解了这一点,你就能预测每一次组合输入的行为。

5. 字符串输入讲究安全:gets已死,fgets当立

讲字符串输入,就绕不开一个江湖往事:gets这个函数,曾经被写进教材,后来被C标准委员会判了“死刑”,从C11标准中彻底移除了。原因很简单:它无法限制读取长度,缓冲区溢出是必然的。

5.1 gets的致命缺陷

gets(str)的行为是:从标准输入读取一整行,直到换行符或EOF,然后把它存入str,并且丢弃换行符,在字符串结尾加一个'\0'。

问题在于,gets并不知道str这个数组到底有多长。你声明了一个char str[10],但用户输入了20个字符,gets照样把这20个字符全往数组里写,后面的10个字符就越界了。越界写入会导致未定义行为:轻则覆盖其他变量的值,重则直接让程序崩溃,甚至被恶意利用来执行任意代码。C语言历史上很多著名的安全漏洞,源头就是gets。

有些老编译器还能编译gets,顶多发个警告。但你应该无条件放弃它。任何教你用gets的教程,都已经过时了。

5.2 fgets的参数与末尾换行保留

替代gets的,是fgets。它的完整声明是:

char *fgets(char *str, int n, FILE *stream);

它最多读入n-1个字符(给最后一个位置留'\0'),遇到换行符或EOF就停止。如果读到了换行符,它会把换行符放进字符串里。这一点和gets不一样,gets会丢掉换行符,fgets会保留。

这个“保不保留换行”的差异,让很多新手栽了第二个跟头:

char name[50]; fgets(name, sizeof(name), stdin); printf("你的名字是:%s\n", name);

你输入“张三”回车,程序输出:

你的名字是:张三

多了一个空行。因为name里面存的是"张三\n",你再打印一个\n,自然就多了一个空行。解决方法很简单:用完fgets之后,如果字符串末尾有换行符,就把它去掉或替换掉,通常的做法是:

#include <string.h> char name[50]; fgets(name, sizeof(name), stdin); name[strcspn(name, "\n")] = '\0'; // 去掉末尾换行

strcspn返回name中第一个出现'\n'的位置,然后把那个位置替换成字符串结束符'\0'。这行代码值得记下来,它是处理fgets后遗症的经典写法。

5.3 fgets与程序设计的统一性

fgets的第三个参数stream,让它不只是键盘输入的专用工具。你可以传stdin来读键盘输入,也可以传一个FILE*指针来读文件内容。这意味着,你只需要学会一种读行的方法,无论是读键盘还是读文件,都统一用fgets。

这和scanf家族的族谱完全不同:scanf专门面向格式化输入,fgets面向行输入。在需要读“一整行原始字符串”的场景下,fgets几乎是无可替代的。我写得多了之后,对从键盘读字符串这件事,基本只用fgets,再搭配手动“去换行”的逻辑,几乎从不出岔子。

6. 输出字符串的不同姿势:puts、printf、fputs

输入讲完,输出也得讲究讲究。很多人觉得printf天下无敌,什么都能干。确实,它最灵活,但你也要知道puts和fputs在特定场景下更合适。

6.1 自动换行的puts

puts只有一个参数:

int puts(const char *str);

它把字符串输出到标准输出,并且自动在后面加上一个换行符。所以:

puts("hello");

等价于:

printf("hello\n");

看起来只是省了一个\n,但实际还有个细节:puts在输出时,性能通常比printf好。因为它不需要解析格式串。当你只是打印一个固定字符串时,puts是更快的选择。当然,这个“更快”在普通程序里你感觉不到,但它确实更简洁、更不容易出错。

常见错误是:用了puts,又在字符串里自带了一个\n,结果打出两个换行。比如:

puts("hello\n");

这会在输出的“hello”后面跟两个空行。所以使用puts时,字符串里就不要再加\n了。

6.2 printf的精细控制

printf的强大在于它可以用占位符控制输出的格式。字符串输出常用这些:

  • %s:输出一个字符串
  • %10s:输出字符串,右对齐,最短宽度10个字符,不足补空格
  • %-10s:输出字符串,左对齐,最短宽度10个字符
  • %.5s:最多输出前5个字符

比如:

printf("%-10s|%10s\n", "left", "right");

输出结果:

left | right

这在做表格对齐、输出漂亮日志时非常有用。我在写一些命令行小工具的时候,经常用这个技巧对齐列名和数值。

还有一点:printf输出一个末尾带\n的字符串,不需要额外做什么。但如果你用printf("%s", str)而str里没有换行符,下一个输出就会接在后面,容易造成“粘行”。这时候你需要在逻辑上保证换行的输出,或者干脆改成puts。

6.3 fputs的用武之地

fputs和puts很像,区别是它不会自动添加换行符,而且可以指定输出流:

int fputs(const char *str, FILE *stream);

你既可以fputs(str, stdout),也可以fputs(str, stderr),还可以往文件里写。因为它不自动加换行,所以你想把多个字符串拼在一起输出时,它比puts更可控。比如:

fputs("错误:", stderr); fputs(errorMsg, stderr);

这样输出到错误流就不会被自动换行打断,逻辑清晰。

7. 综合练习:写一个逐词读取的命令行小工具

光说不练假把式。我们把前面所有知识点串起来,写一个稍微有点意思的小程序:统计用户输入的单词数量。这个程序经典到几乎是C语言入门的“回字有几样写法”,但正因为它经典,才值得用正确的姿势从头实现一遍。

7.1 整体思路

我们要写一个循环:从标准输入逐行读取(用fgets),然后逐行逐字符检查。如果读到一个字母连续出现,那就说明我们正在一个单词里。当我们遇到空白符(空格、制表符、换行)时,一个单词就结束了。

添加一个状态变量inWord,初始为0(还没进入单词),遇到非空白字符且inWord为0,则单词数加1,并置inWord为1;遇到空白字符,置inWord为0。这样就能准确数出单词数量,原理是经典的“状态机”思想。

7.2 代码实现

#include <stdio.h> #include <ctype.h> #include <string.h> #define BUF_SIZE 256 int main(void) { char line[BUF_SIZE]; int wordCount = 0; int inWord = 0; printf("请输入文本(Ctrl+Z或Ctrl+D结束输入):\n"); while (fgets(line, sizeof(line), stdin) != NULL) { for (int i = 0; line[i] != '\0'; i++) { int isAlpha = isalpha((unsigned char)line[i]); if (isAlpha) { if (!inWord) { inWord = 1; wordCount++; } } else { inWord = 0; } } } printf("单词总数:%d\n", wordCount); return 0; }

我们用了fgets循环读取,fgets既可以读键盘输入,也可以读文件重定向过来的内容。如果用户用重定向符把整个文本文件喂给程序,这个程序同样能统计出单词数。这意味着,你写的这个程序,是一个标准的命令行文本处理工具雏形。

7.3 代码里的细节

第一个细节:sizeof(line)。因为line是数组名,sizeof(line)返回整个数组的字节数,也就是256。如果你写成sizeof(char*),那就传了个指针大小进去,运行时一定会出问题。这两者之间的差距,是初学者最容易踩的坑。

第二个细节:(unsigned char)line[i]。isalpha接受一个int参数,但这个参数的要求是“EOF,或者可以表示为unsigned char的值”。如果直接传一个普通的char,而char在某种平台上是带符号的,当字符的ASCII码大于127时,它会被解释成负数,这样传给isalpha的行为是未定义的。加上(unsigned char)强制转换可以消除这个问题。这种细微平台差异带来的Bug,通常只在某些系统上复现,特别折磨人。

第三个细节:在理解fgets“读到EOF”时,我们依赖的是循环条件fgets != NULL。当用户在Linux下按Ctrl+D或Windows下按Ctrl+Z,输入流关闭,fgets返回NULL,循环退出,程序自然结束。

8. 每次都会踩的坑:回顾几个输入输出细节

写到最后,我再系统性地梳理一遍我在多年写C代码过程中,反复遇到、反复教导别人、也偶尔自己再犯的细节。这些细节很碎,但每一碎,都可能浪费你半小时去调试。

8.1 字符变量与EOF的类型不匹配

我已经说过一次,但值得再说一次:getchar的返回值必须存进int,不能存进char。很多人把代码写成:

char ch; while ((ch = getchar()) != EOF) { ... }

如果char是无符号的,EOF即-1,会被转换成255。你从键盘永远输入不了255这个字符值,但这个循环会把任何一个ASCII为255的字节(比如从文件读入时)误判成EOF,或者更常见的情况是,循环在预期之外的地方终止。把这个变量换成int,所有症状立刻消失。

8.2 输出完要“flush”吗?

这个问题流派很多。严格来说,标准输出通常是行缓冲的:当输出流遇到换行符时,缓冲区才被刷到终端。如果你用一个printf输出没有换行的字符串,它可能不会立刻出现在屏幕上。在交互式程序里,如果你希望字先打出来,再等用户输入,最好在printf里带上\n,或者调用fflush(stdout)。

比如:

printf("你确定吗?"); getchar();

在某些环境中,这个提示可能一直不出来,直到用户按下回车。加上一个\n或者fflush(stdout)就稳定了。这个问题在IDE的控制台和实际命令行终端下表现不同,也是许多“神秘现象”的来源。

8.3 不同平台的行尾差异

在Windows下,文本模式中换行符会被翻译成\r\n两个字符。当你在Windows上用fgets读键盘输入时,字符串末尾不仅有\n,它前面可能还有一个\r。如果你用strcspn(name, "\n")把换行去掉,那么字符串末尾还可能残留一个可见字符\r,显示出来有点像一个奇怪的回车符。

一个更稳妥的做法是用strcspn(name, "\r\n")把两种都可能出现的行尾符号都截掉:

name[strcspn(name, "\r\n")] = '\0';

这样无论文本文件是来自Linux还是Windows,你处理的都是“干干净净”的字符串。这个习惯很值得养成。

8.4 别把“吸收缓冲区”和“清空缓冲区”搞混

网上流传很多“清空输入缓冲区”的办法,比如fflush(stdin)。我必须明确说:在C标准中,fflush只对输出流(或更新流)有效,对输入流的行为是未定义的。有些编译器实现了让fflush(stdin)清除输入缓冲区的扩展功能,但这不是标准行为。依赖它是不可移植的,换一个平台,你的代码行为就不可预期了。

正确做法是前面说过的:用循环把残留的字符全部读掉,直到读到换行,或者用设计更合理的输入函数(如fgets)从根上避免这类问题。设计上避免,比事后清理要优雅得多。

我个人的习惯是:字符串输入一律用fgets,混合类型输入处理残留换行时,用一个统一的“清行”辅助函数。时间久了,你会发现大多数输入问题都不是函数本身的bug,而是程序员对字符流的理解有偏差。这一点想通之后,C语言的输入输出,也就这么回事了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/21 18:44:32

用D2声明式图表打造团队统一的代码化架构图方案

上周刚把团队里最后一张用拖拽工具画出来的部署图替换掉&#xff0c;换成了统一生成的版本。发到群里之后&#xff0c;有个同事回了一句&#xff1a;“这几张图看着像同一个人画的&#xff0c;舒服。”这句话让我挺有成就感&#xff0c;因为折腾了大半个星期的项目总算有了回报…

作者头像 李华
网站建设 2026/10/21 18:44:21

欠驱动AUV水平轨迹跟踪的全局积分滑模控制设计与仿真

1. 项目拆解&#xff1a;一个看似基础但细节很多的控制问题 1.1 为什么欠驱动AUV是工程主流 做水下机器人控制这些年&#xff0c;我接触过不少实际项目方案&#xff0c;一个感受特别明显&#xff1a;真正下水跑的AUV&#xff0c;九成以上都是欠驱动构型。所谓欠驱动&#xff0…

作者头像 李华
网站建设 2026/10/21 18:42:59

电网不平衡跌落下并网逆变器正负序分离与负序电流抑制策略

1. 这个项目到底在解决什么问题并网装置做久了&#xff0c;对“电网电压跌落”这四个字特别敏感。不管你做的是光伏逆变器、储能PCS还是回馈式电子负载&#xff0c;前端只要并着公用电网&#xff0c;就绕不开这个问题&#xff1a;电网电压不是理想的三相正弦。尤其是不平衡跌落…

作者头像 李华
网站建设 2026/10/21 18:41:30

区块链项目商业计划书技术评估与最小可验证版本开发指南

简介&#xff1a;这份区块链项目商业计划书PDF面向区块链创业者、项目负责人及投资分析人员&#xff0c;用于梳理项目从技术方案到商业落地的完整逻辑&#xff0c;解决立项论证、融资路演与内部汇报时缺乏系统框架的问题。资源包内仅含1个PDF文件&#xff0c;压缩包整体约2.98M…

作者头像 李华
网站建设 2026/10/21 18:41:10

AI Agent上下文工程:从失效到性能倍增的五个优化方案

你有没有遇到过这种情况&#xff1a;自家AI Agent开场三连惊艳全场&#xff0c;追问几轮以后直接翻车——答非所问、重复推荐、连最基础的格式约定都开始无视&#xff0c;甚至像个失忆患者&#xff0c;把你前面交代过的关键约束忘得一干二净。如果你点头了&#xff0c;那恭喜你…

作者头像 李华
网站建设 2026/10/21 18:41:10

三菱FX3U PLSR脉冲指令底层原理与加减速调试指南

1. 用了多年PLSR&#xff0c;我终于把这条指令拆开看了1.1 一条常见的程序&#xff0c;背后却有四层动作打开三菱FX3U的官方编程软件&#xff0c;在梯形图里写下这段程序&#xff0c;几乎是做步进电机控制的人入门后的第一件事&#xff1a;LD X0 PLSR D0 D10 D20 Y000D0放脉冲总…

作者头像 李华