首页
社区
课程
招聘
[分享]C语言基础九-文件操作
2021-6-7 00:50 3885

[分享]C语言基础九-文件操作

2021-6-7 00:50
3885

文件概述

文件是数据源的一种,最主要的作用是保存数据。在操作系统中,为了统一对各种硬件的操作,简化接口,不同的硬件设备也都被看成一个文件。例如显示器为标准输出文件,printf就是向这个文件输出数据;键盘为标准输入文件,scanf就是从这个文件读取数据。
操作文件的正确流程为:打开文件-->读写文件-->关闭文件。文件在进行读写操作之前要先打开,使用完毕之后要关闭。

文件流

我们把数据在数据源和内存之间传递的过程叫做数据流,相应的,数据从数据源到内存的过程叫做输入流,从内存到数据源的过程叫做输出流。数据在文件和内存之间传递的过程叫做文件流。

文件的打开和关闭

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*
函数功能:打开文件
参数一:文件名
参数二:文件打开方式
返回值:FILE结构体,保存了文件名、文件状态、当前读写位置等。
*/
FILE *fopen(char* filename,char* mode);
 
/*
函数功能:关闭文件
参数一:文件指针
返回值:正常关闭文件返回零,错误返回非零值。
*/
int fclose(FILE* fp)

文件打开方式

不同的操作需要不同的文件权限。例如,只想读取文件中的数据的话,只读权限就够了;既想读取又想写入数据的话,读写权限就是必须的。另外,文件也有不同的类型,按照数据的存储方式可以分为二进制文件和文本文件,它们的操作细节是不同的。
文件打开方式
整体来说,文件打开方式由 r、w、a、t、b、+ 六个字符拼成,各字符的含义如下:

  • r(read):读
  • w(write):写
  • a(append):追加
  • t(text):文本文件
  • b(binary):二进制文件
  • +:读和写

以字符形式读写文件

