C语言-文件
一,为什么使用文件
为什么使用文件呢?
如果我们在编写一个程序,程序中有很多数据需要保存下来,以便下次使用。
例如,我在之前投稿的一个通讯录的一个小程序,载每次使用,关闭程序后,
再次打开程序的时候发现之前存的练习人不见了,这是为什么呢?
是因为我们程序中的数据是放在内存上的,一旦我们程序关闭,内存上的数据就会被清理掉。
所以我们可以将之前联系人的信息在程序结束前,存放到文件中,文件时磁盘上的空间不会因为
程序的结束而导致数据的丢失。
二,文件的打开与关闭
那文件是如何使用的呢?
首先,我们要打开文件
打开文件后进行读写
在我们打开文件的时候,内存中就会自动产生一个文件信息区,这是一个结构体类型的变量
用来管理文件的,打开文件后fopen函数会一个文件类型的指针来指向文件信息区这块内存空间。
读写之后关闭文件
这是运用文件的一套流程
下面我们来介绍文件的打开与关闭
1,fopen

第一个参数是文件名,第二个参数文件的打开形式
下面介绍一下各种打开方式
(1)r w a
r(只读):为了输入数据打开一个文件,当文件不存在时就会出错
w(只写):为了输出数据而打开一个文件,当文件不存在时会创建一个新的文件
a(追加):向文件尾部追加数据,当文件不存在时会创建一个新的文件
(2)rb wb ab
rb(只读):为了输入数据打开一个二进制文件,当文件不存在时就会出错
wb(只写):为了输出数据而打开一个二进制文件,当文件不存在时会创建一个新的文件
ab(追加):向一个二进制文件尾部追加数据,当文件不存在时会创建一个新的数据
(3)r+ w+ a+
r+(读写):为了读写打开一个文件,当文件不存在时就会出错
w+(读写):为了读写打开一个文件,当文件不存在时会创建一个新的文件
a+(读写):打开一个文件在文件尾部进行读写,文件不存在时会创建一个新的文件
(4)rb+ wb+ ab+
r+(读写):为了读写打开一个二进制文件,当文件不存在时就会出错
w+(读写):为了读写打开一个二进制文件,当文件不存在时会创建一个新的文件
a+(读写):打开一个二进制文件在文件尾部进行读写,文件不存在时会创建一个新的文件
2,fclose

就一个参数,及指向文件流的指针变量
下面演示一下,文件打开关闭的具体流程
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "w");
if (pf == NULL)
{
perror("fopen:");
return 1;
}
//文件读写
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
三,文件顺序读写函数
1,fgetc,fputc

以字符的模式输出数据
例如,我们可以将26个字母输出到文件中
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "w");
if (pf == NULL)
{
perror("fopen:");
return 1;
}
//文件读写
int i = 0;
for (i = 0; i < 26; i++)
{
fputc('a' + i, pf);
}
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}


参数只有一个指向文件流的指针变量
下面演示一下如何读取数据
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen:");
return 1;
}
//文件读写
char arr[27] = "";
int i = 0;
for (i = 0; i < 26; i++)
{
arr[i]=fgetc(pf);
}
printf("%s\n", arr);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}

2,fgets,fputs

以文本行的方式输出数据
下面举个例子
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "w");
if (pf == NULL)
{
perror("fopen:");
return 1;
}
//文件读写
char arr[] = "hello world";
fputs(arr, pf);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}


以文本行的方式读入数据。
注意第二个参数num,是读到的字符个数(实际是num-1个,第num个字符是'\0'),
例如一行有20个字符但是num写的10,那么第一次读了九个字符,下次读的时候接上
上次的位置继续读
如果num大于一行的字符数,那么也只会读取一行的数据,不会在往下读了。
例如我们读取刚刚写入的hello world
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen:");
return 1;
}
//文件读写
char arr[12] = "";
fgets(arr,20, pf);
printf("%s\n", arr);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}

3,fscanf,fprintf


这两个函数是按照一定的格式,输入/输出的函数与scanf printf 非常类似
下面我们以输出和输入一个结构体的数据为例
struct stu
{
char name[20];
int age;
float score;
};
int main()
{
//打开文件
struct stu s1 = { "zhangsan",20,98.5f };
FILE* pf = fopen("test.txt", "w");
if (pf == NULL)
{
perror("fopen:");
return 1;
}
//文件读写
fprintf(pf, "%s %d %f", s1.name, s1.age, s1.score);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}

下面,我们再用sscanf从文件中把这个结构体的数据读入到内存中
int main()
{
//打开文件
struct stu s2 = { 0 };
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen:");
return 1;
}
//文件读写
fscanf(pf, "%s %d %f", s2.name, &(s2.age), &(s2.score));
printf("%s %d %f", s2.name, s2.age, s2.score);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}

4,fread,fwrite


这两个函数适用于二进制文件的读写
第一个参数是指向存放读入/读出数据空间的指针变量,第二个参数是这种数据的大小(字节)
第三个数据是要读入/读出多少个这种数据
第四个参数是指向文件流的指针变量
我们依然以这个结构体为例子将它读出到二进制文件中,
并且将它从二进制文件中读取到内存中
int main()
{
//打开文件
struct stu s1 = { "zhangsan",20,98.5f };
FILE* pf = fopen("test.txt", "wb");
if (pf == NULL)
{
perror("fopen:");
return 1;
}
//文件读写
fwrite(&s1, sizeof(s1), 1, pf);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}

