StdIO.png

版权声明:本文为 cdeveloper 原创文章,可以随意转载,但必须在明确位置注明出处!

标准 IO 库

上一篇文章我们学习了 5 个底层的 IO 函数,这次我们来学习标准的 IO 函数,既然是标准那肯定在多个平台都有使用到,例如 Linux,Windows...,上层应用用标准库开发还是很常见的,因为它们可以跨平台。

标准 IO 库是由 Dennis Ritchie 在 1975 年左右写的,后来被美国国际标准化组织(ISO)制定成了 C 语言的标准库,称为 ANSI C。因为现在所有的 UNIX 系统都提供 C 标准中定义的库函数,所以学习标准 C 库非常重要。

这篇文章主要介绍一些常用的 C 库函数,例如 fopen, fclose, fread, fwrite...,不可能将全部的库函数介绍完毕,你需要根据那本经典的Unix 环境高级编程来深入的学习,一定掌握学习的方法:

  1. man 手册
  2. glibc 官网
  3. Google

流和 FILE 对象

在学习标准库之前,有两个概念需要理解,我们的开发都是以它们为基础

1. 文件流

当用标准库打开或操作文件时,我们可以看作用一根水管与这个文件连接,里面流淌的是文件数据。文件流又分为下面 2 种:

  1. 文本流:传输文本
  2. 字节流:传输字节

他们主要有 3 点不同:

  1. 从文本流中读取的数据被划分成以换行 \n 字符终止的行,而二进制流只是一系列长的字符
  2. 在某些系统上,文本文件只能包含打印字符,水平制表符和换行符,因此文本流可能不支持其他字符,而二进制流可以处理任何字符值。
  3. 当文件再次读入时,在文本流中的换行符之前立即写入的空格字符可能会消失。更一般地说,不需要在从文本流中读取或写入文本流的字符与实际文件中的字符之间进行一对一映射。

注意:在 GNU C 库和所有 POSIX 系统中,文本流和二进制流之间没有区别,当您打开流时,无论是否要求二进制,都可以获得相同的流,此流可以处理任何文件内容,并且没有文本流有时具有的限制。

文本流和字符流更加详细的区别请参考这里

2. FILE 对象

我们用 FILE 对象来在用户空间表示一个打开的文件,该对象是一个结构体,包含了标准 IO 库管理当前文件流的信息。

例如,fopen 函数成功打开文件的操作:

FILE *fp = fopen("1.txt", "r");

fopen & fclose

使用 fopen 和 fclose 来打开和关闭一个文件,来分别看看它们的声明:

fopen

使用 fopen 来打开一个文件:

#include <stdio.h>/** path: 要打开的文件名称,必须时包含路径* mode: 文件的打开模式* return: 成功返回 FILE 指针,失败返回 NULL,并设置 errno*/
FILE *fopen (const char *path, const char *mode);

其中文件的打开模式分为 6 种:

  1. r:只读,文件必须存在
  2. r+: 可读可写
  3. w: 可读可写,会清空文件
  4. w+: 可读可写,文件不存在就创建
  5. a: 在文件尾部追加内容,如果文件不存在就创建
  6. a+:可读可写

其中 r = readw = writea = append,意思分别是:读,写,追加

fclose

使用 fclose 来关闭一个文件:

#include <stdio.h>/** stream: 要关闭的文件指针* return: 成功返回 0,失败返回 EOF,并设置 errno*/
int fclose(FILE *stream);

来看一个打开和关闭文件的例子。

实例 1: fopen_close.c

这个例子使用 fopen 和 fclose 来打开和关闭一个文件 1.txt

/** fopen_close.c* fun: test fopen and fclose function.*/#include <stdio.h>
#include <stdlib.h>int main(int argc, char *argv[]) {if (argc < 2) {printf("Usage: ./fopen_close 1.txt\n");exit(1);} FILE *fp = fopen(argv[1], "r");if (NULL == fp) {printf("file open fail.\n");exit(1);} printf("file open success.\n");fclose(fp);printf("file close success.\n");return 0;
}

