标准库 IO

输入输出功能并非C语言的组成部分,ANSI标准定义了相关的库函数

输入输出 <stdio.h>

流stream是与设备关联的数据的源或者目的地。

  • 文本流:由文本行组成的序列

    不同系统的特性可能不一样,比如行最大长度和行结束符
  • 二进制流:未经处理的字节序列

程序运行时,默认打开 stdin, stdout, stderr

标准 IO 常量

  • EOF:文件尾,实际值是几个字符避免混淆
  • FOPEN_MAX:一个程序同时最多能够打开的文件数量,与编译器有关,值至少为 8
  • FILENAM_MAX:编译器支持的最长文件名

文件流操作

文件指针指向包含文件信息的结构,包括缓冲区,读写状态等

打开与关闭

FILE *fopen(const char *filename, const char *mode);                    // 打开文件流
/*
mode:
* r, 打开读
* w, 打开或创建写,删除原有内容
* a, 打开或者创建,文件尾追加
* +, 更新,读或写,交叉操作前需要执行fflush或者定位操作
* r+, 打开文件更新(读和写)
* w+, 创建文件更新,删除原有内容
* a+, 打开或创建文件,文件尾更新
* b, 二进制流模式 应始终检查返回值
*/ FILE *freopen(const char *filename, const char *mode, FILE *stream); // 先关闭再打开文件流 FILE *fdopen(int fd, const char *type); // POSIX标准,常用于由创建管道和网络通信通道函数返回的描述符,不能直接用 fopen 打开
// type 参数: r, w, a, (+)
// 注意读写模式下读写操作之间需要进行调用定位函数 int fclose(FILE *stream); // 刷新输出缓冲,释放系统缓冲区,关闭流 int fileno(FILE *fp); // POSIX 标准

流的定向

标准IO可用于单字节和多字节字符集,具体由流的定向决定。

  • freopen函数清除一个流的定向
  • fwide函数设置流的定向、
#include <stdio.h>
#include <wchar.h> int fiwde(FILE *fp, int mode);
/*
mode<0, 试图设置为单字节
mode>0,试图设置为多字节
mode=0,不设置定向,返回流的定向 不会改变已经设置的流定向
*/

文件操纵函数

重命名

int remove(FILE *stream);                               // 删除文件

int rename(const char *oldname, const char *newname); // 重命名文件

临时文件

FILE *tmpfile(void);     // 以`wb+`模式创建临时文件,在关闭或者程序结束时自动删除
// 实现上tmpnam,创建文件,unlink(不会删除内容),文件的关闭会在程序结束中自动进行 char *tmpnam(char s[L_tmpnam]); // 创建不同现有文件名的字符串,NULL返回指向静态数组的指针 // UNIX 优势是不存在时间间隙,避免其他进程创建同名文件
char *mkdtemp(char *temple); // 创建目录
int mkstmp(char *temple); // 创建文件,不会自动删除

缓冲操作

标准IO提供缓冲的目的是尽可能减少read/write的调用次数

  • 全缓冲:缓冲区满了才进行实际IO操作

  • 行缓冲:遇到换行符才进行IO操作

    • 行缓冲的长度是固定的,行缓冲满了即使没有换行符也会进行IO操作
    • 标准IO要求从不带缓冲或者行缓冲(需要从内核请求数据)获取数据,会立即刷新所有行缓冲的输出流
  • 不带缓冲

ISO C标准

  • 当且仅当标准输入和标准输出不指向交互式设备才是全缓冲的
  • 标准错误不会是全缓冲的

默认情况

  • 标准错误不带缓冲
  • 若是指向终端设备的流,则是行缓冲的,否则是全缓冲的

更换缓冲的类型:流被打开之后,且在执行任何操作之前

int fflush(FILE *stream);                                               // 刷新缓冲区
/*
- 对于输出流,刷新写入缓冲区的内容至目标文件
- 对于输入流,其结果是未定义的
- NULL,刷新所有的缓冲区 实际使用技巧:每个调试 printf 之后立马调用 fflush
*/ int setvbuf(FILE *stream, char *buf, int mode, size_t size); // 必须在执行读写操作之前设置缓冲
/*
mode:
- _IOFBF 完全缓冲
- _IOLBF 行缓冲
- _IONBF 不设置缓冲
*/ void setbuf(FILE *stream, char *buf); // char buf[BUFSIZ]
/*
- buff为 NULL, 关闭缓冲
- 否则,等价于 `_IOFBF` // 注意:buf 不要使用自动变量类型,尽量使用系统缓冲区或者动态分配内存
*/

