江小南

V1

2023/01/05阅读:19主题:萌绿

【408篇】C语言笔记-第二十二章(文件的操作)

第一节:文件操作原理

1. C文件概述

程序执行时就称为进程,进程运行过程中的数据均在内存中。需要存储运算后的数据时,就需要使用文件。这样程序下次启动后,就可以直接从文件中读取数据。

文件是指存储在外部介质(如磁盘、磁带)上的数据集合。操作系统是以文件为单位对数据进行管理的。

说明:输入和输出缓冲区实际上是同一个,这里为了形象化表示而分开了。

C语言对文件的处理方法如下:

缓冲文件系统:系统自动地在内存中为每个正在使用的文件开辟一个缓冲区。用缓冲区文件系统进行的输入/输出称为高级磁盘输入/输出。

非缓冲区文件系统:系统不自动开辟确定大小的缓冲区,而由程序为每个文件设定缓冲区。用非缓冲区文件系统进行的输入/输出称为低阶输入/输出。

缓冲区原理:

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

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

2. 文件指针介绍

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

  struct _iobuf {
    char *_ptr; // 下一个要被读取的字符地址
    int _cnt;  // 剩余的字符,若是输入缓冲区,则表示缓冲区中还有多少个字符未被读取
    char *_base; // 缓冲区基地址
    int _flag;  // 读写状态标志位
    int _file;  // 问价描述符
    int _charbuf;
    int _bufsiz;  // 缓冲区大小
    char *_tmpfname;
  };
  typedef struct _iobuf FILE;

说明:fp是一个指向FILE类型结构体的指针变量。可以使fp指向某个文件的结构体变量,从而通过该结构体变量中的文件信息来访问该文件

第二节:文件的打开与关闭

1. 文件打开与关闭常用函数

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

FILE *fopen(const char *fname,const char *mode);

常用的mode参数及其各自的意义如下所示:

mode(方式) 意义
r 打开一个用于读取的文本文件
w 创建一个用于写入的文本文件,如果存在会清空文件
rb 打开一个用于读取的二进制文件
wb 创建一个用于写入的二进制文件,如果存在会清空文件
r+ 打开一个用于读/写的文本文件
w+ 创建一个用于读/写的二进制文件,如果存在会清空文件
rb+ 打开一个用于读/写的二进制文件
wb+ 创建一个用于读/写的二进制文件,如果存在会清空文件

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);

2. 代码实战

#include <stdio.h>

int main() {
    FILE *fp;  // 文件类型指针
    int i;
    char c;
    fp= fopen("file.txt","r+");
    if(NULL==fp){
        perror("fopen");
        return -1;
    }
    while ((c= fgetc(fp))!=EOF){
        printf("%c",c);
    }
    printf("\n");
    i= fputc('H',fp);
    if(EOF==i){
        perror("fputc");
    }
    fclose(fp);
    return 0;
}
# file.txt
helloworld
F:\Computer\Project\practice\22\22.2-File\cmake-build-debug\22_2_File.exe
helloworld

进程已结束,退出代码为 0

当执行完fputc函数后,实际file.txt的内容为:

helloworldH

说明:perror函数得到打开失败的原因(对于定位函数失败的原因,常用perror函数)。如果未新建一个文件,即文件不存在,那么会出现如下如所示的失败提示。

注意:perror函数必须紧跟着失败的函数。因为perror函数时读取错误码来分析失败原因的,执行了其他函数错误码会被改为零。

文件打开成功后,使用fgetc函数可以读取文件的每个字符,然后循环打印整个文件,读到文件结尾时返回EOF,所以通过判断返回值是否等于EOF就可以确定是否读到文件结尾。注意要在自己新建的file.txt文件中先填写一些内容

第三节:文件的读写

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,FILE *stream)
;

其中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;
    fp= fopen("file.txt","r+");  // 或者写成rb+
    if(NULL==fp){
        perror("fopen");
        return -1;
    }
    // fwrite和fread不要同时执行
