【ONE·C || 文件操作】

news/2024/11/30 12:40:03/

总言

  C语言:文件操作。
  

文章目录

  • 总言
  • 1、文件是什么?为什么需要文件?
    • 1.1、为什么需要文件?
    • 1.2、文件是什么?
  • 2、文件的打开与关闭
    • 2.1、文件指针
    • 2.2、文件打开和关闭:fopen、fclose
    • 2.3、文件使用方式
  • 3、文件的顺序读写
    • 3.1、字符输入输出:fputc、fgetc
      • 3.1.1、fputc
      • 3.1.2、fgetc
    • 3.2、适用于所有流:stream的简单介绍
    • 3.3、文本输入输出:fputs、fgets
      • 3.3.1、fputs
      • 3.3.2、fgets
    • 3.4、格式化输入输出:fscanf、fprintf
      • 3.4.1、fprintf
      • 3.4.2、fscanf
    • 3.5、与字符串相关的输入输出:sscanf、sprintf
      • 3.5.1、对比说明
      • 3.5.2、使用介绍
    • 3.6、二进制输入输出:fread、fwrite
      • 3.6.1、fwrite
      • 3.6.2、fread
  • 4、文件的随机读写
    • 4.1、fseek
    • 4.2、ftell
    • 4.3、rewind
  • 5、其它相关内容
    • 5.1、文本文件和二进制文件
    • 5.2、文件读取结束判定
    • 5.3、文件缓冲区

  

1、文件是什么?为什么需要文件?

1.1、为什么需要文件?

  模拟实现通讯录时,会面临这样一个实际问题:在通讯录中把信息记录下来后,只有在我们自己选择删除数据的情况下,数据才不复存在,否则需要长久保留数据。
  这就涉及到了数据持久化的问题,一般数据持久化的方法有,把数据存放在磁盘文件、存放到数据库等方式。使用文件我们可以将数据直接存放在电脑的硬盘上,做到了数据的持久化。
  
  

1.2、文件是什么?

  1)、文件分类
  磁盘上的文件是文件。但在程序设计中,从文件功能的角度来看,一般文件有两种:程序文件、数据文件。
  程序文件:包括源程序文件(后缀为.c),目标文件(windows环境后缀为.obj),可执行程序(windows环境后缀为.exe)。
  数据文件: 文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件。
  
  
  2)、文件名称
  一个文件要有一个唯一的文件标识,以便用户识别和引用。为了方便起见,文件标识常被称为文件名
  文件名包含3部分:文件路径+文件名主干+文件后缀

D:\日常\TIM\Tencent Files\All Users\test.txt
文件路径:D:\日常\TIM\Tencent Files\All Users\
文件名主干:test
文件后缀:.txt

  
  
  

2、文件的打开与关闭

2.1、文件指针

  1)、文件指针是什么?

  缓冲文件系统中,关键的概念是“文件类型指针”,简称“文件指针”。
  每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是由系统声明的,取名FILE
在这里插入图片描述

  不同的C编译器的FILE类型包含的内容不完全相同,但是大同小异。每当打开一个文件的时候,系统会根据文件的情况自动创建一个FILE结构的变量,并填充其中的信息,使用者不必关心细节。
  
  
  VS2019下FILE声明:

#ifndef _FILE_DEFINED#define _FILE_DEFINEDtypedef struct _iobuf{void* _Placeholder;} FILE;
#endif

  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;

  

  一般通过一个FILE的指针来维护这个FILE结构的变量,这样使用起来更加方便。
在这里插入图片描述
  
  2)、文件指针的作用

  我们可以创建一个FILE*的指针变量,如下:

FILE* pf;

  定义pf是一个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区(是一个结构体变量)。通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够找到与它关联的文件
  
  
  

2.2、文件打开和关闭:fopen、fclose

  1)、fopen、fclose函数介绍
  相关函数链接:fopen
