上篇介绍了C语言文件操作的基本函数,fopen、fwrite、fread、fclose。这些只能从文件头读写或文件尾追加写入。

本篇介绍文件中随机位置读写的方法,会介绍fseek、ftell、rewind。

此外,再介绍几个字符读写函数:fputs、fgets、fpritf、fscanf,用于编写测试代码时用。

文件随机位置读写基础函数

对于文件的随机位置读写,可以通过 fseek 、ftell与rewind 函数来完成

fseek

fseek用于设置流stream的文件读写位置为给定的偏移

seeK的中文含义是“寻找”

函数原型:

1
2
3
4
5
6
7
8
9
/** @func:  fseek
* @brief: 设置流stream的文件读写位置为给定的偏移
* @para: [fp]:文件指针
* [offset]:偏移量,表示移动的字节数,正数表示正向(结尾)偏移,负数表示负向(开头)偏移
* [from]:表示设定从文件的哪里开始偏移,取值范围如下表所示
* @return:执行成功,返回0 (fp将指向以from为基准,偏移offset个字节的位置)
* 执行失败,返回值-1,并设置errno的值。比如offset超过文件自身大小,则不改变fp指向的位置
*/
int fseek(FILE *fp,long offset,int from);

from参数的取值

起始点 表示 符号 数字表示
文件开头 SEEK_SET 0
当前位置 SEEK_CUR 1
文件末尾 SEEK_END 2

例如:

  • 将读写位置移动到文件开头
1
fseek(fp, 0L, SEEK_SET)
  • 将读写位置移动到文件末尾
1
fseek(fp,0L,SEEK_END);
  • 将读写位置移动到离文件开头100字节
1
fseek(fp,100L,SEEK_SET);
  • 将读写位置移动到离文件当前位置100字节
1
fseek(fp,100L,SEEK_CUR);
  • 将读写位置退回到离文件结尾100字节处(offset为负数表示向开头处移动)
1
fseek(fp,-100L,SEEK_END);

注意:

fseek 函数一般用于二进制文件,当然也可以用于文本文件。

当fseek函数操作文本文件时,要注意回车换行的情况

因为在一般浏览工具(如 UltraEdit)中,回车换行被视为两个字符 0x0D 和 0x0A,但真实的文件读写和定位却按照一个字符 0x0A 进行处理。

这种清空,可以先将文件整个读入内存,然后在内存中手工插入 0x0D。

ftell

fseek 函数只返回执行的结果是否成功,并不返回文件的读写位置

获取当前文件的读写位置,还需要使用 ftell 函数来获取

函数原型:

1
2
3
4
5
6
/** @func:  ftell
* @brief: 得到文件当前的位置指针相对于文件首的偏移字节数
* @para: [fp]:文件指针
* @return:
*/
long ftell(FILE *fp);

fell的主要作用就是获取当前的读写位置,在随机方式存取文件时,由于文件位置频繁前后移动,程序不容易确定文件的当前位置。

在使用 fseek 函数移动了位置后,再调用函数 ftell 就能非常容易地确定文件的当前位置。

fell的一个小应用:获取文件的长度

加入一共文件的读写位置已经被移动了多次,这时若想获得文件的长度,可以向用ftell记录当前的读写位置,然后将其移动到末尾,再利用ftell获取文件尾至头部的位置,就是文件的长度了。获取程度之后,再将读写位置使用fseek复原即可。

1
2
3
4
5
6
7
8
9
10
long getFileLength(FILE *fp)
{
long curPos=0L;/*文件当前的位置指针位置*/
long len=0L;
curPos = ftell(fp);/*记录文件当前的位置指针的位置*/
fseek(fp, 0L, SEEK_END);/*读写位置移动到文件末尾*/
len = ftell(fp);/*获取文件末尾到文件开头的长度*/
fseek(fp, curPos, SEEK_SET);/*再将读写位置移回到之前的位置*/
return len;
}

代码对应的设计思路如下图:

rewind

rewind的中文意思是“倒回”

rewind 函数用于将文件内部的位置指针重新指向一个流(数据流或者文件)的起始位置。

注意,这里的“指针”表示的不是文件指针,而是文件内部的位置指针。即随着对文件的读写,文件的位置指针(指向当前读写字节)向后移动。而文件指针指向整个文件,如果不重新赋值,文件指针不会发生改变。

