用户级文件操作

C语言的文件操作也是用户级的文件操作,通过FILE对象来管理每一个被打开的文件,以及提供了用户级文件缓冲区,因此还涉及到冲刷缓冲区等问题

FILE

FILE类描述了一个文件流。里面存储了文件控制所需的信息:

  • 指向自身缓冲区的的指针
  • 位置指示器
  • 状态指示器

所以C语言中对文件的管理就是对FILE对象的管理

基础操作 - 针对一般文件

基础示例

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>

int main()
{
FILE* pfile = fopen("file.txt", "w");//"w"模式打开文件file.txt
int code = 1;
const char* msg = "this is a msg";
fprintf(pfile, "get msg : %s code:%d", msg, code);//格式化输出字符串
fclose(pfile);//冲刷缓冲区并关闭文件
return 0;
}

以上代码创建了一个file.txt文件,输入格式化字符串(就和使用printf打印一样)。然后用flcose关闭文件流

fopen 打开文件

fopen能够打开以各种模式磁盘上的文件

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

返回值:

  • 成功时,返回一个不为空的FILE*指针,用于控制该文件
  • 失败时,返回NULL空指针并设置了全局变量errno

常见模式

| 模式 | 简述 |
| === | === |
| "w" | 创建一个新的空文件用于输出操作。如果已存在同名文件,清除原文件并当作新文件处理 |
| "r" | 只读模式打开文件。且该文件必须存在 |
| "a" | 打开已有文件时,仅用于在文件末尾追加新的内容。并且重定位函数(fseek,fsetpos,reweind)会被忽略,即使成功调用,也没有效果;当文件不存在时,会创建一个新的空文件 |
| “r+“ | 读写模式打开已有文件,不会清除原文件内容,并且读写时均从文件开头开始。打开后第一次操作为写入时,从文件头部开始逐字符覆盖原文件。读写模式同时只能的一种,第一次取决于先进行哪种操作,可以用fseek函数转换读写模式 |
| “w+“ | 读写模式打开新文件,若存在,则清除原文件内容;读写模式的切换和"r+"模式相同,唯一的区别就是打开时是否清除原文件内容 |
| “a+“ | 从文件末尾打开读写模式,不会清除原文件内容,若打开后第一次操作为写,则从文件末尾开始;若第一次操作为读,则从头开始;读写模式的切换同上 |

二进制模式

如果要以二进制模式打开文件,只需要在上面的模式末尾加上字符b

若有+,则b既可以放在末尾也可以放在中间

  • r+b w+b a+b
  • rb+ wb+ ab+

强制新建文件

新的C语言标准,C2011(不是C++11),添加了一种新的说明符w,可以被添加在任意"w"后面

  • "wx" "wbx" "w+x" "w+bx"/"wb+x"

当文件存在时,w强制fopen函数失败,返回一个NULL空指针

freopen 重定向文件流

FILE* freopen ( const char *filename, const char *mode, FILE * pFile );

  • 如果传入了新的文件名(与pFile控制的文件相比),该函数会关闭pFile原本指向的文件流,并取消关联。然后不论是否成功关闭,freopen会用和fopen同样的方式打开该文件
  • 如果文件名还是原文件,则只会改变打开模式

返回值:

  • 成功时,返回pFile内储存的地址
  • 失败时,返回NULL

特别的

freopen用于进程的输入输出重定向会特别有用

1
2
3
4
5
freopen ("outfile.txt","w",stdout);//标准输出重定向到文件

freopen ("readfile.txt","w",stdin);//标准输入重定向到文件

freopen ("errdfile.txt","w",stderr);//标准错误输出重定向到文件

重定位 文件流位置指示器(stream position indicator)

文件的抽象内存结构

首先我们要明确一下文件的内存结构,如下图

这里及下文用图中的ptr代指标题的中的 文件流位置指示器,这个ptr决定了每一次对文件的读/写操作的起点,同时每一次读/写操作都会使ptr自动往后走,因此要显示控制ptr,就得使用fseek,fsetpos等接口

fseek 重定位

int fseek ( FILE *pFile, long int offset, int origin );

fseek能过直接重定位ptr所指的

