linux学习:标准IO

目录

接口

打开文件

关闭文件

读写

每次一个字符的读写标准 IO 函数接口

每次一行的读写标准 IO 函数接口

每次读写若干数据块的标准 IO 函数接口

获取或设置文件当前位置偏移量

标准格式化 IO 函数


系统 IO 的最大特点一个是更具通用性,不管是普通文件、管道文件、设备节点文件、 套接字文件等等都可以使用,另一个是他的简约性,对文件内数据的读写在任何情况下都是 不带任何格式的,而且数据的读写也都没有经过任何缓冲处理,这样做的理由是尽量精简内 核 API,而更加丰富的功能应该交给第三方库去进一步完善

标准 C 库是最常用的第三方库,而标准 IO 就是标准 C 库中的一部分接口,这一部分 接口实际上是系统 IO 的封装,他提供了更加丰富的读写方式,比如可以按格式读写、按 ASCII 码字符读写、按二进制读写、按行读写、按数据块读写等等,还可以提供数据读写 缓冲功能,极大提高程序读写效率

接口

打开文件

返回的文件指针是一种指向结构体 FILE{}的指针,该结构体在标准 IO 中被定义

typedef struct _IO_FILE FILE; // 定义 FILE 等价于 _IO_FILE

struct _IO_FILE {
    int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
    #define _IO_file_flags _flags

    // The following pointers correspond to the C++ streambuf protocol. 
    // Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. 251 char*                 _IO_read_ptr;
    char* _IO_read_end;
    char* _IO_read_base;
    char* _IO_write_base;
    char* _IO_write_ptr;
    char* _IO_write_end;
    char* _IO_buf_base;
    char* _IO_buf_end;
    /* The following fields are used to support backing up and undo. */
    char *_IO_save_base;
    char *_IO_backup_base;
    char *_IO_save_end;

    struct _IO_marker *_markers;

    struct _IO_FILE *_chain;

    int _fileno; // 文件描述符
    #if 0
        int _blksize;
    #else
        int _flags2;
    #endif
    _IO_off_t _old_offset;

    #define __HAVE_COLUMN /* temporary */
        /* 1+column number of pbase(); 0 is unknown. */
        unsigned short _cur_column;
        signed char _vtable_offset;
        char _shortbuf[1];

        /* char* _save_gptr; char* _save_egptr; */

        _IO_lock_t *_lock;
    #ifdef _IO_USE_OLD_IO_FILE
};

数据将会先存储在一个标准 IO 缓冲区中,而后在一定条件下才被一并 flush(冲洗,或称刷新)至内核缓冲区,而不是像 系统 IO那样,数据直接被flush至内核

标准 IO 函数 fopen( )实质上是系统 IO 函数 open( )的封装,他们是一一对 应的,每一次 fopen( )都会导致系统分配一个 file{ }结构体和一个 FILE{}来保存维护该 文件的读写信息,每一次的打开和操作都可以不一样,是相对独立的,因此可以在多线程或 者多进程中多次打开同一个文件,再利用文件空洞技术进行多点读写

关闭文件

该函数用于释放由 fopen( )申请的系统资源,包括释放标准 IO 缓冲区内存,因此 fclose( )不能对一个文件重复关闭

读写

每次一个字符的读写标准 IO 函数接口

  • fgetc( )、getc( )和 getchar( )返回值是 int,而不是 char,原因是因为他们在出 错或者读到文件末尾的时候需要返回一个值为 -1 的 EOF 标记,而 char 型数据有可能因 为系统的差异而无法表示负整数。
  • 当 fgec( )、getc( )和 getchar( )返回 EOF 时,有可能是发生了错误,也有可能 是读到了文件末尾

  • getchar( )缺省从标准输入设备读取一个字符。
  • putchar( )缺省从标准输出设备输出一个字符。
  • fgetc( )和 fputc( )是函数,getc( )和 putc( )是宏定义。
  • 两组输入输出函数一般成对地使用,fgetc( )和 fputc( ),getc( )和 putc( ), getchar( )和 putchar( ).

普通文件的拷贝操作