在这里插入图片描述

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

  const char * filename:所需要打开的文件名称。
  const char * mode:打开文件的方式,此处类型为字符指针,用于存放字符串首字符的地址。
  FILE *:在内存中创建一个与打开文件有关的文件信息区,同时返回一个文件指针,该指针指向文件信息区的起始地址(结构体)。
  NULL:如果文件打开失败,则返回空指针,同动态内存一致,需要进行判空操作,若错误则显示错误原因,并结束程序。
  
  
  相关链接:fclose
在这里插入图片描述

fclose
int fclose ( FILE * stream );

  FILE * stream:向指定要关闭的FILE指针
  
  
  2)、使用举例

int main()
{//打开文件FILE* pf = fopen("test.txt", "r");//“r”此处为字符串,用双引号,r是一种文件读取模式if (pf == NULL){perror("fopen");return 1;}//读文件//……//关闭文件fclose(pf);pf = NULL;return 0;
}

  情形一:
在这里插入图片描述

  情形二:
在这里插入图片描述
  
  情形三:绝对路径与相对路径说明
  假如要打开的文件不在当前路径下,需要使用对应路径打开:
在这里插入图片描述
  
  
  

2.3、文件使用方式

  以下mode会在后续学习中慢慢用到。
在这里插入图片描述
  
  
  

3、文件的顺序读写

在这里插入图片描述

3.1、字符输入输出:fputc、fgetc

3.1.1、fputc

  相关函数链接:fputc
在这里插入图片描述

fputc
int fputc ( int character, FILE * stream );

  将字符character按顺序写入对应的文件流stream中,此处使用的是文件指针。如果失败则返回EOF。

  演示如下:

int main()
{//打开文件FILE* pf = fopen("D:\\Daily\\test.txt", "w");if (pf == NULL){perror("fopen");return 1;}//读写文件for (char ch = 'a'; ch <= 'z'; ch++){fputc(ch, pf);}//关闭文件fclose(pf);pf = NULL;return 0;
}

在这里插入图片描述

  
  
  

3.1.2、fgetc

  相关函数链接:fgetc
在这里插入图片描述

fgetc
int fgetc ( FILE * stream );

  从对应的文件指针指向的文件中按顺序依次读取字符,若遇到文件结尾/错误则返回EOF,若成功则返回字符读取(提升为 int 值)。
  
  使用演示:

int main()
{//打开文件FILE* pf = fopen("D:\\Daily\\test.txt", "r");if (pf == NULL){perror("fopen");return 1;}//读写文件int ch;while ((ch = fgetc(pf)) != EOF){printf("%c ", ch);}//关闭文件fclose(pf);pf = NULL;return 0;
}

在这里插入图片描述

  
  

3.2、适用于所有流:stream的简单介绍

  我们知道各种各样的外部设备,比如键盘、屏幕、U盘、硬盘、网卡等,数据信息的读写在它们之间进行交换,但不同的设备其读写方式可能存在差异,这样一来使用效率就会大幅降低,因为我们在做数据输入输出的前提是要学习了解它们的原理。因此,为了简化这一过程,我们在这些外设间引入一个中间商,即流,用于输入输出。这样一来,我们在各种数据交换时,就不必关心流与这些外设间是如何做到数据信息交换的,相当于一种封装。

在这里插入图片描述

  相关演示:

int main()
{int ch = fgetc(stdin);printf("%c ", ch);fputc(ch, stdout);return 0;
}

在这里插入图片描述
  
  
  

3.3、文本输入输出:fputs、fgets

3.3.1、fputs

  相关函数链接:fputs
在这里插入图片描述
  相关演示:

int main()
{//打开文件FILE* pf = fopen("D:\\Daily\\test.txt", "w");if (pf == NULL){perror("fopen");return 1;}//读写文件fputs("落霞与孤鹜齐飞,", pf);fputs("秋水共长天一色。\n", pf);fputs("渔舟唱晚,响穷彭蠡之滨;\n", pf);fputs("雁阵惊寒,声断衡阳之浦。\n", pf);//关闭文件fclose(pf);pf = NULL;return 0;
}

  可以看到,没加换行符,fputs会将当前内容放置于上此文尾,若加了换行符则另起一行。
