文件

文件的概念

文件的定义

磁盘文件:指一组相关数据的有序集合,通常存储在外部介质上,使用时才调入内存。

设备文件:在操作系统中把每一个与主机相连的输入、输出设备看作是一个文件,把它们的输入、输出等同于对磁盘文件的读和写。

在 Linux 操作系统中,每一个外部设备都在 /dev 目录下对应着一个设备文件,在程序中操作设备,就必须对与其对应的 /dev 下的设备文件进行操作。

image

我就豁出去是库函数申请的一段内存,由库函数对其进行操作,程序员没有必要找到存放在哪里,只需要知道对文件操作时的一些缓冲特点即可。

行缓冲:

标准 IO 库函数,往标准输出输出东西时是行缓冲的。

所谓行缓冲就是缓冲区碰到换行符时才刷新缓冲区。。

如果不刷新缓冲区,无法对文件进行读写操作。

行缓冲的刷新条件:

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

int main(int argc, char const *argv[]) {
// 由于printf函数是一个标准io,所以只有刷新缓冲区才可以将数据输出到终端
// printf("hello world");

// 刷新缓冲区方法1:使用\n
// printf("hello world\n");

// 刷新缓冲区方法2:程序正常结束
// printf("hello world");
// return 0;

// 刷新缓冲区方法3:使用fflush函数刷新缓冲区
// printf("hello world");
// fflush:刷新函数。可以刷新指定的缓冲区
// stdout:标准输出,就是对终端进行写操作
// fflush(stdout);

// 刷新缓冲区方法4:当缓冲区满的时候自动刷新
// 默认行缓冲的大小为1024个字节
int i;
for (i = 1; i < 300; i++) {
printf("%03d ", i);
}

while (1)
;

return 0;
}

全缓冲:

标准 IO 库函数,往普通文件读写数据时,是全缓冲的,碰到换行符也不刷新缓冲区,只有缓冲区满了才刷新缓冲区。

刷新缓冲区的情况:

  1. 缓冲区满了,刷新缓冲区;
  2. 使用 fflush 函数刷新缓冲区。
  3. 程序正常结束,刷新缓冲区。

无缓冲:

在读写文件时通过系统调用 IO 对文件进行读写操作,这个时候是无缓冲的,即写数据会立马进入文件,读数据会立马进入内存。

写文件的流程:

应用程序空间->内核空间->驱动程序->硬盘

应用程序和内核程序运行在不同的空间里,目的是为了保护内核。

设置缓存区可以减少进出内核的次数,提高效率。

磁盘文件的分类

一个文件通常是磁盘上一段命名的存储区,计算机的存储在物理上是二进制的。

物理上所有的磁盘文件本质上都是一样的:以字节为单位进行顺序存储。

从用户或者操作系统的角度来说把文件分为:

  • 文本文件:基于字符编码的文件;
  • 二进制文件:基于值编码的文件。

文本文件、二进制文件对比:

译码:文本文件基于字符,译码容易些;二进制文件编码是边长的,译码难一些。(不同的二进制文件有不同的编码方式)

空间利用率:二进制文件的基本单位是 bit,而文本文件的基本单位是字符,所有二进制文件的空间利用率高。

可读性:文本文件可以用记事本的等软件阅读,二进制文件需要用特定的译码器。

文件指针

文件指针用来标识一个文件,所有对文件的操作都是通过对文件指针来完成的。

定义文件指针的语法:

1
FILE *指针名;

文件指针是一个结构体指针,结构体中包含了与文件相关的很多信息,但在编程时无需结构体的成员,只需使用文件指针即可。

对文件操作的步骤:

  1. 对文件进行读写等操作之前要打开文件得到文件指针;
  2. 通过文件指针对文件进行读写操作;
  3. 操作结束后,要关闭文件,关闭文件后,就不能再通过此文件指针对文件进行操作了。

C 语言中有三个定义好的特殊文件指针:

  1. stdin:标准输入,使用 scanfgetchar 函数默认从 stdin 中输入;
  2. stdout:标准输出,使用 printfputs 函数默认输出到 stdout
  3. stderr:标准错误输出,使用 perror 函数默认输出到 stderr.