1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <stdbool.h>
4 #include <unistd.h>
5 #include <string.h>
6 #include <strings.h>
7 #include <errno.h>
8
9 #include <sys/stat.h>
10 #include <sys/types.h>
11 #include <fcntl.h>
12
13 int main(int argc, char **argv)
14 {
15     if(argc != 3)
16     {
17         printf("Usage: %s <src> <dst>\n", argv[0]);
18         exit(1);
19     }
20
21     // 分别以只读和只写模式打开源文件和目标文件
22     FILE *fp_src = fopen(argv[1], "r");
23     FILE *fp_dst = fopen(argv[2], "w");
24
25     // 如果返回 NULL 则程序出错退出
26     if(fp_src == NULL || fp_dst == NULL)
27     {
28         perror("fopen()");
29         exit(1);
30     }
31
32     int c, total = 0;
33     while(1)
34     {
35         c = fgetc(fp_src); // 从源文件读取一个字符存储在变量 c 中
36
37         if(c == EOF && feof(fp_src)) // 已达文件末尾
38         {
39             printf("copy completed, " 40 "%d bytes have been copied.\n", total);
41             break;
42         }
43         else if(ferror(fp_src)) // 遇到错误
44         {
45             perror("fgetc()");
46             break;
47         }
48
49         fputc(c, fp_dst); // 将变量 c 中的字符写入目标文件中
50         total++; // 累计拷贝字符个数
51     }
52
53     // 正常关闭文件指针,释放系统资源
54     fclose(fp_src);
55     fclose(fp_dst);
56
57     return 0;
58 }

每次一行的读写标准 IO 函数接口

  • fgets( )跟 fgetc( )一样,当其返回 NULL 时并不能确定究竟是达到文件末尾还是 碰到错误,需要用 feof( )/ferror( )来进一步判断。
  • fgets( )每次读取至多不超过 size 个字节的一行,所谓“一行”即数据至多包含一 个换行符’\n’。
  • gets( )是一个已经过时的接口,因为他没有指定自定义缓冲区 s 的大小,这样很容 易造成缓冲区溢出,导致程序段访问错误。
  • fgets( )和 fputs( ),gets( )和 puts( )一般成对使用,鉴于 gets( )的不安全性, 一般建议使用前者。

普通文件拷贝

1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <stdbool.h>
4 #include <unistd.h>
5 #include <string.h>
6 #include <strings.h>
7 #include <errno.h>
8
9 #include <sys/stat.h>
10 #include <sys/types.h>
11 #include <fcntl.h>
12
13 #define BUFSIZE 100
14
15 int main(int argc, char **argv)
16 {
17     if(argc != 3)
18     {
19         printf("Usage: %s <src> <dst>\n", argv[0])
20         exit(1);
21     }
22
23     // 分别以只读和只写模式打开源文件和目标文件
24     FILE *fp_src = fopen(argv[1], "r");
25     FILE *fp_dst = fopen(argv[2], "w");
26
27     // 如果返回 NULL 则程序出错退出
28     if(fp_src == NULL || fp_dst == NULL)
29     {
30         perror("fopen()");
31         exit(1);
32     }
33
34     char buf[BUFSIZE]; // 自定义缓冲区
35     int total = 0;
36     while(1)
37     {
38         bzero(buf, BUFSIZE);
39         if(fgets(buf, BUFSIZE, fp_src) == NULL) // 从源文件读数据
40         {
41             if(feof(fp_src)) // 已达文件末尾
42             {
43                 printf("copy completed, %d bytes" 
44                     " have been copied.\n", total);
45                 break;
46             }
47             else if(ferror(fp_src)) // 遇到错误
48             {
49                 perror("fgetc()");
50                 break;
51             }
52         }
53
54         fputs(buf, fp_dst); // 将自定义缓冲区中的数据写入目标文件
55         total += strlen(buf); // 累计拷贝的字节个数
56     }
57
58     // 正常关闭文件指针,释放系统资源
59     fclose(fp_src);
60     fclose(fp_dst);
61
62     return 0;
63 }

每次读写若干数据块的标准 IO 函数接口

