文件

1.为什么 需要文件

引例1:统计入学成绩(文件版)

  • 某大学的博士入学考试科目为外语和两门专业课,对于每个考生,输入各科考试成绩并计算总分。要求输入的内容被保存下来,以便将来随时使用。

思路:

为了能够把输入的内容保留下来,已经录入的成绩信息应该用文件保存下来,已经计算的总分也应该保存到文件中,并已追加方式继续录入。

  • 用文件student.txt存放学生的成绩信息,函数WriteToFile实现录入考生的成绩信息并存入文件student.txt:
step1:以追加的方式打开文件student.txt;
step2:输入考生的各项信息
step3:计算该生的总分
step4:将考生的各项信息以及总分以追加的方式存入文件student.txt;
step5:如果继续录入,转step2;否则关闭文件student.txt,算法结束
  • 设函数ReadFromFile实现从文件student.txt中读出考生的成绩信息并输出:
step1:以只读的方式打开文件student.txt;
step2:重复下述操作,直到文件student.txt的末尾:
    step2.1:从文件student.txt读出一个考生的成绩信息;
    step2.2:输出该生的成绩信息;
step3:关闭文件student.txt,算法结束;
#include "stdio.h"
#include "stdlib.h"

typedef struct{
    char no[10];
    char name[10];
    double foreign;
    double spec1;
    double spec2;
    double total;
}StudentType;

void WriteToFile();
void ReadFromFile();
int main(){

    int select;
    do{
        printf("1.录入成绩 2.输出成绩 0.退出\n");
        printf("请输入要执行的操作:");
        scanf("%d", &select);
        switch (select)
        {
        case 1:WriteToFile();break;
        case 2:ReadFromFile();break;
        default:
            printf("退出程序!\n");
            break;
        }
    }while((select == 1 || select == 2));


    return 0;
}

void WriteToFile(){
    FILE *fp = NULL;

    StudentType stu;
    char flag = 'y';

    fp = fopen("student.txt", "a");

    if (fp == NULL){
        printf("文件打开失败");
        exit(1);
    }

    while((flag == 'y' || flag == 'Y')){
        printf("请输入考生考号:");
        scanf("%s", stu.no);
        printf("请输入考生姓名:");
        scanf("%s", stu.name);
        printf("请输入考生外语成绩:");
        scanf("%lf", &stu.foreign);
        printf("请输入考生专业课1成绩:");
        scanf("%lf", &stu.spec1);
        printf("请输入考生专业课2成绩:");
        scanf("%lf", &stu.spec2);

        stu.total = stu.foreign + stu.spec1 + stu.spec2;

        fprintf(fp, "%10s%10s%8.2f", stu.no, stu.name, stu.foreign);
        fprintf(fp, "%8.2f%8.2f%8.2f", stu.spec1, stu.spec2, stu.total);
        fputc('\n', fp);
        fflush(stdin);
        printf("继续输入吗?继续请输入y或Y:");
        scanf("%c", &flag);
    }
    fclose(fp);
    return ;
}
void ReadFromFile(){
    FILE *fp = NULL;
    StudentType stu;
    fp = fopen("student.txt", "r");
    if(fp == NULL){
        printf("打开文件失败!\n");
        exit(1);
    }
    printf("  考生姓名  总分\n");
    while(!feof(fp)){
        fscanf(fp, "%s%s", stu.no, stu.name);
        fscanf(fp, "%lf%lf%lf%lf\n", &stu.foreign, &stu.spec1, &stu.spec2, &stu.total);
        printf("%10s%8.2lf\n", stu.name, stu.total);
    }
    printf("\n");
    fclose(fp);
    return ;
}

2.文件与文件指针

文件的概念

  • 文件:存储在外部介质(磁盘、磁带等)上的一组相关数据的有序集合。文件名标识一个文件的属性,其一般结构为:主文件名.扩展名。
  • 要读取外部介质中的数据,必须首先按照文件名找到相应的文件,然后从这个文件中将数据读取出来;要将数据存储到外部介质中,必须首先在外部介质上建立一个文件,然后将数据写入这个文件。
  • 例如,应用程序的“文件打开”功能实现将文件的内容读入到内存,“文件保存”功能实现数据从内存写入文件。

