你是否还记得c语言的这些文件操作?

Posted 捕获一只小肚皮

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了你是否还记得c语言的这些文件操作?相关的知识,希望对你有一定的参考价值。

前言

学完c已经较长时间了,在学习期间陆陆续续的整理了多篇关于c的详细知识点文章,此篇文章将是博主对于c整理的倒数第二篇文章----------c语言的文件操作.下面博主也放出了一些之前的文章链接,大家若是有兴趣,也可以观看观看.

温馨提示,此文较长,大家可以根据目录选择观看


c语言操作符基础指针一篇面试题的经历
数据存储原理基础结构体指针进阶
数组与指针关系指针与数组经典题字符串与内存函数
自定义类型,结构体进阶动态内存详解^ 异或运算技巧
& 与运算技巧c的小细节

正文引言

此文目录1 ~ 4都是属于理论知识,稍微有点枯燥,大家有兴趣可以看看,没兴趣可以跳过

并不会影响后面的阅览.

目录


1. 什么是文件

现实生活中,文件常常指一些关于 政治理论,时事政策,学术研究等文章.

计算机中,磁盘上的存储的一份份重要信息是文件.

但在程序设计中,我们一般谈的文件有两种: 程序设计,数据文件

  • 程序文件

包括源程序文件(后缀为.c),目标文件(windows环境后缀为.obj),可执行程序(windows环境后缀为.exe)

即一切用c程序得来的相关文件.

  • 数据文件

文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内 容的文件.

总而言之,就是使用文件操作函数中涉及到的 文件,称为数据文件

本章讨论的是数据文件

在此之前,我们所处理数据的输入输出都是以终端为对象的,即从终端的键盘输入数据,运行结果显示到显示器上。 其实有时候我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使用,这里处理的就是磁盘上文件.

2. 文件名

文件名就是一个文件的名字,是一个唯一的文件标识,以便用户识别与引用.

文件名包含3部分: 文件路径 + 主干名 + 后缀

例如: C:\\code\\comment\\text.txt

文件路径: C:\\code\\comment\\

主干名: text

后缀: .txt

3. 文件类型

根据数据的组织形式,数据文件被称为文本文件或者二进制文件

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

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

3.1 一个数据在内存中是怎么存储的呢

字符一律以ASCII形式存储,数值型数据既可以用ASCII形式存储,也可以使用二进制形式存储。

例如有整数10000,如果以ASCII码的形式输出到磁盘,则磁盘中占用5个字节(每个字符一个字节),而二进制形式输出,则在磁盘上只占4个字节,如下图:

image-20210531154208544

4. 文件缓冲区

什么是文件缓冲区?我们先不着急介绍文件缓冲区. 但是大家在写一段涉及到输入输出的程序时,一定会有这样的经历吧.

比如写这样一段程序在运行时,会有下面的图:

#include <stdio.h>
int main()
{
    char str[50] = {0};
    printf("请输入一句话:\\n");
    scanf("%s",str);
    printf("你输入的语句是:%s\\n",str);
    return 0;
}

image-20210531155748658

在我们输入的地方,只要我们还没有 按下回车,就可以在这里继续输入,修改想要的输入的东西.一旦按下回车,这段数据就会被送入

程序中,然后运行,无法再修改.这个输入停留区有个专有名词,叫做输入缓冲区.

输入缓冲区的好处是,在我们还没有确定发送数据时候,还可以进行修改想要发送的数据,防止输入人按键出错引起的一些后果.

当然, 输入缓冲区也是有一定大小的,如果缓冲区满了,就不会听从你的指挥,自动就把数据发送出去.而缓冲区的大小是由编译器决定的

同理,文件缓冲区也是这样,当我们建立文件(在内存中),在文件里面写东西时候,这些数据是并没有发送到硬盘保存的,此时的数据还是停留在计算机中的一个叫做 文件缓冲区的地方.当我们觉得完毕了,就点击保存,此时数据就被真的送进了硬盘. (从内存到硬盘)

如果我们想要看一下某个文件的内容,就会打开文件,然后就看到了文件的内容.但是我们看到的内容并不是我们想象那样一下子就有了,在从硬盘到内存中,也存在一个缓冲区,开始一一读取硬盘内容,当缓冲区满了后,就把读取的内容发送到内存.

图解:

image-20210531162539798


这里有一段关于缓冲区的有趣程序,可以深切体会到缓冲区的概念.

#include <stdio.h>
#include <windows.h>
int main()
{
    while (1)
    {
        Sleep(1000);  //沉睡1秒
        printf("哈");
    }
    return 0;
}

你会发现,和你想像的结果完全不一样,你本来以为是,隔一秒就打印一个 ,但是事实上是,隔了很久突然打印一大坨.

