带你学C带你飞 | 文件操作 | 标准流和错误处理 | IO缓冲区

一、打开和关闭文件

  文件是什么?Linux 和其它类 UNIX 系统的一个重要特征就是:万物皆文件(Everything is a file)。具体可以参考如何理解“万物皆文件”的说法?
  打开和关闭文件函数

  • fopen 函数用于打开一个文件并返回文件指针。
    打开方式要区分文本模式和二进制模式的原因,主要是因为换行符的问题。C 语言用 \n 表示换行符,Unix 系统用 \n,Windows 系统用 \r\n,Mac 系统则用 \r。如果在 Windows 系统上以文本模式打开一个文件,从文件读到的 \r\n 将会自动转换成 \n,而写入文件则将 \n 替换为 \r\n。但如果以二进制模式打开则不会做这样的转换。Unix 系统的换行符跟 C 语言是一致的,所以不管以文本模式打开还是二进制模式打开,结果都是一样的。
  • fclose 函数用于关闭先前由 fopen 函数打开的文件。

举个栗子:

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

int main(void)
{
        FILE *fp;
        int ch;

        if ((fp = fopen("hello.txt", "r")) == NULL)
        {
                printf("打开文件失败!\n");
                exit(EXIT_FAILURE);
        }

        while ((ch = getc(fp)) != EOF)
        {
                putchar(ch);
        }

        fclose(fp);

        return 0;
}

在这里插入图片描述

二、读写文件

  读写文件又分为顺序读写和随机读写。

1.顺序读写

  下面介绍顺序读写的函数:
  读写一个字符串

举个栗子:

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

int main(void)
{
        FILE *fp1;
        FILE *fp2;
        int ch;

        if ((fp1 = fopen("hello.txt","r")) == NULL)
        {
                printf("文件打开失败!\n");
                exit(EXIT_FAILURE);
        }
        if ((fp2 = fopen("fishc.txt","w")) == NULL)
        {
                printf("文件打开失败!\n");
                exit(EXIT_FAILURE);
        }
        while ((ch = fgetc(fp1)) != EOF)
        {
                fputc(ch,fp2);
        }

        fclose(fp1);
        fclose(fp2);

        return 0;
}

  读写整个字符串

  • fgets 函数用于从指定文件中读取字符串。
  • fputs 函数用于将一个字符串写入到指定的文件中,表示字符串结尾的 ‘\0’ 不会被一并写入。

举个栗子:

#include <stdio.h>
#include <stdlib.h>
#define MAX 1024

int main(void)
{
        FILE *fp;
        cahr buffer[MAX];

        if ((fp = fopen("file.txt", "w")) == NULL)
        {
                printf("打开文件失败!\n");
                exit(EXIT_FAILURE);
        }

        fputs("Hello FishC!\n", fp);
		fputs("I love FishC.com!\n", fp);
        fclose(fp);

		if ((fp = fopen("lines.txt","w") == NULL))
		{
			printf("打开文件失败!\n");
			exit(EXIT_FAILURE);
		}
		
		while (!feof(fp))
		{
			fgets(buffer,MAX,fp);
			printf("%s",buffer);
		}
		fclose(fp);
        return 0;
}

  格式化读写文件

举个栗子:将当前日期获取之后写入到新创建的文件中去,再将其读取并打印到屏幕上。

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

int main(void)
{
        FILE *fp;
        struct tm *p;
        time_t t;

        time(&t);
        p = localtime(&t);

        if ((fp = fopen("date.txt","w")) == NULL)
        {
                printf("打开文件失败!\n");
                exit(EXIT_FAILURE);
        }

        fprintf(fp,"%d-%d-%d",1900+p->tm_year,1+p->tm_mon,p->tm_mday);

        fclose(fp);

        int year,month,day;
        if ((fp = fopen("date.txt","r")) == NULL)
        {
                       printf("打开文件失败!\n");
                       exit(EXIT_FAILURE);
        }

        fscanf(fp,"%d-%d-%d",&year,&month,&day);
        printf("%d-%d-%d\n",year,month,day);
        return 0;
}
[liujie@localhost sle46]$ gcc test1.c && ./a.out
2022-11-14

  二进制读写文件

  • fread函数用于从指定的文件中读取指定尺寸的数据
  • fwrite函数用于将指定尺寸的数据写入到指定的文件中

举个栗子:

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

struct Date
{
        int year;
        int month;
        int day;
};

struct Book
{
        char name[40];
        char author[40];
        char publisher[40];
        struct Date date;
};

