C语言笔记:文件操作

 ACM金牌带你零基础直达C语言精通-课程资料

 本笔记属于船说系列课程之一,课程链接:ACM金牌带你零基础直达C语言精通icon-default.png?t=N7T8https://www.bilibili.com/cheese/play/ep159068?csource=private_space_class_null&spm_id_from=333.999.0.0

你也可以选择购买『船说系列课程-年度会员』产品『船票』,畅享一年内无限制学习已上线的所有船说系列课程:船票购买入口icon-default.png?t=N7T8https://www.bilibili.com/cheese/pages/packageCourseDetail?productId=598

做题网站OJ:HZOJ - Online Judge

Leetcode :力扣 (LeetCode) 全球极客挚爱的技术成长平台

一.文件类型

现在假如有一个文件data.txt和一个可执行程序:

在文件中如何存储文件类型:FILE *,FILE指针类型,然后我们可以通过定义一个FILE指针类型定义一个指针变量进行指向我们需要进行操作的文件。

现在fp是没有指向data.txt文件的,现在需要借用一个函数才能进行指向data.txt;

这个函数是fopen

现在又了指向data.txt的文件指针了,如果需要对data.txt文件中输入内容,需要使用函数fprintf

程序测试:

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

int main() {
    FILE *fp = fopen("./data.txt", "w");
    //当没有成功打开文件时,打印提示信息,并结束程序
    if (fp == NULL) {
        printf("failed to open file\n");
        exit(1);
    }
    //向文件中打印内容
    fprintf(fp, "hello world\n");
    int a = 123, b = 456;
    fprintf(fp, "a = %d, b = %d\n", a, b);
    fclose(fp);
    return 0;
}

执行结果:

打开data.txt文件查看:

文件打开模式:

二进制模式(在任何模式后加上"b")

  • 以二进制模式打开文件,用于处理二进制文件。例如,"rb" 表示以只读模式打开二进制文件。

对于r和r+,w和w+的代码演示:

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

void r_access() {
    //以只读方式打开data.txt
    FILE *fp = fopen("./data.txt", "r");
    if (fp == NULL) {
        printf("r : failed to open file\n");
        exit(1);
    }
    char s[100];
    //读取文件中的内容
    fscanf(fp, "%[^\n]", s);
    printf("r : s = %s\n", s);
    fclose(fp);
    fp = fopen("temp.txt", "r");
    //如果以r模式打开不存在的文件,那么会打开失败返回NULL空地址
    if (fp == NULL) {
        printf("r : open erroe\n");
    }
    //使用过后记得关闭
    return ;
}

void rand_file_name(char *file_name, int n) {
    for (int i = 0; i < n; i++) {
        file_name[i] = rand() % 26 + 'a';
    }
    file_name[n] = 0;
    //在文件名末尾加上.txt
    //strcat作用将第一个参数的后面加上第二个参数的字符串
    strcat(file_name, ".txt");
    return ;
}

void w_access() {
    //测试存在的文件
    FILE *fp = fopen("data.txt", "w");
    if (fp == NULL) {
        printf("w : failed to open file\n");
        exit(1);
    }
    //向文件中输入内容,并且在输入前会清空文件中的内容
    fprintf(fp, "w :hahahahah, world\n");
    fprintf(fp, "hello nihao\n");
    fclose(fp);
    char file_name[100] = {0};
    rand_file_name(file_name, 10);
    //当用w打开不存在的文件时,会创建新的文件,并且文件名就是fopen的第一个参数
    printf("w : open new name : %s\n", file_name);
    fp = fopen(file_name, "w");
    fclose(fp);
    return ;
}

void r_plus_access() {
    //现在打开data.txt可以对文件进行读取和写入内容
    FILE *fp = fopen("data.txt", "r+");
    if (fp == NULL) {
        printf("r+ : failed to open file\n");
        exit(1);
    }
    char s[100];
    fscanf(fp, "%s", s);
    printf("r+ : s = %s\n", s);
    //在r+模式下写入内容,默认会在文件末尾进行写入
    fprintf(fp, "000000");
    fclose(fp);
    //打开不存在的文件
    fp = fopen("data2.txt", "r+");
    if (fp == NULL) {
        printf("r+ : open error\n");
    }
    return ;
}