文本文件和二进制文件

  • 文本文件即ASCLL码文件,扩展名是.txt、.c、.cpp、.h、.ini等的文件大多数是文本文件
  • 二进制文件将内存中的数据原样输出(即镜像)到文件中,扩展名是.ext、.dll、.lib、.dat、.dat、.gif、.bmp等的文件大多数是二进制文件。

image-20220415180049454

文件缓冲区

image-20220415180442514

操作文件的一般流程:

  1. 打开文件。在打开一个文件时,系统自动在内存中开辟一个文件缓冲区,缓冲区的大小由具体的语言标准规定。
  2. 文件测试。打开文件后要测试文件是否正确打开,例如不同的程序试图打开同一个文件
  3. 读写操作。从文件中读数据时,操作系统首相自动把一个扇区的数据导入文件缓冲区中,然后由程序控制读入数据并进行处理,一旦数据读入完毕,系统会自动把下一个扇区的数据导入文件缓冲区中。把数据写入文件时,首先由程序控制把数据写入文件缓冲区,一旦写满文件缓冲区,操作系统会自动把这些数据写入磁盘中的一个扇区,然后把文件缓冲区清空。
  4. 关闭文件。不及时关闭文件就会耗尽操作系统的文件资源;强制将文件缓冲区的数据写入文件,以免操作数据丢失。

文件指针

  • 在头文件stdio.h中定义了一个文件结构类型FILEFILE中包含了所有与文件操作相关的信息。
typedef	struct __sFILE {
    unsigned char *_p;	/* current position in (some) buffer */
    int	_r;		/* read space left for getc() */
    int	_w;		/* write space left for putc() */
    short	_flags;		/* flags, below; this FILE is free if 0 */
    short	_file;		/* fileno, if Unix descriptor, else -1 */
    struct	__sbuf _bf;	/* the buffer (at least 1 byte, if !NULL) */
    int	_lbfsize;	/* 0 or -_bf._size, for inline putc */

    /* operations */
    void	*_cookie;	/* cookie passed to io functions */
    int	(* _Nullable _close)(void *);
    int	(* _Nullable _read) (void *, char *, int);
    fpos_t	(* _Nullable _seek) (void *, fpos_t, int);
    int	(* _Nullable _write)(void *, const char *, int);

    /* separate buffer for long sequences of ungetc() */
    struct	__sbuf _ub;	/* ungetc buffer */
    struct __sFILEX *_extra; /* additions to FILE to not break ABI */
    int	_ur;		/* saved _r when _r is counting ungetc data */

    /* tricks to meet minimum requirements even when malloc() fails */
    unsigned char _ubuf[3];	/* guarantee an ungetc() buffer */
    unsigned char _nbuf[1];	/* guarantee a getc() buffer */

    /* separate buffer for fgetln() when line crosses buffer boundary */
    struct	__sbuf _lb;	/* buffer for fgetln() */

    /* Unix stdio files get aligned to block boundaries on fseek() */
    int	_blksize;	/* stat.st_blksize (may be != _bf._size) */
    fpos_t	_offset;	/* current lseek offset (see WARNING) */
} FILE;
  • 定义文件指针的一般形式如下:
FILE * 文件指针变量名;
  • 定义并初始化文件指针变量
FILE *fp = NULL;
  • 文件指针是一种特殊的指针,每个打开的文件都有自己的文件指针和文件缓冲区,通过文件指针可以活动该文件的相关信息(例如文件号、文件的位置指针等),这些相关信息在系统打开文件时自动填入和使用,一般的编程人员不必关系FILE结构的具体内容。

什么是文件的当前位置指针

  • C语言中的文件是流式文件,即文件是由一个个字节组成,对于文本文件,每个字节对应一个字符,对于二进制文件,每个字节对应一个二进制位串。

    image-20220415183328202

  • 文件结构类型FILE中的成员_p表示当前位置的指针,指向当前的读写位置,也就是将要操作的字节。

  • 打开文件时,编译器会根据打开方式将文件的当前位置指针设置在文件头或文件尾。

    • 一般的打开方式下,位置指针设置在文件头
    • 追加方式下,位置指针设置在文件尾
  • 随着文件读写操作的进行,文件的位置指针_p会自动向后移动。最后一个字节之后是文件结束的位置。

  • 因此,文件的位置指针_p的最小值是0,最大值是文件的长度

