C语言标准库(五)

目录

一 标准 I/O 库简介

1 概念:

二  标准I/O库函数相对于系统调用的好处

三  FILE 指针

四 标准输入、标准输出和标准错误

新建文件的权限

五 读文件和写文件

库函数 fread()

库函数 fwrite()

库函数 fseek 定位

fread,fwrite,fseek综合小案例 

ftell()函数


一 标准 I/O 库简介

1 概念:

所谓标准 I/O 库则是标准 C 库中用于文件 I/O 操作(譬如读文件、写文件等)相关的一系列库函数的集合,通常标准 I/O 库函数相关的函数定义都在头文件中,所以我们需要在程序源码中包含头文件。

标准 I/O 库函数是构建于文件 I/O(open()、read()、write()、lseek()、close()等)这些系统调用之上的, 譬如标准 I/O 库函数 fopen()就利用系统调用 open()来执行打开文件的操作、fread()利用系统调用 read()来执 行读文件操作、fwrite()则利用系统调用 write()来执行写文件操作等等。

二  标准I/O库函数相对于系统调用的好处

系统调用也能实现功能,为何还需要设计标准 I/O 库?直接使用文件 I/O 系统调用不是更好吗?

设计库函数是为了提供比底层系统调用更为方便、好用的调用接口,虽然标 准 I/O 构建于文件 I/O 之上,但标准 I/O 却有它自己的优势,标准 I/O 和文件 I/O 的区别如下:

  1. 虽然标准 I/O 和文件 I/O 都是 C 语言函数,但是标准 I/O 是标准 C 库函数,而文件 I/O 则是 Linux系统调用;标准 I/O 是由文件 I/O 封装而来,标准 I/O 内部实际上是调用文件 I/O 来完成实际操作的;
  2. 可移植性:标准 I/O 相比于文件 I/O 具有更好的可移植性,通常对于不同的操作系统,其内核向应 用层提供的系统调用往往都是不同,譬如系统调用的定义、功能、参数列表、返回值等往往都是不 一样的;而对于标准 I/O 来说,由于很多操作系统都实现了标准 I/O 库,标准 I/O 库在不同的操作 系统之间其接口定义几乎是一样的,所以标准 I/O 在不同操作系统之间相比于文件 I/O 具有更好的 可移植性。
  3. 性能、效率:标准 I/O 库在用户空间维护了自己的 stdio 缓冲区,所以标准 I/O 是带有缓存的,而 文件 I/O 在用户空间是不带有缓存的,所以在性能、效率上,标准 I/O 要优于文件 I/O。

三  FILE 指针

当调用 open()函数打开一个文件时,即返回一个文件描述符 fd,然后该文件描述符就用于后续的 I/O 操作。 而对于标准 I/O 库函数来说,它们的操作是围绕 FILE 指针进行的,当使用标准 I/O 库函数打开或创建一个 文件时,会返回一个指向 FILE 类型对象的指针(FILE *),使用该 FILE 指针与被打开或创建的文件相关 联,然后该 FILE 指针就用于后续的标准 I/O 操作(使用标准 I/O 库函数进行 I/O 操作),所以由此可知,FILE 指针的作用相当于文件描述符,只不过 FILE 指针用于标准 I/O 库函数中、而文件描述符则用于文件I/O 系统调用中。 FILE 是一个结构体数据类型,它包含了标准 I/O 库函数为管理文件所需要的所有信息,包括用于实际I/O 的文件描述符、指向文件缓冲区的指针、缓冲区的长度、当前缓冲区中的字节数以及出错标志等。FILE数据结构定义在标准 I/O 库函数头文件 stdio.h 中。

四 标准输入、标准输出和标准错误

  • 所谓标准输入设备指的就是计 算机系统的标准的输入设备,通常指的是计算机所连接的键盘;
  • 而标准输出设备指的是计算机系统中用于 输出标准信息的设备,通常指的是计算机所连接的显示器;
  • 标准错误设备则指的是计算机系统中用于显示 错误信息的设备,通常也指的是显示器设备。

用户通过标准输入设备与系统进行交互,进程将从标准输入(stdin)文件中得到输入数据,将正常输出 数据(譬如程序中 printf 打印输出的字符串)输出到标准输出(stdout)文件,而将错误信息(譬如函数调 用报错打印的信息)输出到标准错误(stderr)文件。

标准输出文件和标准错误文件都对应终端的屏幕,而标准输入文件则对应于键盘。