void w_plus_access() {
    //测试存在的文件
    FILE *fp = fopen("data.txt", "w+");
    if (fp == NULL) {
        printf("w : failed to open file\n");
        exit(1);
    }
    //向文件中输入内容,并且在输入前会清空文件中的内容
    fprintf(fp, "w+ :hahahahah, world\n");
    char s[100] = {0};
    fscanf(fp, "%s", s);
    //对于这里为什么读取的内容为空,在后续代码会进行解释
    printf("w+ : %s\n", s);
    fclose(fp);
    char file_name[100] = {0};
    rand_file_name(file_name, 10);
    //当用w+打开不存在的文件时,会创建新的文件,并且文件名就是fopen的第一个参数
    printf("w+ : open new name : %s\n", file_name);
    fp = fopen(file_name, "w+");
    fclose(fp);
    return ;
}

int main() {
    srand(time(0));
    r_access();
    w_access();
    r_plus_access();
    w_plus_access();
    return 0;
}

        对于这部分代码,从调用的函数顺序一个一个实现和测试,而不是把全部代码写上去在测试,并且最开始当前目录下是有一个data.txt文件的。我的data.txt文件最初内容是hello world

全部代码执行结果

        

这分别是我用w和w+模式生成的新文件

读写文件的方法与技巧:

这里直接用代码演示:

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

//定义一个文件名
const char *file_name = "data3.txt";
//定义一个整形数组,用来写入文件的数据
#define MAX_N 10000
int arr[MAX_N + 5], n = 0;//n代表存储了多少个数据

void output_arr(int *arr, int n) {
    for (int i = 0; i < n; i++) {
        //如果不理解这句代码可以看看之前逻辑关系的知识点
        //和 if (i) printf(" ");的功能一样
        i && printf(" ");
        printf("%d", arr[i]);
    }
    putchar(10);
    return ;
}

//1.写入文件
void output_to_file(int *arr, int n) {
    //需要在文件中追加写入内容,所以打开模式用 a
    FILE *fp = fopen(file_name, "a");
    for (int i = 0; i < n; i++) {
        fprintf(fp, "%d ", arr[i]);
    }
    //打开文件,一定要记得关闭
    fclose(fp);
    return ;
}

//返回值代表读入了多少个整形
//2.从文件中读取数据
int read_from_file(int *arr) {
    int i = 0;
    //由于要向文件中读取数据,所以打开模式用r
    FILE *fp = fopen(file_name, "r");
    //如果文件打开失败,说明读取0个数据返回0
    if (fp == NULL) return 0;
    //循环读入,fscanf的返回值和scanf的返回值一样
    //当读到文件末尾时fscanf会返回EOF,也就是-1
    while (fscanf(fp, "%d", arr + i) != EOF) i++;
    //记得关闭
    fclose(fp);
    return i;
}

//3.清空文件内容
void clear_file() {
    //在打开模式中w和w+模式打开文件都会得到一个清空的文件
    FILE *fp = fopen(file_name, "w");
    fclose(fp);
    return ;
}

//将功能封装为函数是为了减少记忆负担,减小代码出错概率
//测试写入功能是否成功
int output_test() {
    //获取随机种子
    srand(time(0));
    //进行对数组赋值
    for (n = 0; n < 10; n++) {
       arr[n] = rand() % 100;
    }
    //输出数组
    output_arr(arr, n);
    output_to_file(arr, n);
    return 0;
}

//测试从文件中读取数据是否成功
int read_test() {
    n = read_from_file(arr);
    output_arr(arr, n);
    printf("total read %d numbers\n", n);
    return 0;
}


//可以看到我的主函数的代码量非常少,我的功能都封装在函数中
//通过每个函数之间的调用,可以一步一步的吧思路理清
int main() {
    output_test();
    read_test();
    clear_file();
    return 0;
}

最开始我的本地目录是没有data3.txt文件的

然后编译执行我上面代码的源文件3.file_opeartor.c

然后执行后可以发现我本地被我的程序创建了一个data3.txt

最后打开data3.txt可以看到内容是空的

通过下面的流程图可以看到整个程序的流程大概是什么样子的

实现一个简单的学生信息管理系统

1.学生信息管理系统-交互界面设计

交互界面的功能:

1.列出学生列表