函数原型:

1
2
3
4
5
6
/** @func:  rewind
* @brief: 将文件内部的位置指针重新指向一个流(数据流或者文件)的起始位置
* @para: [fp]:文件指针
* @return:无
*/
void rewind(FILE *fp);

由于 rewind 函数没有返回值,所以很难判断rewind(fp)是否执行成功。

因此,应该尽量使用 fseek 来替换 rewind 函数,从而以验证流已经成功地回绕

文件读取写入字符串

fputs

fputs函数用于将一行字符串写入文件

函数原型:

1
2
3
4
5
6
7
8
/** @func:  fputs
* @brief: 将一行字符串写入文件
* @para: [str]:要写入的字符串
* [fp]:文件指针
* @return:写入成功,返回非负数
* 写入失败,返回EOF
*/
int fputs( char *str, FILE *fp );

fgets

fgets 函数用来从指定的文件中读取一个字符串,并保存到字符数组中

函数原型:

1
2
3
4
5
6
7
8
9
/** @func:  fgets
* @brief: 从指定的文件中读取一个字符串,并保存到字符数组中
* @para: [str]:字符数组
* [n]:要读取的字符数目
* [fp]:文件指针
* @return:读取成功,返回字符数组首地址,也即str
* 读取失败,返回 NULL
*/
char *fgets ( char *str, int n, FILE *fp );

fprintf

函数原型:

1
2
3
4
5
6
7
8
/** @func:  fprintf
* @brief: 将格式化的字符串写入文件
* @para: [fp]:文件指针
* [format]:格式化字符串,要被写入到fp中的文本
* @return:写入成功,返回写入的字符数
* 写入失败,返回负数
*/
int fprintf(FILE *fp, const char *format, ...)

使用方法:

1
2
3
4
5
FILE *fp = fopen ("test.txt", "w+");
char *str = "xxpcb.gitee.io";
int num = 666;
fprintf(fp, "%s %d", str, num);
fclose(fp);

fscanf

函数原型:

1
2
3
4
5
6
7
8
/** @func:  fscanf
* @brief: 从文件中读取格式化的字符串
* @para: [fp]:文件指针
* [format]:格式化字符串,从fp中读出的内容
* @return:读取成功,返回读出的字符数
* 读取失败,返回负数
*/
int fscanf(FILE *fp, const char *format, ...)

使用方法:

1
2
3
4
5
FILE *fp = fopen ("test.txt", "r");
char str[64]
int num;
fscanf(fp, "%s %d", str, num);
fclose(fp);

使用示例

下面的测试程序,首先使用fputs函数写入了一段字符串“Hello world”,然后使用fseek函数,将读写位置移动到了文件开头向后的第6个字符,接着在该处,又使用fputs函数写入了一段字符串“xxpcb.github.io”,这样,就会在指定位置处,进行覆盖写入。最后,使用fgets函数,将文件中写入的内容再获取出来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <stdio.h>
#define N 100

int main ()
{
/*打开*/
FILE *fp = fopen("../test-futs.txt","wt+");/*打开一个文件*/
if(NULL == fp)
{
printf("open file fail\r\n");
goto end;
}

/*写入*/
fputs("hello world\n", fp);/*先写入一段信息*/
fseek( fp, 6, SEEK_SET );/*读写位置从开头向后移动6个位置*/
fputs("xxpcb.github.io\n", fp);/*再写入一段信息*/
if(0 != fputs("码农爱学习\n", fp));/*再写入一段信息*/
{
printf("fputs file ok\r\n");
}


/*关闭*/
fclose(fp);

/*再打开*/
fp = fopen("../test-futs.txt","rt");
if(NULL == fp)
{
printf("open file fail\r\n");
goto end;
}

/*读取*/
printf("fgets file ...\r\n");
char str[N+1];
int line = 0;
while(fgets(str, N, fp) != NULL)/*使用while, 可以一行一行的获取*/
{
printf("[%d]:%s", ++line, str);
}

end:
return(0);
}

代码对应的设计思路如下图:

附:本篇以及上篇的测试代码,可从我的gitee仓库获取(地址:https://gitee.com/xxpcb/c-test/tree/master/C-file-operate),或公众号后台回复“**C文件操作**”获取~