xym-ee
  • 计算机与嵌入式开发学习
  • 1-1.编程基础
    • C 语言
      • C 中的数据
      • C 语言基础
      • 字符输入输出
      • 函数
      • 数组和指针
      • 字符串处理
      • 存储类别
      • 文件 I/O
      • 复杂数据类型
      • 位操作
      • 预处理和 C 库
    • 数据结构和算法入门
    • leetcode 刷算法题
      • 递归与栈
      • 二叉树与递归
      • 回溯问题
      • 动态规划 1
    • 基本工具和使用
      • shell
      • shell 脚本
      • vim 编辑器
      • 命令行数据整理
      • 命令行环境和配置
  • 1-2.计算机系统基础(CSAPP)
    • 1.计算机基础
    • 2.数据的表示
    • 3.加减运算
    • 4.乘除运算
    • 5.程序的表示转换和链接
    • 6.IA32指令
    • 7.过程调用
    • 10.程序的链接
  • 1-3.数字电路、计算机组成
    • 1.数字电路、virtual circuit board
    • 2.计算机组成/steam:Turing Complete
    • 3.微机原理与接口技术(8086)
  • 1-4.计算机网络
    • 1.从浏览器开始
    • 2.协议栈和网卡
    • 3.网络设备
    • 4.运营商、接入网
    • 5.服务器
    • 6.数据返回浏览器
    • socket编程
  • 1-5.操作系统
    • 0.绪论
      • 1.应用视角的操作系统
      • 2.硬件视角的操作系统
      • 3.数学视角的操作系统
      • 4.状态机模型的应用
    • 1.并发
      • 1.并发 bug 的解决思路
      • 2.互斥
      • 3.同步
      • 4.信号量
      • 5.真实并发
      • 6.调试技巧
      • 7.os kernel 实现
    • 2.虚拟化
      • 1.操作系统上的进程
      • 2.进程的地址空间
      • 3.系统调用和unix shell
      • 4.C 标准库的实现
      • 5.linux 操作系统
      • 6.可执行文件和加载
      • 7.动态链接和加载
      • 8.内核的实现
      • 9.fork 的应用
    • 3.持久化
      • 1.存储设备的原理
      • 2.输入输出设备模型
      • 3.设备驱动程序
      • 4.文件系统 API
      • 5.fat 和 unix 文件系统
      • 6.持久数据的可靠性
    • 总结
  • 2-1.嵌入式裸机开发
    • 嵌入式系统通信接口与协议
    • cortex-m 内核芯片裸机开发
    • MPU
  • 2-2.中等规模系统开发
    • LVGL 图形库
    • 裸机开发的软件框架
    • 基于 rtos 开发
  • 2-3.armv7-m架构与 rtos 原理
    • armv7-m 架构
    • rt-thread 内核实现
    • rt-thread 驱动开发
  • 3-1.linux 应用开发基础
  • 3-2.linux 镜像构建
    • uboot 使用
    • uboot 适配
    • uboot 启动分析
    • uboot 自定义命令
    • linux 内核适配
    • linux 内核启动分析
    • busybox 根文件系统构建
  • 3-3.linux 驱动开发
    • 驱动开发基础
    • sysfs
    • led 驱动
    • 设备树
    • pinctrl 和 gpio 子系统
    • 并发控制
由 GitBook 提供支持
在本页
  • 单字符 IO getchar() 和 putchar()
  • 缓冲区
  • 结束键盘输入
  • 重定向和文件

这有帮助吗?

  1. 1-1.编程基础
  2. C 语言

字符输入输出

  • 输入输出、有无缓冲的区别

  • 键盘模拟文件结尾(EOF)条件

  • 重定向链接程序和文件

  • 更友好的用户界面

用于输入输出的函数简称 I/O 函数,如 printf() scanf() getchar() putchar()。这些 IO 函数负责把信息传送到程序中。

最初输入输出函数并不是 C 定义的一部分,C 标准把这些函数交给编译器的实现者来实现。UNIX 系统中有这些函数的实行,ANSI C 库借鉴了成功经验,把大量的 UNIX I/O 函数囊括其中。

