18.1 怎样从键盘直接读入字符而不用等RETURN键?

唉, 在C 里没有一个标准且可移植的方法。在标准中跟本就没有提及屏幕和键盘的概念, 只有基于字符“流” 的简单输入输出。

在某个级别, 与键盘的交互输入一般上都是由系统取得一行的输入才提供给需要的程序。这给操作系统提供了一个加入行编辑的机会(退格、删除、消除等),使得系统地操作具一致性, 而不用每一个程序自己建立。当用户对输入满意, 并键入RETURN (或等价的键)后, 输入行才被提供给需要的程序。即使程序中用了读入单个字符的函数(例如getchar() 等), 第一次调用就会等到完成了一整行的输入才会返回。这时, 可能有许多字符提供给了程序, 以后的许多调用(象getchar() 的函数) 都会马上返回。

当程序想在一个字符输入时马上读入, 所用的方式途径就采决于行处理在输入流中的位置, 以及如何使之失效。在一些系统下(例如MS-DOS, VMS 的某些模态), 程序可以使用一套不同或修改过的操作系统函数来扰过行输入模态。在另外一些系统下(例如Unix, VMS 的另一些模态), 操作系统中负责串行输入的部分(通常称为“终端驱动”) 必须设置为行输入关闭的模态, 这样, 所有以后调用的常用输入函数(例如read(), getchar() 等) 就会立即返回输入的字符。最后, 少数的系统(特别是那些老旧的批处理大型主机) 使用外围处理器进行输入, 只有行处理模式。

因此, 当你需要用到单字符输入时(关闭键盘回显也是类似的问题), 你需要用一个针对所用系统的特定方法, 假如系统提供的话。新闻组comp.lang.c 讨论的问题基本上都是C 语言中有明确支持的, 一般上你会从针对个别系统的新闻组以及相对应的常用问题集中得到更好的解答, 例如comp.unix.questions 或comp.os.msdos.programmer。另外要注意, 有些解答即使是对相似系统的变种也不尽相同, 例如Unix 的不同变种。同时也要记住, 当回答一些针对特定系统的问题时, 你的答案在你的系统上可以工作并不代表可以在所有人的系统上都工作。

然而, 这类问题被经常的问起, 这里提供一个对于通常情况的简略回答。某些版本的curses 函数库包含了cbreak(), noecho() 和getch() 函数, 这些函数可以做到你所需的。如果你只是想要读入一个简短的口令而不想回显的话,可以试试getpass()。在Unix 系统下, 可以用ioctl() 来控制终端驱动的模式, “传统”系统下有CBREAK 和RAW 模式, System V 或POSIX 系统下有ICANON,c cc[VMIN] 和c cc[VTIME] 模式, 而ECHO 模式在所有系统中都有。必要时, 用函数system() 和stty 命令。更多的信息可以查看所用的系统, 传统系统下, 查看<sgtty.h> 和tty(4), System V 下, 查看<termio.h> 和termio(4), POSIX 下, 查看<termios.h> 和termios(4)。在MS-DOS 系统下, 用函数getch() 或getche(), 或者相对应的BIOS 中断。在VMS 下, 使用屏幕管理例程(SMG$), 或curses 函数库,或者低层$QIO 的IO$ READVBLK 函数, 以及IO$M NOECHO 等其它函数。也可以通过设置VMS 的终端驱动, 在单字符输入或“通过” 模式间切换。如果是其它操作系统, 你就要靠自己了。

另外需要说明一点, 简单的使用setbuf() 或setvbuf() 来设置sdtin 为无缓冲,通常并不能切换到单字符输入模式。

如果你在试图写一个可移植的程序, 一个比较好的方法是自己定义三套函数: 1) 设置终端驱动或输入系统进入单字符输入模式, (如果有必要的话), 2) 取得字符, 3) 程序使用结束后的终端驱动复原。理想上, 也许有一天, 这样的一组函数可以成为标准的一部分。本常用问题集的扩充版(参见问题20.36) 含有一套适用于几个流行系统的函数。