鱼C论坛

 找回密码
 立即注册
查看: 4080|回复: 32

[已解决]如何让程序一次读一个字符且不需要按下回车就能执行

[复制链接]
发表于 2022-8-14 01:25:42 | 显示全部楼层    本楼为最佳答案   
  1. #include <stdio.h>
  2. #include <unistd.h>
  3. #include <termios.h>
  4. #include <sys/ioctl.h>

  5. int main(void) {
  6.     {
  7.         struct termios termios;
  8.         ioctl(STDIN_FILENO, TCGETS, &termios);
  9.         termios.c_lflag &= ~ICANON;
  10.         ioctl(STDIN_FILENO, TCSETS, &termios);
  11.     }
  12.     char ch;
  13.     scanf("%c", &ch);
  14.     printf("'%c'\n", ch);
  15.     printf("'%c'\n", getchar());
  16.     read(STDIN_FILENO, &ch, 1);
  17.     printf("'%c'\n", ch);
  18.     {
  19.         struct termios termios;
  20.         ioctl(STDIN_FILENO, TCGETS, &termios);
  21.         termios.c_lflag |= ICANON;
  22.         ioctl(STDIN_FILENO, TCSETS, &termios);
  23.     }
  24.     return 0;
  25. }
复制代码
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2022-8-14 07:10:21 | 显示全部楼层
另一个比较有意思的选项是 ECHO,复位这个标志可以让你在输入的时候屏幕上什么也不显示
passwd程序可以让你在输入密码的时候,屏幕上面什么也不显示,passwd程序就是这么整的

  1. #include <stdio.h>
  2. #include <unistd.h>
  3. #include <termios.h>
  4. #include <sys/ioctl.h>

  5. int main(void) {
  6.     {
  7.         struct termios termios;
  8.         ioctl(STDIN_FILENO, TCGETS, &termios);
  9.         termios.c_lflag &= ~(ICANON | ECHO);
  10.         ioctl(STDIN_FILENO, TCSETS, &termios);
  11.     }
  12.     char ch;
  13.     scanf("%c", &ch);
  14.     printf("'%c'\n", ch);
  15.     printf("'%c'\n", getchar());
  16.     read(STDIN_FILENO, &ch, 1);
  17.     printf("'%c'\n", ch);
  18.     {
  19.         struct termios termios;
  20.         ioctl(STDIN_FILENO, TCGETS, &termios);
  21.         termios.c_lflag |= ICANON | ECHO;
  22.         ioctl(STDIN_FILENO, TCSETS, &termios);
  23.     }
  24.     return 0;
  25. }
复制代码
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2022-8-14 07:19:31 | 显示全部楼层
"就算我输入“asdf”,read(0, buf, 1) 读到 buf 里后,调用 write(1, buf, 1) 却输出了 “asdf” 而不是我期望的 'a'。"
看一下你的代码
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2022-8-14 07:21:01 | 显示全部楼层
less 使用 ncurses 实现的,ncurses 了解一下
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2022-8-14 07:27:01 | 显示全部楼层
"当我在终端输入“asdf”且没按回车时,这4个字符显示在了终端上"
这是因为设置了 ECHO 标志的原因,没按回车之前 stdin 应该是没有收到字符,这些字符应该是存在内核缓冲区中的(应该是这样)
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2022-8-14 07:31:05 | 显示全部楼层
"有没有可能直接访问到存储这些字符的地方,并取一个字符出来(取'a')?"
当你的程序调用 scanf、getchar、read这类获取输入的函数以后,你的程序就阻塞了,不再继续运行了
如果没有复位 ICANON 标志,在按下回车之前,你的程序都一直阻塞着,得不到运行的
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2022-8-14 08:21:15 | 显示全部楼层
  1. (gdb) print *stdin
  2. $1 = {_flags = -72539512, _IO_read_ptr = 0x4052a2 "cd1234\n", _IO_read_end = 0x4052a9 "",
  3.   _IO_read_base = 0x4052a0 "abcd1234\n", _IO_write_base = 0x4052a0 "abcd1234\n",
  4.   _IO_write_ptr = 0x4052a0 "abcd1234\n", _IO_write_end = 0x4052a0 "abcd1234\n", _IO_buf_base = 0x4052a0 "abcd1234\n",
  5.   _IO_buf_end = 0x4056a0 "", _IO_save_base = 0x0, _IO_backup_base = 0x0, _IO_save_end = 0x0, _markers = 0x0,
  6.   _chain = 0x0, _fileno = 0, _flags2 = 0, _old_offset = -1, _cur_column = 0, _vtable_offset = 0 '\000',
  7.   _shortbuf = "", _lock = 0x7ffff7e4e750 <_IO_stdfile_0_lock>, _offset = -1, _codecvt = 0x0,
  8.   _wide_data = 0x7ffff7e4b7e0 <_IO_wide_data_0>, _freeres_list = 0x0, _freeres_buf = 0x0, __pad5 = 0, _mode = -1,
  9.   _unused2 = '\000' <repeats 19 times>}
