文件 IO 操作
文章目录
- 一 文件
- 1.1 文本模式
- 1.2 二进制模式
- 二 函数
- 2.1fopen()
- 2.2 getc() 和 putc()
- 2.3 fclose()
- 2.4 fprintf() 和 fscanf()
- 2.5 fgets() 和 fputs()
- 2.6 rewind()
- 2.7 fseek() 和 ftell()
- 2.8 fflush()
- 2.9 fgetpos() 和 fsetpos()
- 2.10 feof() 和 ferror()
- 2.11 ungetc()
- 2.12 setvbuf()
- 2.13 fread() 和 fwrite()
一 文件
C把文件看作是一系列连续的字节,每个字节都能被单独读取。C提供两种文件模式:文本模式 和 二进制模式。
1.1 文本模式
如果文件最初使用二进制编码的字符(例如:ASCLL 码 或 Unicode)表示文本(就像C字符串一样),该文件就是文本文件,其中包含文本内容。
- 程序所见的内容和实际内容不同。
- 读:把本地环境表示的行末尾或文件结尾映射成C模式(\r 转为 \n)
- 写:(\n 转为 \r)
1.2 二进制模式
如果文件中的二进制值代表机器语言代码或数值数据(使用相同的内部表示,假设,用于 long 或 double 类型的值)或图片或音乐编码,改文件就是二进制文件,其中包含二进制内容。
- 程序可以访问文件的每个字节
- 若是写旧式 Mac 格式、MS-DOS格式或UNIX/Linux 格式的文件模式程序,应该用二进制模式,这样程序才能确定实际的文件内容并执行相应的动作。
二 函数
2.1fopen()
FILE *fopen(const char *pathname, const char *mode);
2.2 getc() 和 putc()
int getc(FILE *stream);
//fgetc(), getc() and getchar() return the character read as an unsigned char cast to an int or EOF on end of file or error.
//fgets() returns s on success, and NULL on error or when end of file occurs while no characters have been read.
//ungetc() returns c on success, or EOF on error.
int putc(int c, FILE *stream);
//fputc(), putc() and putchar() return the character written as an unsigned char cast to an int or EOF on error.
//puts() and fputs() return a nonnegative number on success, or EOF on error.
与getchar() 和 putchar() 类似,不过要告诉getc() 和 putc() 函数使用哪一个文件。
例:将文件A 中的数据放在文件B中
#include<stdio.h>
#include<stdlib.h>
int main(int argc,char* argv[])
{
char ch;
FILE* fp1;
FILE* fp2;
if(argc != 3)
{
printf("参数错误\n");
return 0;
}
fp1 = fopen(argv[1],"r");
fp2 = fopen(argv[2],"w");
while( (ch = getc(fp1)) != EOF)
{
putc(ch,fp2);
}
fclose(fp1);
fclose(fp2);
return 0;
}
例子二:简单的文件压缩,保留每三个字符中的第一个
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define LEN 40
int main(int argc,char* argv[])
{
FILE* in;
FILE* out;
int ch;
char name[LEN];
int count = 0;
if(argc < 2)
{
fprintf(stderr,"Usage: %s filename\n",argv[0]);
exit(EXIT_FAILURE);
}
//设置输入
if( (in = fopen(argv[1],"r")) == NULL)
{
fprintf(stderr,"i coulun't open thr file \"%s\"\n",argv[1]);
exit(EXIT_FAILURE);
}
//设置输出
strncpy(name,argv[2],LEN-5);
name[LEN-5] = '\0';
strcat(name,".red"); //在文件名后添加 .red后缀
if( (out = fopen(name,"w")) == NULL)
{
fprintf(stderr,"can't careate output file.\n");
exit(3);
}
//拷贝数据
while( (ch = getc(in)) != EOF)
{
if(count++ % 3 == 0)
{
putc(ch,out);
}
}
if(fclose(in)!=0 || fclose(out)!=0)
{
fprintf(stderr,"Error incloseing files\n");
}
return 0;
}
//拷贝函数:
char *strncpy(char *dest, const char *src, size_t n);
//尾部添加字符strcat
char *strncat(char *dest, const char *src, size_t n);
fprintf()和printf() 类似,fprintf() 的第1个参数必须是一个文件指针。程序中的stderr指针把错误消息发至标准错误,C标准通常这么做。
2.3 fclose()
int fclose(FILE *stream);
//Upon successful completion, 0 is returned. Otherwise, EOF is returned and errno is set to indicate the error. In either case,
//any fur‐ther access (including another call to fclose()) to the stream results in undefined behavior.
//即成功返回0
fclose(fp)函数关闭fp指向的文件,必要时刷新缓冲区。对于比较正式的程序,应该检查是否关闭成功。如果关闭成功,返回0,否则返回EOF。
注:磁盘满、移动硬盘被移出或出现I/O错误,都会导致fclose()函数失败
2.4 fprintf() 和 fscanf()
fprintf()和printf() 类似,fscanf() 和 scanf()类似。区别在于第一个参数指定待处理的文件,fprintf() 的第1个参数必须是一个文件指针。程序中的stderr指针把错误消息发至标准错误,C标准通常这么做。
int fprintf(FILE *stream, const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);
例子:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define LEN 41
int main(int argc,char* argv[])
{
FILE* fp;
char words[LEN];
if( (fp = fopen("wordy","a+")) == NULL)
{
fprintf(stdout,"can't open file\n");
exit(EXIT_FAILURE);
}
puts("enter words to add to the file;press the #");
puts("key at the beginning of a line to terminate.");
while( (fscanf(stdin,"%40s",words) == 1) && (words[0] != '#'))
{
fprintf(fp,"%s\n",words);
}
puts("File contenets:");
rewind(fp);
while(fscanf(fp,"%s",words) == 1)
{
puts(words);
}
puts("Dones");
if(fclose(fp) != 0)
{
fprintf(stderr,"close file failed\n");
}
return 0;
}
从标准输入获取单词,然后输出到另一个文件
将 fscanf()第一个参数改为文件描述符,既可从文件读入
rewind(fp); //rewind接收一个文件指针作为参数,移动到文件开始处。
2.5 fgets() 和 fputs()
第一个参数,fgets也和gets函数一样,也是表示存储输入位置的地址(char*类型)。
第二个参数是一个整数,代表待输入字符串的大小。
最后一个参数是一个文件指针,指定待读取的文件。
用法:fgets(buf,STLEN,fp);
fgets()函数读取输入直到第一个换行符后面,或者读到文件末尾,或者读取 STLEN-1 个字符,然后fgets() 在末尾添加一个空字符使之成为一个字符串。字符串大小其实是字符数加上一个空字符。
若fgets()在读到字符上线之前已读完一整行,它会吧表示行结尾的换行符放在空字符前面,。
fgets()函数在遇到EOF时将返回NULL值,可以利用这一机制检查是否到达文件末尾。
fputs() 接受两个参数,第一个是字符串的地址,第二个是文件指针。该函数根据传入地址找到的字符串写入指定的文件中。和puts()函数不通,fputs()在打印字符串时不会在其末尾添加换行符。
fput(buf,fp);
char *fgets(char *s, int size, FILE *stream);
int fputs(const char *s, FILE *stream);
2.6 rewind()
void rewind(FILE *stream);
返回到文件开始位置
2.7 fseek() 和 ftell()
Fseek 可把文件当做数据,在fopen()打开的文件中直接移动到任意字节处。
ftell()函数返回一个long类型的值,标识文件当前位置。
int fseek(FILE *stream, long offset, int whence);
//If whence is set to SEEK_SET, SEEK_CUR, or SEEK_END, the offset isrelative to the start of the file, the current position indicator, or end-of-file, respectively
long ftell(FILE *stream);
//返回值类型为long,返回的是参数指向的文件的当前位置距文件开始处的字节数;
fseek,第三个参数 whence 若是选择为 SEEK_SET,SEEK_CUR,SEEK_END,可以将文件指针偏移到文件开始,当前位置,文件末尾等位置。
例子:文件内容逆向打印
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define CNTL_Z '\032'
#define SLEN 81
int main(int argc,char* argv[])
{
char file[SLEN];
char ch;
FILE* fp;
long count,last;
puts("enter the name of the file to be processed:");
scanf("%s",file);
if( (fp = fopen(file,"rb")) == NULL)
{
printf("reverse can't open %s\n",file);
exit(EXIT_FAILURE);
}
fseek(fp,0L,SEEK_END); //定位到文件末尾
last = ftell(fp);
for(count = 1L;count <= last; count++)
{
fseek(fp,-count,SEEK_END); //回退
ch = getc(fp);
if(ch != CNTL_Z && ch != '\r')
{
putchar(ch);
}
}
putchar('\n');
fclose(fp);
return 0;
}
fseek(fp,0L,SEEK_SET); //文件开始处
fseek(fp,10L,SEEK_SET); //文件中第10个字节
fseek(fp,2L,SEEK_CUR); //从当前位置往前移两个字节
fseek(fp,0L,SEEK_end); //定位到文件末尾
fseek(fp,-10L,SEEK_end);//从文件末尾回退10个字节
若是一切正常,fseek()返回0;如果出现错误(如试图移动的距离超过文件的范围),返回 -1
2.8 fflush()
调用fflush() 函数引起输出缓冲区中所有的未写入数据被发送到fp指定的输出文件。
这个过程称为刷新缓冲区。如果fp是空指针,所有输出缓冲区都会被刷新。在输入流中使用fflush()函数的效果是未定义的。只要最近一次操作不是输入操作,就可以用该函数来更新流(任何读写模式)
int fflush(FILE *stream);
2.9 fgetpos() 和 fsetpos()
fseek() 和 ftell() 潜在的问题是:他们都把文件大小限制在long类型能表示的范围内。
随着存储设备容量迅猛增长,文件也越来越大。
鉴于此,ANSIC新增了两个处理较大文件的新定位函数:fgetpos() 和 fsetpof()
这两个函数不适用long类型表示位置,而是使用一种新的类型:fpos_t(file position type,文件定位类型)。fpos_t类型是不是基本类型。
fpos_t 类型的变量或数据对象可以在文件中指定一个位置,他不能是数组类型。除此之外,没有其他限制。
int fgetpos(FILE *stream, fpos_t *pos);
//把fpos_t类型的值放在pos指向的位置上,该值描述了文件中的当前位置距文件开头的字节数。如果成功,fgetpos()函数返回0,如果失败返回非0;
int fsetpos(FILE *stream, const fpos_t *pos);
//使用pos指向位置上的fpos_t类型值来设置文件指针指向偏移该值后指定的位置。
成功返回0,失败返回非0.
fpot_t类型的值应该通过之前调用fgetpos获得
2.10 feof() 和 ferror()
int feof(FILE *stream);
int ferror(FILE *stream);
如果标准输入函数返回EOF,则通常表明函数已经到达文件末尾了。然而,出现错误读取时,函数也会返回EOF。
feof() 和 ferror() 函数用于区分两种情况。
当上一次输入调用检测到文件末尾时,feof() 返回一个非零值,否则返回0;
当读或写出现错误,ferror() 函数返回一个非零值,否则返回0;
2.11 ungetc()
int ungetc() 函数把C指定的字符放回输入流中,如果吧一个字符返回输入流,下次调用标准输入函数时将读取该字符。
例如:假设要读取下一个冒号前所有的字符,但是不包括冒号本身,可以使用getchar() 或 getc() 函数读取到冒号,然后使用ungetc() 把冒号放回输入流中。
ANSI C标准保证每次只会放回一个字符,如果实现允许把一行中的多个字符放回输入流,那么下次输入函数读入的字符顺序就会与放回去的顺序相反。
char *fgets(char *s, int size, FILE *stream);
2.12 setvbuf()
int setvbuf(FILE *stream, char *buf, int mode, size_t size);
2.13 fread() 和 fwrite()
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
//返回成功读入项的数量。正常返回值就是nmemb,但如果出现写入错误,就会比nmenb小。
size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);
//返回成功写入项的数量。正常返回值就是nmemb,但如果出现写入错误,就会比nmenb小。
背景:之前用到的IO函数都是面向文本的,用于处理字符和字符串。如何在文件中存储数值?用fprintf() 函数 和 %f 转换说明只是把数据保存为字符串。
例如:
double num = 1.0/3;
fprintf(fp,"%f",num);
//fprintf(fp,"%.2f",num);
//fprintf(fp,"%.12f",num);
把num存储为8个字符,0.333333。使用%.2f说明将其存储为4个字符;使用%.12f说明把他存储为14个字符。
为了保证数值在存储前后一致,最精确的做法是使用与计算机相同的位组合来存储。因此,double类型的值就应该存在一个double大小的单元中。
如果以程序所用的表示法把数据存储在文件中,则称以二进制形式存储数据。
二进制和文本的用法很容易混淆。
可以用二进制模式打开文本格式的文件,可以把文本存储在二进制形式的文件中。可以调用getc()拷贝包含二进制数据的文件。然而,**一般而言,用二进制模式在二进制格式文件中存储二进制数据。**类似的,最常用的还是以文本格式打开文本文件中的文本数据。
- size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);
fwrite()函数是把二进制数据写入文件。size_t是根据C类型定义的类型,是sizeof运算符返回的类型,通常是unsigned int.
ptr 是待写入数据块的地址。
size 表示待写入数据块的大小(以字节为单位)
nmemb表示待写入数据块的数量。
例1:将一块256字节数据从buffer写入文件
char buf[256];
fwrite(buffer,256,1,fp);
例2:
保存一个内含10个double类型的数组
double earnings[10];
fwrite(earnings,sizeof(double),10,fp);
数据被分为10块,每块都是double的大小。
注:第一个类型不是固定类型,而是void*,
- size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
接收参数与fwrite()一致.
ptr是指待读取文件在内存中的地址
fp指待读取的文件。
该函数用于读取被fwrite()写入文件的数据。
例:恢复上例中保存的10个double类型的数据,可以这么做
double earnings[10];
fread(earnings,sizeof(double),10,fp);