2.添加一个学生

3.修改一个学生

4.删除一个学生

5.退出

在mysql>输入需要进行的相关操作

先实现交互界面:
 

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

//利用枚举类型进行减小记忆负担
//这样在阅读代码时更容易理解
enum NO_TYPE {
    LIST = 1,
    ADD,
    MODIFY,
    DELETE,
    QUIT
};


//实现交互页面
int usage() {
    int no;
    do {
        printf("%d. list students\n", LIST);//列出所有学生信息
        printf("%d. add a new stdents\n", ADD);//添加一个学生信息
        printf("%d. list modify a students\n", MODIFY);//修改学生信息
        printf("%d. delete a students\n", DELETE);//删除一个学生信息
        printf("%d. quit system\n", QUIT);//退出系统
        printf("mysql>");
        scanf("%d", &no);
        if (no < 1 || no > 5) printf("intput error\n");
    } while(no < 1 || no > 5);//这里的do_while循环的作用为了让输入的值在系统可匹配范围内
    return no;
}

int main() {
    while  (1) {
        int no = usage();
        switch (no) {
            case LIST:  printf("List\n"); break;
            case ADD:  printf("ADD\n"); break;
            case MODIFY:  printf("MODIFY\n"); break;
            case DELETE:  printf("DELETE\n"); break;
            case QUIT: printf("QUIT\n");exit(0);
        }
    }
    return 0;
}
2.实现相关功能
①.实现打印学生信息:

学生信息有id,name,age,class,height,五个数据。

现在实现从文件中读取学生信息,并打印出学生信息。

准备一份文件先存入一些数据,student_data.txt

zhangsan 33 3 1.73
sue 32 1 1.98
song 50 2 1.99

然后进行实现该功能:

②.添加学生信息:

功能要求:在输入2之后,有提示信息,并且接下来是输入内容的命令行

③.实现修改学生信息功能

④.实现删除学生信息功能

最终整个小项目的代码设计:

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

//定义一个字符串常量用来存储刚刚创建的需要进行读取的文件
const char *file_name = "student_data.txt";


typedef struct Student {
    char name[20];
    int age;
    int class;
    double height;
} Student;

#define MAX_N 10000
//创建学生数组, 用来存每个学生的信息
Student stu_arr[MAX_N + 5];
//记录有多少个学生
int stu_cnt;

//初始化学生数组,开始执行程序然后从文件中读取所有学生信息
//从文件中读取数据
int read_from_file(Student *arr) {
    int i = 0;
    FILE *fp = fopen(file_name, "r");
    //判断是否打开成功
    if (fp == NULL) return 0;
    //循环读取每一行学生数据, 先读入学生的姓名
    while (fscanf(fp, "%s", arr[i].name) != EOF) {
        //然后挨个读取学生的每个信息
        fscanf(fp, "%d%d%lf",
               &arr[i].age,
               &arr[i].class,
               &arr[i].height
              );
        //对于学生ID对应的就是数组中的索引位置
        i++;
    }
    //记得关闭
    fclose(fp);
    return i;
}

//向文件中写入数据
void output_to_file(Student *arr, int n) {
    //因为是在文件末尾追加写入,所以需要用a模式打开文件
    FILE *fp = fopen(file_name, "a");
    for (int i = 0; i < n; i++) {
        fprintf(fp, "%s %d %d %.2lf\n",
                arr[i].name,
                arr[i].age,
                arr[i].class,
                arr[i].height);
    }
    //记得关闭使用的文件指针
    fclose(fp);
    return ;
}


void restor_data_to_file(Student *arr, int n) {
    //在修改学生信息时,如何把修改的数据交换原先的数据
    //那就通过先清空文件原先的内容
    //然后再通过数组向文件中写入
    //这里就通过w打开文件模式进行清空文件
    FILE *fp = fopen(file_name, "w");
    //用完关闭
    fclose(fp);
    //将新的数组内容进行输出到文件中
    output_to_file(arr, n);
    return ;
}


//利用枚举类型进行减小记忆负担
//这样在阅读代码时更容易理解
enum NO_TYPE {
    LIST = 1,
    ADD,
    MODIFY,
    DELETE,
    QUIT
};