复制代码

  1. #include <stdio.h>

  2. int main(void) {
  3.     // getchar函数把输入的一行字符存入stdin中的缓冲区
  4.     // 然后返回第0个字符
  5.     int ch = getchar();
  6.     // 上面的getchar获取了第0个字符,这里读指针减1之后
  7.     // 读指针指向的位置就是上面getchar返回的那个字符
  8.     --stdin->_IO_read_ptr;
  9.     // 这里验证一下上面说的,看看读指针的位置是不是和变量ch中的字符一样
  10.     if(ch == stdin->_IO_read_ptr[0]) {
  11.         for(char *p = stdin->_IO_read_ptr; p != stdin->_IO_read_end; ++p) {
  12.             printf("'%c' ", *p);
  13.         }
  14.         puts("");
  15.     }
  16.     // 让读指针指向当前输入行的后面
  17.     // 让之后使用stdin的函数去获取新的一行字符
  18.     stdin->_IO_read_ptr = stdin->_IO_read_end;
  19.     scanf("%c", (char *)&ch);
  20.     // 再来一次
  21.     --stdin->_IO_read_ptr;
  22.     if(ch == stdin->_IO_read_ptr[0]) {
  23.         for(char *p = stdin->_IO_read_ptr; p != stdin->_IO_read_end; ++p) {
  24.             printf("'%c' ", *p);
  25.         }
  26.         puts("");
  27.     }
  28.     return 0;
  29. }
复制代码
  1. $ gcc-debug -o main main.c
  2. $ ./main
  3. asdf1234
  4. 'a' 's' 'd' 'f' '1' '2' '3' '4' '
  5. '
  6. zxcv09876
  7. 'z' 'x' 'c' 'v' '0' '9' '8' '7' '6' '
  8. '
  9. $
复制代码


注意,stdin里面的这些变量是下划线开头的,下划线开头的名字是保留的,你不应该使用这些名字
上面的程序很有可能无法在你那边运行,因为这个程序假设stdin里面有_IO_read_ptr这些名字
但是stdin完全可以不是这个样子,stdin是什么样子,这取决于实现

小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2022-8-14 09:38:31 | 显示全部楼层
本帖最后由 人造人 于 2022-8-14 09:39 编辑


conio.h,不是C标准库中的头文件,在C standard library,ISO C 和POSIX标准中均没有定义。
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2022-8-14 09:44:51 | 显示全部楼层
jackz007 发表于 2022-8-14 09:42
有何不妥吗,是 VC 不支持还是 GNU C 编译器不支持?