int main()
{
//打开文件
struct stu s2 = { 0 };
FILE* pf = fopen("test.txt", "rb");
if (pf == NULL)
{
perror("fopen:");
return 1;
}
//文件读写
fread(&s2, sizeof(s2), 1, pf);
printf("%s %d %f", s2.name, s2.age, s2.score);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}

四,文件的随机读写
上面介绍了文件的顺序读写,是因为我们以何种形式打开文件时,文件指针就会处在某种位置,
例如我们以r的形式打开文件,那么文件指针就在文件的首部,
以w的形式打开文件,那么文件指针就在文件的尾部
以a的形式打开文件,那么文件的指针就在文件的尾部
但是我们以读 或者 写的方式打开文件,可以通过下面的函数来调整文件指针的位置,
以追加的形式不可以哟。
1,fseek


起始位置,可以时文件首部,文件尾部,或者是当前位置
第二个参数是偏移量,是指据起始位置的偏移量
我们先将文件的内容写成abcdef
例如我们用fgetc函数读入数据是第一次读的是a
那么第二次读的就是b,但是我们可以通过改变文件指针的位置,使它读到的不是b
下面看例子
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen:");
return 1;
}
//文件读写
int ret = fgetc(pf);
printf("%c\n", ret);
fseek(pf, 1, SEEK_CUR);
ret = fgetc(pf);
printf("%c\n", ret);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}

可以看到我们跳过了b,直接读到了c
2,ftell

这个函数的作用是告诉我们文件指针当前的位置(及相对于文件首部的偏移量)
以上段代码为例子,读取完c之后指针自动向后跳一步,及偏移量是3,
我们来验证一下
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen:");
return 1;
}
//文件读写
int ret = fgetc(pf);
printf("%c\n", ret);
fseek(pf, 1, SEEK_CUR);
ret = fgetc(pf);
printf("%c\n", ret);
int pose=ftell(pf);
printf("%d\n", pose);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}

3,rewind

这个函数的作用是将文件指针调整到指向文件首部的位置。
例如,我们继续上述代码将指针rewind后再读取一个字符
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen:");
return 1;
}
//文件读写
int ret = fgetc(pf);
printf("%c\n", ret);
fseek(pf, 1, SEEK_CUR);
ret = fgetc(pf);
printf("%c\n", ret);
int pose=ftell(pf);
printf("%d\n", pose);
rewind(pf);
ret = fgetc(pf);
printf("%c\n", ret);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
可以看到依然读取的是a这个字符,充分说明文件指针又回到了文件首部的位置
五,文件读取结束的判定
我们再从文件中读取数据时,难免会发生某些错误是读取暂停,
我们可以通过下面的两个函数,来表明究竟是遇到IO错误,读取到了文件尾部的EOF标志
导致读取结束的
1,feof

当这个函数的返回值为非零数时,表示遇到EOF导致读取暂停
而返回零时表示,遇到错误导致读取暂停
2,ferror

这个函数的返回值与feof返回值恰好相反
下面我们举个例子,来说明一下这两个函数的运用场景
#include <stdio.h>
enum { SIZE = 5 };
int main(void)
{
double a[SIZE] = {1.,2.,3.,4.,5.};
FILE *fp = fopen("test.bin", "wb"); // 必须用二进制模式
fwrite(a, sizeof *a, SIZE, fp); // 写 double 的数组
fclose(fp);
double b[SIZE];
fp = fopen("test.bin","rb");
size_t ret_code = fread(b, sizeof *b, SIZE, fp); // 读 double 的数组
if(ret_code == SIZE) {
puts("Array read successfully, contents: ");
for(int n = 0; n < SIZE; ++n) printf("%f ", b[n]);
putchar('\n');
} else { // error handling
if (feof(fp))
printf("Error reading test.bin: unexpected end of file\n");
else if (ferror(fp)) {
perror("Error reading test.bin");
}
}
fclose(fp);
}
六,二进制文件与文本文件
我们知道,数据在内存中的存储都是以二进制的方式存储的
如果我们不加转换的输出到外存,那么就是二进制文件
如果要求外存上以ASCLL码的形式存储,那么需要在存储前转换。这样得到的文件就是文本文件
我们拿10000举个例子

我们验证一下,看看二进制文件里储存的是不是二进制码
int main()
{
FILE* pf = fopen("test.txt", "w");
if (pf == NULL)
{
perror("fopen:");
return 1;
}
//文件读写
int a = 10000;
fwrite(&a, sizeof(a), 1, pf);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
我么看下文件中的内容

我们发现不对劲啊,这是因为。txt文件只能以文本的形式打开文件,所以看到的是乱码,
我们可以借助VS ,它可以打开二进制文件



可以看到 10 27 00 00 就是10000 这是用十六进制表示的。
七,输出缓冲区

设置缓冲区是为了提高操作系统的效率,因为我们每次将数据传到硬盘的时候都会麻烦操作系统一次,
所以当缓冲区中的数据达到一定量的时候一起传到硬盘上。
但是也有其他刷新缓冲区的方法。
例如我们fclose(pf)关闭文件的时候就会刷新一次缓冲区,
也可以用fflush(pf)强制刷新一下缓冲区,将其中的内容放到硬盘上去

本文中,可以看到我引用了大量上上面那样的函数信息
下面给大家推荐哥网站,可以用来平时查询函数用的网站
链接: link
本文含有隐藏内容,请 开通VIP 后查看