追踪文件的当前位置指针

  • 为了追踪文件的读写位置,系统提供了ftell函数和feof函数用来检测文件的当前位置指针

  • fetll函数的原型如下:

    long ftell(FILE *filepointer)
    
  • ftell活动filepointer文件的当前位置指针

  • 如果操作成功,返回filepointer文件的当前位置指针,即对于文件开头的位移量(字节数)。否则,返回-1L(L表示该常量为long int型)

  • 用法举例

  • 假设文件指针fp已经与某个打卡的文件相关联 ftell获得文件fp的当前位置,返回相对文件头的字节数的代码如下

    long int p;
    p = ftell(fp);
    

    判断当前位置指针是否达到文件末尾的程序段:

    while(!feop(fp)){
        
    }
    

定位文件的当前位置指针

  • 在C语言中,除了熟悉怒读写方式外,还可以对文件进行随机读写,即根据需要读写特定位置的数据。

  • 系统提供了rewind函数和fseek函数用来改变文件的当前位置指针

  • rewind函数的原型如下:

    void rewind(FILE *filepointer)
    
  • rewind函数将filepoint文件的当前位置指针定位到文件的开头

  • fseek函数的原型如下:

    int fseek(FILE * filepointer, long offset, int origin)
    
  • 其中,filepoint是文件指针;offset为偏移量,其类型是long int型;origin是起始位置,其类型是int。

  • fseek将filepointer文件的当前位置指针移动到距离origin的offset位置处。offset为正值,表示新的位置在origin的后面,offset的值为负值,表示新的位置在origin的前面

  • 如果操作成功,则返回0,否则返回非0

orgin(起始位置) 符号常量 整数值
文件的开头 SEEK_SET 0
文件的当前位置 SEEK_CUR 1
文件的末尾 SEEK_END 2

3.文件的打开与关闭

文件的打开

  • fopen()函数的原型如下:

    FILE *fopen(char *filename, char *mode);
    

    image-20220415204452441

  • 其中,文件名前面可以带路径;打开文件的方式是指打开文件后将到进行哪些操作。

  • fopen按指定方式打开文件,系统分配相应的文件缓冲区。

  • 如果文件打开成功,则返回指向文件的指针, 如果文件打开失败,则返回空指针NULL

image-20220415204738516

FILE *fp;
char *p = NULL;
fp = fopen("f1.txt", "r");
fp = fopen("/Users/luck/Desktop/Code/C/CPrograms/file/f1.txt", "w+");
char filename[] = "f1.txt";
fp = fopen(filename, "a+");

为了保证文件操作的可靠性,调用fopen()函数后进行文件测试:

FILE *fp = NULL;
fp = fopen("f1.txt", "r");
if (fp = NULL){
    printf("File open error!");
    exit(-1);
}

文件的关闭

  • fclose函数的原型如下:

    int fclose(FILE *filepointer);
    
  • fclose关闭指向filepointer指向的文件

  • 如果正常关闭,则返回值为0,否则,返回非0

    if(fclose(fp)){
        printf("File close error!");
        exit(-1);.//终止程序的执行
    }
    

4.文件的读写

  • 文件的读写操作是针对内存而言的,文件读操作是将文件中的数据传送到计算机内存中,文件写操作是从计算机内存中向文件存送数据。

    image-20220415210943148

字符方式文件读写

  • 字符方式文件读写:以字符为单位进行文件读写操作,即每次可从文件读出一个字符或向文件写入一个字符。

  • fgetc函数的原型如下:

    int fgetc(FILE *filepointer);
    
  • fgetc从filepointer文件的当前位置读出一个字符,同时将文件的位置指针_ptr后移一个字节。读出的字节一般要保存到一个字符型变量中。

  • 如果读取成功,则返回读取的字节值;如果读到文件尾或出错,则返回EOF

  • fputc函数的原型如下:

    int fputc(int c, FILE *filepointer);
    
  • fputc向filepointer文件的当前位置写入一个ASCLL码值为c的字符,同时将文件的位置指向下一个字节。如果写入成功,则返回写入的字节值;否则返回EOF