在这里插入图片描述

  
  

3.3.2、fgets

  相关函数链接:fgets
在这里插入图片描述

  若成功,则返回str指针指向地址,若失败,则返回NULL;
  
  相关演示一:

int main()
{//打开文件FILE* pf = fopen("D:\\Daily\\test.txt", "r");if (pf == NULL){perror("fopen");return 1;}char arr[255];//读写文件fgets(arr, 254, pf);printf("%s\n", arr);while (fgets(arr, 254, pf)!=NULL)printf("%s", arr);//关闭文件fclose(pf);pf = NULL;return 0;
}

在这里插入图片描述
  
  
  验证fgets会保留一位将\0放入:

int main()
{//打开文件FILE* pf = fopen("D:\\Daily\\test.txt", "r");if (pf == NULL){perror("fopen");return 1;}char arr[10]="XXXXXXXXXX";//读写文件fgets(arr, 5, pf);printf("%s\n", arr);//关闭文件fclose(pf);pf = NULL;return 0;
}

在这里插入图片描述
  
  
  
  

3.4、格式化输入输出:fscanf、fprintf

3.4.1、fprintf

  相关函数链接:printf
在这里插入图片描述
  printf是将相关内容打印到标准输出,即屏幕上;
  fprintf适用于任意流,可将相关内容打印到指定的stream中。
  
  相关演示:

struct st
{char name[20];int age;double score;
};int main()
{//创建一个学生数据,要求:将该学生数据的内容存入指定文件中struct st s1 = { "张三",25,98.00 };//打开文件FILE* pf = fopen("D:\\Daily\\test.txt", "w");if (pf == NULL){perror("fopen");return 1;}//读写文件fprintf(pf,"%s %d %lf", s1.name, s1.age, s1.score);//关闭文件fclose(pf);pf = NULL;return 0;
}

在这里插入图片描述

  
  

3.4.2、fscanf

  相关函数链接:fscanf
在这里插入图片描述

  scanf是从标准输入流中读取数据,一般是键盘;
  fscanf适用于任意流,可从指定的流stream中读取数据。
  
  相关演示:

struct st
{char name[20];int age;double score;
};int main()
{//创建一个学生数据,要求:从指定文件中读取相关数据信息填入该结构体中struct st s1 = {0};//打开文件FILE* pf = fopen("D:\\Daily\\test.txt", "r");if (pf == NULL){perror("fopen");return 1;}//读写文件fscanf(pf, "%s %d %lf", s1.name, &(s1.age), &(s1.score));//注意,name为数组名,本身就是地址,故无需取地址操作符,其它二者读取时与scanf别无差异fprintf(stdout, "%s %d %lf", s1.name, s1.age, s1.score);//关闭文件fclose(pf);pf = NULL;return 0;
}

在这里插入图片描述

  
  
  

3.5、与字符串相关的输入输出:sscanf、sprintf

3.5.1、对比说明

  1)、scanf/printf、fscanf/fprintf、sscanf、sprintf对比说明

  scanf:针对标准输入输出流的格式化输入函数,Read formatted data from stdin
  printf:针对标准化输入输出流的格式化输出函数,Print formatted data to stdout

scanf
int scanf ( const char * format, ... );
printf
int printf ( const char * format, ... );

  
  fscanf:针对所有输入流的格式化输入函数,Read formatted data from stream
  fprintf:针对所有输出流的格式化输出函数,Write formatted data to stream

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

  
  sscanf:把一个字符串数据转换为格式化数据,Read formatted data from string
  sprintf:把一个格式化的数据转换为字符串数据,Write formatted data to string

sscanf
int sscanf ( const char * s, const char * format, ...);
sprintf
int sprintf ( char * str, const char * format, ... );

  
  

3.5.2、使用介绍

  相关函数链接:sprintf
在这里插入图片描述
  
  相关函数链接:sscanf
在这里插入图片描述

  
  使用举例:

