使用文件可以存放数据,使里面的内容永久保存下来,下次打开的时候不会丢失
磁盘上的文件就是文件
但程序设计中,主要分为程序文件和数据文件
源文件(后缀.c),目标文件(windows环境后缀位.obj),可执行程序(windows环境后缀为.exe)
文件内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件。或者输出内容的文件。
一个文件要有唯一的文件标识(文件名),以便用户识别和引用。
文件名包括三部分:文件路径+文件名主干+文件后缀
例:c:code\test.txt 其中c:code是文件路径,test.txt是文件名主干和文件后缀
关键的概念是“文件类型指针”,简称“文件指针”。
每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态以及文件当前位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是有系统声明的,取名FILE。
可以搜一下MSDN来搜其功能
在操纵其他文件的路径的话要在文件名前加上绝对路径,否则只会在运行程序当前的路径新建。(ps:注意下方的w装的是双引号)
int main()
{
FILE* pf = fopen("test.txt","w");
if(NULL==pf)
{
printf("打开文件失败\n");
return 0;
}
return 0;
}
pf是一个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区(是一个结构体变量)。通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够找到与它关联的文件。
当出现error时,会返回一个空指针
int main()
{
//打开文件
FILE* pf = fopen("test.txt","w");
if(NULL==pf)
{
printf("打开文件失败\n");
return 0;
}
//写文件
//关闭文件
fclose(pf);
pf=NULL;
return 0;
}
字符输入函数——fgetc
字符输出函数——fputc
文本行输入函数——fgets
文本行输出函数——fputs
写文件
int main()
{
FILE* pf = fopen("data.txt","w");
if(pf==NULL)
{
printf("%s\n",strerror(errno));
return 0;
}
//写文件
//fputc('a',pf);
//fputc('b',pf);
//fputc('c',pf);
char ch=0;
for(ch='a',ch<='z';ch++)
{
fputc(ch,pf);
}
fclose(pf);
pf=NULL;
}
A:我们都知道,对文件进行操作的表现是需要用代码来进行输入以及输出的,但是为什么c语言程序输出到屏幕时不需要写什么代码?
Q:C语言程序,只有运行起来。就默认打开三个流:
FILE*类型:stdin-标准输入流 stdout-标准输出流 stderr-标准错误流
所以才能直接在屏幕上出现
所以,当我们在使用fputc中使用pf时,为文件流,在文件中输出,将这一行代码的pf改为strout标准输出流,就会在屏幕上打印出来。
fgetc()——字符输入函数—— int fgetc(FILE* stream)
(接上面)
int ch=fgetc(pf);
printf("%c\n",ch);
//继续往下读
int ch=fgetc(pf);
printf("%c\n",ch);
//继续往下读
int ch=fgetc(pf);
printf("%c\n",ch);
int ch=fgetc(pf);
printf("%c\n",ch);
这个可以一直读,直到读完了,就返回EOF,或者读取中途出现错误,也返回EOF
while((ch=fgetc(pf))!=EOF)
{
printf("%c ",ch);
}
fputs——文本行输出操作 int fputs() (写文件到文本里面去)——标准输出流
写个写一行读一行的操作:
int main()
{
FILE* pf = fopen("data.txt","w");
if(pf==NULL)
{
printf("%s\n",strerror(errno));
return 0;
}
fputs("hello world",pf);
fclose(pf);
pf=NULL;
}
标准输出流:既可以写到文本也可以在屏幕
fputs("hello world",stdout);
读一行:
fgets——char* fgets(char* string,int n,FILE* stream)
n——空间容纳最大个数,例如放1000进去只会读取999
#define _CRT_SECURE_NO_WARNINGS 1
#include
int main()
{
FILE* pf = fopen("data.txt", "r");
if (NULL == pf)
{
printf("打开失败\n");
return 0;
}
char arr[1000] = { 0 };
fgets(arr, 1000, pf);
printf("%s\n", arr);
fclose(pf);
pf = NULL;
return 0;
}
写一份文件拷贝
//拷贝文件
#define _CRT_SECURE_NO_WARNINGS 1
#include
#include
#include
int main()
{
FILE* pr = fopen("data.txt", "r");
if (NULL == pr)
{
printf("openning is:%s\n", strerror(errno));
return 0;
}
FILE* pw = fopen("data2.txt", "w");
if (NULL == pw)
{
printf("openning is:%s\n", strerror(errno));
return 0;
}
int ch = 0;
while ((ch = fgetc(pr)) != EOF)
{
fputc(ch, pw);
}
fclose(pr);
pr = NULL;
fclose(pw);
pw = NULL;
return 0;
}
对于结构化数据,有具体格式化数据的,应该怎么输入输出?
fprintf——格式化输出数据——int fprintf( FILE* stream, const char* format [, argument ]...**);
相较于printf,会发现其实只是多一个FILE*罢了
写格式化数据
//格式化数据的输出
#define _CRT_SECURE_NO_WARNINGS 1
#include
#include
#include
//假设给上一个结构体:
struct Stu
{
char name[20];
int age;
float score;
};
int main()
{
struct Stu s = { "Lisi",18,90.9 };
FILE* pf = fopen("data.txt", "w");
if (NULL == pf)
{
printf("openning is %s\n", strerror(errno));
return 0;
}
fprintf(pf, "%s %d %.2f", s.name, s.age, s.score);
fclose(pf);
pf = NULL;
return 0;
}
读格式化数据
fscanf——格式化输入数据 ——int fscanf( FILE *stream, const char *****format [, argument ]... );
我们使用这个与scanf比较,又可以发现只是多了一个FILE*
//读格式化数据
#define _CRT_SECURE_NO_WARNINGS 1
#include
#include
#include
struct Stu
{
char name[20];
int age;
float score;
};
int main()
{
struct Stu s = { 0 };
FILE* pf = fopen("data.txt", "r");
if (NULL == pf)
{
printf("error is: %s\n", strerror(errno));
}
fscanf(pf, "%s %d %f", s.name, &s.age, &s.score);
printf("%s %d %f\n", s.name, s.age, s.score);
fclose(pf);
pf = NULL;
return 0;
}
fwrite—— size_t fwrite( const void buffer, size_t size,* size_t count , FILE *****stream );
//以二进制的方式写文件
#define _CRT_SECURE_NO_WARNINGS 1
#include
#include
#include
struct Stu
{
char name[20];
int age;
float score;
};
int main()
{
struct Stu s[2] = { {"Zhangsan",18,90.6},{"Lisi",20,95.5} };
FILE* pf = fopen("data.txt", "wb");
if (NULL == pf)
{
printf("openning erroe is %s\n", strerror(errno));
return 0;
}
fwrite(s, sizeof(struct Stu), 2, pf);
fclose(pf);
pf = NULL;
return 0;
}
fread——读取文件 size_t fread( void buffer, size_t size, *size_t count,FILE *****stream );
ps:这里fread的返回值是你读到多少个元素就返回多少值,比如一开始设定读五个,如果有五个,就返回五,如果没有,例如只剩下三个了,就只能返回三。
与上面的二进制方式写入的格式是差不多的
//以二进制方式读
#define _CRT_SECURE_NO_WARNINGS 1
#include
#include
#include
struct Stu
{
char name[20];
int age;
float score;
};
int main()
{
struct Stu s[2] = { 0 };
FILE* pf = fopen("data.txt", "rb");
if (NULL == pf)
{
printf("error is%s\n", strerror(errno));
return 0;
}
fread(s, sizeof(struct Stu), 2, pf);
printf("%s %d %f\n", s[0].name, s[0].age, s[0].score);
printf("%s %d %f\n", s[1].name, s[1].age, s[1].score);
return 0;
}
sprintf——把格式化数据转换成字符串 ——int sprintf( char* buffer, const char* format [, argument] ... );
相比fprintf,sprintf是直接把格式化的数据放到了一个字符串内。
sscanf——与sprintf相似。但是是与sprintf功能相反。将一个字符串按照某些我们写的格式放入结构体之中。 ——int sscanf( const char* buffer, const char* format [, argument ] ... );
ps:这里此时已经与文件相关没有关系了。
//sprintf与sscanf的使用
#define _CRT_SECURE_NO_WARNINGS 1
#include
#include
#include
struct Stu
{
char name[20];
int age;
float score;
};
int main()
{
struct Stu s1 = { "Lisi",20,96.6 };
char arr[100] = { 0 };
sprintf(arr, "%s %d %.2f", s1.name, s1.age, s1.score);
printf("%s\n", arr);
struct Stu s2 = { 0 };
sscanf(arr, "%s %d %f", s2.name, &s2.age, &s2.score);
printf("%s %d %.2f\n", s2.name, s2.age, s2.score);
return 0;
}
注意区别:
根据文件指针的位置和偏移量来定位文件指针
打开文件时用的是指向起始位置,我们可以通过这个函数来调整指向的位置
在正式使用前,要知道几个参数的含义:
fseek——int fseek( FILE stream, long offset,* int origin );
在这里origin又分为一下三种(选择从哪里开始读取):
ps:当选择END的时候要记得偏移量是负数,因为要倒着数
输出数据到文件内:
#define _CRT_SECURE_NO_WARNINGS 1
#include
#include
#include
//这时在已经创造了一个名为data的txt文件并且已经放入abcdef的情况下写的
int main()
{
char ch = 0;
FILE* pf = fopen("data.txt", "r");
if (NULL == pf)
{
printf("error is%s\n", strerror(errno));
return 0;
}
//单个读取字符
ch = fgetc(pf);//这里读到字符a
printf("%c\n", ch);
ch = fgetc(pf);//这里读到字符b
printf("%c\n", ch);
//指定位置到f
//fseek(pf, 3, SEEK_CUR);//从当前b开始读,往后三个偏移量
//fseek(pf, 5, SEEK_SET);//从起始位置开始往后五个偏移量指向f
fseek(pf, -1, SEEK_END);//从末尾开始,往前一个偏移量指向f
ch = fgetc(pf);//经过移动后,直接跳过读到了字符f
printf("%c\n", ch);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
将文件内的数据修改:
//将文件中的某一位置数据给替换掉
#define _CRT_SECURE_NO_WARNINGS 1
#include
#include
#include
int main()
{
char ch = 0;
FILE* pf = fopen("data.txt", "w");
if (NULL == pf)
{
printf("error is%s\n", strerror(errno));
return 0;
}
for (ch = 'a'; ch <= 'z'; ch++)//向data.txt文件先放入a~z
{
fputc(ch, pf);
}
fseek(pf, -2, SEEK_END);//先指向y的位置
ch = '#';
fputc(ch, pf);//把y位置替换成#
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
放回文件指针相对于起始位置的偏移量
ftell——long ftell( FILE *stream );
ftell的实际应用
#define _CRT_SECURE_NO_WARNINGS 1
#include
#include
#include
//这时在已经创造了一个名为data的txt文件并且已经放入abcdef的情况下写的
int main()
{
FILE* pf = fopen("data.txt", "r");
if (NULL == pf)
{
printf("error is%s\n", strerror(errno));
return 0;
}
char ch = 0;
ch = fgetc(pf);//此时读入的是a
printf("%c\n", ch);
ch = fgetc(pf);//此时读入的是b
printf("%c\n", ch);
//假如此时我们不知道指针已经指向哪里了,那么便可以创造一个变量来存储调用ftell函数返回的指针指向的偏移量的值
int ret = ftell(pf);
printf("%d\n", ret);
fclose(pf);
pf = NULL;
return 0;
}
将文件指针指向起始位置
rewind—— void rewind( FILE * );
//假设有一个FILE*变量为pf,此时这个pf代表的文件的指针不知道指向哪里了
rewind(pf);//这个时候就已经把指针提到起始位置了
数据文件的分类
数据文件被分为文本文件与二进制文件
二进制文件:数据在内存中以二进制的形式存储,如果不加转换的输出到外存。
文本文件:如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的文件。
字符一律以ASCII形式存储,数值型数据既可以用ASCII形式存储,也可以使用二进制形式存储。
例如整数10000:
ASII形式存储:
因为有五个字符,所以会用到五个字节,每个字节存储的是对应数字字符
二进制形式存储:
会将10000转化为二进制来存储,用到四个字节。
#include
#include
int main(void)
{
int c; // 注意:int,非char,要求处理EOF
FILE* fp = fopen("test.txt", "r");
if (!fp) {
perror("File opening failed");
return EXIT_FAILURE;
}
//fgetc 当读取失败的时候或者遇到文件结束的时候,都会返回EOF
while ((c = fgetc(fp)) != EOF) // 标准C I/O读取文件循环
{
putchar(c);
}
//判断是什么原因结束的
if (ferror(fp))//读取失败了,会返回非0值,否则返回0
puts("I/O error when reading");
else if (feof(fp))//当是读完全部正常结束的,会返回非0值,否则是因为其他原因结束的,则返回0
puts("End of file reached successfully");
fclose(fp);
}
不同编译器的缓冲方式不同
为了让办事效率高,很多代码都会集中在缓冲区,等到缓冲区放满,到一定程度就会一起运行。
当我们写文件的时候,会先放入缓冲区,不会立马写在文件里面,如果要立马再出去缓冲区,可以刷新缓冲区。
fflush(FILE*) fclose也可以刷新缓冲区