我们在本地IDE(集成开发工具,如vs)中的写的程序中的数据只有在程序运行时才会在存储到内存中,运行结束时数据就不复存在了。如果我们想程序结束时,数据仍然存在,那么就要涉及数据持久化问题。数据持久化的方法有:把数据放到磁盘文件中,存放到数据库等方式。
文件是计算机管理数据的基本单位,同时也是应用程序保存和读取数据的一个重要场所,文件是存储在外部介质(如磁盘)上的以文件名标识 的数据的集合。存储在磁盘上的文件称为磁盘文件, 与计算机相连的设备称为设备文件。这些文件都不在 计算机内,统称为外部文件。在程序设计中,将磁盘文件按照其分类按照文件的功能分为:程序文件(后缀为.c/.obj/.exe)和数据文件(程序运行时读写的数据);而数据文件按照其存储的内容又分为二进制文件和文本文件,我们着眼于磁盘文件
为了区分不同的文件,必须给每个文件命名。
文件名由三部分组成:文件路径+文件名主干+后缀。例:c:\code\test.txt
常见文件的扩展名有.doc(Word文档)、.xls(Excel电子表格)、.ppt(Powerpoint演示文稿)、.txt(文本文档)、.rar(压缩文件)、.mp3(音乐)、.wma(音乐)、.wav(音乐)、.lrc(歌词文件)、.rmvb(视频)、.rm(视频)、.mp4(视频)、.3gp(视频)、.swf(flash动画文件)、.jpg(图片)、.gif(图片)、.bmp(图片)、.exe(程序,可执行文件)。
类比喝水:1.先打开瓶盖。2.喝水或者灌水。3.盖上瓶盖-----文件的操作也是如此:打开文件,读/写文件,关闭文件
类似整形指针,文件指针存放的是文件。
当你使用一个文件时,会在内存开辟一块空间(文件信息区),用来保存文件的信息(文件名,文件所在位置等等),这些信息是存放在一个由系统创建的FILE类型的结构体变量中。fopen函数会返回这个变量的地址,通常用一个FILE类型的结构体指针来指向该变量,以便使用者可以操作文件。
//例如:vs2013中对该结构体有如下定义:
struct _iobuf {
char *_ptr;
int _cnt;
char *_base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char *_tmpfname;
};
typedef struct _iobuf FILE;
//文件指针的定义
FILE* pf; //通过文件指针可以找到打开的文件
ANSIC规定用fopen函数打开文件。
:::
FILE*_ fopen (const char_ * filename, const char * mode);
:::
const char * filename:有两种写法:
1.绝对路径:包括文件路径。如:“c:\code\test.txt”
2. 相对路径:不包括文件路径,默认路径为当前目录。如:“test.txt”
注意:
const char* mode:文件的打开方式
mode | 功能 | 文件不存在 |
---|---|---|
“r” | 以只读方式打开文本文件 | 打开失败 |
“w” | 以只写方式打开文本文件 | 创建一个新文件 |
“a” | 以追加的方式打开文本文件 | 创建一个新文件 |
“r+” | 为了读写,打开文本文件 | 打开失败 |
“w+” | 为了读写,打开文本文件 | 创建一个新文件 |
“a+” | 打开一个文本文件在文件末尾读写 | 创建一个新文件 |
“rb” | 以读的方式打开一个二进制文件 | 创建一个新文件 |
“wb” | 以写的方式打开一个二进制文件 | 创建一个新文件 |
“ab” | 向一个二进制文件末尾追加数据 | 打开失败 |
“rb+” | 为了读和写打开二进制文件 | 打开失败 |
“wb+” | 为了读和写,建立一个二进制文件 | 创建一个新文件 |
“ab+” | 打开二进制文件,在文件尾进行读和写 | 创建一个新文件 |
注意:
FILE :fopen返回值*
注意:
FILE* pf = fopen("test.txt", "r");
if (NULL == pf)
{
perror("fopen::");
return;
}
通常用fclose函数来关闭不在使用的文件
:::
int fclose(FILE* stream);
:::
int 返回值:
FILE *stream:
//打开文件
FILE* pf = fopen("test.txt", "r");
if (NULL == pf)
{
perror("fopen::");
return;
}
//关闭文件
fclose(pf);
pf = NULL;
在c语言中,流表示任意输入的源或者任意输出的目的地。流常常表示存储在不同介质(如硬盘驱动器,CD,DVD和闪存)上的文件,但也很容易和不存储文件的设备(如网络端口,打印机等)相关联。
在学习文件的读写函数时,应该与scanf函数和printf函数对照着来。键盘,屏幕就是一种标准输入/输出流(设备文件)
由于
功能 | 函数名 | 适用于 |
---|---|---|
字符输入函数 | fgetc | 所有输入流 |
字符输出函数 | fputc | 所有输出流 |
文本行输入函数 | fgets | 所有输入流 |
文本行输出函数 | fputs | 所有输出流 |
格式化输入函数 | fscanf | 所有输入流 |
格式化输出函数 | fprintf | 所有输出流 |
二进制输入 | fread | 文件 |
二进制输出 | fwrite | 文件 |
fgetc:
:::
int fgetc(FILE* stream);
:::
返回值:
#include
int main(void)
{
FILE* pf = fopen("test.txt", "r");
if (NULL == pf)
{
perror("fopen::");
return 1;
}
int a = 0;
while ((a = fgetc(pf)) != EOF)
{
printf("%c", a);
}
fclose(pf);
pf = NULL;
return 0;
}
fputc:
:::
int fputc(int character, FILE* stream);
:::
返回值:
#include
int main(void)
{
//打开文件
FILE* pf = fopen("test.txt", "w");
if (NULL == pf)
{
return 1;
}
//写文件
int i = 0;
for (i = 0; i < 26; i++)
{
fputc('a'+i, pf);
}
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
fgets:
:::
char * fgets ( char * str, int num, FILE * stream );
:::
参数:
返回值:
#include
int main(void)
{
//打开文件
FILE* pf = fopen("test.txt", "w");
if (NULL == pf)
{
return 1;
}
//读文件-一行一行读
char arr[20] = "#########";
fgets(arr, 20, pf);
printf("%s", arr);
fgets(arr, 20, pf);
printf("%s", arr);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
fputs:
:::
int fputs ( const char * str, FILE * stream );
:::
返回值:
#include
int main(void)
{
//打开文件
FILE* pf = fopen("test.txt", "w");
if (NULL == NULL)
{
perror("fopen");
return 1;
}
//写入数据
fputs("hello\n", pf);
fputs("world\n", pf);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
fscanf:
:::
int fscanf ( FILE * stream, const char * format, … );
:::
fscanf与scanf的差别只是参数多了一个FILE* stream,
返回值:
struct str
{
char name[20];
int age;
int score;
};
int main(void)
{
struct str stu = { 0 };
FILE* pf = fopen("test.txt", "r");
if (NULL == pf)
{
return 1;
}
fscanf(pf, "%s %d %d", stu.name, &(stu.age), &(stu.score));
printf("%s %d %d", stu.name, stu.age, stu.score);
fclose(pf);
pf = NULL;
return 0;
}
fprintf:
:::
int fprintf ( FILE * stream, const char * format, … );
:::
fprintf与printf的差别也只是参数多一个FILE* stream,
struct str
{
char name[20];
int age;
int score;
};
int main(void)
{
struct str stu = { "zhaowu", 14, 55};
FILE* pf = fopen("test.txt", "w");
if (NULL == pf)
{
return 1;
}
fprintf(pf, "%s %d %d", stu.name, stu.age, stu.score);
fclose(pf);
pf = NULL;
return 0;
}
fread:
:::
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
:::
参数:
返回值:
int main(void)
{
FILE* pf = fopen("test.txt", "rb");
if (NULL == pf)
{
return 1;
}
int a = 0;
fread(&a, 4, 1, pf);
printf("%d", a);
fclose(pf);
pf = NULL;
return 0;
}
fwrite:
:::
size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
:::
fwrite与fread相似的参数,
fread:是从stream中读取count个大小为size字节的数据到ptr中
fwrite:是将ptr中count个大小为size字节的数据写到stream中
int main(void)
{
FILE* pf = fopen("test.txt", "wb");
if (NULL == pf)
{
return 1;
}
int a = 35;
fwrite(&a, 4, 1, pf);
fclose(pf);
pf = NULL;
return 0;
}
:::
printf/fprintf/sprintf
scanf/fscanf/sscanf
:::
sprintf函数:
函数原型:
功能:
用法:
struct str
{
char name[20];
int age;
int score;
};
int main(void)
{
struct str s = { "zhansa", 14, 99 };
char str[100] = { 0 };
//将"%s %d %d"这个字符串给str
sprintf(str, "%s %d %d", s.name, s.age, s.score);
printf("%s", str); //zhansa 14 99
return 0;
}
sscanf函数:
函数原型:
功能:
用法:
struct str
{
char name[20];
int age;
int score;
};
int main(void)
{
struct str s = { 0 };
char str[100] = "zhansa 14 99";
sscanf(str, "%s %d %d", s.name, &(s.age), &(s.score));
printf("%s %d %d", s.name, s.age, s.score); //zhansa 14 99
return 0;
}
总结比较:
printf/scanf:适用于标准输入/输出流的格式化输入/输出语句
fprintf/fscanf:适用于所有输入/输出流的格式化输入/输出语句
sprintf:把格式化的数据按照一定的格式转换为字符串
sscanf:从字符串中按照一定的格式读取出格式化的数据
由于打开文件其文件指针指向最开始,所以使用上述的读写函数是按照顺序的;所谓的文件随机读写就是利用一些函数来改变文件指针的位置然后利用上述的读写函数进行随机读写。
函数原型:
函数参数:
用法:
int main(void)
{
FILE* pf = fopen("test.txt", "wb");
if (NULL == pf)
{
return 1;
}
//写数据
char str[] = "abcdefg";
fprintf(pf, "%s", str);
//用fseek改变文件指针的位置
fseek(pf, 3, SEEK_SET);
//写数据
fprintf(pf, "%s", "123");
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
函数原型:
用法:
int main(void)
{
FILE* pf = fopen("test.txt", "wb");
if (NULL == pf)
{
return 1;
}
//写数据
char str[] = "abcdefg";
fprintf(pf, "%s", str);
//用fseek改变文件指针的位置
fseek(pf, 3, SEEK_SET);
fprintf(pf, "%s", "123");
printf("%d", ftell(pf)); //此时值为6,因为你移动了三位,又写了三个字符进去
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
函数原型:
用法:
int main(void)
{
FILE* pf = fopen("test.txt", "wb");
if (NULL == pf)
{
return 1;
}
//写数据
char str[] = "abcdefg";
fprintf(pf, "%s", str);
//用fseek改变文件指针的位置
fseek(pf, 3, SEEK_SET);
rewind(pf);
fprintf(pf, "%s", "123");
printf("%d", ftell(pf)); //此时结果为3
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
由数据的组织形式,数据文件分为文本文件和二进制文件。数据在内存中以二进制形式存储,如果不加转换的输出到文件中那么该文件就是二进制文件;如果要求文件中的数据需要以ASCII的形式存储,则需要在存储前转换,以ASCII形式存储的文件就是文本文件。因为字符在内存中以ASCII码的形式存储,所以不管是哪种存储方式都是一样的,但是数值型数据就不一样了:既可以以ASCII码的形式存储,也可以以二进制的方式存储,如10000,以字符形式存储是占用5个字节,而以二进制方式存储则占用4个字节。
上述顺序输出函数的解读:
函数原型:
返回值:
功能:
判断文件是否读取结束用上述读写函数的返回值:
feof用法:
int main(void)
{
FILE* pf = fopen("test.txt", "r");
if (NULL == pf)
{
perror("fopen");
return 1;
}
int a = 0;
if (fscanf(pf, "%d", &a) == 1)
{
printf("%d\n", a);
}
else if (ferror(pf)) //判断是否是I/O错误
{
perror("I/O");
}
else if (feof(pf)) //判断是否是遇到EOF而读取结束
{
puts("EOF");
}
fclose(pf);
pf = NULL;
return 0;
}
ANSIC标准采用“缓冲文件系统”来管理数据文件,当使用文件时,系统自动的在内存中开辟一块“文件缓冲区”,从内存中输出的数据都要先存放到缓冲区,装满缓冲区后才会写到磁盘中;如果磁盘向内存输入数据,先存放到输入缓冲区中,待缓冲区满后才会将数据逐个地送到程序数据区(变量等)。因为有缓冲区的原因,必须要刷新缓冲区或者关闭缓冲区,否则会导致读写文件的问题。