关于文件操作个人比较困惑的地方有两点:

  1. 关于 wwb的区别
  2. 如何定位文件的读写位置

文件格式和打开模式

c 中的文件打开模式分为:文本模式和二进制模式,分别处理文本格式文件和二进制格式文件。

两个模式的主要区别是在换行符的处理上,利用文本模式在写文本内容到文件的时候,需要将换行符转换成系统对应的编码方式.

系统不同,对换行符的表示方式也是不一样的 ,例如unix 系统是 \n,而MS-DOS\r\nMac\rC 里面都是用 \n 作为换行符的,所以在文本写入时,底层需要将 C 形式换行符 \n 做对应的转换之后写入文件,读取文件时将对应系统的换行符转成 C 形式的。因为 unix 系统的换行符是 \n,这和C 形式一致,所以 unix 系统下文本模式和二进制模式没有区别。

C 中使用 fopen 函数创建文件句柄,函数原型如下:

1
FILE *fopen(const char *filename, const char *mode)

filename表示文件路径,mode表示打开模式,成功返回一个文件句柄指针,失败返回 null。

mode 有下列几种形态字符串:

  • r 以只读方式打开文件,该文件必须存在。
  • r+ 以可读写方式打开文件,该文件必须存在。
  • rb+ 读写打开一个二进制文件,允许读数据。
  • rw+ 读写打开一个文本文件,允许读和写。
  • w 打开只写文件,若文件存在则文件长度清为 0,即该文件内容会消失。若文件不存在则建立该文件。
  • w+ 打开可读写文件,若文件存在则文件长度清为零,即该文件内容会消失。若文件不存在则建立该文件。
  • a 以附加的方式打开只写文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留。(EOF 符保留)
  • a+ 以附加方式打开可读写的文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾后,即文件原先的内容会被保留。 (原来的 EOF 符不保留)
  • wb 只写打开或新建一个二进制文件;只允许写数据。
  • wb+ 读写打开或建立一个二进制文件,允许读和写。
  • ab+ 读写打开一个二进制文件,允许读或在文件末追加数据。
  • at+ 打开一个文本文件,a表示 append, 就是说写入处理的时候是接着原来文件已有内容写入,不是从头写入覆盖掉,t 表示打开文件的类型是文本文件,+号表示对文件既可以读也可以写。

上述的形态字符串都可以再加一个 b 字符,如 rbw+bab+等组合,加入 b 字符用来告诉函数库以二进制模式打开文件。 如果不加 b,表示默认加了t,即rt,wt, 其中t 表示以文本模式打开文件。

windows 上分别利用 w+wb+模式测试一下文本模式和二进制模式写数据的区别:

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
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char* argv[])
{
// 文件 w+.txt
FILE *fp1 = fopen(".\\w+.txt", "w+");
if (!fp1)
{
fputs("文件打开错误!", stdin);
return EXIT_FAILURE;
}
fprintf(fp1, "%s", "The first line!\nThe second line!\n"); // 写入内容中带有换行符
fclose(fp1);

// 文件 wb+.txt
FILE* fp2 = fopen(".\\wb+.txt", "wb+");
if (!fp2)
{
fputs("文件打开错误!", stdin);
return EXIT_FAILURE;
}
fprintf(fp2, "%s", "The first line!\nThe second line!\n"); // 写入内容中带有换行符
fclose(fp2);
return EXIT_SUCCESS;
}

左侧显示的是 w+.txt,右侧显示的是wb+.txt,明显可以看出保存的换行符是有区别的,wb+ 模式没有将 C 代码中的 \n 进行特殊处理:

文件读写位置定位

如果可以在访问文件的时候,能够直接定位到某个位置进行读取,那就可以实现像数组一样随机访问了。

C 语言提供了几个相关的函数,他们的原型如下:

1
2
3
4
5
int fseek(FILE *stream, long offset, int origin );
long ftell(FILE *stream);
int fgetpos(FILE *restrict stream, fpos_t *restrict pos );
int fsetpos(FILE *stream, const fpos_t *pos );
void rewind(FILE *stream);

