9.标准I/O库

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

一、FILE指针

        使用标准 I/O 库函数打开或创建一个文件时,会返回一个指向 FILE 类型对象的指针(FILE *) ,使用该 FILE 指针与被打开或创建的文件相关联,然后该 FILE 指针就用于后续的标准 I/O 操作。因此FILE指针相当于文件I/O中的文件描述符fd

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

        用户通过标准输入设备与系统进行交互, 进程将从据(譬如程序中 printf 打印输出的字符串) 输出到标报错打印的信息)输出到标准错误(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_FILENO 1 /* Standard output. */
#define STDERR_FILENO 2 /* Standard error output. */

        而在标准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

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


三、 标准I/O函数

        fopen()打开文件

        在文件I/O中通过使用open函数打开或创建文件,而在标准I/O中可以使用库函数fopen打开或创建文件。fopen()函数原型如下所示:

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

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

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

mode说明对应于 open()函数的 flags 参数取值
r以只读方式打开文件。O_RDONLY
r+以可读、可写方式打开文件。O_RDWR
w以只写方式打开文件,如果参数 path 指定的文件
存在,将文件长度截断为 0;如果指定文件不存在
则创建该文件
 

O_WRONLY | O_CREAT | O_TRUNC


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

 
O_WRONLY | O_CREAT | O_APPEND
 
a+以可读、可写方式打开文件,以追加方式写入
(在文件末尾写入),如果文件不存在则创建该
文件。
 
O_RDWR | O_CREAT | O_APPEND
 

        调用 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()关闭文件

#include <stdio.h>
int fclose(FILE *stream);
参数 stream 为 FILE 类型指针,调用成功返回 0;失败将返回 EOF(也就是-1),并且会设置 errno 来
指示错误原因

fwrite()和fread() 

#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()用于读取文件数据,其参数和返回值含义如下:
ptr: fread()将读取到的数据存放在参数 ptr 指向的缓冲区中;
size: fread()从文件读取 nmemb 个数据项,每一个数据项的大小为 size 个字节,所以总共读取的数据大
小为 nmemb * size 个字节。
nmemb: 参数 nmemb 指定了读取数据项的个数。
stream: FILE 指针。
返回值: 调用成功时返回读取到的数据项的数目(数据项数目并不等于实际读取的字节数,除非参数
size 等于 1);如果发生错误或到达文件末尾,则 fread()返回的值将小于参数 nmemb,那么到底发生了错误
还是到达了文件末尾, fread()不能区分文件结尾和错误, 究竟是哪一种情况,此时可以使用 ferror()或 feof()


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

        使用案例如下:

  fwrite.c

        使用 fopen()函数将当前目录下的 test_file 文件打开,调用 fopen()时 mode 参数设置为"w",表示以只写的方式打开文件,并将文件的长度截断为 0,如果指定文件不存在则创建该文件。打开文件之后调用fwrite()函数将"Hello World!"字符串数据写入到文件中

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

int main(void)
{
    char buf[] = "Hello World!\n";
    FILE *fp = NULL;
    /* 打开文件 */
    if (NULL == (fp = fopen("./test_file", "w"))) {
        perror("fopen error");
        exit(-1);
    }

    printf("文件打开成功!\n");


    /* 写入数据 */
    if (sizeof(buf) >
    fwrite(buf, 1, sizeof(buf), fp)) {
        printf("fwrite error\n");
        fclose(fp);
        exit(-1);
    }
    printf("数据写入成功!\n");
    /* 关闭文件 */
    fclose(fp);
    exit(0);
}

fread.c

        使用 fread()函数从文件中读取 12 * 1=12 个字节的数据,将读取到的数据存放在 buf 中,当读取到的字节数小于指定字节数时,表示发生了错误或者已经到达了文件末尾,程序中调用了库函数 ferror()来判断是不是发生了错误。

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


int main(void)
{
    char buf[50] = {0};
    FILE *fp = NULL;
    int size;
    /* 打开文件 */
    if ((fp = fopen("./test_file", "r")) == NULL) {
        perror("fopen error");
        exit(-1);
    }
    printf("文件打开成功!\n");

    /* 读取数据 */
    if ((size = fread(buf, 1, 12, fp)) < 12) {
    if (ferror(fp)) { //使用 ferror 判断是否是发生错误
        printf("fread error\n");
        fclose(fp);
        exit(-1);
    }

    /* 如果未发生错误则意味着已经到达了文件末尾 */
    }
    printf("成功读取%d 个字节数据: %s\n", size, buf);
    /* 关闭文件 */
    fclose(fp);
    exit(0);
}

编译后运行结果如下:

fseek函数

库函数 fseek()的作用类似于系统调用 lseek(), 用于设置文件读写位置偏移量, lseek()用于文件 I/O,而库函数 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()函数的返回值
意义不同,这里要注意!

ftell()函数

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

#include <stdio.h>
long ftell(FILE *stream);
参数 stream 指向对应的文件,函数调用成功将返回当前读写位置偏移量;调用失败将返回-1,并会设置
errno 以指示错误原因

feof()函数
        feof()用于测试参数 stream 所指文件的 end-of-file 标志,如果 end-of-file 标志被设置了,则调用feof()函数将返回一个非零值,如果 end-of-file 标志没有被设置,则返回 0。

#include <stdio.h>
int feof(FILE *stream);
当文件的读写位置移动到了文件末尾时, end-of-file 标志将会被设置。

用法如下:
if (feof(file)) {
/* 到达文件末尾 */
}
else {
/* 未到达文件末尾 */
}

ferror()函数

库函数 ferror()用于测试参数 stream 所指文件的错误标志,如果错误标志被设置了,则调用 ferror()函数将返回一个非零值,如果错误标志没有被设置,则返回 0。

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

当对文件的 I/O 操作发生错误时,错误标志将会被设置。
if (ferror(file)) {
/* 发生错误 */
}
else {
/* 未发生错误 */
}

clearerr()函数

库函数 clearerr()用于清除 end-of-file 标志和错误标志,当调用 feof()或 ferror()校验这些标志后,通常需要清除这些标志,避免下次校验时使用到的是上一次设置的值,此时可以手动调用 clearerr()函数清除标志。

#include <stdio.h>
void clearerr(FILE *stream);

格式化输出: 

        C 库函数提供了 5 个格式化输出函数,包括: printf()、 fprintf()、 dprintf()、 sprintf()、 snprintf(),其函数定义如下所示:

#include <stdio.h>
int printf(const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);
int dprintf(int fd, const char *format, ...);
int sprintf(char *buf, const char *format, ...);
int snprintf(char *buf, size_t size, const char *format, ...);

printf函数 

        作为最常用的函数可以实现,将程序的字符串信息输出到终端。常用的用法如下:

输出Hello Linux

printf("Hello Linux\n");

输出数字5 

printf("%d\n",5);

fprintf函数 

        fprintf()可将格式化数据写入到由 FILE 指针指定的文件中,譬如将字符串写入到标准输出:

#include <stdio.h>

int main(void)
{
    fprintf(stdout, "Hello world\n");
    fprintf(stdout, "数字是%d\n",5);

    return 0;
}

运行结果如下:

dprintf()函数

        dprintf()可将格式化数据写入到由文件描述符 fd 指定的文件中,譬如将字符串“Hello World”写入到标准输出:

#include <stdio.h>
#include <unistd.h>

int main(void)
{
    dprintf(STDOUT_FILENO, "Hello World!\n");
    dprintf(STDOUT_FILENO, "数字是%d\n",6);

    return 0;
}

运行结果如下:

sprintf()函数
        sprintf()函数将格式化数据存储在由参数 buf 所指定的缓冲区中, 譬如将字符串“Hello World”存放在缓冲区中:

#include <stdio.h>
#include <unistd.h>

int main(void)
{
    char buf[20];   //长度为20的缓冲区
    sprintf(buf, "Hello World!");
    printf("%s\n", buf);
    return 0;
}

运行结果如下:

        sprintf()函数可能会造成由参数 buf 指定的缓冲区溢出,调用者有责任确保该缓冲区足够大,因为缓冲区溢出会造成程序不稳定甚至安全隐患!

snprintf()函数

        snprintf()函数;在该函数中,使用参数 size 显式的指定缓冲区的大小,如果写入到缓冲区的字节数大于参数 size 指定的大小,超出的部分将会被丢弃!

格式化输入:

C 库函数提供了 3 个格式化输入函数,包括: scanf()、 fscanf()、 sscanf(),其函数定义如下所示:

#include <stdio.h>
int scanf(const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);
int sscanf(const char *str, const char *format, ...);

scanf()函数

int a, b, c;
scanf("%d %d %d", &a, &b, &c);

fscanf()函数
        fscanf()函数从指定文件中读取数据,作为格式转换的输入数据,文件通过 FILE 指针指定,所以它有两个固定参数, FILE 指针和格式控制字符串 format。譬如从标准输入文件中读取数据进行格式化转换:

int a, b, c;
fscanf(stdin, "%d %d %d", &a, &b, &c);

sscanf()函数

        sscanf()将从参数 str 所指向的字符串缓冲区中读取数据,作为格式转换的输入数据,所以它也有两个固定参数,字符串 str 和格式控制字符串 format,譬如

char *str = "5454 hello";
char buf[10];
int a;
sscanf(str, "%d %s", &a, buf);

文件描述符与 FILE 指针互转

        库函数 fileno()可以将标准 I/O 中使用的 FILE 指针转换为文件 I/O 中所使用的文件描述符,而 fdopen()则进行着相反的操作,其函数原型如下所示:

#include <stdio.h>
int fileno(FILE *stream);
FILE *fdopen(int fd, const char *mode);

fdopen()函数与 fileno()功能相反,给定一个文件描述符,得到该文件对应的 FILE 指针,之后便可以使
用诸如 fread()、 fwrite()等标准 I/O 方式操作文件了。参数 mode 与 fopen()函数中的 mode 参数含义相同

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值