//实现交互页面
int usage() {
    int no;
    //记录文件中有多少个学生
    //并且文件中所有学生的信息已经读入在创建的学生结构体数组中
    stu_cnt = read_from_file(stu_arr);
    do {
        printf("%d. list students\n", LIST);//列出所有学生信息
        printf("%d. add a new stdents\n", ADD);//添加一个学生信息
        printf("%d. list modify a students\n", MODIFY);//修改学生信息
        printf("%d. delete a students\n", DELETE);//删除一个学生信息
        printf("%d. quit system\n", QUIT);//退出系统
        printf("mysql>");
        scanf("%d", &no);
        if (no < 1 || no > 5) printf("intput error\n");
    } while(no < 1 || no > 5);//这里的do_while循环的作用为了让输入的值在系统可匹配范围内
    return no;
}

//实现列出学生信息
//实现功能1.list students
void list_students() {
    //用len记住打印表头的字符数
    int len = 0;
    len = printf("%4s|%10s|%4s|%6s|%7s|\n",
           "id",
           "name",
           "age",
           "class",
           "height") ;
    //输出打印表头相同字符长度的分割线
    while (--len) printf("=");
    putchar(10);
    //输出学生学信息
    for (int i = 0; i < stu_cnt; i++) {
    len = printf("%4d|%10s|%4d|%6d|%7.2lf|\n",
           i,
           stu_arr[i].name,
           stu_arr[i].age,
           stu_arr[i].class,
           stu_arr[i].height) ;
    }
    return ;
}

//实现并封装添加一个学生信息功能
void add_to_student() {
    //输出对应功能要求的提示
    printf("add new item : (name, age, class, height)\n");
    printf("mysql > ");
    //将学生信息读入到学生数组中
    scanf("%s%d%d%lf",
          stu_arr[stu_cnt].name,
          &stu_arr[stu_cnt].age,
          &stu_arr[stu_cnt].class,
          &stu_arr[stu_cnt].height
    );
    //为什么传入参数是stu_arr + stu_cnt
    //为了保证刚刚添加的数据在文件末尾进行写入
    output_to_file(stu_arr + stu_cnt, 1);
    stu_cnt++;//添加一个学生,人数+1
    printf("add a new student success\n");
    return ;
}

//实现并封装修改一个学生信息
void modify_a_student() {
    //如果没有学生那就无法删除
    if (!stu_cnt) {
        printf("there haven't student\n");
        return ;
    }
    //1.列出所有学生信息
    list_students();
    int id;
    //2.输入id
    //判断输入id是否存在,如果不存在将一直输入
    do {
        printf("modify id : ");
        scanf("%d", &id);
    } while (id < 0 || id >= stu_cnt);
    //3.输出提示信息
    printf("modify item : (name, age, class, height)\n");
    //4.输入修改后学生信息
    printf("mysql >");
    scanf("%s%d%d%lf",
          stu_arr[id].name,
          &stu_arr[id].age,
          &stu_arr[id].class,
          &stu_arr[id].height
    );
    restor_data_to_file(stu_arr, stu_cnt);
    return ;
}

void delete_a_student() {
    //1.列出学生列表
    list_students();
    //2.输入需要删除学生id
    //输入id和修改学生信息逻辑相同
    int id;
    do {
        printf("delete id : ");
        scanf("%d", &id);
    } while (id < 0 || id >= stu_cnt);
    //3.确认是否删除
    char c;
    do {
        printf("confirm (y / n) :");
        scanf("%c", &c);
    } while (c != 'y' && c != 'n');
    if (c == 'n') return ;//不确认删除,那就结束该功能
    //通过删除位置后面的信息从前往后依次覆盖
    //比如现在id = 2, 那么id = 3的信息覆盖id = 2的信息,以此类推
    //最后位置后面没有信息就不用覆盖了
    for (int i = id; i < stu_cnt - 1; i++) {
         stu_arr[i] = stu_arr[i + 1];
    }
    stu_cnt--;
    restor_data_to_file(stu_arr, stu_cnt);
    return ;
}


int main() {
    while  (1) {
        int no = usage();
        switch (no) {
            case LIST:  {
                list_students();
            }; break;
            case ADD:  {
                add_to_student();
            } break;
            case MODIFY:  {
                modify_a_student();
            } break;
            case DELETE: {
                delete_a_student();
            } break;
            case QUIT: printf("Thanks used\n");exit(0);
            default : printf("error\n"); exit(1);
        }
    }
    return 0;
}
3.学生管理系统总结:

对于该小项目的总结

1.先分析每个功能的需求;

2.对于每个功能的需求进行封装代码实现;

3.通过实现过程中发现自己对于那个知识点有问题给,比如一段代码出现错误无法Debug,那就是对于代码的分析能力还不足够;

4.学会对于功能封装成函数,这样对于编写代码的逻辑思路更清晰,并且代码的可读性也会变得非常高;

5.对于该小项目的可优化的空间非常大,可以在后续进行自己对于功能的想法进行修改,以及每个功能实现的地方进行优化。

优化1:实现单条数据的修改

理解文件位置指针:文件写入数据是从文件位置指针的位置进行开始写入的,其实文件指针位置和理解可以和数组对应位置一样去理解,每个位置都有相应的值,如果索引就相当于文件指针,数组的索引是什么位置就对数组的相应位置进行操作,而文件指针指到哪儿就进行对该位置的操作。对于文件指针位置,不仅对数据的输出会影响,还对文件的读入也有影响。

特殊情况:对于文件打开模式是追加扩展模式,无论怎么修改文件指针的位置都是在文件末进行写入行为.对于读入行为是可以的.

例如:

现在有一段程序:

通过fopen以w模式打开文件后,文件指针应该在文件最开始的位置,然后进行写入数据然后文件位置指针到达9后面的位置:

现在我想改变文件指针的位置,那么我就需要用到fseek函数进行操作:

通过对fseek的理解,第三个参数表示从文件开头进行偏移,然后正向偏移两个字节。

文件开头的位置就是0的位置,那么偏移两个位置就是到达2的位置,最后文件指针的位置就是在2的位置。

然后我在进行读入数据:

那么就会开始覆盖该位置之前的数据。

通过fseek和ftell进行对学生管理系统的优化

        设计功能那么就需要进行分析功能的需求,对于功能需求进行转换为代码逻辑.功能需求对于学生信息修改进行单条修改,而不是之前代码逻辑,先改变学生数组数据,然后进行对文件清空在输入.

        例如我现在需要修改p1行的学生数据,那么我就需要进行对于p1行的数据进行覆盖.但是覆盖的过程中会出现一个问题,覆盖的数据和被覆盖的数据如果长度不一样,会发生出错,这里的出错不是程序出错,是输出格式的出错,比如覆盖的数据较短,那么内容就无法完全覆盖.

        解决方案就是设计时,使两行数据的长度一样,这样就不会出错.

        这个过程就是功能需求的分析,然后进行转换为代码逻辑进行分析,最后进行代码设计.这样才是编写程序的一个正确逻辑.

        在代码设计过程中去分析如何实现该功能,利用所学的知识,而这个过程就需要大量的练习才可以熟练.

        下面进行对刚才的学生管理系统进行优化,整个优化过程最主要就是维护每个学生信息中的指针位置,然后通过指针位置就可以得到每个学生在文件中的位置,就可以对该学生的信息进行相应的操作。

代码实现:


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

//对于优化后的系统,需要用打开一个新的文本
//对于文件写入的格式不同,所以需要打开新的文本
const char *file_name = "new_student_data.txt";
//设置一个对于学生信息输出的标准
#define output_format  "%10s%4d%4d%7.2lf"


typedef struct Student {
    //添加一个变量,用来记录当前学生信息在文件中文件指针的位置
    long offset;
    char name[20];
    int age;
    int class;
    double height;
} Student;

#define MAX_N 10000
Student stu_arr[MAX_N + 5];
int stu_cnt;