这就是缓冲区的原因.


5. 文件指针

每个被使用的文件都会内存中开辟一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及 文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是有系统声明的,取名FILE.

而我们只需要一个该结构体指针就可以进行维护,操作文件文件信息区,以达到对文件的操作.该结构体指针就叫做 文件指针

image-20210531164832759

6. 文件的打开与关闭

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

在编写程序时,一旦打开一个文件的同时,就会返回一个FILE*的指针变量指向该文件信息区,也就是相当于建立了指针和文件的关系.

  1. 文件的打卡是通过函数 fopen()
  2. 文件的关闭是通过函数fclose()

6.1 打开文件函数 fopen

使用: FILE* fopen(const char* filename,const char* mode);

解释:

  • filename ----文件名,在目录2中介绍过,需要写完好的路径,路径有下面两种:
    • 相对路径-----需要打开的文件相对于该程序文件的相对位置
    • 绝对路径-----从盘路径开始,直到最后. 比如:C:\\code\\mode\\tets.txt
  • mode — 文件打开的方式,传入的是一个字符串.
  • FILE* —如果打开成功,返回值是一个文件指针, 目录5介绍过. 如果打开失败,返回空指针

打开模式表格:

文件使用方式含义如果指定文件不存在
“r” (只读)为了输入数据,打开一个已经存在的文本文件出错
“w” (只写)为了输出数据,打开一个文本文件建立一个新的文件
“a” (追加)**向文本文件尾添加数据 **出错
“rb” (只读)为了输入数据,打开一个二进制文件出错
“wb” (只写)为了输出数据,打开一个二进制文件建立一个新的文件
“ab” (追加)向一个二进制文件尾添加数据出错
“r+” (读写)为了读和写,打开一个文本文件出错
“w+” (读写)为了读和写,建议一个新的文件建立一个新的文件
“a+” (读写)打开一个文件,在文件尾进行读写建立一个新的文件
“rb+” (读写)为了读和写打开一个二进制文件出错
“wb+” (读写)为了读和写,新建一个新的二进制文件建立一个新的文件
“ab+” (读写)打开一个二进制文件,在文件尾进行读和写建立一个新的文件

小结:

  • 带了b的模式就是二进制文件.
  • 在打开一个不曾有的文件时候,除了二进制的读写操作模式外,其余的只要是的操作都是建立一个该新文件.

示例:

#include <stdio.h>
int main ()
{
  	FILE * pFile; //创建文件指针
  	pFile = fopen ("myfile.txt","w"); //只写模式打开,如果没有文件"myfile.txt",就会在该代码文件同级目录下创建.
    
    if(pFile == NULL) // 必须要判断是否打开成功
    {
        perror("打开失败原因");
        return -1;
    }
  	return 0;
}

图解:

image-20210531205443916

清晰的看到,在运行程序之前,同级目录下并没有myfile.txt,但是运行之后建立了一个空文件myfile.txt(之所以是空文件,是因为并没有读写数据)


6.2 文件的关闭

使用: int fclose(FILE * stream)

解释: stream 需要关闭的文件,传入的值是该文件指针.

返回值: 如果成功关闭,返回0;没有成功关闭返回非0

示例:

#include <stdio.h>
int main()
{
   	FILE * pFile; //创建文件指针
  	pFile = fopen ("myfile.txt","w"); 
    if(pFile == NULL) // 必须要判断是否打开成功
    {
        perror("打开失败原因");
        return -1;
    }
    fclose(pFile);  //关闭文件
    return 0;
}

6.3 总结

fopenfclose函数是连在一起使用的,有时候我们经常会忘记关闭文件.

所以,为了防止忘记关闭,我们一定要养成一个好习惯,那就是在打开文件时候.就立马写一个关闭.

然后再在两个函数之间写其他代码.

同时,在打开文件时候必须要检查打开是否成功.

例如:

#include <stdio.h>
int main()
{
    FILE* pFILE = fopen("abc.txt","w");
    if(pFile == NULL) // 必须要判断是否打开成功
    {
        perror("打开失败原因");
        return -1;
    }
    .........//这里才是第三步骤写代码的地方
    fclose(pFILE);
    return 0;
}

7. 文件的读写

现在就是进入了高潮部分,是文件操作的重要部分.

其中文件的读写分为两种,一种是 顺序读写, 一种是 随机读写.

下面博主将会一一介绍两种读写方式,以及相关使用.


7.1 顺序读写

顺序读写函数:

功能函数名适用于
字符输入函数fgetc所有输入流
字符输出函数fputc所有输出流
文本行输入函数fgets所有输入流
文本行输出函数fputs所有输出流
格式化输入函数fscanf所有输入流
格式化输出函数fprintf所有输出流
二进制输入fread文件
二进制输出fwrite文件