gcc 编译:

gcc fopen_close.c -o fopen_close

运行,因为我们用 r 打开文件,所以必须先建立 1.txt 文件

./fopen_close 1.txtfile open success.
file close success.

文本流:单字符读写

在标准 IO 库中有下面 3 组对应的输入输出字符函数:

#include <stdio.h>// 从文件中读取或写入一个字符
int getc(FILE *fp);
int putc(int c, FILE *fp);// 从文件中读取或写入一个字符
int fgetc(FILE *fp);
int fputc(int c, FILE *fp);// 从标准输入读取,或者输出到标准输出
int getchar(void);
int putchar(int c);

这些函数的参数都很好理解,我们以 fgetc 和 fputc 为例:

实例 2:fget_putc.c

我们用 fgetc 和 fputc 来拷贝一个文件:

/** fgetc_putc.c* fun: using fgetc and fputc to copy file.*/#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>int main(int argc, char *argv[]) {if (argc < 3){printf("Usage: ./fgetc_putc 1.txt 1.txt.bak.\n");exit(1);}FILE *fp_in = fopen(argv[1], "r");FILE *fp_out= fopen(argv[2], "w+");if (NULL == fp_in || NULL == fp_out) {printf("file open failed\n");exit(1);}   char ch = 0;while ((ch = fgetc(fp_in)) != EOF)fputc(ch, fp_out);fclose(fp_in);fclose(fp_out);return 0;
}

编译:

gcc fgetc_putc.c -o copy_file

运行:

./copy_file 1.txt 1.txt.bak

想必你也发现了这样一个一个字符的读取和写入效率肯定不高,那有没有一次读取或者写入一行的函数呢?我们来看看行 IO。

文本流:行读写 IO

行 IO 每次读取或者写入文件中的一行,比单个字符的输入输出更加有效率,常用的函数有 2 组:

#include <stdio.h>char *fgets(char *s, int size, FILE *stream);
int fputs(const char *s, FILE *stream);// gets 有缓存区溢出漏洞,建议弃用。
char *gets(char *buf);
int puts(const char *s);

实例 3:fgets_puts.c

这里例子中程序输出我们输入的每一行

/** fgets_puts.c* fun: print stdin to stdout using fgets and fputs.*/#include <stdio.h>
#include <stdlib.h>#define MAXSIZE 100int main(void) {char buf[MAXSIZE] = { 0 };while (fgets(buf, MAXSIZE, stdin) != NULL)fputs(buf, stdout);return 0;
}

编译:

gcc fgets_puts.c -o fgets_puts

运行:

./fgets_puts
hello world
hello world

记住,不要使用 gets 它很危险

字节流:二进制 IO

前面我们每次传输的都是一个个的字符,如果我们要读写指定大小的结构,则我们需要知道一个结构用字符表示的大小等信息,非常麻烦。因此标准库提供了二进制 IO 来读写指定数量的字节,因为我们可以使用 sizeof(type) 来得到一个类型的字节大小,所以可以使用字节流方便的读取和写入数据:

#include <stdio.h>/* * ptr: 存储读取内容的指针* size: 每次读取的数据大小* nmemb: 要读取的数据个数或者次数* stream: 读取的文件指针*/
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);/** ptr: 要写入的数据指针* size: 每次写入的数据大小* nmmeb: 写入的数据个数或者次数* stream: 要写入的文件指针*/
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

实例 4:fread_write.c

/** fread_write.c* fun: write bin data to file and read, print.*/#include <stdio.h>
#include <stdlib.h>int main(int argc, char *argv[]) {if (argc < 2) {printf("Usage: ./fread_write 1.txt.\n");exit(1);}// writeFILE *fp = fopen(argv[1], "w+");int data[5] = { 1, 2, 3, 4, 5 };fwrite(data, sizeof(int), 5, fp);fclose(fp);// readfp = fopen(argv[1], "r");int data_bak[5] = { 0 };fread(data_bak, sizeof(int), 5, fp);fclose(fp);// showfor (int i = 0; i < 5; ++i)printf("%d ", data_bak[i]);printf("\n");return 0;
}