//优化1
//在文件中读入数据进行初始化学生数组时,进行对文件指针位置的初始化
int read_from_file(Student *arr) {
    int i = 0;
    FILE *fp = fopen(file_name, "r");
    if (fp == NULL) return 0;
    while (1) {
        //通过ftell获取当前文件指针的位置
        long offset = ftell(fp);
        //这里while换成if判断,所以需要将!= 改为==
        if (fscanf(fp, "%s", arr[i].name) == EOF) break;
        fscanf(fp, "%d%d%lf", 
                &arr[i].age, 
                &arr[i].class, 
                &arr[i].height
        );
        //对该行学生信息的文件指针位置进行初始化
        //初始化值为该行学生信息的开头
        arr[i].offset = offset;
        //现在文件指针位置是指向换行符
        //那么需要处理换行符,用函数fgetc
        fgetc(fp);
        //fgetc的作用是读取文件中的一个字符,而读取位置就是文件指针的位置
        //那么读取后文件指针就会移动到下一个字符的位置
        //对应在我们这个文件中,就是下个学生信息的开头
        i++;
    }
    fclose(fp);
    return i;
}
//优化2
//维护添加新学生信息时,进行对新学生信息在文件中的指针位置
int output_to_file(Student *arr, int n) {
    FILE *fp = fopen(file_name, "a");
    //将文件指针位置设置到文件末尾
    fseek(fp, 0, SEEK_END);
    long offset = ftell(fp);
    for (int i = 0; i < n; i++) {
        //添加学生信息时,也要进行格式化控制每条信息的长度
        fprintf(fp, output_format"\n",
                arr[i].name,
                arr[i].age,
                arr[i].class,
                arr[i].height);
    }
    fclose(fp);
    return offset;
}


void restor_data_to_file(Student *arr, int n) {
    FILE *fp = fopen(file_name, "w");
    fclose(fp);
    output_to_file(arr, n);
    return ;
}

void modify_data_to_file(Student *arr) {
    FILE *fp = fopen(file_name, "r+");
    //arr->offset 等价于stu_arr[id].offset
    //arr->offset 转换 *(arr).offset
    //而arr是形参他的值是 stu_arr + id
    //那么 arr->offset 等价于 *(stu_arr + id).offset 而*(stu_arr + id) 等价于 stu_arr[id]
    //最后 arr->offset 等价于stu_arr[id].offset
    fseek(fp, arr->offset, SEEK_SET);
    //通过设置的标准输出格式,来进行设置学生信息输出的长度
    //就满足了两行数据相同长度
    fprintf(fp, output_format"\n",
            arr->name,
            arr->age,
            arr->class,
            arr->height
    );
    //打开文件记得关闭
    fclose(fp);
    return ;
}


enum NO_TYPE {
    LIST = 1,
    ADD,
    MODIFY,
    DELETE,
    QUIT
};


int usage() {
    int no;
    stu_cnt = read_from_file(stu_arr);
    do {
        printf("%d. list students\n", LIST);
        printf("%d. add a new stdents\n", ADD);
        printf("%d. list modify a students\n", MODIFY);
        printf("%d. delete a students\n", DELETE);
        printf("%d. quit system\n", QUIT);
        printf("mysql>");
        scanf("%d", &no);
        if (no < 1 || no > 5) printf("intput error\n");
    } while(no < 1 || no > 5);
    return no;
}

void list_students() {
    int len = 0;
    len = printf("%4s|%10s|%4s|%6s|%7s|\n", 
           "id",
           "name",
           "age",
           "class",
           "height") ;
    while (--len) printf("=");
    putchar(10);
    for (int i = 0; i < stu_cnt; i++) {
    len = printf("%4d|%10s|%4d|%6d|%7.2lf|\n", 
           i,
           stu_arr[i].name,
           stu_arr[i].age,
           stu_arr[i].class,
           stu_arr[i].height) ;
    }
    return ;
}

void add_to_student() {
    printf("add new item : (name, age, class, height)\n");
    printf("mysql > ");
    scanf("%s%d%d%lf",
          stu_arr[stu_cnt].name,
          &stu_arr[stu_cnt].age,
          &stu_arr[stu_cnt].class,
          &stu_arr[stu_cnt].height
    );
    stu_arr[stu_cnt].offset = output_to_file(stu_arr + stu_cnt, 1);
    stu_cnt++;
    printf("add a new student success\n");
    return ;
}