每个进程启动之后都会默认打开标准输入、标准输出以及标准错误,得到三个文件描述符,即 0、1、2,其中 0 代表标准输入、1 代表标准输出、2 代表标准错误;在应用编程中可以使用宏 STDIN_FILENO、STDOUT_FILENO 和 STDERR_FILENO 分别代表 0、1、2,这些宏定义在 unistd.h 头文件中:

/* Standard file descriptors. */ 
#define STDIN_FILENO 0 /* Standard input. */ 
#define STDOUT_FILENO1 /* Standard output. */ 
#define STDERR_FILENO 2 /* Standard error output. */ 

0、1、2 这三个是文件描述符,只能用于文件 I/O(read()、write()等),那么在标准 I/O 中,自然是无 法使用文件描述符来对文件进行 I/O 操作的,它们需要围绕 FILE 类型指针来进行,在 stdio.h 头文件中有相 应的定义,如下:

/* Standard streams. */ 
extern struct _IO_FILE *stdin; /* Standard input stream. */ 
extern struct _IO_FILE *stdout; /* Standard output stream. */ 
extern struct _IO_FILE *stderr; /* Standard error output stream. */ 
/* C89/C99 say they're macros. Make them happy. */ 
#define stdin stdin 
#define stdout stdout 
#define stderr stderr 

Tips:struct _IO_FILE 结构体就是 FILE 结构体,使用了 typedef 进行了重命名。

所以,在标准 I/O 中,可以使用 stdin、stdout、stderr 来表示标准输入、标准输出和标准错误。

六 打开文件 fopen()

在 0 所介绍的文件 I/O 中,使用 open()系统调用打开或创建文件,而在标准 I/O 中,我们将使用库函数fopen()打开或创建文件,fopen()函数原型如下所示:

#include <stdio.h> 
 
FILE *fopen(const char *path, const char *mode); 

函数参数和返回值含义如下:

  • path:参数 path 指向文件路径,可以是绝对路径、也可以是相对路径。
  • mode:参数 mode 指定了对该文件的读写权限,是一个字符串,稍后介绍。
  • 返回值:调用成功返回一个指向 FILE 类型对象的指针(FILE *),该指针与打开或创建的文件相关联, 后续的标准 I/O 操作将围绕 FILE 指针进行。如果失败则返回 NULL,并设置 errno 以指示错误原因。

参数 mode 字符串类型,可取值为如下值之一:

mode参数取值

  1. r 以只读方式打开文件。
  2. r+ 以可读、可写方式打开文件。
  3. w 以只写方式打开文件,如果参数 path 指定的文件 存在,将文件长度截断为 0;如果指定文件不存在 则创建该文件。
  4. w+ 以可读、可写方式打开文件,如果参数 path 指定 的文件存在,将文件长度截断为 0;如果指定文件 不存在则创建该文件。
  5. a 以只写方式打开文件,打开以进行追加内容(在 文件末尾写入),如果文件不存在则创建该文 件。
  6. a +以可读、可写方式打开文件,以追加方式写入 (在文件末尾写入),如果文件不存在则创建该 文件。

新建文件的权限

由 fopen()函数原型可知,fopen()只有两个参数 path 和 mode,不同于 open()系统调用,它并没有任何一 个参数来指定新建文件的权限。当参数 mode 取值为"w"、"w+"、"a"、"a+"之一时,如果参数 path 指定的文 件不存在,则会创建该文件,那么新的文件的权限是如何确定的呢?

虽然调用 fopen()函数新建文件时无法手动指定文件的权限,但却有一个默认值:

S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH (0666)

使用示例

  • 使用只读方式打开文件: fopen(path, "r");
  • 使用可读、可写方式打开文件: fopen(path, "r+");
  • 使用只写方式打开文件,并将文件长度截断为 0,如果文件不存在则创建该文件:
fopen(path, "w");

fclose()关闭文件

调用 fclose()库函数可以关闭一个由 fopen()打开的文件,其函数原型如下所示:

#include <stdio.h> 
 
int fclose(FILE *stream); 

参数 stream 为 FILE 类型指针,调用成功返回 0;失败将返回 EOF(也就是-1),并且会设置 errno 来 指示错误原因。

五 读文件和写文件

 fread()和 fwrite()库函数

对文件进行读、写操 作了,函数原型如下所示:

#include <stdio.h> 
 
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); 
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); 

