C语言----文件操作

 



1.为什么使用文件?

如果没有⽂件,我们写的程序的数据是存储在电脑的内存中,如果程序退出,内存回收,数据就丢失了,等再次运⾏程序,是看不到上次程序的数据的,如果要将数据进⾏持久化的保存,我们可以使⽤⽂件。

int main()
{
    int a = 0;
    printf("a=%d\n", a);
    scanf("%d", &a);
    printf("a=%d\n", a);
    return 0;
}
//a是内存上的一块区域
//我们只要退出了这个代码,之前对a的输入,之前的数据就都没了,退出代码就清除

2.什么是文件?

磁盘(硬盘)上的⽂件是⽂件。

但是在程序设计中,我们⼀般谈的⽂件有两种:程序⽂件、数据⽂件(从⽂件功能的⻆度来分类

的)。

程序文件

程序⽂件包括源程序⽂件(后缀为.c),⽬标⽂件(windows环境后缀为.obj),可执⾏程序(windows环境后缀为.exe)。

数据文件

⽂件的内容不⼀定是程序,⽽是程序运⾏时读写的数据,⽐如程序运⾏需要从中读取数据的⽂件,或者输出内容的⽂件。

本章讨论的是数据⽂件。

在以前各章所处理数据的输⼊输出都是以终端为对象的,即从终端的键盘输⼊数据,运⾏结果显⽰到显⽰器上。

其实有时候我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使⽤,这⾥处理的就是磁盘上⽂件。

文件名

⼀个⽂件要有⼀个唯⼀的⽂件标识,以便⽤⼾识别和引⽤。

⽂件名包含3部分:⽂件路径+⽂件名主⼲+⽂件后缀

例如: c:\code\test.txt

为了⽅便起⻅,⽂件标识常被称为⽂件名。

3.二进制文件和文本文件

根据数据的组织形式,数据⽂件被称为⽂本⽂件或者⼆进制⽂件。

数据在内存中以⼆进制的形式存储,如果不加转换的输出到外存的⽂件中,就是⼆进制⽂件。

如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的⽂件就是⽂本⽂件。

⼀个数据在⽂件中是怎么存储的呢?

字符⼀律以ASCII形式存储,数值型数据既可以⽤ASCII形式存储,也可以使⽤⼆进制形式存储。

如有整数10000,如果以ASCII码的形式输出到磁盘,则磁盘中占⽤5个字节(每个字符⼀个字节),⽽⼆进制形式输出,则在磁盘上只占4个字节。

下面的10000里面的每个数字就是以ASCII进行存储的

0的ASCII大小是48

1的ASCII大小是49

那么10000在内存中的存储形式就是下面的样子了

将10000转换为二进制写到文件里面去 

#include <stdio.h>
int main()
{
    int a = 10000;
    FILE * pf = fopen("test.txt", "wb");//二进制写的方式打开文件
    fwrite(&a, 4, 1, pf);//⼆进制的形式写到⽂件中
    //a的地址,4个字节,写1次,写到关联的问题里面去
    fclose(pf);
    pf = NULL;
    return 0;
}
//打开文件,写文件,再关闭文件,最后再将pf置为空指针

//这个代码就是将10000转换为二进制写到文件里面去

 

 

4.文件的打开和关闭

铺垫

我们程序的数据需要输出到各种外部设备,也需要从外部设备获取数据,不同的外部设备的输⼊输出

操作各不相同,为了⽅便程序员对各种设备进⾏⽅便的操作,我们抽象出了流的概念,我们可以把流

想象成流淌着字符的河。

C程序针对⽂件、画⾯、键盘等的数据输⼊输出操作都是通过流操作的。

⼀般情况下,我们要想向流⾥写数据,或者从流中读取数据,都是要打开流,然后操作。

打开流,读\写,关闭流

标准流

那为什么我们从键盘输⼊数据,向屏幕上输出数据,并没有打开流呢?

那是因为C语⾔程序在启动的时候,默认打开了3个流:

• stdin - 标准输⼊流,在⼤多数的环境中从键盘输⼊,scanf函数就是从标准输⼊流中读取数据。

• stdout - 标准输出流,⼤多数的环境中输出⾄显⽰器界⾯,printf函数就是将信息输出到标准输出

流中。

• stderr - 标准错误流,⼤多数环境中输出到显⽰器界⾯。

这是默认打开了这三个流,我们使⽤scanf、printf等函数就可以直接进⾏输⼊输出操作的。

stdin、stdout、stderr 三个流的类型是: FILE * ,通常称为⽂件指针。

C语⾔中,就是通过 FILE* 的⽂件指针来维护流的各种操作的。

文件指针

缓冲⽂件系统中,关键的概念是“⽂件类型指针”,简称“⽂件指针”。

每个被使⽤的⽂件都在内存中开辟了⼀个相应的⽂件信息区,⽤来存放⽂件的相关信息(如⽂件的名

字,⽂件状态及⽂件当前的位置等)。这些信息是保存在⼀个结构体变量中的。该结构体类型是由系

统声明的,取名 FILE.

只要打开文件我们就会创建一个文件信息区,然后这个文件信息区会和这个文件产生关联

例如,VS2013 编译环境提供的 stdio.h 头⽂件中有以下的⽂件类型申明:

struct _iobuf {
 char *_ptr;
 int _cnt;
 char *_base;
 int _flag;
 int _file;
 int _charbuf;
 int _bufsiz;
 char *_tmpfname;
};
typedef struct _iobuf FILE;

不同的C编译器的FILE类型包含的内容不完全相同,但是⼤同⼩异。

每当打开⼀个⽂件的时候,系统会根据⽂件的情况⾃动创建⼀个FILE结构的变量,并填充其中的信