//优化4
void modify_a_student() {
    if (!stu_cnt) {
        printf("there haven't student\n");
        return ;
    }
    list_students();
    int id;
    do {
        printf("modify id : ");
        scanf("%d", &id);
    } while (id < 0 || id >= stu_cnt);
    printf("modify item : (name, age, class, height)\n");
    printf("mysql >");
    scanf("%s%d%d%lf",
          stu_arr[id].name,
          &stu_arr[id].age,
          &stu_arr[id].class,
          &stu_arr[id].height
    );
    //代码到这儿,修改学生信息只是在数组中进行操作了
    //而还没有在文件中进行操作
    //我们想要的效果就是进行在当前学生信息的位置进行单条修改
    //restor_data_to_file(stu_arr, stu_cnt);那么在这儿这个函数调用方法就行不通了
    modify_data_to_file(stu_arr + id);
    return ;
}

void delete_a_student() {
    if (stu_cnt == 0) {
        printf("there no have one students\n");
        return ;
    }
    list_students();
    int id; 
    do {
        printf("delete id : ");
        scanf("%d", &id);
    } while (id < 0 || id >= stu_cnt);
    char c;
    do {
        printf("confirm (y / n) :");
        scanf("%c", &c);
    } while (c != 'y' && c != 'n');
    if (c == 'n') return ;
    for (int i = id; i < stu_cnt - 1; i++) {
        //优化3
        //如果进行直接覆盖,那么每个学生信息中的指针位置也会被覆盖
        //所以需要进行记录下来,覆盖后的学生信息的指针位置需要修改为之前学生信息的指针位置
        long offset = stu_arr[i].offset;
        stu_arr[i] = stu_arr[i + 1];
        stu_arr[i].offset = offset;
    }
    stu_cnt--;
    restor_data_to_file(stu_arr, stu_cnt);
    return ;
}


int main() {
    while  (1) {
        int no = usage();
        switch (no) {
            case LIST:  {
                list_students();
            }; break; 
            case ADD:  {
                add_to_student();
            } break;
            case MODIFY:  {
                modify_a_student();
            } break;
            case DELETE: {
                delete_a_student();
            } break;
            case QUIT: printf("Thanks used\n");exit(0);
            default : printf("error\n"); exit(1);
        }
    }
    return 0;
}

 优化2:实现数据的二进制存储

        数据在计算机底层都是由二进制进行存储的。

        文本存储:是将字符形式进行存储在文本中,可读性很强

        二进制存储:以二进制形式存储在文本中,对于计算机而言是看得懂的,对于我们没有可读性。

        使用二进制方式进行存储文件的优势:提高了读取和写入的效率。

        看下面的图就可以理解,为什么可以提高读写效率。

        

        那么在实现二进制存储方式会用到两个函数:

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

//第一步,由于操作的是二进制文件,所以要把文件后缀名改为dat
const char *file_name = "new_student_data.dat";
#define output_format  "%10s%4d%4d%7.2lf"


typedef struct Student {
    long offset;
    char name[20];
    int age;
    int class;
    double height;
} Student;

#define MAX_N 10000
Student stu_arr[MAX_N + 5];
int stu_cnt;

//对文件进行了操作就需要,修改为二进制形式的操作
int read_from_file(Student *arr) {
    int i = 0;
    FILE *fp = fopen(file_name, "rd");//1.修改打开模式
    if (fp == NULL) return 0;
    while (1) {
        long offset = ftell(fp);
        //读取文件,换为fread读取
        //fread的返回值是读取了多少个字节
        if (fread(arr + i, sizeof(Student), 1, fp) == 0) break;
        arr[i].offset = offset;
        //fgetc(fp); 在二进制文件中没有换行符了,所以就没有必要去吞掉换行符了
        i++;
    }
    fclose(fp);
    return i;
}
int output_to_file(Student *arr, int n) {
    FILE *fp = fopen(file_name, "ab");//修改打开模式
    fseek(fp, 0, SEEK_END);
    long offset = ftell(fp);
    //修改写入模式
    fwrite(arr, sizeof(Student), n, fp);
    fclose(fp);
    return offset;
}


void restor_data_to_file(Student *arr, int n) {
    FILE *fp = fopen(file_name, "w");
    fclose(fp);
    output_to_file(arr, n);
    return ;
}

void modify_data_to_file(Student *arr) {
    FILE *fp = fopen(file_name, "rb+");//修改打开模式
    fseek(fp, arr->offset, SEEK_SET);
    //修改写入模式
    fwrite(arr, sizeof(Student), 1, fp);
    fclose(fp);
    return ;
}