struct st
{char name[20];int age;double score;
};int main()
{//打开文件FILE* pf = fopen("D:\\Daily\\test.txt", "r");if (pf == NULL){perror("fopen");return 1;}//读写操作struct st s1 = { "李四",23,94.5 };//原数据struct st stof = { 0 };//未赋有效值的结构体:用于存放从字符串中转换过来的格式化数据char ftos[255];//字符数组:用于存放从格式化数据中转换得到的字符串//使用sprintf,将格式化数据转换成字符串sprintf(ftos, "%s %d %lf", s1.name, s1.age, s1.score);printf("ftos数组中:%s\n", ftos);//使用sscanf,将字符串转换为格式化数据sscanf(ftos, "%s %d %lf", stof.name, &(stof.age), &(stof.score));printf("stof结构体中:%s %d %lf\n", stof.name, stof.age, stof.score);//关闭文件fclose(pf);pf = NULL;return 0;
}

在这里插入图片描述
  
  
  

3.6、二进制输入输出:fread、fwrite

3.6.1、fwrite

  相关函数链接:fwrite
在这里插入图片描述
  注意理解该函数各参数含义,ptr是要写入的元素数组的指针,size是单个此类数据的大小(每个元素的大小),count是要写入这类数据的数目(元素个数),stream为指定输出的流。

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

  相关演示:

struct st
{char name[20];int age;double score;
};int main()
{//打开文件FILE* pf = fopen("D:\\Daily\\test.txt", "wb");if (pf == NULL){perror("fopen");return 1;}//读写操作struct st s1 = { "李四",23,94.5 };fwrite(&s1, sizeof(struct st), 1, pf);//含义:将s1中的数据输出至pf中,个数为1个,每个大小为sizeof(struct st)//关闭文件fclose(pf);pf = NULL;return 0;
}

  
在这里插入图片描述

  
  

3.6.2、fread

  相关函数链接:fread
在这里插入图片描述

  
  相关演示:

struct st
{char name[20];int age;double score;
};int main()
{//打开文件FILE* pf = fopen("D:\\Daily\\test.txt", "rb");if (pf == NULL){perror("fopen");return 1;}//读写操作struct st s1 = { 0 };fread(&s1, sizeof(struct st), 1, pf);printf("%s %d %lf\n", s1.name, s1.age, s1.score);//关闭文件fclose(pf);pf = NULL;return 0;
}

在这里插入图片描述
  
  
  

4、文件的随机读写

4.1、fseek

  相关函数链接:fseek
  根据文件指针的位置和偏移量,重新定位文件指针。
在这里插入图片描述

SEEK_SET	Beginning of file
SEEK_CUR	Current position of the file pointer
SEEK_END	End of file *

  
  1)、关于随机位置读取


int main()
{//打开文件FILE* pf = fopen("D:\\Daily\\test.txt", "r");if (pf == NULL){perror("fopen");return 1;}//文件读写////文件中存储的例句:turn lemon into lemonade.for (int i = 0; i < 4; i++){printf("%c", fgetc(pf));//turn}printf("\n");//至此,文件指针指向trun lemon中的空白字符//演示一:以当前文件指针作为参考点fseek(pf, -1, SEEK_CUR);//当前指向turn中的nprintf("文件向左偏移一位后:%c\n", fgetc(pf));//fgetc读取后后挪一位,到空白字符fseek(pf, 1, SEEK_CUR);//当前指向为lemon中的lprintf("文件向右偏移一位后:%c\n", fgetc(pf));//fgetc读取后后挪一位,到eprintf("\n");//演示二:以文件头作为参考点,此时偏移量不能为负数fseek(pf, 3, SEEK_SET);//当前指向trun中的nprintf("文件向右偏移三位后:%c\n", fgetc(pf));printf("再次读取一位:%c\n", fgetc(pf));printf("\n");//演示三:以文件尾作为参考点,此时偏移量不能为正数fseek(pf, -3, SEEK_END);//当前指向为lemonade.中的dprintf("文件向左偏移三位后:%c\n", fgetc(pf));printf("再次读取一位:%c\n", fgetc(pf));printf("\n");////关闭文件fclose(pf);pf = NULL;return 0;
}