息,使⽤者不必关⼼细节。

⼀般都是通过⼀个FILE的指针来维护这个FILE结构的变量,这样使⽤起来更加⽅便

我们通过FILE*的指针来找到文件信息区

FILE* pf;//⽂件指针变量

定义pf是⼀个指向FILE类型数据的指针变量。可以使pf指向某个⽂件的⽂件信息区(是⼀个结构体变

量)。通过该⽂件信息区中的信息就能够访问该⽂件。也就是说,通过⽂件指针变量能够间接找到与

它关联的⽂件。

5.文件的顺序读写

重点:文件的打开和关闭

文件在使用之前应该打开文件,在使用结束后应该关闭文件

所以文件的操作就是:

1.打开文件----打开流

2.读写文件---读/写流

3.关闭文件---关闭流

在编写程序的时候,在打开⽂件的同时,都会返回⼀个FILE*的指针变量指向该⽂件,也相当于建⽴了指针和⽂件的关系。

ANSI C 规定使⽤ fopen 函数来打开⽂件, fclose 来关闭⽂件。

fopen是用来打开文件的

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

第一个参数是文件名 ,第二个参数是文件打开方式

|文件使用方式|含义|如果指定文件不存在| |-|-|-| |“r”(只读)|为了输⼊数据,打开⼀个已经存在的⽂本⽂件|出错| |“w”(只写)|为了输出数据,打开⼀个⽂本⽂件|建⽴⼀个新的⽂件| |”a”(追加)|向⽂本⽂件尾添加数据|建⽴⼀个新的⽂件| |“rb”(只读)|为了输⼊数据,打开⼀个⼆进制⽂件|出错| |”wb”(只写)|为了输出数据,打开⼀个⼆进制⽂件|建⽴⼀个新的⽂件| |“ab”(追加)|向⼀个⼆进制⽂件尾添加数据|建⽴⼀个新的⽂件| |“r+”(读写)|为了读和写,打开⼀个⽂本⽂件|出错| |“w+”(读写)|为了读和写,建议⼀个新的⽂件|建⽴⼀个新的⽂件| |”a+”(读写)|打开⼀个⽂件,在⽂件尾进⾏读写|建⽴⼀个新的⽂件| |“rb+”(读写)|为了读和写打开⼀个⼆进制⽂件|出错| |“wb+”(读 写)|为了读和写,新建⼀个新的⼆进制⽂件|建⽴⼀个新的⽂件| |“ab+”(读 写)|打开⼀个⼆进制⽂件,在⽂件尾进⾏读和写|建⽴⼀个新的⽂件|

//打开文件

/*
fopen是用来打开文件的

FILE*fopen(const char * filename ,const char *mode)
                  文件名            文件打开方式

*/

//int main()
//{
//    //1.打开文件
//    FILE* pf=fopen("test.txt", "w");//打开文件是"w",这个函数的返回值是FILE*
//    //那么我们就用一个FILE*的指针pf进行接收
//    //打开文件成功的话,那么返回的就是有效的指针,文件的指针
//    //如果打开失败的话,则返回一个空指针,所以我们要进行判断
//    if (pf == NULL)
//    {
//        perror("fopen");
//        return 1;//直接返回,没必要往下面走了
//    }
//    //...打开成功了
//    
//    return 0;
//}

/*
当我们打开文件储存的路径我们会发现
会多出一个test.txt文件,之前是没有的,我们现在是想打开它的,但是因为没有这个文件,
所以系统帮我们创建了一个文件

*/
/*
我们现在已经创建了这个文件,我们在里面编写数据:abcdef
保存后退出
我们再次运行这个代码打开文件,我们会发现之前的文件变成0kb了,里面的数据没有了

原因就是我们以w的形式打开的话,如果文件存在,他会将文件内容清空掉
如果文件不存在的话就会新建一个文件



*/


//int main()
//{
//    //1.打开文件
//    FILE* pf = fopen("test.txt", "r");
//    
//    if (pf == NULL)
//    {
//        perror("fopen");
//        return 1;
//    }
//    
//
//    return 0;
//}
/*
这里我们用r形式来打开文件
我们将之前的文件删除
那么我们再次运行代码

fopen: No such file or directory
就会报错,因为没有这个文件
*/



//关闭文件
//基本格式:int fclose(FILE*stream)
int main()
{
    //1.打开文件
    FILE* pf = fopen("test.txt", "w");

    if (pf == NULL)
    {
        perror("fopen");
        return 1;
    }

    //写文件

    //关闭文件
    fclose(pf);
    //关闭这个文件指针后,pf已经变成了野指针了
    pf = NULL;
    return 0;
}

切记:pf被fclose关闭之后我们要将pf赋值为NULL

因为关闭之后pf就变成野指针了

如果这个文件是桌面上的文件

我们需要知道这个文件的路径

FILE* pf=fopen("C:\\Users\\27890\\Desktop\\test.txt", "w");

我们要在文件名之前加上文件的路径就能打开非代码文件夹里面的文件了

但是我们会担心害怕转移字符的生成了,所以我们可以加上两个\

那么现在我们打开的就是这个文件

//打开非代码文件夹里面的文件
//int main()
//{
//    //1.打开文件
//    FILE* pf = fopen("C:\\Users\\27890\\Desktop\\test.txt", "r");
//    
//    if (pf == NULL)
//    {
//        perror("fopen");
//        return 1;
//    }
//    
//    //写文件
//    
//    //关闭文件
//    fclose(pf);
//    //关闭这个文件指针后,pf已经变成了野指针了
//    pf = NULL;
//    return 0;
//}