​#TODO#​查看流缓冲状态 《UNIX高级环境编程》

读写流操作

字符 IO

​#TODO#​输入输出函数家族 《C和指针》P301

int fgetc(FILE *stream);  // unsigned char 转 int, 兼容EOF
int getc(FILE *stream); // 等价于 fgetc,注意实现为宏
int getchar(void); // 等价于 getc(stdin) int fputc(int c, FILE *stream);
int putc(int c, FILE *stream); // 等价于 fputc, 注意实现为宏
int putchar(int c); // 等价于 fputc(stdout) // 只有fgetc和fputc是函数,其他都是宏 int ungetc(int c, FILE *stream); // 将字符退回流中,依赖于当前位置
// 不同于写操作,仅涉及流本身而无关设备存储

未格式化的行IO

char *fgets(char *s, int n, FILE *stream);  // 自动包含换行符,\n 换为 \0,最多n-1
char *gets(char *s); // 不自动包含换行符。没有缓冲区长度参数,可能导致越界;不推荐使用,已废弃 int fputs(const char *s, FILE *stream); // 不自动包含换行符\n,逐字符输入任意个数换行符
int puts(const char *s); // 自动添加换行符\n ssize_t getline(char **lineptr, size_t *n, FILE *stream);
// 根据输入动态分配内存,无需预先确定输入字符串的最大长度
// 在存储的字符串中将换行符替换为字符串结束符'\0'

格式化 IO

int fprintf(FILE *stream, const char *format, ...);
int printf(const char *format, ...); // 等价于 `fprintf(stdout, fotmat, ...)`
int sprintf(char *s, const char *format, ...); // 包含结束符 NUL,无长度参数,可能越界溢出
int snprintf(char *buf, szie_t n, const char *format, ...); // 超出部分截断,出错返回负值 int fdprintf(int fd, const char *format, ...); // 变长参数列表变体
int vprintf(const char *format, va_list arg);
int vfprintf(FILE *stream, const char *format, va_list arg);
int vsprintf(char *s, const char *format, va_list arg);

转换格式:

  • 普通字符:复制到输出流

  • 转换说明:控制参数的格式转换

    • 开头 %

    • 标志[可选]

      • -​左对齐,缺省为右对齐
      • +​显示正负号
      • 空格 有符号值转换
      • 0​ 宽度不足时填充 0
      • #​指定另一种输出形式
    • 宽度数值[可选]:指定最小字段宽度

    • 精度数值[可选]:点号开始,后接十进制数值

    • 长度修饰符[可选]:指定参数的长度

      • h 按照short/unsigned short 输出
      • l 按照long/unsigned long 输出
      • L 按照long double 输出
    • 结尾: 转换字符, d, c, s, f, x等

int fscanf(FILE *stream, const char *format, ...);
int scanf(const char *format, ...);
int sscanf(const char *s, char *format, ...); int fdscanf(int fd, const char *format,...); // 变长参数列表变体 ...
  • 参数必须是指针
  • 到文件尾或者出错返回EOF,否则返回实际输入的字符数

转换格式

  • 空格或者制表符

  • 普通字符:匹配下一个输入

  • 转换说明

    • 开始标志%​[可选]
    • 赋值屏蔽符号*​[可选]
    • 最大字段宽度数值[可选]
    • 限定符 h\l\L​,指定参数的长度[可选]
    • 结束标志:转换字符 d,f,c, s, x等等

​#TODO#​4种使用场景P309

二进制 IO

直接IO/二进制IO,通常一次处理一个结构,能够处理null字节和换行符。

注意事项:只能用于同一系统,不同系统的偏移对齐以及存储格式可能不同。因此网络通信需要指定规范。

size_t fread(void *buffer, size_t size, size_t nobj, FILE *stream);

size_t fwrite(const void *buffer, size_t size, size_t nobj, FILE *stream);