字符串方式读写

  • 字符串方式文件读写:以字符串为单位进行文件读写操作,即每次可从文件读出一个字符串或像文件写入一个字符串。

  • fgets的函数原型如下:

    char *fgets(char *str, int n, FILE *filepointer);//读取的长度为n-1
    
  • 从filepointer文件的当前位置读取长度为n-1的字符串,在末尾加上字符串终结符'\0'存入str所指内存单元中,同时将文件的位置指针后移n-1个字节。

  • 如果读取成功,则返回指向字符串的指针;如果读到文件尾或出错,则返回NULL。

  • fputs函数的原型如下:

    int fputs(char *str, FILE *filepointer);
    
  • 向filepointer文件的当前位置写入字符串str,同时将文件的位置指针向后移动字符串长度个字节。

  • 如果写入成功,则返回最后写入的字节值;否则返回EOF

格式化读写方式

  • 格式化读写文件:以某种格式进行的文件读与,C语言提供了fscanffprintf函数实现格式化读写 功能。

  • 格式化文件读写函数fscanf(和fprintf()与格式化输入/输出函数scanf()和printf()类似,区别在于fscanf( )和fprintf()函数的操作对象是磁盘文件,scanf()和printf()函数的操作对象是标准输人(键盘)/输出(显示器)文件。

  • fcanf函数的原型如下:

    fscanf(FILE *filepointer, char* format[,address,...]);
    
  • 其中,format是格式串,与scanf的格式串相同; address是输入列表,通常为变量的地址。

  • fscanf从filepointer文件按format格式读取输入列表中的数据。如果读取成功,则返回读取的数据项的 个数,如果读到文件尾或出错,贝返回EOF。

    int x;
    char *str;
    fscanf(fp, "%d%s", &x, str);
    
  • fprintf函数的原型如下:

    fprintf(FILE *filepointer, char* format[,address,...]);
    
  • 其中,format是格式串,与printf的格式串相同; address是输入列表,与printf的输出列表相同。

  • 将输出列表中的数据按照指定格式写入到文件filepointer中当前的读写位置

  • 如果写入成功,则返回写入的字节数,否则返回EOF

    int x = 20;
    float y = 5.8;
    fpritnf(fp, "%d, %6.2f", x, y);
    

二进制方式文件读取

  • 二进制方式文件读写:对二进制文件进行读写操作,通常以字节为单位。

  • fread函数的原型如下

    unsigned fread(void *ptr, unsigned size, unsigned n, FILE *filepointer);
    

    image-20220415214825967

  • 从filepointer文件的当前位置读出n个数据,每个数据的大小是size个字节,并将送出的数据存放在ptr所指向的内存单元中,同时,将文件的位豊指针白后移动n*size个字节。

  • 若操作成功,则返回读出的数据个数;否则这回0。

  • fwrite函数的原型如下:

    unsigned fwrite(void *ptr, unsigned size, unsigned n, FILE *filepointer);
    

    image-20220415215022952

  • 将ptr所指内存的n个大小为size个字节的效据写入filepointer文件的当前位置,同时,将文件的位置: 针向后移动n*size个字节。

  • 若操作成功,则返回写入数据的个数;否则返回0。

5.程序设计实例

实例1——文件复制

  • 模拟实现操作系统的文件复制功能。

  • 思路:假设将文件fleS的内容复制到文件fi¢r中可以传次读取文件fileS的每一个字符,并写入文件fileT中,则文件fileS应该以县读方式打开,文件fileT应该以只写方式打开

  • 设函数copy实现交件复制,其算法描述如下:

    step1:以只读方式打开文件fileS;以只写万式打开文件fileT;
    step2:当fileS尚未到文件尾,重复执行下述操作:
        step2.1:ch=从文件files谈出一个字符:;
        step2.2:将ch写入文件fleT;
    step3:关闭文件fleS;关团文件fileT;
    
    #include"stdio.h"
    #include "stdlib.h"
    
    
    void copy(char fileS[], char fileT[]);
    long get_file_len(FILE * p);
    int main(){
    
        char fileS[30], fileT[30];
        printf("请输入你要复制的源文件:");
        scanf("%s", fileS);
    
        printf("请输入你要复制的目标文件:");
        scanf("%s", fileT);
    
        copy(fileS, fileT);
        return 0;
    }
    
    void copy(char fileS[], char fileT[]){
    
        char ch;
        FILE *fpSource, *fpTarget;
        if ((fpSource=fopen(fileS, "r")) == NULL){
            printf("文件打开失败!\n");
            exit(-1);
        }
    
        if ((fpTarget=fopen(fileT, "w")) == NULL){
            printf("文件打开失败!\n");
            exit(-1);
        }
    
        long end = get_file_len(fpSource);
        while(1){
            if (ftell(fpSource) == end){
                break;
            }
            ch = fgetc(fpSource);
            fputc(ch, fpTarget);
        }
        fclose(fpSource);
        fclose(fpTarget);
        printf("文件复制成功!\n");
    
        return;
    }
    
    long get_file_len(FILE * p){
        long f_len = 0;
        if (NULL == p){
            return f_len;
        }
    
        fseek(p, 0, SEEK_END);
    
        f_len = ftell(p);
        fseek(p, 0, SEEK_SET);
        return f_len;
    }
    

实例2——二进制文件的写入

  • 从键盘上输入5个学生的有关结构体数据,然后把他们转存到磁盘文件中。解题思路如下:
    1. 定义一个有5个元素的结构体数组;
    2. 从main中输入学生的数据,调用saveIntoFile函数把学生数据写入文件。
#include "stdio.h"
#include "stdlib.h"

#define SIZE 5

struct studentType{
    char name[10];
    int num;
    int age;
    char addr[15];
};

void saveToFile(struct studentType stud[], int num){
    FILE *fp;
    int i;

    if ((fp = fopen("stu.txt", "wb")) == NULL){
        printf("cannot open file\n");
        return ;
    }

    for(int i = 0;i < num;i++){
        if (fwrite(&stud[i], sizeof(struct studentType), 1, fp) != 1){
            printf("file write error\n");
        }
    }
    fclose(fp);
}

int main(){


    struct studentType stud[SIZE];

    printf("Please enter data of students:\n");

    for (int i = 0; i < SIZE; i++)
    {
        scanf("%s%d%d%s", stud[i].name, &stud[i].num, &stud[i].age, &stud[i].addr);
    }

    saveToFile(stud, SIZE);
    


    return 0;
}

实例3——二进制文件的读取

  • 把磁盘文件中的数据读取出来显示在屏幕上。
//读取全部
#include "stdio.h"
#include "stdlib.h"

#define SIZE 5

struct studentType{
    char name[10];
    int num;
    int age;
    char addr[15];
};

void readFromFile();
int main(){

    readFromFile();

    return 0;
}

void readFromFile(){
    struct studentType student;
    int i;
    FILE *fp;

    if ((fp = fopen("stu.txt", "rb")) == NULL){
        printf("cannot open file!\n");
        return ;
    }

    while(!feof(fp)){
        if(fread(&student, sizeof(struct studentType), 1, fp) == 1){
            printf("%-10s %4d %4d %-15s\n", student.name, student.num, student.age, student.addr);
        }
    }

    fclose(fp);
}
//读取指定元素
#include "stdio.h"
#include "stdlib.h"

#define SIZE 5

struct studentType{
    char name[10];
    int num;
    int age;
    char addr[15];
};

void readFromFile(int num);
int main(){

    readFromFile(0);

    return 0;
}

void readFromFile(int num){
    struct studentType student;
    int i;
    FILE *fp;

    if ((fp = fopen("stu.txt", "rb")) == NULL){
        printf("cannot open file!\n");
        return ;
    }

    int offset = sizeof(struct studentType) * num;

    fread(&student, sizeof(struct studentType), 1, fp);
    printf("%-10s %4d %4d %-15s\n", student.name, student.num, student.age, student.addr);

    fclose(fp);
}