int main()
{
    //1.打开文件
    //假设文件在这个代码文件位置的上两级

    //.表示当前路径
    //..表示上一级路径
    //下面的就是当前路径的上一级路径里面的文件
    FILE* pf = fopen(".\\..\\test.txt", "r");
    //   .\\..\\..\\当前路径的上一级路径的上一级路径里面的文件
    //那么这个就是相对路径,相对与当前位置的路径
    if (pf == NULL)
    {
        perror("fopen");
        return 1;
    }

    //写文件

    //关闭文件
    fclose(pf);
    //关闭这个文件指针后,pf已经变成了野指针了
    pf = NULL;
    return 0;
}

打开相对路径的文件的时候直接在文件名字之前加上

.\..\..\

在当前文件的上一级的文件的上一级文件里面,这样就能打开相对文件夹里面的文件了

如果是其他位置的文件,我们仅仅需要再文件名字前面加上地址就行了

另外:为了防止转义字符出现,我么就在每个右斜杠旁边再加一条右斜杠,这样防止转义字符的产生

我们以读的形式打开就只能读

以写的形式打开就只能写

不可做多余的事情


顺序读写介绍

除了最后两行的,其他的都是读和写文本信息

第一组:fputc 和fgetc

//fputc--写字符
int main()
{
    FILE* pf = fopen("test.txt", "r");
    if (pf == NULL)
    {
        perror("fopen");
        return 1;
    }

    //写文件
    /*fputc('a', pf);
    fputc('b', pf);
    fputc('c', pf);*/

    //循环写入
    for (int i = 'a'; i <= 'z'; i++)
    {
        fputc(i, pf);
    }

    //关闭文件
    fclose(pf);
    pf = NULL;
    return 0;
}
/*
* fputc--写字符
int fputs(int *character,FILE*stream);
第一个参数是要写的字符

*/
/*
fgetc---读取一个字符
int fgets(FILE*stream)
只有一个参数就是那个文件的流

读取失败就会返回EOF,
读取正常的话会返回对应字符的ASCII码值

EOF是enf of file
文件的结束标志
*/

int main()
{
    FILE* pf = fopen("test.txt", "r");
    if (pf == NULL)
    {
        perror("fopen");
        return 1;
    }
    //读文件
    //现在文件里面存放的是之前写入的26个字母
    int ch = fgetc(pf);
    printf("%c\n", ch);
    ch = fgetc(pf);
    printf("%c\n", ch);
    ch = fgetc(pf);
    printf("%c\n", ch);
//    //输出的就是a b c
//    //我们在一开始读文件的时候,光标指向的是a,读完a之后,光标就指向了a的后一位的字母
//    //文件的光标一直随着我们在读在变化
//
//    //关闭文件
    fclose(pf);
    pf = NULL;
    return 0;
}

fputc函数返回的是对应字符的ASCII码值,两个参数,第一个参数是要写的字符

第二个参数是文件对应的流,文件指针

fgetc读取字符,参数是对应的文件的指针

读取失败就会返回EOF,

读取正常的话会返回对应字符的ASCII码值

int main()
{
    FILE* pf = fopen("test.txt", "r");
    if (pf == NULL)
    {
        perror("fopen");
        return 1;
    }
    int ch=0;
    while ((ch = fgetc(pf)) != EOF)//只要返回值不是EOF我们一直进行读取
    {
        printf("%c ", ch);
    }
    //那么这个输出的就是26个字母
    return 0;
}
int main()
{
    int ch=fgetc(stdin);//从键盘(标准输入流)上读取
    fputc(ch, stdout);//将字符输出(写)到屏幕(标准输出流)
    return 0;
}

从键盘上输入,在屏幕上输出

第二组:fputs和fgets

fputs:

int fputs(const charstr,FILEstream)

第一个参数是一个字符指针,指向了一个字符串,第二个是一个文件指针

返回类型是int

int main()
{
    //1.打开文件
    FILE* pf = fopen("hu.txt", "w");
    if (pf == NULL)//判断打开是否成功
    {
        perror("fopen");
        return 1;
    }
    //2.写文件
    fputs("I am a student", pf);//传过去的是字符串首元素的地址
    int ret=fputs("are you ok?", pf);
    //这两个输入的字符串在一行上面
    printf("%d", ret);//返回值是0,说明成功了,不是负数
    //3.关闭文件
    fclose(pf);
    pf = NULL;
    return 0;
}

//如果fputs输入成功的话,返回的就是一个非负整数

//失败的话,函数会返回EOF

如果fputs输入成功的话,返回的就是一个非负整数

失败的话,函数会返回EOF

fgets:

charfgets(char str,int nmu,FILE*stream)

第一个参数就是一个指针,指向复制到读取字符串的字符数组的指针

第二个参数num 是这个字符串能拷贝多少个字符

//fgets--读文件
int main()
{
    //1.打开文件
    FILE* pf = fopen("hu.txt", "r");
    if (pf == NULL)//判断打开是否成功
    {
        perror("fopen");
        return 1;
    }
    //2.读文件
    char arr[20] = { 0 };//将要读的文件放到这个数组内,相当于拷贝到这个数组内
    fgets(arr,20,pf);
    printf("%s", arr);
    //将读到的数据放到arr里面
//但是这里的arr并不是合适的,I am a studentare y我们还有些数据并没有读到
 //读取到的字符末尾还有一个'\0',总共下来就是20个字符了,真实读到的只有19个   

 //3.关闭文件
    fclose(pf);
    pf = NULL;
    return 0;
}
//从流中读取字符串,将字符串赋值拷贝在字符串数组中,最多读num-1个字符

//如果遇到换行的话,我们会直接停下来的,那么我们会读取\n。\n后面还是要读取\0的

fgets在使用的时候,我们要先创创建一个字符串数组,这个数组会存储我们读到的数据的

