【C语言督学训练营 第二十二天】C语言操作文件

前言

其实本篇博客标题应该是第二十三天,因为督学营讲的是二十三天的内容,至于为什么将第二十三天内容调为二十二天是因为博主大致浏览了第二十二天的笔记,讲的均是与汇编相关的计算机组成原理真题,由于学习计组时间太长导致计组相关知识已经尽数遗忘,在这个时刻如果直接进行真题可能会直接打消学计组的念头(难!!!)所以我决定跳过第二十二天或者等学完计组相关部分再回来总结第二十二天的内容。


今天介绍的内容呢是有关计算机操作系统的知识。
在这里插入图片描述

1.文件操作原理解析

程序执行时就称为进程,进程运行过程中的数据均在内存中。需要存储运算后的数据时,就需要使用文件。这样程序下次启动后,就可以直接从文件中读取数据。(不像我们之前的程序,每次运行都需要手动输入数据)。文件是指存储在外部介质(如磁盘、磁带)上的数据集合。操作系统(Windows、Linux、Mac等)是以文件为单位对数据进行管理的,如下图所示。
在这里插入图片描述
语言对文件的处理方法如下。

  • 缓冲文件系统:系统自动地在内存区为每个正在使用的文件开辟一个缓冲区。用缓冲文件系统进行的输入/输出称为高级磁盘输入/输出。
  • 非缓冲文件系统:系统不自动开辟确定大小的缓冲区,而由程序为每个文件设定缓冲区。用非缓冲文件系统进行的输入/输出称为低级输入/输出。

下面介绍缓冲区原理。
缓冲区其实就是一段内存空间,分为读缓冲、写缓冲。C语言缓冲的三种特性如下。

  • (1)全缓冲:在这种情况下,当填满标准V/O缓存后才进行实际I/O操作。全缓冲的典型代表是对磁盘文件的读写操作。
  • (2)行缓冲:在这种情况下,当在输入和输出中遇到换行符时,将执行真正的V/O操作。这时,我们输入的字符先存放到缓冲区中,等按下回车键换行时才进行实际的VO操作。典型代表是标准输入缓冲区(stdin)和标准输出缓冲区(stdout) .
  • (3)不带缓冲:也就是不进行缓冲,标准出错情况(stderr)是典型代表,这使得出错信息可以直接尽快地显示出来(无需掌握,考研不考)。

打开一个文件后,我们会得到一个 FILE*类型的文件指针 fp,然后通过该文件指针对文件进行操作。FILE是一个结构体类型,其具体内容如下所示:

struct _iobuf i
	char *_ptr;l/下一个要被读取的字符地址
	int_ont;//剩余的字符,若是输入缓冲区,则表示缓冲区中还有多少个字符未被读取
	char *_base;//缓冲区基地址
	int_flag; //读写状态标志位
	int _file;l/文件描述符int_charbuf;
	int_bufsiz;//缓冲区大小
	char *_tmpfname;
};
typedef struct _iobuf FILE;

FILE*fp;//在main.c 写了FILE*fo;然后ctrl+左键就可以跳转到上面的结构体类型定义位置

fp是一个指向FILE类型结构体的指针变量。可以使fp指向某个文件的结构体变量,从而通过该结构体变量中的文件信息来访问该文件。
Windows 操作系统下的FILE结构体与Linux操作系统,Mac操作系统下的FILE结构体中的成员变量名是不一致的,但是其原理可以互相参考。

2.文件打开及关闭实战

fopen函数用于打开由fname (文件名)指定的文件,并返回一个关联该文件的流。如果发生错误,那么fopen返回NULL。mode(方式)用于决定文件的用途(如输人、输出等) ,具体形式如下所示:

FILE*fopen(const char *fname, const char *mode);
在这里插入图片描述

fclose函数用于关闭给出的文件流,并释放已关联到流的所有缓冲区。fclose执行成功时返回0,否则返回EOF。具体形式如下所示:

int fclose(FILE *stream);

fputc函数用于将字符ch的值输出到fp指向的文件中,如果输出成功,那么返回输出的字符;如果输出失败,那么返回EOF。具体形式如下所示:

int fputc(int ch, FILE*stream);