/* 返回值是实际读写的元素的个数而非字节数
fread:少于nobj, 出错或者EOF,需要进一步分辨
fwrite:少于nobj,错误
*/

内存流

无关文件,直接在缓冲区和主存之间进行字节IO。非常适用于字符串。

Linux支持。

FILE *fmemopen(void *buf, size_t size, const char *type); // buf=null, 读写无意义;对于null字节的处理十分特殊

FILE *open_memstream(char **bufp, size_t sizep);   // 面向字节的流

FILE *open_wmemstream(wchar_t **bufp, size_t *sizep);  // 面向宽字节的流

文件定位函数

  • 二进制文件:使用字节偏移量,不一定支持 SEEK_END
  • 文本文件:格式不同不能使用字节,orgin=SEEK_SET, offset=0/ftell
int fseek(FILE *stream, long offset, int origin);
/*
- 二进制文件
- origin
- `SEEK_SET` 文件开始处
- `SEEK_CUR` 当前位置
- `SEEK_END` 文件结束处,可能不支持
- 文本文件
- `SEEK_SET`;offset是 0 或者 `ftell`返回值
- `SEEK_CUR`/`SEEK_END`:offset只能是 0 注意事项
- 行末指示符将被清除
- 退回的字符将被丢弃
- 更新模式中的读写操作切换
*/ long ftell(FILE *stream); void rewind(FILE *stream); // 重置为起始位置
//等价于 `fseek(stream, 0L, SEEK_SET); clearerr(stream);`
// off_t, 大于32位 UNIX 标准
off_t ftello(FILE *fp);
int seeko(FILE *fp, off_t offset, int origin);
// fpos_t ISO C标准,更加通用
int fgetpos(FILE *stream, fpos_t *ptr); int fsetpos(FILE *stream, const fpos_t *ptr);

错误处理函数

发生错误或者到达文件尾时会设置状态指示符

整型表达式 errno 包含错误编号,定义在 <errno.h>

  • 只有库函数失败时才会设置 errno, 成功执行并不会修改 errno
  • 任何函数都不会将常量置为0
/* ----- 流错误 -----*/
int feof(FILE *stream); // 流设置了文件结束指示符,返回非0值 int ferror(FILE *stream); // 流设置了错误指示符,返回非0值 int clearerr(FILE *stream); // 清除流的所有指示符
#include <string.h>
char *strerror(int crrno); // 映射错误信息 #include <stdio.h>
int perror(const char *s); // 打印字符串和 errno 错误信息
// 类似于 fprintf(stderr, "%s: %s\n", s, "error essage");

标准IO的替代

标准IO 的效率不高,调用行IO需要进行两次数据的复制

  • 快速IO fio: 使用指针而不是复制整行
  • sfio: 提高速度,同时推广IO流
  • mmap

适用于嵌入式系统的更低内存要求的实现

  • uClibc C库
  • Newlib C库

参考

  • 《C程序设计语言》
  • 《UNIX 环境高级编程》