num就是我们读取的数据个数,但是因为读取到的还有一个\0,所以我们实际读到的仅仅只有num-1个字符

//在键盘上读,输出在屏幕上
int main()
{
    char arr[20] = { 0 };//存储我们读到的数据
    fgets(arr, 20, stdin);//stdin是标准输入流
    fputs(arr, stdout);//stdout是标准输出流
    return 0;
}

fgets如果读取成功的话,那么会返回str--目标空间的起始地址,接不接收无所谓的

但是读取失败的话会返回一个空指针的

第三组:fscanf和fprintf

fprintf:

int fprintf(FILE*stream,const char *format,….)

第三个参数就是可变参数列表

而printf的参数没有第一个,因为printf默认操作的就是stdout

fprintf可以适用于所有的操作流,可以适用于文件流,也可以适用于标准输出流

fprintf将数据写到文件内

struct S
{
    char name[20] ;
    int age ;
    float score ;
};
int main()
{

    struct S s = { "lisi",18,88.5f };
    //1.打开文件
    FILE* pf = fopen("hu.txt", "w");
    if (pf == NULL)
    {
        perror("fopen");
        return 1;
    }
    //2.写文件
    /*fprintf()*/
    fprintf(pf,"%s %d %f", s.name,s.age,s.score);//在前面加上文件指针,将后面所指的数据写到文件内
    //printf("%s %d %f", name, age, score);
    // 如果写入是结构体的数据也是可以的
    // 
    // 将s里面的数据写到文件里面
    //3.关闭文件
    fclose(pf);
    pf = NULL;
    return 0;
}

fscanf将文件读出来

fscanf:

int fscanf(FILE*stream,const char *format,….)

scanf和fscanf的差异也仅仅只是缺失文件指针

struct S
{
    char name[20];
    int age;
    float score;
};

int main()
{
    struct S s = { 0 };

    //1.打开文件
    FILE* pf = fopen("hu.txt", "r");
    if (pf == NULL)
    {
        perror("fopen");
        return 1;
    }
    //2.读文件
    //scanf("%s %d %f",&name,&age,&score)
    //从文件中读取信息,将读取的信息存放在s的各个成员中
    fscanf(pf, "%s %d %f", s.name, &(s.age), &(s.score));//数组名就是地址,不用加取地址符号
    //打印在屏幕上
    //printf("%s %d %f", s.name, s.age, s.score);
    fprintf(stdout,"%s %d %f", s.name, s.age, s.score);//默认输出流
    //3.关闭文件
    fclose(pf);
    pf = NULL;
    return 0;
}

fscanf将文件内的数据拷贝存在结构体中

对比一组函数,介绍sscanf和sprintf的用法


scanf/fscanf/sscanf

printf/fprintf/sprintf

scanf/printf 针对标准输入流(stdin)/标准输出流 (stdout) /格式化输入/输出函数

fscanf/fprintf 针对所有输入流(stdin)/输出流(stdout) /格式化输入/输出函数

第一种只能在键盘上输入和输出

第二种可以在文件和键盘上输入和输出

那么sprintf和sscanf有什么作用呢?

sprintf

int sprintf(char* str,const char* format,…)

sprintf作用就是将格式化的数据输入到指针str所指向的空间(字符串中)

可以理解为将格式化的数据转换为字符串

struct S
{
    char name[20];
    int age;
    float score;
};


int main()
{
    char arr[125] = { 0 };
    struct S s = { "lisi",18,98.5f };
    sprintf(arr, "%s %d %f", s.name, s.age, s.score);
    //将后面的数据转换为字符串存在arr中
    printf("%s", arr);//将数据以字符串形式打印出来
    return 0;
}

/*
sprintf可以理解为将数据转换为字符串,然后存储在指定的字符数组中
*/

ascanf的作用就和sprintf作用相反

sprintf的作用是将数据以字符串的形式存储在数组内

那么sscanf就是将数组中的这些已经转化为字符串的格式化数据提取出来

sscanf

int sscanf(const char * s,const char* format , ….)

struct s
{
    char name[20];
    int age;
    float score;
};


int main()
{
    char arr[125] = { 0 };
    struct s s = { "lisi",18,98.5f };

    //临时变量
    struct s tmp = { 0 };
    //将s中的各个数据转换成字符串放在arr中
    sprintf(arr, "%s %d %f", s.name, s.age, s.score);

    //从字符串arr中提取格式化的数据,存放在tmp中
    sscanf(arr,"%s %d %f", tmp.name, &(tmp.age), &(tmp.score));//name 是字符串首元素的地址

    printf("%s %d %f", tmp.name, tmp.age, tmp.score);
    return 0;
}

从字符串中提取格式化的数据,将字符串转化为格式化数据

总结:sprintf:将格式化的数据转换为字符串

sscanf:将字符串转化成格式化的数据

第五组:fread和fwrite

fwrite

size*t fwrite( const void *ptr ,sizet size,size_t count, FILE**stream)

有四个参数

将ptr指向的这块空间里面的count个大小为size的元素写到stream所指向的文件里

fread

size*t fread( const void *ptr ,sizet size,size_t count, FILE**stream)

两个函数的参数是一模一样的

那么fread的作用就是把stream里面的count个大小为size的元素写到ptr所指向的空间

从文件stream中读取count个大小为size个字节的数据,存放在ptr指向的空间中