参数

  • pFile:用于控制文件的FILE*类型指针
  • offset:则是偏移量,长整型,表示偏移多少字节
  • origin:该形参标注了偏移量相对于哪个位置计算实际位置

origin有三个宏可以选
| 宏 | 实际位置 |
| SEEK_SET | 偏移量从文件头开始算 |
| SEEK_CUR | 偏移量从当前文件指针ptr(上文介绍的)所在位置开始算 |
| SEEK_END | 偏移量从文件尾开始算 |

返回值

  • 成功时,返回0
  • 失败时,返回非零值,同时,这条语句失效,上文说的ptr没有改变

fgetpos 和 fsetpos 设置 ptr

可以用fgetpos获取ptr的当前位置,并使用输出型参数输出一个fpos_t类型的变量,而fsetpos可以用fpos_t类型的形参设置ptr的当前位置

就好比ptr是当前坐标,每次fgetpos得到一个传送点信息,而fsetpos就可以用这个传送点信息传送ptr过去

示例如下

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

int main()
{
//准备一个文件
FILE* pfile = fopen("file.txt", "w");//"w"模式打开文件file.txt
int code = 1;
const char* msg = "this is a msg";
fprintf(pfile, "get msg : %s code:%d", msg, code);
fclose(pfile);
//===========

fpos_t pos1,pos2;
pfile = fopen("file.txt", "r");

fgetpos(pfile, &pos1);
fgetc(pfile);
fgetpos(pfile, &pos2);
for (int i = 0; i < 3; ++i)
{
fsetpos(pfile, &pos2);//循环令ptr指向第二个字符
printf("第2个字符为: %c\n", fgetc(pfile));
}
fsetpos(pfile, &pos1);//令ptr指向第一个字符
printf("第1个字符为: %c\n", fgetc(pfile));

fclose(pfile);

return 0;
}
1
2
3
4
第2个字符为: e
第2个字符为: e
第2个字符为: e
第1个字符为: g

fclose 关闭文件流

可以用fclose显式地关闭文件流

用法为fclose(pFile);

进程正常退出时,也会自动关闭文件流

fprintf 格式化输出字符串

fprintf能格式化输出字符串到指定文件流,除了要指定文件流,格式化字符串的方式和printf一样

  • fprintf(stdout,format,...)printf(format,...)效果一样

fputs 输出字符串

int fputs ( const char * str, FILE * stream );

fputs能将C语言的字符串输入到指定文件流中

fwrite 输出内存数据块

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

  • ptr是指向内存数据块的指针
  • size是每个数组元素的大小
  • nmemb是元素数量
  • stream是文件流

fwrite可以向指定文件流输入特定大小的内存数据块

1
2
3
4
5
6
7
8
9
10
int main()
{
FILE* pfile = fopen("file.txt", "w");//"w"模式打开文件file.txt
char msg[] = "this is a msg";
fwrite(msg, sizeof(char), strlen(msg), pfile);

fclose(pfile);

return 0;
}

fscanf 格式化输入

fscanf能像scanf读取标准输入流一样,读取指定文件流

  • scanf(stdin,format,...)printf(format,...)效果一样

fegts 获取一行

char * fgets (char *str, int num, FILE *stream );

行为

fegts会一直读取直到换行符EOF文件结尾结束读取,但换行符作为非法字符不会被拷贝到形参str

  • 字符串结尾的\0会被自动添加,且计算在读入的最大字符数
  • fgetsgets有很大差别,它需要指定最大的读入字符数

形参

  • str为传入的字符数组作为缓冲区
  • num为拷贝的最大字符数,包括自动添加的结尾\0
  • stream为指定的文件流

返回值

  • 成功时,返回str的值
  • 失败时,返回NULL

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>

int main()
{
//准备一个文件
FILE* pfile = fopen("file.txt", "w");//"w"模式打开文件file.txt
int code = 1;
const char* msg = "this is a msg";
fprintf(pfile, "get msg : %s code:%d", msg, code);
fclose(pfile);
//=====

pfile = fopen("file.txt", "r");
char str[5] = { 0 };
fgets(str, sizeof(str), pfile);//除去自动添加的\0,最多从文件里读入4个字符
printf(str);
printf("|");
return 0;
}
1
get |