在详细介绍各个函数的使用之前,大家还需要知道一个概念-------流(stream)

是一种实际数据输入输出映射出的一种 理想化数据流, 是不是太抽象了??? 哈哈哈,没事下面用白话解释.

我们输入数据是怎么输入的? — ---- – -- – 使用键盘把想要输入的数据进行输入. – — – — --- — --- ---- 键盘就是一个输入流.

我们输出数据是怎么输出的?— ---- — ----- 把想要的数据逐渐的打印在屏幕上面.------- – -- - - - - - - - — 屏幕就是一个 输出流

而我们习惯把 键盘称为 stdin,标准输入流.

且我们习惯把 屏幕称为stdout,标准输出流.

而现在我们需要努力做的事情就是 把各种文件理解为 键盘屏幕.

如果该文件达到的是键盘作用,就称该文件是 输入流

如果该文件达到的是屏幕作用,就称该文件是 输出流


下面开始介绍各个顺序读写函数

① fgetc 字符输入函数

使用文档: int fgetc(FILE* stream)

stream此时是一个 输入流,起着键盘作用.代表着 从一个文件中挨个读取字符并返回该字符的ASCII值.

示例:

#include <stdio.h>
/*
这里有一个fgetc.c文件,文件内容如下:
If you are feeling that life just cannot be any worse for you, it can be challenging to think positive thoughts. When we are stressed, depressed, upset, or otherwise in a negative state of mind because we perceive that "bad things" keep happening to us, it is important to shift those negative thoughts to something positive.
*/
int main()
{
    FILE* stream;
    char str[100] = { 0 };
    int i = 0;

    stream = fopen("fgetc.txt", "r"); // 以只读模式打开
    if (stream == NULL) // 必须要判断是否打开成功
    {
        perror("打开失败原因");
        return -1;
    }

    //顺序读取80个字符.
    for (int i = 0; i < 80; i++)
    {
        str[i] = fgetc(stream);
    }

    //关闭文件
    fclose(stream);

    //打印读取的内容
    printf("%s", str);
    return 0;
}

结果:

image-20210531231051023


② fputc 字符输出函数

使用文档: int fputc(int c,FILE* stream)

c是一个字符. 有人会问,c不是int类型吗? 但是大家要记得哦~ ,字符也是整型家族的成员哦,且是以ASCII值形式存储.

stream此时是 输出流,起着屏幕作用.

#include <stdio.h>
/*
此时同级目录下有一个空文件fputc.txt
*/
int main()
{
   FILE* stream;
   stream = fopen("fputc.txt","w");
   if(stream == NULL)
   {
       perror("打开失败原因:");
       return -1;
   }
   fputc('c',stream);
   
   fclose(stream);//不要忘记关闭文件
   return 0;
}

结果: 成功读入一个字符c

image-20210531234508516

③ fgetc与fputc函数的结合使用.

该两个函数结合使用的作用是,把一个文件的内容拷贝到另一个文件中去.

示例:

/*
同级目录下fgetc.txt中的内容是
If you are feeling that life just cannot be any worse for you, it can be challenging to think positive thoughts. When we are stressed, depressed, upset, or otherwise in a negative state of mind because we perceive that "bad things" keep happening to us, it is important to shift those negative thoughts to something positive.

同级目录下fputc.txt中无内容.
*/
#include <stdio.h>
int main()
{
    FILE* instream = fopen("fgetc.txt","r");//只读模式打开
    FILE* outstream = fopen("fputc.txt","w");//只写模式打开
    
    if((instream == NULL) || (outstream == NULL)) //检测是否打开失败
    {
        perror("错误原因:");
        return -1;
    }
    
    int ch = 0; //准备开始读取
    while((ch = fgetc(stream)) != EOF) //EOF是文件结束标志,代表着文件读取完毕
    {
        fputc(ch,outstream);
    }
    
    fclose(instream);//不要忘记关闭文件
    fclose(outstream);
    return 0;
}

在程序未有运行前,两个文件的内容:

image-20210531235657441

在程序运行后,两个文件的内容:

image-20210531235900793

④ fgets 文本输入函数

使用文档: char* fgets(char* string,int n,FILE* stream)

解释: 他的功能是从某一个输入流(键盘作用)中读取n个字符,放到string中去.

所以stream是输入流(键盘作用),n代表多少个字符,string代表着需要放的位置.

如果成功读取,返回string,如果失败返回NULL

示例:

/*
同级目录下有一个fgets.txt文件,内容是:
Mom bought me a pair of skating shoes at my fifth birthday.
From then on, I developed the hobby of skating.It not only
 makes me stronger and stronger, 
but also helps me know many truths of life.I
 know that it is normal to fall, and if only you can 
 get on your feet again and keep on moving, you are very good!
*/

