文章目录
语言文件操作
使用文件是为了数据持久化(保存信息)
文件不一定需要后缀名
文件
程序设计中,常见的文件有两种:程序文件,数据文件(从文件功能角度分类)
程序文件
包括源程序文件后缀为.c,目标文件.obj,可执行文件.exe(均为windows环境)
数据文件
文件内容不一定是程序,而是程序文件读写时的数据,如从程序运行从中读取或输出的文件
文件名
一个文件要有一个唯一的文件标识,方便识别使用
文件名由三部分:文件路径,文件名主干,文件后缀
文件标识常常被叫文件名
文件打开和关闭
文件指针
文件类型的指针,简称“文件指针”
操作文件->打开文件->内存中创建文件信息区(系统声明的结构体变量类型为struct FILE)->文件名,文件状态,位置等等
一般通过FILE*指针维护FILE结构变量
文件指针变量可以找到与它关联的文件进行操作
文件的开关
fopen
打开文件
<stdio.h>
FILEfopen(const char * filename,const char mod)
filename
文件名
mod
文件的打开方式
打开文件时就会在内存中创建一个文件信息区,并和文件关联,同时返回文件的起始地址
fcols
关闭文件
<stdio.h>
int fclose(FILE*stream)
stream
指向要关闭的FILE对象的指针
成功关闭,返回零
失败返回空指针
int main()
{
FILE* pf=fopen("D:\\code\\test.txt","r");
//FILE*是结构体指针
//绝对路径非常详细
//相对路径
//.表示当前目录,../上一级目录,../../上一级的上一级目录
FILE* pp=fopen("..\\test2.txt","r");
//以读的形式打开
//打开失败返回空指针
if(pf==NULL)
{
perror("fopen");
return 1;
}
else
{
printf("打开成功\n");
}
//读文件...
//关闭文件
fclose(pf);
pf=NULL;
return 0;
}
使用方式 | 意义 | 文件不存在 |
---|---|---|
“r”(只读) | 为了输入数据,打开一个已经存在的文本文件 | 报错 |
“w”(只写) | 为了输出数据,打开一个文本文件 | 删除原来的内容覆盖写入一个文件 |
“a”(追加) | 向文本文件尾添加数据 | 建立一个新的文件 |
“rb”(只读) | 为了输入数据,打开一个二进制文件 | 出错 |
“wb”(只写) | 为了输出数据,打开一个二进制文件 | 新建一个文件 |
“ab”(追加) | 向一个二进制文件尾添加数据 | 出错 |
“r+”(读写) | 为了读和写,打开一个文本文件 | 出错 |
“w+”(读写) | 为了读和写,建一个新的文件 | 新建一个文件 |
“a+”(读写) | 打开一个文件,在文件尾进行读写 | 新建一个文件 |
“rb+”(读写) | 为了读和写打开一个二进制文件 | 出错 |
“wb+”(读写) | 为了读和写,新建一个新的二进制文件 | 新建一个文件 |
“ab+”(读写) | 打开一个二进制文件,在文件尾进行读和写 | 新建一个文件 |
文件顺序读写
功能 | 函数名 | 适用于 |
---|---|---|
字符输入函数 | fgetc | 所有输入流 |
字符输入函数,向文件写入字符 | fputc | 所有输出流 |
文本行输入函数 | fgets | 所有输入流 |
文本行输出函数 | fputs | 所有输出流 |
格式化输入函数 | fscanf | 所有输入流 |
格式化输出函数 | fprintf | 所有输出流 |
二进制输入函数 | fred | 文件 |
二进制输出函数 | fwrite | 文件 |
流
数据->流->硬件设备
流是由FILE指针定义管理的
任何c语言程序运行时,默认打开三个流
stdin->标准输入(键盘) 类型:FILE 维护键盘输入流
stdout->标准输出(屏幕) 类型:FILE 维护屏幕输出流
stderr->标准错误(屏幕) 类型:FILE 维护屏幕输出流
struct S
{
char name[20];
int age;
float score;
};
int main()
{
struct S s = { 0 };
fscanf(stdin, "%s %d %f", s.name, &(s.age), &(s.score));
fprintf(stdout, "%s %d %f\n", s.name, s.age, s.score);
return 0;
}
fgetc
<stdio.h>
int fgetc(FILE* stream)
stream:FILE*类型的指针对象
读取成功,返回读到的字符(int类型)
读取失败,返回空指针
每次读取一个字符
int main()
{
//打开一个文件
//"r"为只读
FILE* ptr = fopen("..\\test.txt", "r");
if (ptr == NULL)
{
perror("fopen:");
return 1;
}
//使用文件
char a = 'a';
for (a; a <= 't'; a++)
{
fputc(a, ptr);
}
//关闭文件
fclose(ptr);
ptr = NULL;
return 0;
}
fputc
<stdio.h>
int fputc(int character,FILE*strem)
character:要输入的int类型的字符
strem:指向文件的指针
将字符写入文件,并且指针自动前进一,以便下一次输入
成功,返回输入字符
失败,返回EOF并设置ferror
int main()
{
//打开一个文件
//"w"为只写
FILE* ptr = fopen("..\\test.txt", "w");
if (ptr == NULL)
{
perror("fopen:");
return 1;
}
//使用文件
int i=0;
for(i;i<10;i++)
{
}
//关闭文件
fclose(ptr);
ptr = NULL;
return 0;
}
fgets
<stdio.h>
char * fgets ( char * str, int num, FILE * stream )
str:读到的字符存放的地方
num:最多读取的字符数,读取数要考虑‘\0’也在内
stream:FILE*类型的文件
int main()
{
//打开一个文件
//"r"为只读
FILE* ptr = fopen("..\\test.txt", "r");
if (ptr == NULL)
{
perror("fopen:");
return 1;
}
//使用文件
char ch[20]={0};
fgets(ch,5,ptr);
printf("%s",ch);
//关闭文件
fclose(ptr);
ptr = NULL;
return 0;
}
fputs
<stdio.h>
int fputs(const char*str,FILE *stream)
将str指针指向的字符串,写入到stream指向的文件中
int main()
{
//打开一个文件
//"w"为只写
FILE* ptr = fopen("..\\test.txt", "w");
if (ptr == NULL)
{
perror("fopen:");
return 1;
}
//使用文件
fputs("hello world\n",ptr);
//关闭文件
fclose(ptr);
ptr = NULL;
return 0;
}
fprintf
<stdio.h>
int fprintf(FILE*stream,const char *format,…)
…为可变参数列表
FILE:FILE类型的指针
format:C 字符串,包含一系列字符,这些字符控制如何处理从流中提取的字符
typedef struct Class
{
char name[20];
int age;
double height;
}Clase;
int main()
{
Clase C = { "xiaosan",18,1.65 };
FILE* ptr = fopen("..\\test.txt","w");
if (ptr == NULL)
{
perror("fopen:");
return 1;
}
//使用
fprintf(ptr,"%s\n%d\n%.2lf\n" ,C.name, C.age, C.height);
//关闭文件
fclose(ptr);
ptr = NULL;
return 0;
}
fscanf
<stdio.h>
int fscanf ( FILE * stream, const char * format, … )
stream:FILE指针类型的变量
format:C 字符串,包含一系列字符,这些字符控制如何处理从流中提取的字符
typedef struct Class
{
char name[20];
int age;
double height;
}Clase;
int main()
{
Clase C = {0};
FILE* ptr = fopen("..\\test.txt","r");
if (ptr == NULL)
{
perror("fopen:");
return 1;
}
int a=fscanf(ptr,"%s %d %lf", C.name, &(C.age), &(C.height));
if (a == EOF)
{
perror("fscanf:");
return 1;
}
printf("%s %d %.2lf", C.name, C.age, C.height);
//关闭文件
fclose(ptr);
ptr = NULL;
return 0;
}
输入输出
printf
内存->输出/写 ->屏幕
键盘->输入/读->内存
scanf
fputc/fputs/fprintf
内存->输出/写->文件
文件->输入/读->内存
fgetc/fgets/fscanf
二进制输入输出
fwrite
<stdio.h>
size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream )
ptr:指向要被写的数据
size:元素大小
count:元素个数
stream:指向的FILE
struct S
{
char name[20];
int age;
float score;
};
int main()
{
struct S s={"张三",20,99.5};
FILE* p=fopen("..\\test.txt","wb");
if(NULL==pf)
{
perror("fopen");
return 1;
}
fwrite(&s,sizeof(struct S),1,pf);
fclose(p);
p==NULL;
return 0;
}
fread
<stdio.h>
size_t fread(void*ptr,size_t size,size_t count,FILE * stream)
从stream流中读count个大小为size的数据,放到ptr位置上
ptr:指向要被读的数据
size:元素大小
count:元素个数
stream:FILE指针类型
返回成功读取的个数
struct S
{
char name[20];
int age;
float score;
};
int main()
{
struct S s={0};
FILE* p=fopen("..\\test.txt","rb");
if(NULL==pf)
{
perror("fopen");
return 1;
}
//读文件
fread(&s,sizeof(struct S),1,pf);
printf("%s %d %f\n",s.name,s.age,s.score);
fclose(p);
p==NULL;
return 0;
}
函数对比
scanf :从键盘上输入(读)取格式化的数据 stdin
printf :把数据输出(写)到屏幕上 stdout
fscanf : 针对所有输入流的格式化的输入函数 stdin…
fprintf : 针对所有输出流的格式化输出函数 stdout…
sscanf : 从一个字符串中还原出一个格式化的数据
sprintf : 把格式化的数据存放在一个字符串中
sprintf
<stdio.h>
int sprintf(char* str,const char* format,…);
把可变列表中的内容按照format格式放到str中
struct Class
{
char name[20];
int age;
float score;
};
int main()
{
struct Class c={"xiaosan",18,60.0};
char buf[100]={0};
//把结构体数据转换成字符串
sprintf(buf,"%s %d %f",c.name,c.age,c.score);
printf("%s\n",buf);
return 0;
}
sscanf
<stdio,h>
int sscanf(const char* s,const char *format,…)
从字符串中读取格式化数据
从 s 读取数据并根据参数格式将它们存储到附加参数给出的位置,就像使用 scanf 一样,但从 s 而不是标准输入 (stdin) 读取。
其他参数应指向格式字符串中相应格式说明符指定的类型的已分配对象。
struct Class
{
char name[20];
int age;
float score;
};
int main()
{
struct Class c={"xiaosan",18,60.0};
struct Class C={0};
char buf[100]={0};
//把结构体数据转换成字符串
sprintf(buf,"%s %d %.2f",c.name,c.age,c.score);
sscanf(buf,"%s %d %f",C.name,&(C.age),&(C.score));
printf("%s\n".buf);
printf("%s %d %.2f");
return 0;
}
struct Class
{
char name[20];
int age;
float score;
};
int main()
{
struct Class c={"xiaosan",18,60.0};
struct Class C={0};
char buf[100]={0};
//把结构体数据转换成字符串
sprintf(buf,"%s %d %.2f",c.name,c.age,c.score);
sscanf(buf,"%s %d %f",C.name,&(C.age),&(C.score));
printf("%s\n".buf);
printf("%s %d %.2f");
return 0;
}
文件随机访问
fseek
<stdio.h>
int fseek(FILE*stream,long int offset,int origin)
stream: 指向文件
offset:偏移量向左移动为负数,向右移动为正数
origin:
origin有三个选择
SEEK_SET:文件起始位置
SEEK_CUR:文件当前位置
SEEK_END:文件末尾位置
int main()
{
FILE* p = fopen("..\\test.txt", "r");
if (p == NULL)
{
perror("fopen");
}
else
{//使用一次fgetc指针就会指向下一个位置
int ch = fgetc(p);
printf("%c\n", ch);
//此时指向b
//设定的偏移起始位置才是最关键的
fseek(p,0,SEEK_SET);
ch = fgetc(p);
printf("%c\n", ch);
}
return 0;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SKvvDY0o-1676191866298)(C:\Users\小八\AppData\Roaming\Typora\typora-user-images\image-20230211224315660.png)]
ftell
<stdio.h>
返回文件指针相对于起始位置的偏移量
long int ftell(FILE* stream)
int main()
{
FILE* p = fopen("..\\test.txt", "r");
if (p == NULL)
{
perror("fopen");
}
else
{//使用一次fgetc指针就会指向下一个位置
int ch = fgetc(p);
printf("%c\n", ch);
//此时指向b
//设定的偏移起始位置才是最关键的
fseek(p,0,SEEK_SET);
ch = fgetc(p);
printf("%c\n", ch);
int i = ftell(p);
printf("%d\n", i);
fclose(p);
p=NULL;
}
return 0;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-76dVGB61-1676191866299)(C:\Users\小八\AppData\Roaming\Typora\typora-user-images\image-20230211224507600.png)]
rewind
更新指针指向位置使它指向起始位置
<stdio.h>
void rewind(FILE*stream)
int main()
{
FILE* P = fopen("..\\test.txt", "r");
if (P == NULL)
{
perror("fopen");
}
else
{
int c = fgetc(P);
printf("%c\n", c);
fseek(P,0,SEEK_CUR);
c = fgetc(P);
printf("%c\n", c);
rewind(P);
c = fgetc(P);
printf("%c\n", c);
fclose(P);
P=NULL;
}
return 0;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y2zQEO1S-1676191866299)(C:\Users\小八\AppData\Roaming\Typora\typora-user-images\image-20230211230212987.png)]
文本文件和二进制文件
所有文件的内容都是以二进制形式存储,但是如果文件最初使用二进制编码的字符如ASCLL码标识文本像字符串那样,这个文件就是文本文件
如果内容中的二进制值代表机器语言代码或数值数据使用相同的内部表示如long,double类型的值或者图片,这个文件就是二进制文件,包含二进制内容
int main()
{
int a = 10000;
FILE* pt = fopen("..\\test.txt", "wb");
fwrite(&a, 4, 1, pt);
fclose(pt);
pt = NULL;
return 0;
}
程序运行后,用文本编辑器打开test.txt文件,后发现10000被转换成
10 27 00 00
10000二进制是 0000 0000 0000 0000 0010 0111 0001 0000
十六进制: 00 00 27 10
小端存储: 10 27 00 00
左边的0000…是地址
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Dfu4bxAx-1676191866299)(C:\Users\小八\AppData\Roaming\Typora\typora-user-images\image-20230211234302601.png)]
文件读取结束的判定
feof的错误
EOF-文件结束标志->feof
在读取文件时不能用feof的返回值来判断文件的读取失败结束,还是遇到文件尾结束
- feof用于判断文件是否因为读到文件末尾而结束
- 如: fgetc函数读取失败返回EOF,读取成功返回读到的字符
- fgets函数读取失败返回NULL,读取失败返回空指针
- fread(void* ptr,size_t size,size_t nmemb,FILE*fp)
- 返回成功读取的数量,如果读取错误或失败就返回小于nmemb的数
- feof:返回真,说明文件正常读取遇到结束标志而结束
- ferror:返回真,说明文件在读取过程中出错了而结束判断是什么原因导致结束
文件缓冲区
#include<stdio.h>
#include<windows.h>
int main()
{
//以写的方式打开文件
FILE* pf = fopen("..\\test.txt", "w");
if (pf == NULL)
{
perror("fopen:");
return 1;
}
fputs("abcdef", pf);
//将字符串放到缓冲区中
printf("打开文件发现并没有字符串\n");
Sleep(10000);
printf("刷新缓冲区\n");
fflush(pf);
//刷新缓冲区
printf("再次打开文件字符串写入了文件\n");
Sleep(10000);
fclose(pf);
//fclose关闭文件时会刷新缓冲区
pf == NULL;
return 0;
}
第一次打开文件并没有写入文件
刷新缓冲区后再次打开文件发现字符串写入
从此处可以发现缓冲区的存在,所以C语言在操作文件的时候,需要做刷新缓冲区或者在文件操作结束的时候关闭文 件。 如果不做,可能导致读写文件的问题