//struct S
//{
//    char name[20];
//    int age;
//    float score;
//};
//
//int main()
//{
//    struct S s = { "lisi",18,98.6f };
//    //将这些数据以二进制的形式写到文件中
//
//    //打开文件
//    FILE* pf = fopen("test.txt", "wb");//b就是二进制的形式,这里的wb就是二进制的写
//    
//    //判断文件打开成功没?
//    if (pf == NULL)
//    {
//        perror("fopen");
//        return 1;
//    }
//     //写文件
//    fwrite(&s, sizeof(struct S), 1, pf);//写的数据来自s,我们要提供地址
//    //这里只有1个结构体的数据,那么我们写1,写到pf所指向的文件里面去
//        
//        //关闭文件
//    fclose(pf);
//    pf = NULL;
//    return 0;
//}
//这里我们已经将s中的数据以二进制的形式写到文件中去了

//那么下面的代码我们就进行解读
struct S
{
    char name[20];
    int age;
    float score;
};

int main()
{
    struct S s = { 0 };
    //读取二进制的数据到文件中

    //打开文件
    FILE* pf = fopen("test.txt", "rb");

    //判断文件打开成功没?
    if (pf == NULL)
    {
        perror("fopen");
        return 1;
    }
    //读取文件
    fread(&s, sizeof(struct S), 1, pf);
    //第一个参数是我们读完存放信息的地址
    //第二个参数是一个元素的大小,
    // 第三个元素是元素的个数 
    // pf就是我们要进行读的文件

    printf("%s %d %f", s.name, s.age, s.score);
        //关闭文件
    fclose(pf);
    pf = NULL;
    return 0;
}

这两个函数的返回值都是size_t 类型的

成功读取几个就返回

假设我们在用fread的时候,我们从文件中读取5个大小为size的数据放到ptr里面去,但是这个文件只有三个,读不到5个

那么我们这个函数就会返回我们实际读到数据的真实个数

 

6.文件的随机读写

想在哪里读就在哪里读,想在哪里写就在哪里写

文件的随机读写要确定我们这个文件里面写进去了很多信息

我们要根据我们的需要,将文件指针的指针(文件内容的光标)位置进行调整

fseek的使用

fseek

int fseek(FILE* stream,long int offest,int origin)

第一个参数是文件指针,第二个参数是偏移量 ,第三个参数是起始位置

从第二个参数我们能回想起之前学的offsetof---计算结构体成员相较于起始位置的偏移量

我们一定要知道偏移量和起始位置,我们才能定位这个文件指针(光标)

对于第三个参数,我们有三种选项

1.SEEK_SET:文件的起始位置

2.SEEK_CUR:文件指针(光标)当前的位置

3.SEEK_END:文件的末尾

int main()
{

    //读取二进制的数据到文件中

    //打开文件
    FILE* pf = fopen("test.txt", "r");

    //判断文件打开成功没?
    if (pf == NULL)
    {
        perror("fopen");
        return 1;
    }
    //读取文件,fgetc读取字符,返回被读字符的ASCII码值
    int ch = 0;
    ch=fgetc(pf);
    printf("%c\n", ch);
    //最开始,光标指向的是a,读完a之后,光标就会往后跳 
    ch = fgetc(pf);
    printf("%c\n", ch);//这里打印出来的就是b,打印完b,光标就指向了c


    //按照常规的话,下面的代码中的光标就指向了c,但是我们想直接读e,我们该做什么呢?
    //所以我们现在要重新定位文件指针,将文件指针的(光标)指向e的位置

    //fseek(pf, 4, SEEK_SET);//从文件起始位置偏移4下就能到e的位置了

    //fseek(pf, 2, SEEK_CUR);//因为上面已经进行了b的打印了,那么光标就指向了c的位置,
    //那么我们从当前位置进行光标的偏移,偏移两下光标就指向了e

    fseek(pf, -2, SEEK_END);
    //最后的位置就是f后面,光标指向了f的后面,那么这个光标前进两个指向的就是e了

    ch = fgetc(pf);
    printf("%c\n", ch);

    //关闭文件
    fclose(pf);
    pf = NULL;
    return 0;
}

向后偏移的话偏移量就是正数,向前的话偏移量就是负数

ftell的使用

ftell

long int ftell(FILE*stream)

返回文件指针相对于起始位置的偏移量

int main()
{

    //读取二进制的数据到文件中

    //打开文件
    FILE* pf = fopen("test.txt", "r");

    //判断文件打开成功没?
    if (pf == NULL)
    {
        perror("fopen");
        return 1;
    }
    //读取文件,fgetc读取字符,返回被读字符的ASCII码值
    int ch = 0;
    ch = fgetc(pf);
    printf("%c\n", ch);//a
    //最开始,光标指向的是a,读完a之后,光标就会往后跳 
    ch = fgetc(pf);//b
    printf("%c\n", ch);//这里打印出来的就是b,打印完b,光标就指向了c



    fseek(pf, -2, SEEK_END);
    //我们从文件的末尾进行访问,这是一个字符串,隐藏着一个'\0',所以我们要偏移两下才能到e

    ch = fgetc(pf);
    printf("%c\n", ch);//e

    //读完e我们想知道当前位置相较于起始位置的偏移量
    printf("%d", ftell(pf));//5
    //为什么是5呢?
    //因为上面我们刚读完e了,光标指向了f,那么这个位置距离起始位置的距离就是5了
    //最开始的起始位置在a的前面
    //关闭文件
    fclose(pf);
    pf = NULL;
    return 0;
}

rewind的使用

让文件指针位置回到文件的起始位置

void rewind(FILE*stream)