fgetc函数用于从指定的文件中读入一个字符,该文件必须是以读或读写方式打开的。如果读取一个字符成功,那么赋给ch。如果遇到文件结束符,那么返回文件结束标志EOF。具体形式如下所示:

int fgetc(FILE*stream);

接下来结合一个实例来看看如何打开关闭一个文件。

#include <stdio.h>

//练习文件打开
int main() {
    FILE *fp;//定义一个FILE类型的指针变量
    fp=fopen("file.txt","r+");//打开文件
    if(NULL==fp)//判断文件是否打开失败了
    {
        perror("fopen");//perror帮忙定位失败原因
        return -1;
    }
    char c;
//    c=fgetc(fp);
//    printf("%c\n",c);
//    c=fgetc(fp);
//    printf("%c\n",c);
    while((c=fgetc(fp))!=EOF)//读取文件内的所有内容
    {
        printf("%c",c);
    }
    printf("\n");
    c=fputc('H',fp);
    if(-1==c)
    {
        perror("fputc");
        return -1;
    }
    fclose(fp);//关闭文件
    return 0;
}

在这里插入图片描述

冒号之前的内容是我们写入perror函数内的字符串,冒号之后的内容是perror提示的函数失败原因,注意perror函数必须紧跟失败的函数,如果中间执行了printf这样的打印函数,那么perror 函数将提示Success,也就是没有错误,原因是每个库函数执行时都会修改错误码,一旦函数执行成功,错误码就会被改为零,而 perror函数是读取错误码来分析失败原因的。(perror考研不考,是帮助大家定位文件操作失败原因的)
文件打开成功后,使用fgetc函数可以读取文件的每个字符,然后循环打印整个文件,读到文件结尾时返回EOF,所以通过判断返回值是否等于EOF就可以确定是否读到文件结尾。注意要在自己新建的file.txt文件(和 exe在同级目录,注意查看视频操作),文件中先填写一些内容。

3.文件读写实战

3.1 fread&fwrite

fread 函数与fwrite函数的具体形式如下:

int fread(void *buffer, size_t size, size_t num, FILE *stream);
int fwrite(const void buffer, size_t size, size_t count,FILEstream);

其中 buffer是一个指针,对fread 来说它是读人数据的存放地址,对fwrite来说它是输出数据的地址(均指起始地址) ; size是要读写的字节数; count是要进行读写多少size字节的数据项;fp是文件型指针; fread函数的返回值是读取的内容数量,fwrite写成功后的返回值是已写对象的数量。

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

int main() {
    char buf[20]="hello\nworld";
    FILE *fp;
    int ret;//存储函数的返回值
    //r+代表以文本方式打开文件
    fp=fopen("file.txt","rb+");
    if(NULL==fp)
    {
        perror("fopen");
        return -1;
    }
//    ret=fwrite(buf,sizeof(char),strlen(buf),fp);//把buf中的字符串写入文件
    char buf1[20]={0};
    ret=fread(buf1,sizeof(char),sizeof(buf1),fp);
    printf("%s\n",buf1);
    fclose(fp);
    return 0;
}

fread和 fwrite 函数既可以以文本方式对文件进行读写,又可以以二进制方式对文件进行读写。以"r+"即文本方式打开文件进行读写时,向文件内写入的是字符串,写完后右键文件,选择属性(如下图所示),会发现大小是12个字节。
在这里插入图片描述

这是因为在文本方式下,向文本文件中写入"\n"时实际存入磁盘的是"\r\n",所有的接口调用都是Windows的系统调用,这是Windows的底层实现所决定的(Mac和 Linux 不会)。当然,以文本方式写入,一定要以文本方式读出,遇到"\r\n"时底层接口会自动转换为"\n",因此用fread函数再次读取数据时,得到的依然是"hello\nworld",共11字节。
如果把fopen 函数中的"r+“改为"rb+”,也就是改为二进制方式,那么当我们向磁盘写入11字节时,磁盘实际存储的就是11字节,如果这时双击打开该文件,那么会发现没有换行,即helloworld是连在一起的,中间没有换行符,原因是txt 文本编辑器必须遇到"\r\n"时才进行换行操作。
在这里插入图片描述