其中,rewind 函数用于将文件内部的位置指针重新指向一个流(数据流或者文件)的起始位置。这里需要注意的是,这里的“指针”表示的不是文件指针,而是文件内部的位置指针。即随着对文件的读写,文件的位置指针(指向当前读写字节)向后移动。而文件指针指向整个文件,如果不重新赋值,文件指针不会发生改变。

例如,使用 w+ 模式打开一个文件写入内容之后,再输出文件内容,代码可以这么写:

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
#include <stdio.h>
#include <stdlib.h>
#define MAXLEN 80

int main()
{
// 打开文件
char filename[MAXLEN] = ".\\test.txt";
FILE* fp = fopen(filename, "w+");
if (!fp)
{
fputs("文件打开失败!", stdout);
exit(EXIT_FAILURE);
}
// 写入文本
char* text = "This is a test file!";
fputs(text, fp);
// 还原位置指针
rewind(fp);
// 读取文件内容
char c;
while ((c = fgetc(fp)) != EOF)
{
putchar(c);
}
// 关闭文件
fclose(fp);
return EXIT_SUCCESS;
}

rewind功能比较简单,只能用于返回到文件开头,如果想要跳转到其他位置,则 fseek 功能更加强大,它用来设定文件的读写位置,可以实现文件的随机访问。

fseek的三个参数, 第一个是文件句柄,第三个参数是基准位置,第二个是相对于基准位置的偏移处,基准位置有三个:

名称 代表位置 值形式
SEEK_SET 文件首部 0
SEEK_CUR 当前位置 1
SEEK_END 文件尾部 2

示例代码:

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
#include <stdio.h>
#include <stdlib.h>

int main()
{
// 打开文件
FILE* fp = fopen(".\\test.txt", "w+");
if (!fp)
{
fputs("文件打开失败!", stdout);
exit(EXIT_FAILURE);
}
// 先写入 123,然后改成 abc
fputc('1', fp);
fputc('2', fp);
fputc('3', fp);
// 先将指针转到中间改 b
fseek(fp, -2, SEEK_END);
fputc('b', fp);
// 将指针转到开头改 a
fseek(fp, 0, SEEK_SET);
fputc('a', fp);
// 将指针转到第三个字符改 c
fseek(fp, 1, SEEK_CUR);
fputc('c', fp);
// 关闭文件
fclose(fp);
return EXIT_SUCCESS;
}

需要注意的是,SEEK_END指向了文件结尾,所以需要向前偏移 2,才能将指针指到 1 的后面。

对于以文本模式打开的流,使用 fseek 函数时候需要注意,因为’\n’换行符与系统换行符之间的转换会导致 fseek 产生意外的结果。fseek只有在下面两种情况下才能保证当文件以文档模式打开时能正确使用 fseek 函数

  • 与起始位置相对偏移为 0 的重置,即没有改动指针位置
  • origin设置为 SEEK_SEToffset 为调用 ftell 返回的值时进行的指针位置重置情况

fsetpos 和 fseek

fsetpos/fgetposfseek/ftell 感觉很像,刚开始觉得他们可以用来互相替换,fsetpos也可以用来实现随机访问,后来发现错了,fseek之所以能够实现随机访问文件是因为可以传入一个整型的参数作为文件偏移,而 fsetpos 接收的参数是 fpos_t *,这个fpos_t 只能使用通过 fgetpost 返回的值,不能直接指定,所以两者还是有区别的。




root@kali ~# cat 重要声明
本博客所有原创文章,作者皆保留权利。> 转载必须包含本声明,保持文本完整,并以超链接形式注明出处【[Techliu](https://scriptboy.cn)】。查看和编写文> 章评论都需翻墙,为了更方便地获取文章信息,可订阅[RSS](https://feeds2.feedburner.com/techliu),如果您还没有 一款喜爱的阅读器,不妨试试[Inoreader.](https://www.inoreader.com)。
r oot@kali ~# Thankyou!

⬆︎TOP