ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Linux软件编程学习笔记(二)——标准IO

Linux软件编程学习笔记(二)——标准IO 一、什么是 IOIO 是IInput 输入 OOutput 输出例如键盘 ─────→ 程序 输入 程序 ─────→ 屏幕 输出 文件 ─────→ 程序 输入 程序 ─────→ 文件 输出在 Linux 软件编程中IO 主要包括文件 IO 标准 IO 设备 IO 网络 IO本阶段主要学习文件 IO 和标准 IO。二、Linux 中的文件文件可以理解为存放在磁盘空间中的一段数据的集合。课程资料中对文件的定义也是“存放在磁盘空间中的一段数据的集合”。Linux 中一个非常重要的思想是Linux 系统中一切皆是文件。Linux 内核的重要功能之一就是文件系统管理。因此在 Linux 中不仅普通文本文件可以进行 IO一些设备、管道等也可以通过文件相关机制进行操作。三、文件操作的三个步骤对文件进行操作一般需要经过三个步骤文件 ↓ ① 打开 ↓ ② 读写 ↓ ③ 关闭也就是open ↓ read / write ↓ close对于 C 标准 IO则对应fopen ↓ fgetc / fputc fgets / fputs fprintf / fscanf fread / fwrite ↓ fclose课程笔记中也按照“打开文件 → 读写文件 → 关闭文件”的顺序整理了文件 IO。四、ASCII 文件和二进制文件文件从数据存储形式上可以分为ASCII 文件 二进制文件1. ASCII 文件ASCII 文件中的数据主要以字符形式保存。例如hello world 123456 Linux常见的文本文件.txt .c .h .sh例如 C 源代码#include stdio.h int main(void) { printf(Hello Linux\n); return 0; }本质上就是一个文本文件。2. 二进制文件二进制文件中的数据不一定能够直接以可显示字符的形式呈现在终端中。常见的二进制数据图片 音频 视频 压缩包 可执行文件例如a.out .jpg .mp3 .mp4 .zip需要注意ASCII 文件本质上也是二进制文件的一种特殊形式。课程资料中也明确指出ASCII 文件属于特殊的二进制文件。五、C 标准 IOC 语言中提供了一套标准 IO 接口主要包含fopen() fclose() fputc() fgetc() fputs() fgets() fprintf() fscanf() fwrite() fread()这些函数主要定义在#include stdio.h中。标准 IO 使用FILE *来表示一个文件流。例如FILE *fp NULL;六、fopen() 打开文件函数原型FILE *fopen(const char *pathname, const char *mode);其中pathname ↓ 文件路径 mode ↓ 打开方式例如FILE *fp fopen(file.txt, r);表示以只读方式打开file.txt。课程资料中给出了fopen()的基本接口及其返回值规则成功返回文件流指针失败返回NULL。七、fopen() 常见打开方式1.r只读。FILE *fp fopen(file.txt, r);特点文件存在 → 打开 文件不存在 → 失败2.r读写。FILE *fp fopen(file.txt, r);特点文件存在 → 读写 文件不存在 → 失败3.w只写。FILE *fp fopen(file.txt, w);特点文件不存在 → 创建 文件存在 → 清空4.w读写。FILE *fp fopen(file.txt, w);特点文件不存在 → 创建 文件存在 → 清空5.a追加写。FILE *fp fopen(file.txt, a);特点文件不存在 → 创建 文件存在 → 在末尾追加6.a追加读写FILE *fp fopen(file.txt, a);课程笔记中对r、r、w、w、a、a的区别进行了完整整理。八、fopen() 返回值判断打开文件后一定要判断是否成功#include stdio.h int main(void) { FILE *fp NULL; fp fopen(file.txt, r); if(fp NULL) { perror(fopen); return -1; } printf(open success\n); fclose(fp); return 0; }核心if(fp NULL)说明fp NULL ↓ 打开失败 fp ! NULL ↓ 打开成功九、fclose() 关闭文件打开文件后使用完一定要关闭。fclose(fp);函数原型int fclose(FILE *stream);返回值0 ↓ 成功 EOF ↓ 失败课程资料中也明确记录了fclose()的返回值。因此基本结构FILE *fp NULL; fp fopen(file.txt, r); if(fp NULL) { perror(fopen); return -1; } /* 文件操作 */ fclose(fp);十、fputc() —— 写一个字符函数int fputc(int c, FILE *stream);作用向指定文件流中写入一个字符。例如#include stdio.h int main(void) { FILE *fp NULL; fp fopen(a.txt, w); if(fp NULL) { perror(fopen); return -1; } fputc(H, fp); fputc(e, fp); fputc(l, fp); fputc(l, fp); fputc(o, fp); fclose(fp); return 0; }执行后a.txt中会出现Hello课程资料中指出fputc()成功时返回写入字符对应的 ASCII 码失败时返回EOF。十一、fgetc() —— 读取一个字符函数int fgetc(FILE *stream);作用从指定文件流中读取一个字符。例如char ch; ch fgetc(fp);但是这里有一个非常重要的细节为什么不用char接收推荐int ch;而不是char ch;因为fgetc()不仅需要返回字符还需要返回EOF所以返回值类型是int例如int ch; while((ch fgetc(fp)) ! EOF) { putchar(ch); }这种写法非常重要。课程资料中也明确说明fgetc() 成功 → 返回字符 ASCII 码 失败 → EOF 文件结束 → EOF十二、利用 fgetc() 统计文件行数例如统计file.txt有多少行#include stdio.h int main(void) { FILE *fp NULL; int ch 0; int cnt 0; fp fopen(file.txt, r); if(fp NULL) { perror(fopen); return -1; } while((ch fgetc(fp)) ! EOF) { if(ch \n) { cnt; } } printf(line %d\n, cnt); fclose(fp); return 0; }这里需要注意ch \n表示遇到换行符。而ch EOF表示读取结束。十三、fputs() —— 写字符串函数int fputs(const char *s, FILE *stream);例如fputs(Hello Linux\n, fp);会把字符串写入文件。例如#include stdio.h int main(void) { FILE *fp NULL; fp fopen(a.txt, w); if(fp NULL) { perror(fopen); return -1; } fputs(Hello Linux\n, fp); fputs(Linux IO\n, fp); fclose(fp); return 0; }课程资料中指出fputs()不会额外添加\n。所以fputs(hello, fp);不会自动换行。如果需要换行fputs(hello\n, fp);十四、fgets() —— 读取一行字符串函数char *fgets(char *s, int size, FILE *stream);例如char buf[128] {0}; fgets(buf, sizeof(buf), fp);作用从文件流中读取一行字符串。例如#include stdio.h int main(void) { FILE *fp NULL; char buf[128] {0}; fp fopen(file.txt, r); if(fp NULL) { perror(fopen); return -1; } while(fgets(buf, sizeof(buf), fp) ! NULL) { printf(%s, buf); } fclose(fp); return 0; }课程资料指出fgets()从流中读取一行字符串并且读取失败或到达文件末尾时返回NULL。十五、fgets() 和 gets() 的区别这两个函数非常容易混淆。gets()gets(buf);存在缓冲区溢出风险现代 C 编程中不应该使用。fgets()fgets(buf, sizeof(buf), stdin);可以指定最大读取长度更安全。另外fgets()从终端读取时如果输入中包含换行符通常会将\n一起读入字符串。所以char buf[100]; fgets(buf, sizeof(buf), stdin);输入hello实际可能得到h e l l o \n \0课程笔记也特别记录了gets()和fgets()对换行符处理的区别。十六、fprintf() —— 格式化写文件fprintf()和printf()很像。区别printf()默认向标准输出写。而fprintf()可以指定输出流。例如fprintf(fp, name %s\n, linux); fprintf(fp, age %d\n, 20);最终写入name linux age 20因此可以记printf ↓ 标准输出 fprintf ↓ 指定文件流十七、fscanf() —— 从文件格式化读取类似scanf()fscanf()可以从指定流中读取数据。例如文件100 200代码int a 0; int b 0; fscanf(fp, %d %d, a, b);读取后a 100 b 200所以scanf() ↓ 标准输入 fscanf() ↓ 指定文件流十八、fread() 和 fwrite()对于二进制文件常用fread() fwrite()例如fwrite(data, sizeof(data), 1, fp);读取fread(data, sizeof(data), 1, fp);它们适合处理结构体 图片 音视频数据 二进制数据课程资料将fwrite/fread与字符、字符串、格式化 IO 一起列为标准 IO 的文件读写接口。十九、标准 IO 的三个默认流程序启动后系统会默认打开三个标准流stdin stdout stderr分别是流含义默认设备stdin标准输入键盘stdout标准输出屏幕stderr标准错误屏幕课程资料中也明确列出了这三个默认打开的流。例如printf(Hello\n);本质上是向stdout输出。而scanf(%d, num);则是从stdin读取。二十、标准 IO 的缓存标准 IO 一个非常重要的特点标准 IO 是有缓存的 IO。用户调用标准 IO 函数后数据通常会先进入用户空间的缓冲区在满足刷新条件后再传递给内核。主要有三种全缓存 行缓存 不缓存1. 全缓存缓存区满时刷新。文件流通常采用全缓存。课程资料中给出的典型大小是4KB 4096 Bytes刷新条件包括① 缓冲区满 ② fflush() ③ fclose() ④ 程序结束2. 行缓存遇到\n时刷新。终端相关的流通常采用行缓存。典型大小1KB 1024 Bytes例如printf(hello\n);遇到\n后通常会触发刷新。3. 不缓存数据产生后立即刷新。例如stderr通常采用不缓存方式。适用于错误处理 实时性要求高 人机交互二十一、标准 IO 与系统 IOLinux 编程中需要区分标准 IOfopen() fclose() fgetc() fputc() fgets() fputs() fprintf() fscanf() fread() fwrite()使用FILE *系统 IO后续 Linux 系统编程中会学习open() close() read() write() lseek()使用int fd;可以简单理解C 标准 IO ↓ FILE * ↓ 带缓存 Linux 系统 IO ↓ 文件描述符 fd ↓ 系统调用另外Linux 中的库函数通常是对系统调用的封装可以提供更方便、更通用的接口系统调用则是 Linux 内核提供的接口。二十二、标准 IO 学习流程以后写文件程序可以按照这个思路开始 ↓ 定义 FILE * ↓ fopen() ↓ 判断是否 NULL ↙ ↘ 是 否 ↓ ↓ 报错 文件操作 ↓ 读 / 写 ↓ fclose ↓ 结束代码模板#include stdio.h int main(void) { FILE *fp NULL; /* 1. 打开 */ fp fopen(file.txt, r); if(fp NULL) { perror(fopen); return -1; } /* 2. 读写文件 */ /* 3. 关闭 */ fclose(fp); return 0; }二十三、本节重点总结这一节最重要的是下面这些知识① 文件操作三个步骤打开 → 读写 → 关闭② 标准 IOfopen() fclose() fgetc() fputc() fgets() fputs() fprintf() fscanf() fread() fwrite()③ 文件打开方式r 只读 r 读写 w 只写清空 w 读写清空 a 追加写 a 追加读写④ 三个标准流stdin 标准输入 stdout 标准输出 stderr 标准错误⑤ 标准 IO 的缓存全缓存 行缓存 不缓存⑥ 文件类型ASCII/文本文件 二进制文件二十四、学习 IO 时最容易出错的几个地方1.fopen()后忘记判断错误FILE *fp fopen(a.txt, r); fgetc(fp);正确FILE *fp fopen(a.txt, r); if(fp NULL) { perror(fopen); return -1; }2.fgetc()不要用char判断 EOF不推荐char ch; while((ch fgetc(fp)) ! EOF)推荐int ch; while((ch fgetc(fp)) ! EOF) { putchar(ch); }因为fgetc()的返回值类型是int需要能够表示所有字符值以及EOF。3.fputs()不会自动换行fputs(hello, fp);不会换行。需要fputs(hello\n, fp);4.fgets()要注意数组大小推荐char buf[100]; fgets(buf, sizeof(buf), fp);而不是随意指定一个超过数组实际容量的长度。5. 文件打开后要关闭fclose(fp);这是良好的资源管理习惯。二十五、IO 的整体知识框架Linux IO │ ├── 文件 │ ├── ASCII 文件 │ └── 二进制文件 │ ├── 文件操作 │ ├── 打开 │ ├── 读写 │ └── 关闭 │ ├── C 标准 IO │ ├── fopen │ ├── fclose │ ├── fputc / fgetc │ ├── fputs / fgets │ ├── fprintf / fscanf │ └── fwrite / fread │ ├── 标准流 │ ├── stdin │ ├── stdout │ └── stderr │ ├── IO 缓存 │ ├── 全缓存 │ ├── 行缓存 │ └── 不缓存 │ └── Linux 系统 IO ├── open ├── read ├── write ├── close └── lseek
返回列表