15.12 刷新和定位函数
在处理流时,另外还有一些函数也较为有用。首先是fflush,它迫使一个输出流的缓冲区内的数据进行物理写入,不管它是不是已经写满。它的原型如下:
int fflush( FILE *stream );
当需要立即把输出缓冲区的数据进行物理写入时,应该使用这个函数。例如,调用fflush函数可以保证调试信息实时打印出来,而不是保存在缓冲区中直到以后才打印。
在正常情况下,数据以线性的方式写入,这意味着在文件中,后面写入的数据是在以前所有写入数据的后面。C同时支持随机访问I/O,也就是以任意顺序访问文件的不同位置。随机访问时通过在读取或写入先前定位到文件中需要的位置来实现的。有两个函数用于执行这项操作,它们的原型如下:
long ftell( FILE *stream );
int fseek( FILE *stream, long offset, int from );
ftell函数返回流的当前位置,也就是说,下一个读取或写入将要开始的位置距离文件起始位置的偏移量。这个函数允许保存一个文件的当前位置,这样可能在将来会返回到这个位置。在二进制流中,这个值就是当前位置距离文件起始位置之间的字节数。
在文本流中,这个值表示一个位置,但它并不一定准确地表示当前位置和文件起始位置之间的字符数,因为有些系统将对行末字符进行翻译转换。但是,ftell函数返回的值总是可以用于fseek函数,作为一个距离文件起始位置的偏移量。
fseek函数允许在一个流中进行定位。这个操作将改变下一个读取或写入操作的位置。它的第1个参数是需要改变的流。它的第2个和第3个参数表示文件中需要定位的位置。表15.10描述了第2个和第3个参数可以使用的3种方法。
试图定位到一个文件的起始位置之前是一个错误。定位到文件尾之后并进行写入将扩展这个文件。定位到文件尾之后并进行读取将导致返回一条“到达文件尾”的信息。在二进制流中,从SEEK_END进行定位可能不被支持,所以应该避免。在文本流中,如果from是SEEK_CUR或SEEK_END,offset必须是零。如果from是SEEK_SET,offset必须是一个从同一个流中以前调用ftell时所返回的值。
表15.10 fseek参数
如果from是... 将定位到...
SEEK_SET 从流的起始位置起offset个字节,offset必须是一个非负值
SEEK_CUR 从流的当前位置起offset个字节,offset的值可正可负
SEEK_END 从流的尾部位置起offset个字节,offset的值可正可负。如果它是正值,将定 位到文件尾的后面
之所以存在这些限制,部分原因在于文本流所指向的行末字符映射。由于这种映射的存在,文本文件的字节数可能和程序写入的字节数不同。因此,一个可移植的程序不能根据实际写入字符数的计算结果定位到文本流的一个位置。
用fseek改变一个流的位置会带来3个副作用。首先,行末指示字符被清除。其次,如果在fseek之前使用ungetc把一个字符返回到流中,那么这个被退回的字符会被丢弃,因为在定位操作之后,它不再是“下一个字符”。最后,定位允许从写入模式切换到读取模式,或者回到打开的流以便更新。
程序15.6使用fseek访问一个学生信息文件。记录数参数的类型是size_t,这是因为它不可能是个负值。需要定位的文件位置通过将记录数和记录长度相乘得到。只有当文件中的所有记录都是同一长度时,这个计算方法才是可行的。最后,fread的结果被返回,这样调用程序就可以判断操作是否成功。
另外还有3个额外的参数,它们用一些限制更严的方式执行相同的任务。它们的原型如下:
void rewind( FILE *stream );
int fgetpos( FILE *stream, fpos_t *position );
int fsetpos( FILE *stream, fpos_t const *position );
rewind函数将读/写指针设置回指定流的开始位置,同时清除流的错误提示标志。fgetpos和fsetpos函数分别是ftell和fseek函数的替代方案。
它们的主要区别在于这对函数接受一个指向fpos_t的指针作为参数。fgetpos在这个位置存储文件的当前位置,fsetpos把文件位置设置为存储在这个位置的值。
用fpos_t表示一个文件位置的方式并不是由标准定义的。它可能是文件中的一个字节偏移量,也可能不是。因此,使用一个从fgetpos函数返回的fpos_t类型的值唯一安全的用法,是把它作为参数传递给后续的fsetpos函数。
/*
**从一个文件读取一个特定的记录。参数分别是进行读取的流、需要读取的记录数和指向放置数据的缓冲区指针。
*/
#include <stdio.h>
#include "student_info.h"
int read_random_record( FILE *f, size_t rec_number, StudentInfo *buffer ){
fseek( f, (long)rec_number * sizeof( StudentInfo ), SEEK_SET );
return fread( buffer, sizeof( StudentInfo ), 1, f );
}
程序15.6 随机文件访问 rd_rand.c
/*
** 刷新和定位函数。
*/
/*
**从一个文件读取一个特定的记录。参数分别是进行读取的流、需要读取的记录数和指向放置数据的缓冲区指针。
*/
#ifndef STUDENT_INFO_H
#define STUDENT_INFO_H
#define NAME_LEN 20
typedef struct STUDENT_INFO{
char name[NAME_LEN];
int age;
char gender;
} StudentInfo;
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "student_info.h"
#define ARR_LEN 5
char *my_gets( char *buffer );
int read_random_record( FILE *f, size_t rec_number, StudentInfo *buffer );
int main( void ){
FILE *output_stream;
FILE *input_stream;
int rec_number;
output_stream = fopen( "students.txt", "w" );
if( !output_stream ){
printf( "fail to open students.txt file.\n" );
perror( "reason:" );
exit( EXIT_FAILURE );
}
StudentInfo students[ARR_LEN];
int i;
char *p;
for( i = 0; i < ARR_LEN; ++i ){
p = my_gets( students[i].name );
if( !p ){
printf( "fail to put information into student's name.\n" );
exit( EXIT_FAILURE );
}
scanf( "%d", &students[i].age );
scanf( " %c", &students[i].gender );
int ch;
/*
** delete redundant character in a line.
*/
while( (ch = getchar()) != EOF && ch != '\n' )
;
}
rec_number = fwrite( students, sizeof(StudentInfo), ARR_LEN, output_stream );
printf( "rec_number = %d, ARR_LEN = %d\n", rec_number, ARR_LEN );
input_stream = freopen( "students.txt", "r", output_stream );
if( !input_stream ){
printf( "fail to open students.txt file.\n" );
perror( "reason:" );
exit( EXIT_FAILURE );
}
StudentInfo buffer;
rec_number = 0;
rec_number = read_random_record( input_stream, rec_number, &buffer );
printf( "rec_number = %d, buffer.name = %s, buffer.age = %d, buffer.gender = %c\n",
rec_number, buffer.name, buffer.age, buffer.gender );
fclose( input_stream );
return EXIT_SUCCESS;
}
char *my_gets( char *buffer ){
char *p = fgets( buffer, NAME_LEN, stdin );
if( p ){
char *p2 = strchr( p, '\n' );
if( p2 ){
*p2 = '\0';
}
}
return p;
}
int read_random_record( FILE *f, size_t rec_number, StudentInfo *buffer ){
fseek( f, (long)rec_number * sizeof( StudentInfo ), SEEK_SET );
return fread( buffer, sizeof( StudentInfo ), 1, f );
}
/* 输出:
*/