这一组标准 IO 函数被称为“直接 IO 函数”或者“二进制 IO 函数”,因为他们对数 据的读写严格按照规定的数据块数和数据块的大小来处理,而不会对数据格式做任何处理, 而且当数据块中出现特殊字符,比如换行符’\n’、字符串结束标记’\0’等时不会受到影响

  • 如果 fread( )返回值小于 nmemb 时,则可能已达末尾,或者遇到错误,需要借助 于 feof( )/ferror( )来加以进一步判断。
  • 当发生上述第 1 种情况时,其返回值并不能真正反映其读取或者写入的数据块数, 而只是一个所谓的“截短值”,比如正常读取 5 个数据块,每个数据块 100 个字节,在执行成功的情况下返回值是 5,表示读到 5 个数据块总共 500 个字节,但是如果只读到 499 个数据块,那么返回值就变成 4,而如果读到 99 个字节,那么 fread( )会返回 0。因此当发生返回值小于 nmemb 时,需要仔细确定究竟读取了几个字节,而不能直接从返回值确定。

获取或设置文件当前位置偏移量

  • fseek( )的用法基本上跟系统 IO 的 lseek( )是一致的。
  • rewind(fp)相等于 fseek(fp, 0L,

标准格式化 IO 函数

格式化 IO 函数中最常用的莫过于 printf( )和 scanf( )了,但从上表中可以看到,他 们其实各自都有一些功能类似的兄弟函数可用

  • fprintf( )不仅可以像 printf( )一样向标准输出设备输出信息,也可以向由 stream 指定的任何有相应权限的文件写入数据。
  • sprintf()和 snprintf()都是向一块自定义缓冲区写入数据,不同的是后者第二个参 数提供了这块缓冲区的大小,避免缓冲区溢出,因此应尽量使用后者,放弃使用前者。
  • fscanf( )不仅可以像 scanf( )一样从标准输入设备读入信息,也可以从由 stream 指定的任何有相应权限的文件读入数据。
  • sscanf( )从一块由 s 指定的自定义缓冲区中读入数据。 
  • 最重要的一条:这些函数的读写都是带格式的

假设有一个文件存储了班级学生的姓名、性别、年龄和身高,需要将这个文件读入程序,再将之打印到屏幕 显示出来

Mike M 18 167.2
Lucy F 17 155.3
Jack M 22 171.0
Joe  M 19 175.1
Rose F 21 169.1
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <stdbool.h>
4 #include <unistd.h>
5 #include <string.h>
6 #include <strings.h>
7 #include <errno.h>
8
9 #include <sys/stat.h>
10 #include <sys/types.h>
11 #include <fcntl.h>
12
13 #define NAMELEN 20
14
15 struct student // 用来存放一个带有一定数据格式的学生节点
16 {
17     char name[NAMELEN];
18     char sex;
19     int age;
20     float stature;
21
22     struct student *next; // 链表
23 };
24
25 struct student *init_list(void) // 初始化一个空链表
26 {
27     struct student *head = malloc(sizeof(struct student));
28     head->next = NULL;
29
30     return head;
31 }
32
33 // 将新节点 new 添加到链表 head 中
34 void add_student(struct student *head, struct student *new)
35 {
36     struct student *tmp = head;
37
38     while(tmp->next != NULL)
39     tmp = tmp->next;
40
41     tmp->next = new;
42 }
43
44 // 显示链表中的所有节点
45 void show_student(struct student *head)
46 {
47     struct student *tmp = head->next;
48
49     while(tmp != NULL)
50     {
51         fprintf(stdout, "%-5s %c %d %.1f\n", 52 tmp->name, tmp->sex, tmp->age, tmp->stature);
53
54         tmp = tmp->next;
55     }
56 }
57
58 int main(int argc, char **argv)
59 {
60     FILE *fp = fopen("format_data", "r");
61
62     // 创建一个用来保存学生节点的空链表
63     struct student *head = init_list();
64
65     int count = 0;
66     while(1)
67     {
68         struct student *new = malloc(sizeof(struct student));
69
70         // 从文件 fp 中按照格式读取数据,并将之填充到 new 中
71         if(fscanf(fp, "%s %c %d %f", 
72             new->name, &(new->sex), 
73             &(new->age), &(new->stature)) == EOF)
74         {
75             break;
76         }
77
78         // 将新节点 new 加入到链表 head 中
79         add_student(head, new);
80         count++;
81     }
82
83     printf("%d students have been added.\n", count);
84     show_student(head); // 打印所有的节点
85
86     fclose(fp); // 关闭文件指针,释放系统资源
87
88     return 0;
89 }

  • 27
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

码农小白

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

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

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

打赏作者

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

抵扣说明:

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

余额充值