enum NO_TYPE {
    LIST = 1,
    ADD,
    MODIFY,
    DELETE,
    QUIT
};


int usage() {
    int no;
    stu_cnt = read_from_file(stu_arr);
    do {
        printf("%d. list students\n", LIST);
        printf("%d. add a new stdents\n", ADD);
        printf("%d. list modify a students\n", MODIFY);
        printf("%d. delete a students\n", DELETE);
        printf("%d. quit system\n", QUIT);
        printf("mysql>");
        scanf("%d", &no);
        if (no < 1 || no > 5) printf("intput error\n");
    } while(no < 1 || no > 5);
    return no;
}

void list_students() {
    int len = 0;
    len = printf("%4s|%10s|%4s|%6s|%7s|\n", 
           "id",
           "name",
           "age",
           "class",
           "height") ;
    while (--len) printf("=");
    putchar(10);
    for (int i = 0; i < stu_cnt; i++) {
    len = printf("%4d|%10s|%4d|%6d|%7.2lf|\n", 
           i,
           stu_arr[i].name,
           stu_arr[i].age,
           stu_arr[i].class,
           stu_arr[i].height) ;
    }
    return ;
}

void add_to_student() {
    printf("add new item : (name, age, class, height)\n");
    printf("mysql > ");
    scanf("%s%d%d%lf",
          stu_arr[stu_cnt].name,
          &stu_arr[stu_cnt].age,
          &stu_arr[stu_cnt].class,
          &stu_arr[stu_cnt].height
    );
    stu_arr[stu_cnt].offset = output_to_file(stu_arr + stu_cnt, 1);
    stu_cnt++;
    printf("add a new student success\n");
    return ;
}

void modify_a_student() {
    if (!stu_cnt) {
        printf("there haven't student\n");
        return ;
    }
    list_students();
    int id;
    do {
        printf("modify id : ");
        scanf("%d", &id);
    } while (id < 0 || id >= stu_cnt);
    printf("modify item : (name, age, class, height)\n");
    printf("mysql >");
    scanf("%s%d%d%lf",
          stu_arr[id].name,
          &stu_arr[id].age,
          &stu_arr[id].class,
          &stu_arr[id].height
    );
    modify_data_to_file(stu_arr + id);
    return ;
}

void delete_a_student() {
    if (stu_cnt == 0) {
        printf("there no have one students\n");
        return ;
    }
    list_students();
    int id; 
    do {
        printf("delete id : ");
        scanf("%d", &id);
    } while (id < 0 || id >= stu_cnt);
    char c;
    do {
        printf("confirm (y / n) :");
        scanf("%c", &c);
    } while (c != 'y' && c != 'n');
    if (c == 'n') return ;
    for (int i = id; i < stu_cnt - 1; i++) {
        long offset = stu_arr[i].offset;
        stu_arr[i] = stu_arr[i + 1];
        stu_arr[i].offset = offset;
    }
    stu_cnt--;
    restor_data_to_file(stu_arr, stu_cnt);
    return ;
}


int main() {
    while  (1) {
        int no = usage();
        switch (no) {
            case LIST:  {
                list_students();
            }; break; 
            case ADD:  {
                add_to_student();
            } break;
            case MODIFY:  {
                modify_a_student();
            } break;
            case DELETE: {
                delete_a_student();
            } break;
            case QUIT: printf("Thanks used\n");exit(0);
            default : printf("error\n"); exit(1);
        }
    }
    return 0;
}

本章小结:

        本章内容主要是对文件的操作,对文件的增删改查的,文件的打开模式,还有对文件指针位置的控制,以及用二进制形式读写数据。

        而对于学生管理的小系统阶段,最重要的是步骤,先理好每个功能对应的作用,然后通过分析每个功能会怎么样去实现,在通过分析进行对程序设计,最后才是写代码。而对于每一份程序而言,步骤都应该是这样。而不是看完需求就直接开始写代码,这样的代码不仅可读性低,而且在后续的Debug和修改阶段也会很麻烦。

        最后对于该小项目,先实现,在优化。而不是边实现边优化,我们这样的小白现在是没有那些实力的,只有通过不断地训练和实战才可以有那样的水平。

        加油,看到这里你已经超过百分之95的人了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

初猿°

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

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

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

打赏作者

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

抵扣说明:

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

余额充值