int main()
{

    //读取二进制的数据到文件中

    //打开文件
    FILE* pf = fopen("test.txt", "r");

    //判断文件打开成功没?
    if (pf == NULL)
    {
        perror("fopen");
        return 1;
    }
    //读取文件,fgetc读取字符,返回被读字符的ASCII码值
    int ch = 0;
    ch = fgetc(pf);
    printf("%c\n", ch);//a
    //最开始,光标指向的是a,读完a之后,光标就会往后跳 
    ch = fgetc(pf);//b
    printf("%c\n", ch);//这里打印出来的就是b,打印完b,光标就指向了c



    fseek(pf, -2, SEEK_END);
    //我们从文件的末尾进行访问,这是一个字符串,隐藏着一个'\0',所以我们要偏移两下才能到e

    ch = fgetc(pf);
    printf("%c\n", ch);//e

    //让文件指针回归到最初的位置
    //将文件指针重新定位到文件起始位置
    rewind(pf);
    ch = fgetc(pf);
    printf("%c\n", ch);
    //那么这一次的打印就是a了
    fclose(pf);
    pf = NULL;
    return 0;
}

7.文件的读取结束的判定

被错误使⽤的 feof

因为eof是文件结束的标志

以为feof函数是用来判断文件是否结束的,但其实不是的

牢记:在⽂件读取过程中,不能⽤feof函数的返回值直接来判断⽂件的是否结束。

feof 的作⽤是:当⽂件读取结束的时候,判断是读取结束的原因是否是:遇到⽂件尾结束。

在文件的读取过程中,有可能读取文件结束

结束的原因:1.遇到文件末尾

2.遇到错误了

,是判断结束的原因的

  1. ⽂本⽂件读取是否结束,判断返回值是否为 EOF ( fgetc ),或者 NULL ( fgets )

例如:

• fgetc 判断是否为 EOF .

• fgets 判断返回值是否为 NULL .

  1. ⼆进制⽂件的读取结束判断,

判断返回值是否⼩于实际要读的个数

例如:

• fread判断返回值是否⼩于实际要读的个数

#include <stdio.h>
#include <stdlib.h>
int main(void)
{
    int c; // 注意:int,⾮char,要求处理EOF
    FILE* fp = fopen("test.txt", "r");
    if (!fp)//判断fp是不是空指针
    {//如果是空指针的话,!为真,那么打印错误
        perror("File opening failed");
        return EXIT_FAILURE;
    }
    //fgetc 当读取失败的时候或者遇到⽂件结束的时候,都会返回EOF
    while ((c = fgetc(fp)) != EOF) // 标准C I/O读取⽂件循环
    {
        putchar(c);
    }
    //这个循环结束了,那么下面就是我们进行探讨读取结束的原因
    //判断是什么原因结束的
    if (ferror(fp))//是不是遇到错误而结束的
    {
        puts("I/O error when reading");
    }
    else if (feof(fp))//是不是遇到结尾结束的
    {
        puts("End of file reached successfully");
    }
    fclose(fp);
    fp = NULL;
    return 0;
}
#include <stdio.h>
enum { SIZE = 5 };//枚举类型定义一个size=5
int main(void)
{
    double a[SIZE] = { 1.,2.,3.,4.,5. };
    FILE* fp = fopen("test.bin", "wb"); // 打开写文件。必须⽤⼆进制模式
    fwrite(a, sizeof * a, SIZE, fp); // 写 double 的数组
    //a是数组名,首元素的地址,那么*a就是首元素,那么sizeof*a计算的就是每个元素的大小
    //将a中的数据以二进制的形式写到fp所指向的文件里面去

    fclose(fp);//关闭文件

    double b[SIZE];
    fp = fopen("test.bin", "rb");//打开读文件,二进制形式rb
    size_t ret_code = fread(b, sizeof * b, SIZE, fp); // 读 double 的数组
    //code是fread是实际读的个数,将fp中的数据以格式化数据的形式读到b中
    if (ret_code == SIZE)//读取的个数等于size的话,就是正常读取
    {
        puts("Array read successfully, contents: ");
        //对数组元素进行打印
        for (int n = 0; n < SIZE; ++n)
            printf("%f ", b[n]);
        putchar('\n');
    }
    else { // error handling
        if (feof(fp))//是不是读取的时候遇到了文件末尾而结束的
            printf("Error reading test.bin: unexpected end of file\n");
        else if (ferror(fp))//是不是遇到其他错误而结束的
        {
            perror("Error reading test.bin");
        }
    }
    fclose(fp);
}
//拷贝文件
//将test1.txt拷贝到test2.txt
int main()
{
    //打开文件
    FILE*pfread=fopen("test1.txt", "r");//这个文件我们是用来读的
    if (pfread == NULL)//打开失败的情况
    {
        perror("fopen\n");
        return 1;
    }
    FILE* pfwrite = fopen("test2.txt", "w");//写文件
    if (pfwrite == NULL)//打开失败的情况
    {
        perror("fopen\n");
        fclose(pfread);//第二个文件都打开失败了,我们就直接将第一个文件关掉
        return 1;
    }

    //读写文件
    int ch = 0;
    //读pfread所指向的文件,将文件内的数据通过ch写到pfwrite里面

    while ((ch = fgetc(pfread)) != EOF)//不等于EOF就说明读取正常
    {
        fputc(ch, pfwrite);//fputc的第一个参数是要写的字符,第二个是文件指针
    }
    //当返回值是EOF的时候就结束了
    //关闭文件
    fclose(pfread);
    pfread = NULL;

    fclose(pfwrite);
    pfwrite = NULL;
    return 0;
}
/*我们在while循环中,先用ch = fgetc(pfread) != EOF,
* fgetc的返回值就是对应字符的ASCII码值
* 那么我们先读pfread里面的每个字符,然后在每层循环为ch附上每个字符的ASCII码值
* 在循环内,fputc第一个参数就是要写的字符数据,第二个参数就是所指向的文件指针,
* ch已经被赋上值了,那么我们就直接利用fputc将字符数据写到prwrite所指向的文件中
* 
* 
* 
*/
int main()
{
    //打开文件
    FILE* pf = fopen("test.txt", "r");//这个文件我们是用来读的
    if (pf == NULL)//打开失败的情况
    {
        perror("fopen\n");
        return 1;
    }


    //读写文件
    //int ch = 0;
    //ch = fgetc(pf);

    //int reet = feof(pf);//检测文件末尾
    //printf("%d", reet);//打印出来的是0,如果不是结尾结束的话,那么就是不正常的
    int ch = 0;
    while ((ch = fgetc(pf)) != EOF)
    {
        printf("%c ", ch);//将字符依次打印出来
    }
    int reet = feof(pf);
    printf("%d", reet);//那么这里打印出来的就是1了

    reet = ferror(pf);
    printf("%d", reet);//输出的结果是0,因为此时已经是文件末尾了,并没有出现错误的情况
    //关闭文件

    return 0;
}
//如果这个是正常结束的话,到文件末尾的话,对于feof(pf)的话,返回值就是非0的数字
//如果不是结尾结束的话,那么返回的就是一个0