相信读者此时已经理解了文本方式和二进制方式的差异(在Mac和 Linux 操作系统下并不存在这样的问题)。以文本方式下写入"\n"后,磁盘存储的是"\r\n",当然读取时会以"\n"的形式读出"\r\n"。而以二进制方式写入"\n"后,磁盘存储的是"\n"。二者在其他方面没有差异。那么如何避免出错呢?如果是以文本方式写入的内容,那么一定要以文本方式读取;如果是以二进制方式写人的内容,那么一定要以二进制方式读取,不能混用!(大家主要理解文本文件与二进制文件的区别即可)。

#include <stdio.h>

int main() {
    FILE* fp;
    int i=123456;
    int ret;
    fp=fopen("file.txt","rb+");//以rb+模式打开文件
    if(NULL==fp)
    {
        perror("fopen");
        return -1;
    }
    //向文件中写入整型数,如果我们双击打开文件会发现乱码,因为打开文件都是以字符格式去解析的
//    ret=fwrite(&i,sizeof(int),1,fp);
    i=0;
    fread(&i,sizeof(int),1,fp);
    printf("i=%d",i);
    fclose(fp);
    return 0;
}

上例中写入整型数,浮点数时,一定要用二进制方式时,需要以"rb+"方式打开文件,二进制方式下内存中存储的是什么,写人文件的就是什么,是一致的。例如,写入整型变量i,其值为123456,内存存储为4字节,即Ox0001E240,那么写入内存的也是4字节。这时双击打开文件看到的是乱码,所以读取时也要用一个整型变量来存储。

3.2 fgets&fputs

函数fgets 从给出的文件流中读取[num-1]个字符,并且把它们转储到str(字符串)中。fgets在到达行末时停止, fgets成功时返回str(字符串),失败时返回NULL,读到文件结尾时返回NULL。其具体形式如下:

char *fgets(char str, int num, FILEstream);

fputs函数把str(字符串)指向的字符写到给出的输出流。成功时返回非负值,失败时返回EOF。其具体形式如下:

int fputs(const char *str, FILE *stream);

#include <stdio.h>

int main()
{
    char buf[20]={0};//用于存储读取数据
    FILE* fp;
    fp=fopen("file.txt","r+");//可读可写打开文件
    if(NULL==fp)
    {
        perror("fopen");
        return -1;
    }
    //一次读一行,读空文件
    while(fgets(buf,sizeof(buf),fp)!=NULL)//fgets读取到文件结束时返回NULL
    {
        printf("%s",buf);
    }
    return 0;
}

使用fgets 函数,我们可以一次读取文件的一行,这样就可以轻松地统计文件的行数,注意,在做一些机试题目时(机试是用 fgets读标准输入,因为gets部分学校机试不可用),用于 fgets
函数的 buf不能过小(buf大于最长行的长度),否则可能无法读取"'\n",导致行数统计出错.fputs 函数向文件中写一个字符串,不会额外写入一个"\n",可以不用 fputs,掌握fwrite即可.

4.文件位置指针偏移实战

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

#include <stdio.h>
#include <string.h>
int main()
{
    FILE *fp;
    char str[20]="hello\nworld";
    int len=0;//用于保存字符串长度
    long pos;
    int ret;//接函数返回值
    fp =fopen("file.txt","r+");//打开文件
    if(NULL==fp)
    {
        perror("fopen");
        return -1;
    }
    len=strlen(str);
    fwrite(str,sizeof(char),len,fp);
    ret=fseek(fp,-5,SEEK_CUR);
    if(ret!=0)
    {
        perror("fseek");
        fclose(fp);
        return -1;
    }
    pos= ftell(fp);
    printf("now pos=%ld\n",pos);
    memset(str,0,sizeof(str));//清空str
    fread(str,sizeof(char),sizeof(str),fp);//读取
    printf("str=%s\n",str);
    return 0;
}

最终的运行效果如图1所示。
我们向文件中写入了"hello \nworld",因为是文本方式,所以总计为12字节,通过fseek函数向前偏移5字节后,用ftell函数得到的位置指针距离文件开头的位置即为7,这时再用fread函数读取文件内容,得到的是"world"。
在这里插入图片描述


在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

酷尔。

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

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

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

打赏作者

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

抵扣说明:

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

余额充值