C语言之输入输出的更多相关文章

  1. 3013C语言_输入输出

    第三章 输入输出 3.1输入输出概念及其实现 (1)数据从计算机内部向外部输出设备(如显示器.打印机等)输送的操作称为 “输出”,数据从计算机外部向输入设备(如键盘.鼠标.扫描仪等)送入的操作称为 “ ...

  2. C语言:输入输出

    C语言无I/O语句,i/o操作由函数实现 #include<stdio.h> 字符输出函数putchar: 格式:putchar(c) 参数:c为字符常量,变量或者表达式 功能:把字符c输 ...

  3. C语言中输入输出重定,freopen()妙用。

    使用的理由(范围):如果输入数据很庞大,需要一次又一次的重新输入和调试时可采用本函数. freopen ()函数: 1.格式 FILE * freopen ( const char * filenam ...

  4. C语言格式化输入输出

    %i和%d之间的区别 作为匹配整数的转换说明,printf格式串中两者并没有区别,但是在scanf格式串中%d只能匹配十位制整数,而%i可以匹配八进制(前缀为0,如086).十进制或十六进制(前缀0x ...

  5. C语言的输入输出操作函数小结

    一.scanf()&printf()函数 scanf() 函数用于从标准输入(键盘)读取并格式化, printf() 函数发送格式化输出到标准输出(屏幕).  scanf()函数原型为int ...

  6. C语言学习笔记---3.字符串格式化输入输出

    1.C语言字符串 字符串(character string)是一个或多个字符的序列,例如:"Zing went the strings of my heart!" C语言没有专门用 ...

  7. 《C语言入门1.2.3—一个老鸟的C语言学习心得》—清华大学出版社炮制的又一本劣书及伪书

    <C语言入门1.2.3—一个老鸟的C语言学习心得>—清华大学出版社炮制的又一本劣书及伪书 [薛非评] 区区15页,有80多个错误. 最严重的有: 通篇完全是C++代码,根本不是C语言代码. ...

  8. 第一章 C++语言入门

            标准数据类型         C++语言提供了丰富的数据类型,如整数类型.实数类型(浮点数).字符类型等.每种数据类型均有均值范围,Dev-C++(4.9.9.2)是Windows平台 ...

  9. c++输入输出,保留几位小数

    #include <iomanip> //头文件 //第一种写法 cout<<setiosflags(ios::); //第二种写法 cout.setf(ios::fixed) ...

  10. 一、c++语言基础

    1. 程序员的第一条代码"Hello,world!" #include <cstdio> //头文件,主要负责输入.输出 using namespace std;//C ...

随机推荐

  1. centos 查看jdk 安装路径

    命令行: which java 输出: /usr/bin/java 再次输入: ls -lr /usr/bin/java 输出: lrwxrwxrwx 1 root root 22 4月 26 17: ...

  2. 神经网络之卷积篇:详解边缘检测示例(Edge detection example)

    详解边缘检测示例 卷积运算是卷积神经网络最基本的组成部分,使用边缘检测作为入门样例.在这个博客中,会看到卷积是如何进行运算的. 在之前的博客中,说过神经网络的前几层是如何检测边缘的,然后,后面的层有可 ...

  3. JS Map对象与map方法

    前言  最近遇到一个遍历的问题,查资料的过程中,发现有个 Map 对象,提供了很多方法可以轻松地获取我们想要的数据,之前只知道有 map ,没想到还有 Map ,是不是有点绕?不急,这两个东西都是虽然 ...

  4. RabbitMq高级特性之死信队列 通俗易懂 超详细 【内含案例】

    RabbitMq高级特性之死信队列 又称 死信交换机 DLX 介绍 当消息成为 Dead message 后,会重新发送到另一个交换机,这个交换机就是 DLX(死信交换机) 消息成为死信的情况公有三种 ...

  5. CH06_函数

    CH06_函数 概述 作用:将一段可复用的代码封装起来,减少代码重复. 一个较大的程序,一般分为若干个程序块,每个模块实现特定的功能. 函数的定义 函数的定义一般主要有5个步骤: 返回值类型 函数名 ...

  6. Mac升级Ventura 13.0.1后无法远程ssh连接服务器

    原因 原因是Mac os Ventura升级了ssh到9.0,ssl到3.3.6,而服务器上的sshd还是老版本:服务器上的老版本ssh和ssl无法和mac上的新版本ssh和ssl交互,新版本ssh加 ...

  7. 查看 Homebrew 管理的服务的日志

    TL;DR 首先找到 log 文件的位置: 对于 macOS (arm64),log 文件在 /opt/homebrew/var/log 目录下 对于 macOS (x86_64),log 文件在 / ...

  8. Ubuntu 切换显示管理器

    比较流行的显示管理器有: gdm3 - GNOME Display Manager lightdm - Light Display Manager sddm - Simple Desktop Disp ...

  9. LaTeX 插入表格

    普通表格 \begin{table}[h] % h: here \begin{center} % 一个字母代表一列 \begin{tabular}{|c|cccc|} % c: center, l: ...

  10. manim边学边做--曲线类

    manim中曲线,除了前面介绍的圆弧类曲线,也可以绘制任意的曲线. manim中提供的CubicBezier模块,可以利用三次贝塞尔曲线的方式绘制任意曲线. 关于贝塞尔曲线的介绍,可以参考:https ...