在这里插入图片描述

  
  
  2)、关于随机位置写入

int main()
{//打开文件FILE* pf = fopen("D:\\Daily\\test.txt", "w");if (pf == NULL){perror("fopen");return 1;}//文件读写//for (char ch = 'a'; ch <='z'; ch++){fputc(ch,pf);}//演示一:以当前文件指针作为参考点fseek(pf, -22, SEEK_CUR);fputc('S', pf);//演示二:以文件头作为参考点,此时偏移量不能为负数fseek(pf, 6, SEEK_SET);fputc('O', pf);//演示三:以文件尾作为参考点,此时偏移量不能为正数fseek(pf, -3, SEEK_END);fputc('S', pf);////关闭文件fclose(pf);pf = NULL;return 0;
}

在这里插入图片描述

  
  
  

4.2、ftell

  返回文件指针相对于起始位置的偏移量
  相关函数链接:ftell
  
  代码演示如下:

int main()
{//打开文件FILE* pf = fopen("D:\\Daily\\test.txt", "w");if (pf == NULL){perror("fopen");return 1;}//文件读写//for (char ch = 'a'; ch <='z'; ch++){fputc(ch,pf);}fseek(pf, -22, SEEK_CUR);fputc('S', pf);long int pos = ftell(pf);printf("%ld\n", pos);////关闭文件fclose(pf);pf = NULL;return 0;
}

在这里插入图片描述

  
  

4.3、rewind

  相关函数链接:rewind
  让文件指针的位置回到文件的起始位置
在这里插入图片描述

  相关演示:

int main()
{//打开文件FILE* pf = fopen("D:\\Daily\\test.txt", "w");if (pf == NULL){perror("fopen");return 1;}//文件读写//for (char ch = 'a'; ch <='z'; ch++){fputc(ch,pf);}fseek(pf, -22, SEEK_CUR);fputc('S', pf);long int pos = ftell(pf);printf("%ld\n", pos);rewind(pf);printf("%ld\n", ftell(pf));////关闭文件fclose(pf);pf = NULL;return 0;
}

在这里插入图片描述
  
  
  
  

5、其它相关内容

5.1、文本文件和二进制文件

  1)、数据文件分类
  根据数据的组织形式,数据文件被称为文本文件或者二进制文件
  数据在内存中以二进制的形式存储,如果不加转换直接输出到外存,就是二进制文件。
  如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的文件就是文本文件。
在这里插入图片描述

  
  
  2)、数据在内存中的存储方式
  字符型数据一律以ASCII形式存储;
  数值型数据既可以用ASCII形式存储,也可以使用二进制形式存储。
  
  

  3)、举例演示
  以整数10000为例子:
在这里插入图片描述
  
  一个测试代码:
  我们以二进制的形式将正数10000写入文件中

#include <stdio.h>
int main()
{int a = 10000;FILE* pf = fopen("D:\\Daily\\test.txt", "wb");fwrite(&a, 4, 1, pf);//二进制的形式写到文件中fclose(pf);pf = NULL;return 0;
}

  假如用文本文件的形式打开,可以看到是乱码
在这里插入图片描述
  现在我们在VS中以二进制的形式打开,需要做一点设置,操作如下:
在这里插入图片描述
在这里插入图片描述

  
  
  
  

5.2、文件读取结束判定

  1)、关于feof函数说明
  在文件读取过程中,不能用feof函数的返回值直接用来判断文件的是否结束。feof是应用于当文件读取结束的时候,判断是读取失败结束,还是遇到文件尾结束。
在这里插入图片描述
  
  
  2)、关于文件读取结束说明
  根据文件类型的区别,判断文件读取结束具有一定区别:

  对文本文件读取是否结束,有两种判断方法:1、判断返回值是否为 EOF;2、判断返回值是否为 NULL 。
  例如:fgetc 函数中,判断文件读取结束是看是否返回 EOF,而fgets判断文件读取结束是看返回值是否为 NULL

  对二进制文件读取是否结束,可通过返回值是否小于实际要读的个数来判断。
  例如:fread判断返回值是否小于实际要读的个数。
  
  
  