打开文件 fopen

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
FILE *fopen(const char *path, const char *mode);
功能:创建或者打开一个文件
参数:
path:文件名,如果只写文件名,默认就是当前路径,也可以添加路径
mode:文件权限
r 只读,如果文件不存在则报错
r+ 读写,如果文件不存在则报错
w 只写,如果文件不存在则创建,如果文件存在则清空
w+ 读写,如果文件不存在则创建,如果文件存在则清空
a 只写,如果文件不存在则创建,如果文件存在则追加
a+ 读写,如果文件不存在则创建,如果文件存在则追加
返回值:
成功:文件指针
失败:NULL
1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>

int main(int argc, char *argv[]) {

FILE *fp = fopen("./file.txt", "w");
if (fp == NULL) {
printf("fail to fopen\n");
return -1;
}

return 0;
}

关闭文件 fclose

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
int fclose(FILE *stream);
功能:关闭一个文件指针,无法在对当前文件进行操作
参数:
stream:指定的文件指针,fopen函数的返回值
返回值:
成功:0
失败:EOF
注意:注意一个文件只能关闭一次,不能多次关闭。
关闭文件之后就不能再文件指针对文件进行读写等操作了
1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>

int main(int argc, char *argv[]) {

FILE *fp = fopen("./file.txt", "a");
if (fp == NULL) {
printf("fail to fopen\n");
return -1;
}
fclose(fp);
return 0;
}

一次读写一个字符

fgetc

1
2
3
4
5
6
7
8
9
#include <stdio.h>
int fgetc(FILE *stream);
功能:从文件指针标识的文件中读取一个字符
参数:
stream:指定的文件指针
返回值:
成功:读取的字符
失败:EOF
如果文件读取完毕,也会返回EOF

文件内容:

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

int main(int argc, char *argv[]) {
FILE *fp = fopen("./file.txt", "r");
if (fp == NULL) {
printf("fail to fopen\n");
return -1;
}
int c;
while ((c = fgetc(fp)) != EOF) {
printf("c = [%c] - %d\n", c, c);
}
return 0;
}

输出结果:

image

fputc

1
2
3
4
5
6
7
8
9
#include <stdio.h>
int fputc(int c, FILE *stream);
功能:向文件指针标识的文件中写入一个字符
参数:
c:要写入的字符
stream:指定的文件指针
返回值:
成功:要写入的字符
失败:EOF
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>

int main(int argc, char *argv[]) {
FILE *fp = fopen("./file.txt", "w");
if (fp == NULL) {
printf("fail to fopen\n");
return -1;
}

fputc('w', fp);
fputc('h', fp);
fputc('a', fp);
fputc('t', fp);
fputc('\n', fp);
fputc('o', fp);

return 0;
}

执行结果:

image

一次读写一个字符串

fgets

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
char *fgets(char *s, int size, FILE *stream);
功能:从文件中读取内容
参数:
s:保存读取到的内容
size:每次读取的最大个数
stream:文件指针
返回值:
成功:读取的数据的首地址
失败:NULL
如果文件内容读取完毕,也返回NULL
注意:从stream所指的文件中读取字符,在读取的时候碰到换行符或者是碰到文件的末尾停止读取,或者是读取了size‐1个字节停止读取,在读取的内容后面会加一个\0,作为字符串的结尾

文件内容:

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

int main(int argc, char *argv[]) {
FILE *fp = fopen("./file.txt", "r");
if (fp == NULL) {
printf("fail to fopen\n");
return -1;
}

char buf[32] = "";

fgets(buf, 32, fp);
printf("buf = %s\n", buf);

return 0;
}

执行结果:

image

fputs

1
2
3
4
5
6
7
8
9
#include <stdio.h>
int fputs(const char *s, FILE *stream);
功能:向文件写入数据
参数:
s:要写入的内容
stream:文件指针
返回值:
成功:写入文件内容的字节数
失败:EOF
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>

int main(int argc, char *argv[]) {
FILE *fp = fopen("./file.txt", "w");
if (fp == NULL) {
printf("fail to fopen\n");
return -1;
}
fputs("66666666666666\n", fp);
fputs("nihao", fp);

return 0;
}
image

读文件 fread

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
功能:从文件中读取数据
参数:
ptr:保存读取的数据
size:每次读取的字节数
nmemb:一共读取的次数
stream:文件指针
返回值:
成功:实际读取的次数(对象数、块数)
失败:0
如果文件内容读取完毕,返回0

例如:

1
2
3
4
5
6
int num = fread(str,100,3,fp);
从fp所代表的文件中读取内容存放到str指向的内存中,读取的字节数为 ,每块100个字节,3块。
返回值num,如果读到300个字节返回值num为3
如果读到了大于等于200个字节小于300个字节 返回值为2
读到的字节数,大于等于100个字节小于200个字节 返回1
不到100个字节返回0

