在C语言中,文件的操作是通过FILE结构体进行了,具体实现时,先利用fopen返回一个指向FILE结构体的指针:
FILE *fopen( const char *filename, const char *mode );
filename:文件名,mode:打开的模式,规定了是可读、可写、追加之类的属性。
"r":可读,如果文件不存在,fopen调用失败
"w":可写,如果文件存在,那么原来的内容会被销毁。
"a":在文件尾追加,在新的数据写到文件里之前,不改变EOF标记,如果文件不存在,创建一个新的文件。
"r+":可读可写,文件必须存在。
"w+":打开一个空文件用来读写,如果文件存在,则内容被销毁。
"a+":可读可追加,在新的数据写到文件里之前,改变EOF标记;如果文件不存在,创建一个新的文件。
如果调用失败,返回一个空指针。
size_t fwrite( const void *buffer, size_t size, size_t count, FILE *stream );
buffer:写入文件的内容;size:每一项的大小;count:写入了多少项;stream:指向文件的指针。返回值为写入的总的字节数。
其实,我们完全可以手动的关闭文件:
int fclose( FILE *stream );
stream:指向文件的指针。如果成功,返回0;失败返回EOF。这样,当执行完关闭文件后,文件里面就有值了。
有时候,我们会反复读写一个文件,而且每次读写后都希望立即看到结果。这时候每次读完就关闭,然后重新打开的话实在太麻烦了,有没有简单的办法呢?可以使用fflush来刷新流:
int fflush( FILE *stream );
stream:指向文件的指针。
如果我们在接着向文件中写入数据:
fwrite("欢迎访问",1,strlen("欢迎访问"),pFile);
我们会发现,新写入的数据会在原来文件的末尾后加上。可系统是如何知道原来文件的末尾在哪里呢?
我们先看看FILE结构体:
我们使用ftell函数获取当前文件指针的位置:
long ftell( FILE *stream );
返回值为与文件头的偏移量。
通过它,我们可以获得文件的长度。
读取文件使用的是fread函数:
size_t fread( void *buffer, size_t size, size_t count, FILE *stream );
buffer指明了读取的文件储存在哪里,size表明每个项的大小,count表明了读多少项,stream是指向FILE类型的指针。返回值为实际读取的字节数。
当然,在你读取文件之前,最好确保文件指针指向的是文件的头部,通过rewind函数来让指针复位:
void rewind( FILE *stream );
stream:指向文件的指针
下面看一个综合的例子:
基本的内容就是这么多,下面看一个细节问题:
我们看看文件的16进制:61 0D 0A 62 ,其中61、62对应的是a和b,0A对应的是10,那么0D对应的是什么呢?答案是回车字符,这个字符是系统自动加进去的。而在读取文件时,我们也并没有读取个字节,只用读取3个字节,就能正确获取内容了。
与这个问题相关的一组概念是:二进制文件和文本文件。C语言中,默认是以文本的方式打开文件的,如果我们使用二进制文件方式打开:
FILE *pFile = fopen("2.txt","w+b");
也不会出什么问题,只不过文件的大小为3个字节,对应的16进制为:61 0A 62。但是如果你在写入时使用的是文本文件,而读取时使用的是二进制文件,就会出错,因为它会把回车当做一个字符输出。总而言之,读和写的方式要对应。
FILE *fopen( const char *filename, const char *mode );
filename:文件名,mode:打开的模式,规定了是可读、可写、追加之类的属性。
"r":可读,如果文件不存在,fopen调用失败
"w":可写,如果文件存在,那么原来的内容会被销毁。
"a":在文件尾追加,在新的数据写到文件里之前,不改变EOF标记,如果文件不存在,创建一个新的文件。
"r+":可读可写,文件必须存在。
"w+":打开一个空文件用来读写,如果文件存在,则内容被销毁。
"a+":可读可追加,在新的数据写到文件里之前,改变EOF标记;如果文件不存在,创建一个新的文件。
如果调用失败,返回一个空指针。
size_t fwrite( const void *buffer, size_t size, size_t count, FILE *stream );
buffer:写入文件的内容;size:每一项的大小;count:写入了多少项;stream:指向文件的指针。返回值为写入的总的字节数。
先看一个简单的例子:
int main()
{
FILE *pFile = fopen("1.txt","w");
fwrite("hello,world!",1,strlen("hello,world!"),pFile);
return 0;
}
这个程序看似平平,但还是大有文章可做:如果单步调试的话,执行完pFile后,就会在程序对应的文件夹下面产生一个名为“1.txt”的文件(大小为0);执行完fwrite后,大小还是为0,;只有当程序完全运行完以后,hello,world!才会被写入文件。这是为什么呢?因为C语言对文件的操作使用了缓冲文件系统:为每个正在使用的文件开辟了一段内存,当我们向硬盘上写数据时,是先写到内存里的(这与兼容DC有异曲同工之妙),直到内存满了,或者是我们通知系统要关闭这个文件了,才把内存里的数据拷贝到硬盘上。为什么这么设计呢?因为内存之间的操作速度要远远快于内存到硬盘的速度。如果我们每写一个字符后就把它保存在硬盘上,代价太大了。回过头来继续看,当整个程序执行完后,关闭这个文件,此时,才会把内存里的数据拷贝到硬盘上。
其实,我们完全可以手动的关闭文件:
int fclose( FILE *stream );
stream:指向文件的指针。如果成功,返回0;失败返回EOF。这样,当执行完关闭文件后,文件里面就有值了。
有时候,我们会反复读写一个文件,而且每次读写后都希望立即看到结果。这时候每次读完就关闭,然后重新打开的话实在太麻烦了,有没有简单的办法呢?可以使用fflush来刷新流:
int fflush( FILE *stream );
stream:指向文件的指针。
如果我们在接着向文件中写入数据:
fwrite("欢迎访问",1,strlen("欢迎访问"),pFile);
我们会发现,新写入的数据会在原来文件的末尾后加上。可系统是如何知道原来文件的末尾在哪里呢?
我们先看看FILE结构体:
struct _iobuf {
char *_ptr; //文件输入的下一个位置
int _cnt; //当前缓冲区的相对位置
char *_base; //指基础位置(应该是文件的其始位置)
int _flag; //文件标志
int _file; //文件的有效性验证
int _charbuf; //检查缓冲区状况,如果无缓冲区则不读取
int _bufsiz; //文件的大小
char *_tmpfname;//临时文件名
};
typedef struct _iobuf FILE;
注意,这只是VS2010中对FILE的实现!标准库中并没有规定FILE中必须是什么样的,之规定了我们可通过哪些函数去调用、访问它。但是从这个实例中,我们可以看出:结构体的第一个成员是一个指向文件输出的下一个位置的指针,我们可以通过fseek来移动文件的指针,它指向文件下一个要写入的位置:
int fseek( FILE *stream, long offset, int origin );
stream:指向文件的指针,offset:偏移量;origin:初始位置,它有3种取法:
SEEK_CUR:当前位置
SEEK_END:文件尾
SEEK_SET:文件头
如果我们这样使用:
fwrite("hello,world!",1,strlen("hello,world!"),pFile);
fflush(pFile);
fseek(pFile,0,SEEK_SET);
fwrite("欢迎访问",1,strlen("欢迎访问"),pFile);
那么文件中的内容就变为了:“欢迎访问rld!”
我们使用ftell函数获取当前文件指针的位置:
long ftell( FILE *stream );
返回值为与文件头的偏移量。
通过它,我们可以获得文件的长度。
读取文件使用的是fread函数:
size_t fread( void *buffer, size_t size, size_t count, FILE *stream );
buffer指明了读取的文件储存在哪里,size表明每个项的大小,count表明了读多少项,stream是指向FILE类型的指针。返回值为实际读取的字节数。
当然,在你读取文件之前,最好确保文件指针指向的是文件的头部,通过rewind函数来让指针复位:
void rewind( FILE *stream );
stream:指向文件的指针
下面看一个综合的例子:
int main()
{
FILE *pFile = fopen("1.txt","w+");
fwrite("hello,world!",1,strlen("hello,world!"),pFile);
fflush(pFile);
fseek(pFile,0,SEEK_SET); //文件指针设为起点,写操作覆盖原来的内容
fwrite("欢迎访问",1,strlen("欢迎访问"),pFile);
fseek(pFile,0,SEEK_END); //文件指针设为终点
int len = ftell(pFile); //获取文件字节数
char* ch = (char*)malloc(sizeof(char)* (len+1)); //分配内存,多一个字节
memset(ch,0,(len+1)); //清0
// fseek(pFile,0,SEEK_SET); //文件指针指向头部
rewind(pFile);
fread(ch,1,len,pFile); //读取文件
fclose(pFile);
printf("%s",ch);
return 0;
}
需要说明一点,因为%s是遇到一个空字符串后才停止的,所以我们分配的内存比文件的字节数多了一个,然后全部清0,这样完成fread以后,剩下的那个字节刚好为0,用来表示字符串的结束。
基本的内容就是这么多,下面看一个细节问题:
int main()
{
FILE *pFile = fopen("2.txt","w+");
char ch[3];
ch[0] = 'a';
ch[1] = 10;
ch[2] = 'b';
fwrite(ch,1,3,pFile);
fflush(pFile);
char buf[100];
memset(buf,0,100);
rewind(pFile);
fread(buf,1,3,pFile);
fclose(pFile);
printf("%s",buf);
return 0;
}
我们明明写了3个字符,为什么文件大小会是4个字节呢?
我们看看文件的16进制:61 0D 0A 62 ,其中61、62对应的是a和b,0A对应的是10,那么0D对应的是什么呢?答案是回车字符,这个字符是系统自动加进去的。而在读取文件时,我们也并没有读取个字节,只用读取3个字节,就能正确获取内容了。
与这个问题相关的一组概念是:二进制文件和文本文件。C语言中,默认是以文本的方式打开文件的,如果我们使用二进制文件方式打开:
FILE *pFile = fopen("2.txt","w+b");
也不会出什么问题,只不过文件的大小为3个字节,对应的16进制为:61 0A 62。但是如果你在写入时使用的是文本文件,而读取时使用的是二进制文件,就会出错,因为它会把回车当做一个字符输出。总而言之,读和写的方式要对应。