20.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) 含有一套适用于几个流行系统的函数。
参见问题 20.2
参考资料: [PCS, Sec. 10 pp. 128-9, Sec. 10.1 pp. 130-1]; [POSIX, Sec. 7]。