//w是写,r是读

对于feof来说的话,如果这个是正常结束的话,到文件末尾的话,对于feof(pf)的话,返回值就是非0的数字

如果不是结尾结束的话,那么返回的就是一个0

对于ferror来说的话,如果不是其他的错误信息导致停止话,就是返回的是0,

如果是什么错误信息导致的,那么这个返回的就是非0数字

8.文件缓冲区

ANSIC 标准采⽤“缓冲⽂件系统” 处理的数据⽂件的,所谓缓冲⽂件系统是指系统⾃动地在内存中为

程序中每⼀个正在使⽤的⽂件开辟⼀块“⽂件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓

冲区,装满缓冲区后才⼀起送到磁盘上。如果从磁盘向计算机读⼊数据,则从磁盘⽂件中读取数据输

⼊到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓

冲区的⼤⼩根据C编译系统决定的

#include <stdio.h>
#include <windows.h>
//VS2022 WIN11环境测试
int main()
{
    //打开文件
    FILE* pf = fopen("test.txt", "w");
    //写文件,将这个一个字符串放到文件内
    //先将abcdef放到缓冲区内
    fputs("abcdef", pf);//先将代码放在输出缓冲区
    printf("睡眠10秒-已经写数据了,打开test.txt⽂件,发现⽂件没有内容\n");
    Sleep(10000);//10000毫秒就是10秒
    printf("刷新缓冲区\n");
    fflush(pf);//刷新缓冲区时,才将输出缓冲区的数据写到⽂件(磁盘)
    //注:fflush 在⾼版本的VS上不能使⽤了
    printf("再睡眠10秒-此时,再次打开test.txt⽂件,⽂件有内容了\n");
    Sleep(10000);
    fclose(pf);
    //注:fclose在关闭⽂件的时候,也会刷新缓冲区
    pf = NULL;
    return 0;
}

这⾥可以得出⼀个结论:

因为有缓冲区的存在,C语⾔在操作⽂件的时候,需要做刷新缓冲区或者在⽂件操作结束的时候关闭⽂件。

如果不做,可能导致读写⽂件的问题

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/767097.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

递归(二)—— 初识暴力递归

如何理解暴力递归&#xff1f; 字面意思就是——“暴力的递归”&#xff0c;就是——“别纠结细节&#xff0c;开整&#xff08;递归&#xff09;&#xff01;” 暴力递归就是尝试。即&#xff1a;只要满足递归条件就不断的递归下去&#xff0c;直到达到base case&#xff0c…

力扣习题--哈沙德数

一、前言 本系列主要讲解和分析力扣习题&#xff0c;所以的习题均来自于力扣官网题库 - 力扣 (LeetCode) 全球极客挚爱的技术成长平台 二、哈沙德数 1. 哈沙德数 如果一个整数能够被其各个数位上的数字之和整除&#xff0c;则称之为 哈沙德数&#xff08;Harshad number&…

LeetCode刷题之HOT100之除自身以外数组的乘积

2024 7/3 今天天气依旧很好&#xff0c;想起来做一题。 1、题目描述 2、算法分析 给定一个数组&#xff0c;要返回初自身以外数组的乘积。咋做呢&#xff1f;是的&#xff0c;我只能想到暴力解法&#xff0c;这不符合时间复杂度O(n)的要求&#xff0c;所以我只能看一下题解了…

零一万物: Yi Model API的使用

一、获取API Key 通过官方网址注册账号并且认证: 零一万物大模型开放平台 创建API Key 二、安装及调用 安装OpenAI SDK ​ 零一万物API 接口兼容 OpenAI 的 Python SDK&#xff0c;只需要简单配置即可使用。 安装 OpenAI SDK。请确保使用的 Python 版本至少为 3.7.1&a…

检索生成(RAG) vs 长文本大模型:实际应用中如何选择?

编者按&#xff1a;大模型的上下文理解能力直接影响到 LLMs 在复杂任务和长对话中的表现。本期内容聚焦于两种主流技术&#xff1a;长上下文(Large Context Windows)和检索增强生成(RAG)。这两种技术各有何优势&#xff1f;在实际应用中&#xff0c;我们又该如何权衡选择&#…

数据质量管理-可访问性管理

前情提要 根据GB/T 36344-2018《信息技术 数据质量评价指标》的标准文档&#xff0c;当前数据质量评价指标框架中包含6评价指标&#xff0c;在实际的数据治理过程中&#xff0c;存在一个关联性指标。7个指标中存在4个定性指标&#xff0c;3个定量指标&#xff1b; 定性指标&am…

【Windows】draw.io(免费的开源跨平台绘图软件)软件介绍