使用二进制 IO 可以也可以将文件读入内存或者写入内存文件到磁盘上。

文件流的定位

在底层 IO 我们可以使用 lseek 来移动文件指针,在标准 C 中我们也有 3 个非常常用的移动文件指针的函数

#include <stdio.h>// 与 lseek 参数相同
int fseek(FILE *stream, long offset, int whence);// 返回当前文件指针的位置
long ftell(FILE *stream);// 移动文件指针到开头
void rewind(FILE *stream);

实例 5:file_length.c

我们来使用标准库的文件定位函数来求一个文件的长度:

#include <stdio.h>int main(int argc, char *argv[]) {FILE *fp = fopen(argv[1], "r");// 移动文件指针到文件末尾fseek(fp, 0, SEEK_END);// 求出文件长度long file_length = ftell(fp);printf("file length = %ld\n", file_length);// 定位到文件开头rewind(fp);// 查看当前文件指针位置printf("cur pos = %ld\n", ftell(fp));return 0;
}

编译:

gcc file_length.c -o file_length

运行,1.txt 文件的内容是 hello,加上末尾的 '\0' 一共 6 个字节:

./file_length 1.txtfile length = 6
cur pos = 0

成功求出了文件的长度 = 6 。

格式化 IO

在标准库中,我们可以使用 printf 的许多衍生函数来格式化输出到文件,buf 中等:

#include <stdio.h>// 输出到 stdout
int printf(const char *format, ...);// 输出到文件
int fprintf(FILE *stream, const char *format, ...);// 输出到缓存 str 中
int sprintf(char *str, const char *format, ...);

实例 6:test_printf.c

我们来格式化打印一段文本,来练习上面的 3 个函数:

#include <stdio.h>int main(int argc, char *argv[]) {// stdoutprintf("Hello World\n");// fileFILE *fp = fopen(argv[1], "w+");fprintf(fp, "%s", "Hello World");fclose(fp);// bufferchar buf[20] = { 0 };sprintf(buf, "%s", "Hello World");puts(buf);return 0;
}

编译:

gcc test_printf.c -o test_printf

运行:

./test_printf 1.txt
Hello World
Hello World# 查看 1.txt
cat 1.txt
Hello World

可以看到我们成功在 stdoutbuf1.txt 中写入了 Hello World 文本,实现了格式化输出的功能。

结语

这次主要介绍了 C 标准库的一些常用的函数,掌握这些常用的函数即可应付大多数的工作,如果遇到不能实现的功能,一定要搜索 Google,因为 API 那么多不可能全部介绍完啊。就算我全部介绍完,你也不一定有耐心看下去是不,所以啊,最重要的是形成自己的学习方法,则比什么都重要。