库函数 fread()

用于读取文件数据,其参数和返回值含义如下:

  1. ptr:fread()将读取到的数据存放在参数 ptr 指向的缓冲区中;
  2. size:fread()从文件读取 nmemb 个数据项,每一个数据项的大小为 size 个字节,所以总共读取的数据大 小为 nmemb * size 个字节。
  3. nmemb:参数 nmemb 指定了读取数据项的个数。
  4. stream:FILE 指针。
  5. 返回值:调用成功时返回读取到的数据项的数目(数据项数目并不等于实际读取的字节数,除非参数size 等于 1);

库函数 fwrite()

用于将数据写入到文件中,其参数和返回值含义如下:

  1. ptr:将参数 ptr 指向的缓冲区中的数据写入到文件中。
  2. size:参数 size 指定了每个数据项的字节大小,与 fread()函数的 size 参数意义相同。
  3. nmemb:参数 nmemb 指定了写入的数据项个数,与 fread()函数的 nmemb 参数意义相同。
  4. stream:FILE 指针。
  5. 返回值:调用成功时返回写入的数据项的数目(数据项数目并不等于实际写入的字节数,除非参数 size等于 1);如果发生错误,则 fwrite()返回的值将小于参数 nmemb(或者等于 0)。

由此可知,库函数 fread()、fwrite()中指定读取或写入数据大小的方式与系统调用 read()、write()不同, 前者通过 nmemb(数据项个数)*size(每个数据项的大小)的方式来指定数据大小,而后者则直接通过一 个 size 参数指定数据大小。

库函数 fseek 定位

库函数 fseek()则用于标准 I/O,其函数原型如下所示:

#include <stdio.h> 
 
int fseek(FILE *stream, long offset, int whence); 
  • 函数参数和返回值含义如下: stream:FILE 指针。
  • offset:与 lseek()函数的 offset 参数意义相同。
  • whence:与 lseek()函数的 whence 参数意义相同。
  • 返回值:成功返回 0;发生错误将返回-1,并且会设置 errno 以指示错误原因;与 lseek()函数的返回值 意义不同,这里要注意!

调用库函数 fread()、fwrite()读写文件时,文件的读写位置偏移量会自动递增,使用 fseek()可手动设置 文件当前的读写位置偏移量。

  • 譬如将文件的读写位置移动到文件开头处: fseek(file, 0, SEEK_SET);
  • 将文件的读写位置移动到文件末尾: fseek(file, 0, SEEK_END);
  • 将文件的读写位置移动到 100 个字节偏移量处: fseek(file, 100, SEEK_SET);

fread,fwrite,fseek综合小案例 

#include <stdio.h> 
#include <stdlib.h> 
 

int main(void) 

{ 
 FILE *fp = NULL; 
 char rd_buf[100] = {0}; 
 char wr_buf[] = "hello world"; 
 int ret; 
 
 /* 打开文件 */ 
 if (NULL == (fp = fopen("./test_file", "w+"))) { 
 perror("fopen error"); 
 exit(-1); 
 } 
 printf("文件打开成功!\n"); 
 
 /* 写文件 */ 
 if (sizeof(wr_buf) > 
 fwrite(wr_buf, 1, sizeof(wr_buf), fp)) { 
 
 

 printf("fwrite error\n"); 
 fclose(fp); 
 exit(-1); 
 } 
 printf("数据写入成功!\n"); 
 
 /* 将读写位置移动到文件头部 */ 
 if (0 > fseek(fp, 0, SEEK_SET)) { 
 perror("fseek error"); 
 fclose(fp); 
 exit(-1); 
 } 
 
 /* 读文件 */ 
 if (sizeof(wr_buf) > 
 (ret = fread(rd_buf, 1, sizeof(wr_buf), fp))) { 
 printf("fread error\n"); 
 fclose(fp); 
 exit(-1); 
 } 
 
 printf("成功读取%d 个字节数据: %s\n", ret, rd_buf); 
 
 /* 关闭文件 */ 
 fclose(fp); 
 exit(0); 

}

ftell()函数

库函数 ftell()可用于获取文件当前的读写位置偏移量,其函数原型如下所示:

#include <stdio.h> 
 
long ftell(FILE *stream); 

参数 stream 指向对应的文件,函数调用成功将返回当前读写位置偏移量;调用失败将返回-1,并会设置errno 以指示错误原因。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@ChenPi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值