//    ret= fwrite(buf,sizeof(char), strlen(buf),fp); // 把buf中的字符写入文件
    ret= fread(buf, sizeof(char), sizeof(buf)-1,fp);
    puts(buf);
    fclose(fp);
    return 0;
}
F:\Computer\Project\practice\22\22.4-fread-fwrite\cmake-build-debug\22_4_fread_fwrite.exe
hello
world

进程已结束,退出代码为 0

fread和fwrite函数既可以以文本方式对文件进行读写,又可以以二进制方式对文件进行读写

说明:以“r+”即文本方式打开文件进行读写时,项文件内写入的是字符串"hello\nworld",会发现是12个字节。

原因:向文本文件中写入"\n"时实际存入磁盘的是"\r\n",这是Windows系统的底层实现所决定的(Mac和Linux不会)。以文本的方式写入,一定要以文本的方式读出

如果以“rb+”二进制方式写入"hello\nworld",会发现还是11个字节。

通过以上差异,我们可以知道:对于字符型数据,如果是以文本方式写入的内容,那么一定要以文本方式读取;如果是以二进制方式写入的内容,那么一定要以二进制方式读取,不能混用

而对于整型数,浮点数,一定要以二进制方式"rb+"方式打开文件,二进制方式下内存中存储的是什么,写入文件的就是什么,是一致的

#include <stdio.h>

int main() {
    FILE *fp;
    int i=123456;
    int ret;
    fp= fopen("file.txt","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\n",i);
    fclose(fp);
    return 0;
}
F:\Computer\Project\practice\22\22.3-int-float\cmake-build-debug\22_3_int_float.exe
i=123456

进程已结束,退出代码为 0

2. fgets函数与fputs函数

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

char *fgets(char *str,int num,FILE *stream);

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

int puts(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;
}
F:\Computer\Project\practice\22\22.4-fgets-fputs\cmake-build-debug\22_4_fgets_fputs.exe
hello
world
how are you

进程已结束,退出代码为 0

使用fgets函数,我们可以一次读取文件的一行,这样就可以轻松地统计文件的行数。

注意:在一些机试题目中,用于fgets函数的buf不能过小,否则可能无法读取"\n",导致统计出错。

fputs函数向文件中写一个字符串,不会额外写入"\n",可以不用fputs,掌握fwrite即可。

第四节:文件位置指针偏移

1. fseek函数

fseek函数的功能是改变文件的位置指针。具体形式如下:

int fseek(FILE *stream,long offset,int origin);
// fseek(文件类型指针,位移量,起始点)

起始点的说明:

  1. 文件开头 SEEK_SET 0
  2. 当前位置 SEEK_CUR 1
  3. 文件末尾 SEEK_END 2

位移量是指以起始点为基点,向前移动的字节数。一般要求为long型。

fseek函数调用成功时返回零,调用失败时返回非零。

2. ftell函数

ftell函数返回stream(流)当前的文件位置,发生错误时返回-1,放我们想知道位置指针距离文件开头的位置时,就需要用到ftell函数,具体形式如下:

long ftell(FILE *stream);
#include <stdio.h>
#include <stdlib.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); // 保存字符串长度
    ret= fwrite(str, sizeof(char),len,fp); // 写入字符串到文件中
    ret= fseek(fp,-5,SEEK_CUR); // 从当前位置向前偏移5个字节
    if(ret!=0){
        perror("fseek");
        fclose(fp);
        return -1;
    }
    pos= ftell(fp); // 获取位置指针举例文件开头的距离
    printf("now pos=%ld\n",pos);
    memset(str,0sizeof(str)); // 把str清空
    ret= fread(str, sizeof(char), sizeof(str),fp); // 这时候会读到字符串world
    printf("%s\n",str);
    fclose(fp);
    return 0;
}
F:\Computer\Project\practice\22\23.5-ftell-fseek\cmake-build-debug\23_5_ftell_fseek.exe
now pos=7
world

进程已结束,退出代码为 0

说明:为什么pos=7,因为Windows多保存了一个"\r"。

分类:

后端

标签:

C++

作者介绍

江小南
V1