C 作为底层语言需要保证这些标准函数在不同系统上都能使用,所以避免使用特殊系统才有的特性。因此有些供应商会利用硬件特性实现更多的 I/O 函数。这里只讨论所有计算机系统都通用的标准 I/O 函数。使用标准 IO 函数写的程序很容易从一个系统移植到另一个系统。

单字符 IO getchar() 和 putchar()

这两个函数每次只处理一个字符,这种方法很适合计算机,这也是绝大多数文本处理程序使用的核心方法。

自 ANSI C 标准后,这两个函数就关联在 stdio.h 文件中。事实上,这两个不是真正的函数,这两个是宏。

// echo.c
#include <stdio.h>

int main(void)
{
    char ch;
    while( (ch = getchar()) != '#' )
        putchar(ch);
    return 0;
}

这段代码会读取键盘输入,直到遇到 # 退出。首先需要了解 C 程序如何处理键盘输入,尤其是缓冲和标准输入文件的概念。

abcdefg

输入之后并没有回显,这些内容是存放在缓冲区的,

缓冲区

如果没有缓冲区,上面这段代码在每收到一个字符就会立刻显示出来,正在等待的程序可以立即获得输入的字符。大部分系统需要用户按下 ENTER 之前不会重复打印。用户输入的字符都被收集并存储在缓冲区(buffer),按下 ENTER 后,缓冲区的内容送给程序。

缓冲区的存在也是出于系统运行效率考虑的。每个字符单独传输的效率要比打包传输低得多。(内核态与硬件交互,集中起来送给用户态程序,更多细节参考操作系统课程)。

并不是所有程序都需要缓冲区,如游戏程序,使用缓冲区是不明智的。

缓冲分为两类:完全缓冲I/O 和 行缓冲I/O。

  • 完全缓冲输入:当缓冲区满时才刷新缓冲区(内容发送至目的地)

    • 常出现在文件输入

    • 缓冲区大小取决于系统,常见 512 字节和 4096 字节

  • 行缓冲输入:键盘输入,按下 ENTER 才刷新缓冲区

ANSI C 和后续的 C 标准都规定输入是缓冲的。

UNIX 系统可以使用 ioctl() 函数指定输入的类型,然后用 getchar 执行操作。

结束键盘输入

上面程序中的 # 也是一个普通字符,更好的方法是用一个在文本中用不到的字符来标记输入完成,这样的字符不会再文本中出现,C 提供了这样的字符,即 EOF,linux 里按键组合为 ctrl + D

文件是对存储数据的抽象,可以将文件视为一个字节序列,C 中有处理文件的函数,从概念上看,C 程序处理的是流而不是直接处理文件。流即字节流。这里提出文件,是要理解把键盘输入输出也可以视为字节流,即文件。可以把键盘和显示器视为 C 程序自动打开的文件,stdin 为键盘输入,stdout 为屏幕输出。(linux 文件描述符012,更多内容查看操作系统)。

在处理文件时,C 是可以检测到文件尾的,既然把键盘输入视为文件流的输入,那么也应该可以使用检测文件结尾的方法检测结束键盘输入。

文件结尾,早期 CP/M、IBM-DOS、MS-DOS 的方法是在文件尾放一个特殊字符即ctrl + Z ,现在也有可以使用文件大小判断文件结尾,但是如果有这个字符标记,那么一定就是文件结尾。使用 getchar 读取时,会返回一个 EOF 。

现在这个程序可以把用户输入的内容复制到屏幕上。假如通过某种方式可以把一个文件传送给这个程序,那么岂不是可以把这个文件显示在屏幕i上?或者把键盘输入的内容保存到一个文件中?所以这段小程序有查看文件内容、创建新文件、复制文件的潜力,问题的关键是我们需要编程控制输入输出流。

#include <stdio.h>

int main()
{
    char ch;
    while ((ch = getchar()) != EOF)
        putchar(ch);
    return 0;
}

在这个代码中,需要键盘输入 CTRL + D 来模拟文件结束。

重定向和文件

除了用键盘输入,向屏幕输出,也可以从文件输入,输出到文件,这就是重定向。

比如

echo hello world > txt
./a.out < txt

这些文本文件在读到结尾时,操作系统会自动返回一个 EOF ,程序也就结束了。

上一页C 语言基础下一页函数

最后更新于8个月前

这有帮助吗?