5.3、文件缓冲区

  1)、什么是文件缓冲区
  ANSIC 标准采用“缓冲文件系统”处理的数据文件。所谓缓冲文件系统,是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”。

  2)、为什么需要文件缓冲区
  文件缓冲区是一个链接内存与硬盘的过渡段,从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。之所以要这样做,是因为内存和硬盘处理数据的速度不一,若直接从磁盘中读取数据输送到内存, 会面临频繁访问、占用内存资源的现象。故而在二者之间搭建出一个中间商。
  需要注意的是,缓冲区的大小是根据C编译的系统来决定。
  
  
  
  
  


http://www.ppmy.cn/news/30255.html

相关文章

【Java】反射机制和代理机制

目录一、反射1. 反射概念2. 反射的应用场景3. 反射机制的优缺点4. 反射实战获取 Class 对象的四种方式二、代理机制1. 代理模式2. 静态代理3. 动态代理3.1 JDK动态代理机制1. 介绍2.JDK 动态代理类使用步骤3. 代码示例3.2 CGLIB 动态代理机制1.介绍2.CGLIB 动态代理类使用步骤3…

Hyperf使用RabbitMQ消息队列

Hyperf连接使用RabbitMQ消息中间件 传送门 使用Docker部署RabbitMQ&#xff0c;->传送门<使用Docker部署Hyperf&#xff0c;->传送门-< 部署环境 安装amqp扩展 composer require hyperf/amqp安装command命令行扩展 composer require hyperf/command配置参数 假…

如何打造一款专属于自己的高逼格电脑桌面

作为一名电脑重度使用者&#xff0c;你是否拥有一款属于你自己的高逼格电脑桌面呢&#xff1f;你是不是也像大多数同学一样&#xff0c;会把所有的内容全部都堆积到电脑桌面&#xff0c;不仅找东西困难&#xff0c;由于桌面内容太多还会导致C盘空间不足&#xff0c;影响电脑的反…

G. Special Permutation(构造)

1、题目 G. Special Permutation 这道题的意思是给我们从111到nnn的排列&#xff0c;然后我们对这个排列的顺序上进行调换&#xff0c;需要满足的条件是任意两个相邻元素的绝对值的差满足条件&#xff1a;2≤∣pi−pi1∣≤42\leq |p_i-p_{i 1}|\leq 42≤∣pi​−pi1​∣≤4 …

守护进程 || 精灵进程

目录 守护进程&#xff08;deamon&#xff09; || 精灵进程 特点 什么是前台进程组 把自己写的服务器deamon deamon代码 守护进程&#xff08;deamon&#xff09; || 精灵进程 特点 01. 他的PPID是1&#xff08;附件特征&#xff09;02. COMMAND --- 称为进程启动的命令03…

Python每日一练(20230305)

目录 1. 正则表达式匹配 ★★★ 2. 寻找旋转排序数组中的最小值 II ★★★ 3. 删除排序链表中的重复元素 II ★★ 1. 正则表达式匹配 给你一个字符串 s 和一个字符规律 p&#xff0c;请你来实现一个支持 . 和 * 的正则表达式匹配。 . 匹配任意单个字符* 匹配零个或多个…

向Spring容器中注入bean有哪几种方式?

文章前言&#xff1a; 写这篇文章的时候&#xff0c;我正在手机上看腾讯课堂的公开课&#xff0c;有讲到 Spring IOC 创建bean有哪几种方式&#xff0c;视频中有提到过 set注入、构造器注入、注解方式注入等等&#xff1b;于是&#xff0c;就想到了写一篇《Spring注入bean有几种…

什么是Hibernate框架?

简单介绍&#xff1a;Hibernate框架是当今主流的java持久层框架之一&#xff0c;是一个开放源码的ORM&#xff08;Object Relational Mapping&#xff0c;对象关系映射&#xff09;框架&#xff0c;它对JDBC进行了轻量级的封装&#xff0c;使得JAVA开发人员可以使用面向对象的编…