文件内容:

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

int main(int argc, char *argv[]) {
FILE *fp;
fp = fopen("./file.txt", "r");
if (fp == NULL) {
printf("fail to fopen\n");
return -1;
}
int num;
char buf[128] = "";
num = fread(buf, 5, 4, fp);
printf("buf = %s\n", buf);
printf("num = %d\n", num);

return 0;
}

输出结果:

image

写文件 fwrite

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
功能:向文件中写入数据
参数:
ptr:要写入的数据
size:一次写入的字节数
nmemb:一共写入的次数
stream:文件指针
返回值:
成功:实际写入的次数
失败:0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>

typedef struct {
int a;
int b;
char c;
} MSG;

int main(int argc, char *argv[]) {
FILE *fp = fopen("./file.txt", "w+");
if (fp == NULL) {
printf("fail to fopen\n");
return -1;
}
MSG msg[4] = {1, 2, 'a', 3, 4, 'b', 5, 6, 'c', 7, 8, 'd'};
fwrite(msg, sizeof(MSG), 4, fp);
rewind(fp);
MSG rcv[4];
fread(rcv, sizeof(MSG), 4, fp);
for (int i = 0; i < 4; i++) {
printf("%d - %d - %c\n", rcv[i].a, rcv[i].b, rcv[i].c);
}
return 0;
}

输出结果:

image

输出的文件内容:

image

格式化读写文件函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
函数调用:
fprintf(文件指针, 格式字符串, 输出表列);
fscanf(文件指针, 格式字符串, 输入表列);

函数功能:
从磁盘文件中读入或输出字符

fprintfprintf函数类似:
printf是将数据输出到屏幕上(标准输出),
fprintf函数是将数据输出到文件指针所指定的文件中。

fscanfscanf 函数类似:
scanf是从键盘(标准输入)获取输入,
fscanf是从文件指针所标示的文件中获取输入。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>

int main(int argc, char *argv[]) {
FILE *fp;
char ch1 = 'a', ch2;
int num1 = 50, num2;
char string1[20] = "hello", string2[20];
float score1 = 85.5, score2;

if ((fp = fopen("./file.txt", "w+")) == NULL) {
printf("fail to fopen\n");
return -1;
}

fprintf(fp, "%c %d %s %f\n", ch1, num1, string1, score1);
rewind(fp);
fscanf(fp, "%c %d %s %f\n", &ch2, &num2, string2, &score2);
printf("%c %d %s %f\n", ch2, num2, string2, score2);
fclose(fp);

return 0;
}

输出结果:

image

文件内容:

image

随机读写

rewind

1
2
3
4
5
6
#include <stdio.h>
void rewind(FILE *stream);
功能:将文件位置定位到起始位置
参数:
stream:文件指针
返回值:无

ftell

1
2
3
4
5
6
7
#include <stdio.h>
long ftell(FILE *stream);
功能:获取当前文件的偏移量
参数:
stream:文件指针
返回值:
获取当前文件的偏移量

fseek

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
int fseek(FILE *stream, long offset, int whence);
功能:设置文件位置指针的偏移量
参数:
stream:文件指针
offset:偏移量,可正可负也可为0
whence:相对位置
SEEK_SET 文件起始位置
SEEK_CUR 文件当前位置
SEEK_END 文件末尾位置(最后一个字符后面一个位置)
返回值:
成功:0
失败:‐1

rewind(fp) <==> fseek(fp, 0, SEEK_SET);
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
#include <stdio.h>

int main(int argc, char *argv[]) {
FILE *fp;
if ((fp = fopen("./file.txt", "w+")) == NULL) {
printf("fail to fopen\n");
return -1;
}
fputs("123456789\n", fp);
fputs("abcdefghijklmn", fp);
// 获取当前文件指针的读写位置
printf("offset = %ld\n", ftell(fp));
// 将当前文件的读写文件设置到文件的起始位置
// rewind(fp);
// fseek(fp, 0, SEEK_SET);
// 将当前文件的读写位置设置为倒数第五个位置
fseek(fp, -5, SEEK_END);

char buf[32] = "";
while (fgets(buf, 32, fp) != NULL) {
printf("[%s]\n", buf);
}

return 0;
}

输出结果:

image

文件内容:

image