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]。
13.24 既然 fflush() 不能, 那么怎样才能清除输入呢?
这取决于你要做什么。如果你希望丢掉调用 scanf() (参见问题 12.16 - 12.17) 之后所剩下的换行符和未预知的输入, 你可能需要重写你的
scanf() 或者换掉它, 参见问题 13.18。或者你可以用下边这样 的代码吃掉一行中多余的字符
while((c = getchar()) != '\n' && c != EOF)
/* 丢弃 */ ;
你也可以使用 curses 的 flushinp() 函数。
没有什么标准的办法可以丢弃标准输入流的未读取字符, 即使有, 那也不够, 因为未读取字符也可能来自其它的操作系统级的输入缓冲区。如果你希望严格 丢弃多输入的字符 (可能是预测发出临界提示), 你可能需要使用系统相关的 技术; 参加问题 20.1 和 20.2。
参考资料: [ISO, Sec. 7.9.5.2]; [H&S, Sec. 15.2]。
20.43 我不能使用这些非标准、依赖系统的函数, 程序需要兼容 ANSI!
你很不走运。要么你误解了要求, 要么这不可能做到。 ANSI/ISO C 标准 没有定义做这些事的方法; 它是个语言的标准, 不是操作系统的标准。 国际标准 POSIX (IEEE 1003.1, ISO/IEC 9945-1) 倒是定义了许多这方面 的方法, 而许多系统 (不只是 Unix) 都有兼容 POSIX 的编程接口。
可以做到, 也是可取的做法是使程序的大部分兼容 ANSI, 将依赖系统的功能 集中到少数的例程和文件中。这些例程或文件可以大量使用 #ifdef 或针对 每一个移植的系统重写。
[
本帖最后由 TonyDeng 于 2014-12-11 13:44 编辑 ]