软件介绍 draw.io 是一款免费且易于使用的在线流程图绘图软件&#xff0c;后来更名为 diagrams.net。它最初作为一个基于 Web 的应用程序提供&#xff0c;支持用户创建各种类型的图表、流程图、网络图、组织结构图、UML 图等。它是完全免费的、强大的、专业的、易于使用的和高…

C++使用Poco库封装一个HTTP客户端类--Query参数

0x00 概述 我们使用Poco库的 Poco::Net::HTMLForm 类可以轻松实现表单数据的提交。 0x01 ApiPost提交表单数据 0x02 HttpClient类 #ifndef HTTPCLIENT_H #define HTTPCLIENT_H#include <string> #include <map> #include <Poco/URI.h> #include <Poco/N…

引领视觉基础模型新纪元! | 微软宣布开源Florence-2

01 模型介绍 &#x1f389;重大突破&#xff01;微软宣布开源Florence-2视觉基础模型&#xff0c;引领AI新纪元&#xff01;&#x1f680; Florence-2这一创新力作&#xff0c;以统一的提示为基础&#xff0c;跨越式地解决了计算机视觉与视觉语言领域的多样任务难题。从字幕生…

Hyper-V虚拟机固定IP地址(手把手教设置)

链接虚拟机修改网络配置文件 输入指令 sudo vi /etc/sysconfig/network-scripts/ifcfg-eth0 然后 输入 按 i 键 再按回车 (enter) 进入编辑模式 修改配置(这几项)其中 IPADDR 就是你想给虚拟机固定的 IP 地址 多台的话只需要修改这个IP 就行其他不变 BOOTPROTO=static…

半导体划片研磨废水的处理效果

半导体划片研磨废水处理是一个复杂而关键的过程&#xff0c;因为这类废水中含有大量颗粒物、有机物、重金属等有害物质&#xff0c;具有浓度高、毒性大、难以处理等特点。以下是对半导体划片研磨废水处理过程的详细阐述&#xff0c;结合相关数字和信息进行归纳&#xff1a; 一、…

【Java集合类】ArrayList

方法 subList(int fromIndex, int toIndex) 可以看一下subList源码片段 public List<E> subList(int fromIndex, int toIndex) {subListRangeCheck(fromIndex, toIndex, size);return new SubList<>(this, fromIndex, toIndex);} private static class SubList…

nginx的vim nginx.conf配置文件内容详解及实验,nginx的优化和防盗链

一、nginx网络服务器&#xff1a; 1. nginx是开源的&#xff0c;是一款高性能&#xff0c;轻量级的web服务软件&#xff1b;稳定性高&#xff0c;而且版本迭代比较快&#xff1b;修复bug速度比较快&#xff0c;安全性高&#xff1b;消耗资源低&#xff0c;http的请求并发连接&…

My sql 安装,环境搭建

以下以MySQL 8.0.36为例。 一、下载软件 1.下载地址官网&#xff1a;https://www.mysql.com 2. 打开官网&#xff0c;点击DOWNLOADS 然后&#xff0c;点击 MySQL Community(GPL) Downloads 3. 点击 MySQL Community Server 4.点击Archives选择合适版本 5.选择后下载第二个…

bWAPP靶场安装

bWAPP安装 下载 git地址&#xff1a;https://github.com/raesene/bWAPP 百度网盘地址&#xff1a;链接&#xff1a;https://pan.baidu.com/s/1Y-LvHxyW7SozGFtHoc9PKA 提取码&#xff1a;4tt8 –来自百度网盘超级会员V5的分享 phpstudy中打开根目录&#xff0c;并将下载的文…

【C++知识点总结全系列 (06)】:STL六大组件详细总结与分析- 配置器、容器、迭代器、适配器、算法和仿函数

STL六大组件目录 前言1、配置器(1)What(2)Why(3)HowA.调用new和delete实现内存分配与销毁B.STL Allocator (4)allocator类A.WhatB.HowC.allocator的算法 2、容器(1)What(2)Which&#xff08;有哪些容器&#xff09;(3)序列容器&#xff08;顺序容器&#xff09;A.WhichB.array&…

Unity编辑器工具---版本控制与自动化打包工具

Unity - 特殊文件夹【作用与是否会被打包到build中】 Unity编辑器工具—版本控制与自动化打包工具&#xff1a; 面板显示&#xff1a;工具包含一个面板&#xff0c;用于展示软件的不同版本信息。版本信息&#xff1a;面板上显示主版本号、当前版本号和子版本号。版本控制功能…

音视频开发35 FFmpeg 编码- 将YUV 和 pcm合成一个mp4文件

一 程序的目的 /*** *该程序的目的是: * 将 一个pcm文件 和 一个 yuv文件&#xff0c;合成为一个 0804_out.mp4文件 * pcm文件和yuv文件是从哪里来的呢&#xff1f;是从 sound_in_sync_test.mp4 文件中&#xff0c;使用ffmpeg命令 抽取出来的。 * 这样做的目的是为了对比前…

【C语言】文件的顺序读写

©作者:末央&#xff06; ©系列:C语言初阶(适合小白入门) ©说明:以凡人之笔墨&#xff0c;书写未来之大梦 目录 前言字符输入输出函数 - fgetc和fputc文本行输入输出函数 - fgets和fputs格式化输入输出函数 - fscanf和fprintf 前言 对文件数据的读写可以分为顺序…

【Elasticsearch】一、概述,安装

文章目录 概述全文搜索引擎概述ES&#xff08;7.x&#xff09; 安装ES&#xff08;Docker&#xff09;测试&#xff0c;是否启动成功 可视化工具配置中文 客户端Postman下载 概述 ES是开源的高扩展的分布式全文搜索引擎&#xff0c;实时的存储、检索数据&#xff1b;本身扩展性…