Linux后台开发系列之「13.标准 IO 库」相关推荐

  1. Linux后台开发系列 之「03.vim 基础教程」

    vim 版权声明:本文为 cdeveloper 原创文章,可以随意转载,但必须在明确位置注明出处! vim 简介 vim 被称为编辑器之神! 在 Linux 下我们经常使用 vim 来写程序,学会使用 ...

  2. Linux后台开发系列之「07.gcc 编译基础」

    gcc 版权声明:本文为 cdeveloper 原创文章,可以随意转载,但必须在明确位置注明出处! 本文摘要 这篇文章主要介绍 gcc 相关的技术,包括以下 8 个方面: gcc 简介 gcc 参数 ...

  3. Linux后台开发系列之「10.Autoconf 打包软件」

    Autoconf 版权声明:本文为 cdeveloper 原创文章,可以随意转载,但必须在明确位置注明出处! Autoconf 简介 Autoconf 是一种用于生成 shell 脚本的工具,可自动配 ...

  4. Linux后台开发系列之「09.Makefile 基础语法」

    Makefile 版权声明:本文为 cdeveloper 原创文章,可以随意转载,但必须在明确位置注明出处! Makefile 简介 Makefile 是一个管理项目的配置文件,它主要有 2 个作用: ...

  5. Linux后台开发系列之「11.IO 概述」

    IO 版权声明:本文为 cdeveloper 原创文章,可以随意转载,但必须在明确位置注明出处! IO 概述 这篇文章主要介绍 Linux IO 的基本知识和学习方法,掌握这些再学习 IO 操作会更加 ...

  6. Linux后台开发系列之「06.Shell 编程基础

    程序员的工作台 版权声明:本文为 cdeveloper 原创文章,可以随意转载,但必须在明确位置注明出处! 本文摘要 本文主要介绍 Linux Shell 编程的基础知识,包含下面 8 个方面: Sh ...

  7. Linux后台开发系列之「08.15 个 gdb 调试基础命令」

    代码键盘 版权声明:本文为 cdeveloper 原创文章,可以随意转载,但必须在明确位置注明出处! gdb 简介 gdb 是 UNIX 及 UNIX-like 下的调试工具,在 Linux 下一般都 ...

  8. Linux后台开发系列之「04.安装 4 个必备的 vim 插件」

    vim 版权声明:本文为 cdeveloper 原创文章,可以随意转载,但必须在明确位置注明出处! 读这篇博客你能学到些什么? 这篇博客主要介绍 vim 常用插件的安装配置方法,你可以学到下面这些内容 ...

  9. Linux后台开发系列之「05.解析 Linux 命令机制」

    版权声明:本文为 cdeveloper 原创文章,可以随意转载,但必须在明确位置注明出处! 为何要了解 Linux 命令机制? 如果你每天都在使用 Linux 命令,那么你了解命令的基本原理吗?你学习 ...

最新文章

  1. 无线+传感技术将物联网推向云端
  2. c 调用java post方法_C#调用Java方法(详细实例)
  3. 银行流水你真的会看吗?
  4. android 代码获取屏幕图像,安卓获取屏幕以及获得像素点 ~ 大树洞
  5. MQTT在游戏运营发行中的实践
  6. python自带的解释器叫做_21条python面试题,值得收藏!
  7. VS中生成、清理项目、调试、開始运行(不调试)、Debug 和 Release等之间的差别...
  8. docker helowin 迁移_禅道在docker上部署与迁移
  9. XP远程桌面连接强制登录
  10. vue token过期如何处理_超市货架上摆放有过期食品如何定性处理?总局这样答复...
  11. SQL经典面试50题 | 附答案
  12. 3DSMAX和ZBRUSH打造神秘性感美女
  13. 混音师的混音之道|处理母带和混音的差别?母带处理的真相(上)|MZD Studios
  14. web页面视频播放器选型
  15. 磁盘结构损坏且无法读取硬盘磁盘结构损坏且无法读取的找回方案
  16. 《费马大定理》-站在巨人的肩膀上
  17. 中国电信SIM卡绝杀冷、热钱包,区块链手机还没火就要凉凉?
  18. 开源的容器虚拟化平台Docker学习笔记,个人私藏分享,不谢!
  19. 转战物联网·基础篇01-物联网之我见
  20. 20182316胡泊 第2,3周学习总结

热门文章

  1. [Python第0课] 教你从零开始学Python
  2. 怎么分辨是808鼓_学架子鼓为什么不能用电鼓
  3. 2021年全球干细胞收入大约2832.7百万美元,预计2028年达到5673.4百万美元,2022至2028期间,年复合增长率CAGR为11.3%
  4. mysql yum包,如何使用MySQL yum源来安装更新MySQL相关软件包
  5. 如何在php中加样式,vue如何给组件加css样式,php中加入css样式
  6. 简述游标原理 mysql_mysql游标的原理与用法实例分析
  7. centos 修改root密码_MYSQL8.0以上版本正确修改ROOT密码
  8. php5.5 反序列化利用工具_利用Python反序列化运行加载器实现免杀
  9. 网络秒杀背后猫腻多 秒杀器侵害买卖双方权益
  10. dbutilsjar包下载_commons dbutils 下载|