#include <stdio.h>
int main()
{
    char string[100] = {0};
    FILE* stream = fopen("fgets.txt","r");//只读模式打开
    
    if(stream == NULL)
    {
        perror("错误原因:");
        return -1;
    }
    
    fgets(string,100,stream);//读取100个字符
    
    fclose(stream);//不要忘记关闭文件
    printf("%s",string);//检验是否成功
    return 0;
}

结果:

image-20210601090500370

⑤ fputs 文本输出函数

使用文档: int fputs(const char* string,FILE* stream)

解释: string是目标字符串,即它的内容会被写进stream.

— ---stream是输出流,起着屏幕作用.

示例:

/*
同级目录下有一个fputs.txt文件.
内容是空的.
*/
#include <stdio.h>
int main()
{
    char string[100] = {0};
    FILE* stream = fopen("fputs.txt","W");//只写模式打开
    if(stream == NULL)
    {
        perror("错误原因:");
        return -1;
    }
    
    printf("请输入你想要输出的句子:\\n");
    scanf("%s",string);
    
    fputs(string,stream);
    fclose(stream);//不要忘记关闭文件
    return 0;
}

程序运行时输入的句子:

image-20210601092026312

运行后fputs文件的内容:

image-20210601092059408

⑥ fgets函数与fputs函数结合

实现把一个文件的内容拷贝到另一个文件.

/*
同级目录下有一个文件fgets.txt
内容为:
qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnm
qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnm
qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnm
加上回车共236个字节.同级目录下游一个文件fputs.txt文件内容空
*/
#include <stdio.h>
int main()
{
    //打开文件
    FILE* instream = fopen("fgets.txt","r");
    FILE* outstream = fopen("fputs.txt","w");
    //检测打开成功与否
    char string[240] = {0};
    if((instream == NULL)||(outstream == NULL))
    {
        perror("错误原因:");
        return -1;
    }
	int i = 3;
    while(i-->0)
    {
        fgets(string,80,instream);//开始执行,79是26个字母的三倍加一个\\n     
        //但是为什么要写80个字节呢? 这是因为fgets函数是读取n-1个字符,并且在第n个位置补\\0
    	fputs(string,outstream);
    }
 //关闭文件   
    fclose(instream);
    fclose(outstream);
    return 0;
}

运行前:

image-20210601093421750

运行后:

image-20210601103656660

⑦ fscanf 格式化输入函数

使用文档:int fscanf(FILE* stream,const char* format[,arguement]...)

解释: stream是输入流(起着键盘作用), format格式其实就是就是我们使用scanf时候写的东西,比如"%s",s,表示给s输入字符串. format就是这样

示例:

/*
在统计目录下有一个fscanf.txt文件
内容为: I-love-you 1234 123.456
*/
#include <stdio.h>
int main()
{
    FILE* stream = fopen("fscanf.txt", "r");
    if (stream == NULL)
    {
        perror("错误原因:");
        return -1;
    }

    char s[20] = { 0 };
    int a = 0;
    float b = 0.0;
    fscanf(stream, "%s", s);
    fscanf(stream, "%d", &a); //注意这里哦,必须有取地址,是和scanf一模一样的.
    fscanf(stream, "%f", &b);

    printf("s的值是%s\\na的值是%d\\nb的值是%f", s, a, b);
    
    fclose(stream);//必须关闭文件
    return 0;
}

image-20210601104456443

⑧ fprintf 格式化输出函数

使用文档: int fprintf(FILE* stream,const char* format[,arguement]...)

解释: stream是一个输出流,起着屏幕作用. format格式同理,与printf()格式一样的.

示例:

/*
同级目录下有一个fprintf.txt文件,内容为空.
*/
#include <stdio.h>
ing main()
{
    char s[20] = {0};
    int a = 0;
    float b = 0.0;
    FILE* stream = fopen("fprintf.txt","w");
    if(stream == NULL)
    {
        perror("错误原因:");
        return -1;
    }
    printf("请输入字符串:\\n");
    scanf("%s",s);
    
    printf("请输入整数:\\n");
    scanf("%d",&a);
    
    printf("请输入浮点数:\\n");
    scanf("%f",&b以上是关于你是否还记得c语言的这些文件操作?的主要内容,如果未能解决你的问题,请参考以下文章

你知道的Go切片扩容机制可能是错的

译文:18个实用的JavaScript代码片段,助你快速处理日常编程任务

C,java,Python,这些名字背后的江湖!

获奖名单还能认出这些常用测试工具吗?你不记得它,它还认得你!

写了一个程序可以编译c语言,怎么自动再链接然后执行生成的可执行文件?

还能认出这些常用测试工具吗?你不记得它,它还认得你!