linux 下新版本的gcc也没有这个头文件

  1. $ gcc --version
  2. gcc (GCC) 12.1.1 20220730
  3. Copyright (C) 2022 Free Software Foundation, Inc.
  4. This is free software; see the source for copying conditions.  There is NO
  5. warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

  6. $ gcc -g -Wall -o main main.c
  7. main.c:2:10: fatal error: conio.h: No such file or directory
  8.     2 | #include <conio.h>
  9.       |          ^~~~~~~~~
  10. compilation terminated.
  11. $
复制代码
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2022-8-14 10:00:07 | 显示全部楼层

看起来只有windows有这个头文件
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2022-8-14 10:41:01 | 显示全部楼层
jackz007 发表于 2022-8-14 10:26
看来所谓的 "非标准" 就是指的这个了。
       以前,我曾经在 Unix 系统上使用过 ncurses 库 ...

嗯,我也了解过ncurses
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2022-8-14 11:25:50 | 显示全部楼层
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2022-8-14 12:07:16 | 显示全部楼层
阳间祝福 发表于 2022-8-14 12:03
就。。。写得很糙,这里我 BUFFER_SIZE 设成 1 了,还是能读很多,刚才以为是需要对齐到 4 或 8字节 ...

这个程序不就是输入什么输出什么吗?
一直while循环读取字符,不是 'Q' 就把这个字符输出
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2022-8-14 12:10:10 | 显示全部楼层
阳间祝福 发表于 2022-8-14 12:03
就。。。写得很糙,这里我 BUFFER_SIZE 设成 1 了,还是能读很多,刚才以为是需要对齐到 4 或 8字节 ...

还有,你的buf多少个字节了?
  1. $ cat main.c
  2. #include <stdio.h>
  3. #include <unistd.h>

  4. #define BUFFER_SIZE 1

  5. int main() {
  6.     //ssize_t read(int fd, void *buffer, size_t count);
  7.     void *buf[BUFFER_SIZE];
  8.     printf("%lu\n", sizeof(buf));
  9.     /*
  10.     while (read(0, buf, 1) != 'Q')
  11.         write(1, buf, 1);
  12.     */
  13.     return 0;
  14. }
  15. $ gcc-debug -o main main.c
  16. $ ./main
  17. 8
  18. $
复制代码
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2022-8-14 12:13:24 | 显示全部楼层
阳间祝福 发表于 2022-8-14 12:03
就。。。写得很糙,这里我 BUFFER_SIZE 设成 1 了,还是能读很多,刚才以为是需要对齐到 4 或 8字节 ...

等一下,read函数的返回值是什么?
读取到的这1个字符?
那如果是这样呢?
read(0, buf, 2)
返回读取到的两个字符?
你把read函数的返回值和 'Q' 比较是什么意思?
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2022-8-14 12:40:03 | 显示全部楼层
阳间祝福 发表于 2022-8-14 12:27
这里第 10 行 打印的是 buf 指针的大小吧?

我刚才试了一下,输入很长的串也可以,是不是因为它不检查 ...

  1. void *buf[1];
复制代码

buf是什么?
buf是一个数组,数组里面有一个元素,元素的类型是 void *
1 * sizeof(void *)
1 * 8 = 8


你这个程序在做什么?
这个程序不就是输入什么输出什么吗?
输入一个字符,输出一个字符,一直循环

小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2022-8-14 12:42:58 | 显示全部楼层
输入一个字符,输出一个字符,一直循环
你的buf有3个字节的空间,怎么会溢出呢?
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2022-8-14 12:46:54 | 显示全部楼层
阳间祝福 发表于 2022-8-14 12:43
是的,这个程序就是输入什么输出什么,只不过我在输入之后得按回车才会输出,所以我就好奇能不能不按回车 ...

"是不是因为它不检查越界什么的?"
什么?
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2022-8-14 13:00:21 | 显示全部楼层
把 'a' 写出来,然后再读 'b' , 存到 buf[0](还是 buf[1]?)
是存到 buf[0]
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2025-10-27 03:57

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表