以字符形式读写文件时,每次可以从文件中读取一个字符,或者向文件中写入一个字符。主要使用两个函数,分别是fgetc()和fputc()。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include<stdio.h>
int main()
{
    FILE *fp;
    char ch;
    //如果文件不存在,给出提示并退出
    if((fp=fopen("D:\\demo.txt","rt")) == NULL)
    {
        puts("Fail to open file!");
        exit(0);
    }
    //每次读取一个字节,直到读取完毕
    while((ch=fgetc(fp)) != EOF)
    {
        putchar(ch);
    }
    //输出换行符
    putchar('\n');
 
    if(ferror(fp))
    {
        puts("读取出错");
    }else
    {
        puts("读取成功");
    }
    fclose(fp);
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include<stdio.h>
int main()
{
    FILE *fp;
    char ch;
    //判断文件是否成功打开
    if((fp=fopen("D:\\demo.txt","wt+")) == NULL)
    {
        puts("Fail to open file!");
        exit(0);
    }
    printf("Input a string:\n");
    //每次从键盘读取一个字符并写入文件
    while ((ch=getchar()) != '\n')
    {
        fputc(ch,fp);
    }
    fclose(fp);
    return 0;
}

EOF是end of file的缩写,表示文件末尾,是在stdio.h中定义的宏,它的值是一个负数,往往是-1。EOF不绝对是-1,也可以是其他负数,这要看编译器的实现。EOF本来表示文件末尾,意味着读取结束,但是很多函数在读取出错时也返回EOF,我们可以借助stdio.h中的两个函数来判断,分别是feof()和ferror()。

1
2
3
4
5
/*判断文件操作是否出错,出错时返回非零值,否则返回零值。*/
int ferror (FILE* fp);
/*判断文件内部指针是否指向了文件末尾,
当指向文件末尾时返回非零值,否则返回零值*/
int feof (FILE* fp);

以字符串形式读写文件

fgetc()和fputc()函数每次只能读写一个字符,速度较慢,实际开发中往往是每次读写一个字符串或者一个数据块,这样能明显提高效率。fgets()函数用来从指定的文件中读取一个字符串,并保存到字符数组中;fputs()函数用来向指定的文件写入一个字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
#include <stdlib.h>
#define N 100
int main()
{
    FILE *fp;
    char str[N+1];
    if((fp=fopen("d:\\demo.txt","rt")) == NULL)
    {
        puts("Fail to open file!");
        exit(0);
    }
    while(fgets(str, N, fp) != NULL)
    {
        printf("%s", str);
    }
    fclose(fp);
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include<stdio.h>
int main()
{
    FILE *fp;
    char str[102] = {0}, strTemp[100];
    if((fp=fopen("D:\\demo.txt", "at+")) == NULL)
    {
        puts("Fail to open file!");
        exit(0);
    }
    printf("Input a string:");
    gets(strTemp);
    strcat(str, "\n");
    strcat(str, strTemp);
    fputs(str, fp);
    fclose(fp);
    return 0;
}

需要注意的是,读取到的字符串会在末尾自动添加'\0',n个字符也包括'\0'。也就是说,实际只读取到了n-1个字符,如果希望读取100个字符,n的值应该为101。另外,在读取到n-1个字符之前如果出现了换行,或者读到了文件末尾,则读取结束。这就意味着,不管n的值多大,fgets()最多只能读取一行数据,不能跨行。在C语言中,没有按行读取文件的函数,我们可以借助fgets(),将n的值设置的足够大,每次就可以读取到一行数据。

以数据块形式读写文件

如果希望读取多行内容,需要使用fread()函数,相应地写入函数为fwrite()。对于Windows系统,使用fread()和fwrite()时应该以二进制的形式打开文件。

1
2
3
4
5
//从指定文件中读取块数据
size_t fread(void* ptr,size_t size,size_t count,FILE *fp);
//向文件中写入块数据
size_t fwrite( void* ptr,size_t size,size_t count,FILE *fp);
//所谓块数据,也就是若干个字节的数据,可以是一个字符,可以是一个字符串,可以是多行数据,并没有什么限制。

参数说明

  • ptr:为内存区块的指针,它可以是数组、变量、结构体等。fread()中的ptr用来存放读取到的数据,fwrite()中的ptr用来存放要写入的数据。
  • size:表示每个数据块的字节数。
  • count:表示要读写的数据块的块数。
  • fp:表示文件指针。
  • size_t是在stdio.h 和stdlib.h头文件中使用typedef 定义的数据类型,表示无符号整数,也即非负数,常用来表示数量。

返回值说明

返回成功读写的块数count,如果返回值小于count:

  • 对于fwrite()来说,肯定发生了写入错误,可以用ferror()函数检测。
  • 对于fread()来说,可能读到了文件末尾,可能发生了错误,可以用ferror()或feof()检测。

示例:从键盘输入两个学生数据,写入一个文件中,再读出这两个学生的数据显示在屏幕上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include<stdio.h>
#define N 2
struct stu
{
    char name[10]; //姓名
    int num;  //学号
    int age;  //年龄
    float score;  //成绩
}boya[N], boyb[N], *pa, *pb;
int main()
{
    FILE *fp;
    int i;
    pa = boya;
    pb = boyb;
    if((fp=fopen("d:\\demo.txt", "wb+")) == NULL)
    {
        puts("Fail to open file!");
        exit(0);
    }
    //从键盘输入数据
    printf("Input data:\n");
    for(i=0; i<N; i++,pa++)
    {
        scanf("%s %d %d %f",pa->name,&pa->num,&pa->age,&pa->score);
    }
    //将数组 boya 的数据写入文件
    fwrite(boya, sizeof(struct stu), N, fp);
    //将文件指针重置到文件开头
    rewind(fp);
    //从文件读取数据并保存到数据 boyb
    fread(boyb, sizeof(struct stu), N, fp);
    //输出数组 boyb 中的数据
    for(i=0; i<N; i++,pb++)
    {
        printf("%s %d %d %f\n",pb->name,pb->num,pb->age,pb->score);
    }
    fclose(fp);
    return 0;
}

格式化读写文件

fscanf()和fprintf()函数与前面使用的scanf()和printf()功能相似,都是格式化读写函数,两者的区别在于fscanf()和fprintf()的读写对象不是键盘和显示器,而是磁盘文件。
示例:用 fscanf 和 fprintf 函数来完成对学生信息的读写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include<stdio.h>
#define N 2
struct stu
{
    char name[10];
    int num;
    int age;
    float score;
} boya[N], boyb[N], *pa, *pb;
int main()
{
    FILE *fp;
    int i;
    pa=boya;
    pb=boyb;
    if( (fp=fopen("D:\\demo.txt","wt+")) == NULL )
    {
        puts("Fail to open file!");
        exit(0);
    }
    //从键盘读入数据,保存到boya
    printf("Input data:\n");
    for(i=0; i<N; i++,pa++)
    {
        scanf("%s %d %d %f", pa->name, &pa->num, &pa->age, &pa->score);  
    }
    pa = boya;
    //将boya中的数据写入到文件
    for(i=0; i<N; i++,pa++)
    {
        fprintf(fp,"%s %d %d %f\n", pa->name, pa->num, pa->age, pa->score);  
    }
    //重置文件指针
    rewind(fp);
    //从文件中读取数据,保存到boyb
    for(i=0; i<N; i++,pb++)
    {
        fscanf(fp, "%s %d %d %f\n", pb->name, &pb->num, &pb->age, &pb->score);
    }
    pb=boyb;
    //将boyb中的数据输出到显示器
    for(i=0; i<N; i++,pb++)
    {
        printf("%s  %d  %d  %f\n", pb->name, pb->num, pb->age, pb->score);
    }
    fclose(fp);
    return 0;
}

随机读写文件

实际开发中经常需要读写文件的中间部分,要解决这个问题,就得先移动文件内部的位置指针,再进行读写。这种读写方式称为随机读写,实现随机读写的关键是要按要求移动位置指针,这称为文件的定位。

1
2
3
4
//将位置指针移动到文件开头
void rewind ( FILE *fp );
//将位置指针移动到任意位置
int fseek ( FILE *fp, long offset, int origin );

参数说明:

  • fp 为文件指针,也就是被移动的文件。
  • offset为偏移量,也就是要移动的字节数。之所以为long类型,是希望移动的范围更大,能处理的文件更大。offset为正时,向后移动;offset为负时,向前移动。
  • origin为起始位置,也就是从何处开始计算偏移量。
  • C语言规定的起始位置有三种,分别为文件开头、当前位置和文件末尾,每个位置都用对应的常量来表示:
    起始位置

示例:从键盘输入三组学生信息,保存到文件中,然后读取第二个学生的信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include<stdio.h>
#define N 3
struct stu
{
    char name[10]; //姓名
    int num;  //学号
    int age;  //年龄
    float score;  //成绩
}boys[N], boy, *pboys;
int main()
{
    FILE *fp;
    int i;
    pboys = boys;
    if( (fp=fopen("d:\\demo.txt", "wb+")) == NULL )
    {
        printf("Cannot open file, press any key to exit!\n");
        getch();
        exit(1);
    }
    printf("Input data:\n");
    for(i=0; i<N; i++,pboys++)
    {
        scanf("%s %d %d %f", pboys->name, &pboys->num, &pboys->age, &pboys->score);
    }
    fwrite(boys, sizeof(struct stu), N, fp);  //写入三条学生信息
    fseek(fp, sizeof(struct stu), SEEK_SET);  //移动位置指针
    fread(&boy, sizeof(struct stu), 1, fp);  //读取一条学生信息
    printf("%s  %d  %d %f\n", boy.name, boy.num, boy.age, boy.score);
    fclose(fp);
    return 0;
}

github:https://github.com/0I00II000I00I0I0

bilibili:https://space.bilibili.com/284022506


[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

收藏
点赞2
打赏
分享
最新回复 (2)
雪    币: 311
活跃值: (336)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
SmallDolphin 2021-6-7 12:25
2
0
很详细的文件读写教程,学习了!
雪    币: 7904
活跃值: (2685)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
小流子 2021-6-7 14:21
3
0
解读的很深刻
游客
登录 | 注册 方可回帖
返回