一、文件的概念
C语言的文件是指用来存储数据的一种存储设备。存储在文件中的数据并不会随着计算机的关闭而消失。文件通常是存储在硬盘上的。
二、文件的访问
(一)打开文件
使用函数:fopen()
使用方法:
char *path = "H:\\AndroidNDK\\cworkspace\\files\\friends.txt";
FILE *fp = fopen(path, "r");
函数的第一个变元是一个字符串指针,他是要处理的外部文件的名称。
函数的第二个变元是也是一个字符串,称为文件模式。它指定对文件进行什么处理。这里介绍常用的三种模式:
如果我们需要处理的是一个二进制的文件,比如一个音频、视频或者图片时,那么文件模式为:"wb"、"ab"、"rb"。b 是 binary(二进制)的简写。
如果成功调用fopen()函数,它会返回一个FILE *类型的指针(该指针称为文件指针或者流指针),通过该指针可以引用文件,使用其他库函数执行输入输出的操作。如果文件因为某种原因但不开,会返回一个空指针(NULL)。
一次能打开的文件数由
打开文件更安全的可选备用函数是:fopen_s():
errno_t fopen_s(_Outptr_result_maybenull_ FILE ** _File, _In_z_ const char * _Filename, _In_z_ const char * _Mode);
- 第一个变量需要我们传入一个文件指针的地址
- 第二个变量需要我们传入文件名(带路径或不带路径)。
- 第三个变量需要我们传入文件模式(w、a、r)。
1.写入模式(w)
FILE *fp;
char *fileName = "H:\\AndroidNDK\\cworkspace\\hello.txt";
errno_t err = fopen_s(&fp, fileName, "w");
if (err != 0){
printf("%s\n","文件打开失败");
return;
}
printf("%s","请输入需要写入的字符(以#号结束):");
char str_append;
while ((str_append = getchar()) != '#'){
fputc(str_append, fp);
}
fclose(fp);
对于第二个参数 fileName 可以写全路径
//如果包含这个文件的目录不存在,fopen_s()函数不会创建目录,也不会创建文件,而是失败。
//如果目录存在,但是该目录下没有找到文件,就在该位置上创建文件。
char *fileName = "H:\\AndroidNDK\\cworkspace\\hello.txt";
也可以不带路径,直接写文件名称:
//不带路径的话,该文件会保存到当前项目所在的目录下
char *fileName = "hello.txt";
注:如果fopen_s()函数调用失败,就返回非0整数,pfile设置为NULL。
使用fopen_s()函数对文件进行写入操作时,是不允许并发访问的。
2.追加模式(a)
如果要在已有的文本文件中添加数据,而不是覆盖数据,可以指定模式为“a”,它是操作的追加模式。将文件指针放在前一次写入的数据的末尾。如果文件不存在,就会创建新文件。
fopen_s(&fp, fileName, "a");
3.读取模式(r)
如果要读取文件,可以使用如下代码:
fopen_s(&fp, fileName, "r");
如果是读取模式,文件就必须存在,如果要读取的文件不存在,fopen_s()会把指针设置为NULL。
(二)缓存文件
打开文件后,就可以调用 setvbuf() 控制如何缓存输入操作,其函数原型如下:
int setvbuf(FILE * restrict pfile, char * restrict buffer, int mode, size_t size);
- 参数一:打开文件的文件指针
- 参数二:用于缓存的数组,第四个参数是该数组的大小。如果把第二个参数置为NULL,就用第四个变量指定的大小分配一个缓存。一般建议把第二个变量置为NULL,这样就不用考虑缓存的创建或其生命周期了。
- 第三个参数:指定缓存模式。其值如下:
_IOFBF:使文件完全缓存。输入输出完全缓存时,数据块会以任意大小读写。
_IOLBF:使操作缓存一行。输入输出缓存一行时,读写的数据用换行符来分块。
_IONBF:使用输入输出不缓存。对于不缓存的输入输出,数据会逐个字符地传递。这是非常低效的。所以仅在需要时使用这个模式。
一切正常时,setvbuf()返回 0 整数。示例代码:
//对pfile指向的文件使用setvbuf()
size_t bufsize = 1024;
if(setvbuf(pfile, NULL, _IOFBF, bufsize))
printf("文件缓存失败!\n");
如果只是希望完全缓存输入或输出,可以调用 setbuf() ,其函数原型为:
void setbuf(FILE * restrict pfile, char * restrict buffer);
- 第一个参数:文件指针
- 第二个参数:用作缓冲区的数组地址。可以为NULL,此时自动创建缓冲区。如果指定缓冲区,其长度必须为
BUFSIZ 字节,BUFSIZ在 stdio.h 中定义。下面使用自己的缓冲区给pfile指针指向的文件缓存操作:
char *buf = malloc(BUFSIZ);
setbuf(pfile,buf);
(三)重命名文件
使用函数 rename()
函数原型:
int rename(const char *oldname, const char *newname);
如果文件名称修改成功返回0,否则返回非零值。调用rename()函数时,文件必须关闭,否则操作会失败。
示例代码如下:
if (rename("H:\\cworkspace\\friends.txt", "H:\\cworkspace\\boy.txt")){
printf("文件名修改失败\n");
}
else{
printf("文件名修改成功\n");
}
(四)关闭文件
使用函数:fclose()
fclose(fp);
fp=NULL;
如果成功关闭文件,就返回0,否则返回EOF。
注:EOF是一个特殊的字符,称为文件结束字符。EOF一般表示不能再从流中获取数据了
使用完文件后最好马上关闭文件
三、写入文本文件
最简单的写入操作由函数 fputc 提供,它将一个字符写入文本文件。其原型如下:
int fputc(int ch,FILE *pfile)
函数fputc()将第一个变量指定的字符写入第二个变量指定的文件中。如果操作成功,就返回写入的字符,否则就返回EOF。
注:实际上,字符不是一个一个地写入物理文件的,这样做效率太低了。字符先被写入到内存中的缓冲区,缓冲区累积到一定的数量后,就一次将它们写入文件。
四、读取文本文件
fgetc() 函数从打开的文本文件中读取一个字符,需要传入一个文件指针对象作为参数,读取的字符返回为 int 类型,示例代码如下:
int ch = fgetc(pfile)
如果读取到文件末尾,就返回 EOF。读取文件的机制与写入文件正好相反。在一次操作中将一整块字符写入缓冲区,接着一次将一个字符传送给程序,直到缓冲区为空,此时再读取另一块。
如果需要再次读取文本内容,可以调用 rewind() 函数把文件指针变量指定的文件定位到开头。
rewind(pfile)
pfile必须是已经打开的文件。
五、在文本文件中读写字符串
(一)读取字符串
函数 fgets()
char *fgets(char * restrict str, int nchars, FILE * restrict pfile);
该函数会一直从文件中读取字符串,直到读到了'\n'字符或读入 nchars-1 个字符为止。如果读到换行符会保留在字符串中。字符'\0'会附加到字符串的末尾。如果没有错误,fgets()就会返回str指针,否则返回NULL。读取EOF会返回NULL。
(二)写入字符串
函数 fputs()
int *fputs(char * restrict str, FILE * restrict pfile);
第一个参数是要写入文件的字符串指针,第二个变量是文件指针。
fputs("hello world\n",pfile);
如果发生错误,fputs()函数返回EOF,如果正常就返回正整数。
六、二进制文件的输入输出
(一)二进制文件与文本文件的区别
1.二进制文件不需要转换数据,也不需要格式字符串控制输入输出;
2.文本模式下具有特殊意义的字符,如'\n'和‘\0’,在二进制模式下就没有意义了。
(二)二进制模式的优点
二进制模式的优点是没有数据转换,也没有精度的损失。而文本模式因为有格式化过程,有数据转换和精度损失。另外,二进制模式比文本模式的速度快。两种模式的比较如下图:
(三)以二进制模式打开文件
要指定二进制模式,只需要在基本打开模式说明符后附加 b 。
(四)写入二进制文件
使用函数:fwrite()
函数原型:
- 第一个参数是要写入的数组的地址(任何类型的数组都可以)
- 第二个参数是数组元素的字节数(sizeof(数组类型))
- 第三个参数是数组元素的个数
- 第四个参数是文件流的指针
函数返回值:
函数fwrite()将实际写入的数据项个数返回为一个整数。如果出现写入错误,禁止写入所有数据,这个整数就小于nitems。如果size或nitems是0,就不给文件写入任何数据。
void main(){
char *read_path = "H:\\AndroidNDK\\cworkspace\\files\\liuyan.png";
char *write_path = "H:\\AndroidNDK\\cworkspace\\files\\liuyan_new.png";
//读的文件
FILE *read_fp = fopen(read_path, "rb");
//写的文件
FILE *write_fp = fopen(write_path, "wb");
//复制
int buff[50];//缓冲区域
int len = 0;//每次读到的数据长度
while ((len = fread(buff, sizeof(int), 50, read_fp)) != 0){
fwrite(buff, sizeof(int), len, write_fp);
}
//关闭流
fclose(read_fp);
read_fp = NULL;
fclose(write_fp);
write_fp= NULL;
getchar();
}
(五)读取二进制文件
使用函数:fread()
函数原型:
- pdata是要读取的数组的地址
- size是每个数据项的字节数
- nitems是要读取的数据项个数
- pfile是文件指针
函数的返回值:返回读取的个数。
关于文件IO的知识就给大家介绍到这里