int main(void)
{
        FILE *fp;
        struct Book *book_for_write, *book_for_read;

        // 为结构体分配堆内存空间
        book_for_write = (struct Book *)malloc(sizeof (struct Book));
        book_for_read = (struct Book *)malloc(sizeof (struct Book));

        if (book_for_write == NULL || book_for_read == NULL)
        {
                printf("内存分配失败!\n");
                exit(EXIT_SUCCESS);
        }

        // 填充结构体数据
        strcpy(book_for_write->name, "《带你学C带你飞》");
        strcpy(book_for_write->author, "小甲鱼");
        strcpy(book_for_write->publisher, "清华大学出版社");
        book_for_write->date.year = 2017;
        book_for_write->date.month = 11;
        book_for_write->date.day = 11;

        if ((fp = fopen("file.txt", "w")) == NULL)
        {
                printf("打开文件失败!\n");
                exit(EXIT_SUCCESS);
        }

        // 将整个结构体写入文件中
        fwrite(book_for_write, sizeof(struct Book), 1, fp);

        // 写入完成,关闭保存文件
        fclose(fp);

        // 重新打开文件,检测是否成功写入数据
        if ((fp = fopen("file.txt", "r")) == NULL)
        {
                printf("打开文件失败!\n");
                exit(EXIT_FAILURE);
        }

        // 在文件中读取结构体并打印到屏幕上
        fread(book_for_read, sizeof(struct Book), 1, fp);

        printf("书名:%s\n", book_for_read->name);
        printf("作者:%s\n", book_for_read->author);
        printf("出版社:%s\n", book_for_read->publisher);
        printf("出版日期:%d-%d-%d\n", book_for_read->date.year, book_for_read->date.month, book_for_read->date.day);

        fclose(fp);

        return 0;
}                           

2.随机读写文件

  .随机读写文件也就是允许从文件的任意位置开始读写。系统为每一个打开的文件设置了位置指示器。位置指示器记录当前的读写位置,要获取位置指示器的值需要使用ftell函数。

举个栗子:

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

int main(void)
{
        FILE *fp;

        if ((fp = fopen("hello.txt","w")) == NULL)
        {
                printf("文件打开失败!\n");
                exit(EXIT_FAILURE);
        }

        printf("%ld\n",ftell(fp));
        fputc('F',fp);
        printf("%ld\n",ftell(fp));
        fputs("ishC\n",fp);
        printf("%ld\n",ftell(fp));

        fclose(fp);
        return 0;
}
[liujie@localhost sle46]$ gcc test2.c && ./a.out
0
1
6

  .那么,应该如何修改位置指示器呢?

  • rewind函数:将文件指示器移动到文件头(位置为0),此时插入数据,会覆盖原始数据。
  • fseek函数用于设置文件流的位置指示器。

  可移植性问题:想要编写可移植的代码,就需要考虑以下问题:

  • 对于以二进制模式打开的文件,fseek函数在某些操作系统可能不支持SEEK_END位置
  • 对于以文本模式打开的文件,fseek函数的whence参数只能取SEEK_SET才是有意义的,并且传递给offset参数的值要么是0,要么是上一次对同个文件调用ftell函数获得的返回值

三、标准流和错误处理

1.标准流

  当一个程序被执行的时候,C语言自动为其打开三个面向终端的文件流,
在这里插入图片描述
  由于标准输出和标准错误输出通常都是直接打印到屏幕上,为了区分它们,我们可以使用Linux shell的重定向功能:

  • 重定向标准输入使用<
  • 重定向标准输出使用>
  • 重定向标准错误输出使用2>

2.错误处理

  每个流对象内部都有两个指示器:一个是文件结束指示器,当遇到文件尾时,该指示器被设置;另一个是错误指示器,当读写文件出错时,该指示器被设置。

  • 检测文件结束指示器:feof
  • 错误指示器:ferror

使用clearerr函数可以人为地清除文件末尾指示器和错误指示器的状态。ferror函数只能检测是否出错,但无法获取错误原因。不过,大多数系统函数在出现错误的时候会将错误原因记录在errno中。perror函数可以直观地打印出错误原因。
在这里插入图片描述
在这里插入图片描述
strerror函数直接返回错误码对应的错误信息。
在这里插入图片描述

四、IO缓冲区

  IO的速度与cpu的速度相差好几个量级,为了协调IO设备与Cpu设备速度不匹配问题,就设计出IO缓冲区。
在这里插入图片描述
  标准IO提供的三种类型的缓冲模式:

  • 按块缓存
    按块缓存也称为全缓存,即在填满缓冲区后才进行实际的设备读写操作;
  • 按行缓存
    按行缓存是指在接收到换行符(’ \n’)之前,数据都是先缓存在缓冲区的;
  • 不缓存
    最后一个是不缓存,也就是允许你直接读写设备上的数据。

  setvbuf 函数用于指定一个数据流的缓存模式。
举个栗子:

#include <stdio.h>
#include <string.h>

int main(void)
{
        char buff[1024];

        memset(buff, '\0', sizeof(buff));

        // 指定 buff 为缓冲区,_IOFBF 表示当缓冲区已满时才写入 stdout
        setvbuf(stdout, buff, _IOFBF, 1024);

        fprintf(stdout, "This is bbs.fishc.com\n");
        fprintf(stdout, "This output will go into buff\n");

        // fflush强制将上面缓存中的内容写入stdout
        fflush(stdout);

        fprintf(stdout, "this will appear when progream\n");
        fprintf(stdout, "will come after sleeping 5 seconds\n");

        sleep(5);

        return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值