目录
5.2 scanf/fscanf/sscanf printf/sprintf/fprintf
1.为什么要使用文件
#include<stdio.h>
int main()
{
int a = 10;
return 0;
}//这里a里面确实放的是10的值,但是退出程序内存回收,就没有这个a了
2. 什么是文件
2.1 程序文件
2.2 数据文件
2.3 文件名
3. 二进制文件与文本文件
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
FILE* fp1 = fopen("data.txt", "wb");
int a = 10000;
fwrite(&a, sizeof(a), 1, fp1);//这里表示将a这个数据以二进制的方式写进文件data.txt里面去
fclose(fp1);
fp1 = NULL;
return 0;
}
4. 文件的打开与关闭
在生活中,喝一瓶水,需要以下步骤
1.拧开瓶盖
2.喝水
3.关上瓶盖
那么,类似的,对文件的操作有以下步骤
1.打开文件
2.操作
3.关闭文件
4.1 流与标准流
4.1.1 流
解到每一个外部设备与C程序是怎么进行操作的,这无疑是非常麻烦的。为了方便程序员对各种设
备进行方便的操作,我们抽象出了流的概念,我们可以把流想象成流淌着字符的河。
4.1.2 标准流
4.2 文件指针
struct _iobuf
{
char *_ptr;
int _cnt;
char *_base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char *_tmpfname;
};
typedef struct _iobuf FILE;
FILE * fp;
4.3 文件的打开与关闭
文件打开方式 | 含义 | 若指定文件不存在 |
"r"(只读) | 为输入数据,打开一个已经存在的文件 | 出错 |
"w"(只写) | 为写数据,打开一个文本文件 | 建立一个新的文件 |
"a"(追加) |
向⽂本⽂件尾添加数据
| 建立一个新的文件 |
"rb"(只读) |
为了输⼊数据,打开⼀个⼆进制⽂件
| 出错 |
"wb"(只写) | 为输出数据,打开一个二进制文件 | 建立一个新的文件 |
"ab"(追加) | 向一个二进制文件尾添加数据 | 建立一个新的文件 |
"r+"(读写) | 为了读和写,打开一个文本文件 | 出错 |
"w+"(读写) |
为了读和写,建立⼀个新的⽂件
|
建立⼀个新的⽂件
|
"a+"(读写) |
打开⼀个⽂件,在⽂件尾进⾏读写
|
建立⼀个新的⽂件
|
"rb+"(读写) |
为了读和写打开⼀个⼆进制⽂件
| 出错 |
“wb+”(读写) |
为了读和写,新建⼀个新的⼆进制⽂件
| 建立一个新的文件 |
"ab+"(读写) |
打开⼀个⼆进制⽂件,在⽂件尾进⾏读和写
| 建立一个新的文件 |
int main ()
{
FILE * pFile;
//打开⽂件
pFile = fopen ("myfile.txt","w");
//⽂件操作
if (pFile!=NULL)
{
fputs ("fopen example",pFile);
//关闭⽂件
fclose (pFile);
}
return 0;
}
5. 文件的顺序读写
5.1 顺序读写函数介绍
函数名 | 功能 | 适用于 |
fgetc | 字符输入函数 | 所有输入流 |
fputc | 字符输出函数 | 所有输出流 |
fgets | 文本行输入函数 | 所有输入流 |
fpuits | 文本行输出函数 | 所有输出流 |
fscanf | 格式化输入函数 | 所有输入流 |
fprintf | 格式化输出函数 | 所有输出流 |
fread | 二进制输入 | 文件 |
fwrite | 二进制输出 | 文件 |
下面是示例当首次打开一个文件的时候,文件指针是默认指向文件开头的位置
我们可以看到,他的返回值是返回读到的字符,那为什么返回值要设定为int类型呢
这里可以看到,当读取发生错误或读取到文件尾的时候,会返回EOF,而EOF就是-1,为了适合所有情况,所以将返回值类型设置成int。
类似的,fgetc也是如此
//在当前目录下放一个文件data.txt,写一个程序,将data.txt文件拷贝一份,生成data_copy.txt文件。
int main()
{
FILE* fp1 = fopen("data.txt", "r");//从data.txt里面读
FILE* fp2 = fopen("data_copy.txt", "w");//写道data_copy里面去
int ch = 0;
if (fp1 == NULL || fp2 == NULL)//判断是否打开失败
{
perror("fopen");
return 1;
}
while ((ch = fgetc(fp1)) != EOF)
{
fputc(ch, fp2);
}
fclose(fp1);
fclose(fp2);
FILE * fp3 = fopen("data_copy.txt", "r");//以读的方式打开,把拷贝的读出来,看看是不是拷进去了
int b = 0;
if (fp3 == NULL)
{
perror("fopen");
return 1;
}
while ((b = fgetc(fp3))!= EOF)
{
printf("%c ", b);
}
fclose(fp3);
return 0;
}
data.txt中存放的是下列
下面是fgets的运用
int main()
{
char arr[10];
FILE* p1 = fopen("data.txt","r");//以读的方式打开
if (p1 == NULL)//判断是否能正常打开
{
perror("fopen");//如果打开失败,打印一下错误信息
return 1;
}
fgets(arr, 7, p1);//给定7个字符的长度,不够读整个第一行的字符数
printf(arr);//这边输出结果是Abcdef,
//调试可以看到因为要留个位置放\0
fgets(arr, 7, p1);//这里给定字符长度超出了文件指针所指向的字符串长度
//可以看到只会在原字符串后打印一个g,以及一个空的行
printf(arr);//调试可以看到,将f后的g \n \0都读进去后,就没有再读了
fgets(arr, 7, p1);//这里已经从下一行开始读了,长度同样也不够读第二行的字符
printf(arr);
fgets(arr, 7, p1);//长度同样也不够读文件指针指向的字符串长度
printf(arr);//可以看到会输出world!,调试看到同样的,要留个位置放 \0
fgets(arr, 7, p1);
printf(arr);
return 0;
}
得出结论,当fgets从文件中读字符串的时候,始终要给str留一个元素的空间放\0
如果当前文件指针所指向的字符串长度小于n-1(有个空间要放\0)时,fgets在进行读操作时,会将\n也读过来
如果当前文件指针所指向的字符串长度大于等于n时,那么会读n-1个字符(有个空间要放\0)
这里是fputs的运用
int main()
{
char arr1[20] = { "hello world!" };
char arr2[20] = { "work at alibaba\n" };
char arr3[20] = { "china num.1\n" };
file* fp1 = fopen("data2.txt", "w");//fputs往文件里写
if (fp1 == null)
{
perror("fopen");
return 1;
}
fputs(arr1, fp1);
fputs(arr2, fp1);//这里运行程序后,发现文件中将两组字符串都写在了同一行
fputs(arr3, fp1);//这里运行后,可以发现文件换行写入了,原因就是arr1字符串没有\n
//而arr2字符串有\n
fputs("china is the most powerful country in this city!\n", fp1);
//也可以直接这样写字符串进去
fclose(fp1);
return 0;
}
5.2 scanf/fscanf/sscanf printf/sprintf/fprintf
scanf与printf我们已经非常熟悉
我们来看一下printf 与 fprintf 这两个函数
可以看到这两函数在参数上的区别仅是fprintf多了一个文件类型的指针
struct S
{
char name[20];
int age;
};
int main()
{
FILE* fp1 = fopen("test.txt", "w");
if (fp1 == NULL)
{
perror("fopen");
return 1;
}
struct S s = { "张三",38 };
fprintf(fp1, "%s %d", s.name, s.age);//将s的信息输出到文件里去
fclose(fp1);
return 0;
}
这里我们打开文件看一下,是不是写进去了
可以看到,确实是写进去了,且是以文本的方式写进去的。
既然可以写进去, 那就可以读出来
int main()
{
struct S s = { 0 };
FILE* fp1 = fopen("test.txt", "r");//将数据再读出来
if (fp1 == NULL)
{
perror("fopen");
return 1;
}
fscanf(fp1, "%s %d", s.name, &(s.age));
printf("%s %d", s.name, s.age);
fclose(fp1);//看一下都出来没
return 0;
}
那么我们其实也可以用fprintf进行打印
int main()
{
struct S s = { 0 };
FILE* fp1 = fopen("test.txt", "r");//将数据再读出来
if (fp1 == NULL)
{
perror("fopen");
return 1;
}
fscanf(fp1, "%s %d", s.name, &(s.age));
fprintf(stdout ,"%s %d", s.name, s.age);//stdout就是显示器
fclose(fp1);//看一下都出来没
return 0;
}
也是没问题的
再来看下sprintf sscanf
sscanf
从指定的字符串中读格式化的数据出来
int main()
{
char arr1[20] = {"王五 48"};
struct S s;
sscanf(arr1, "%s %d", s.name, &(s.age));//name本身就是数组名,是个地址,所以不用&
printf("%s %d",s.name,s.age);//看一下成没成功
return 0;
}
sprintf
将格式化的字符串写入到一个字符串中去
int main()
{
char arr1[20];
struct S s = { "李四",38 };
sprintf(arr1, "%s %d", s.name, s.age);
printf(arr1);//看一下成没成功
return 0;
}
可以看到都成功了
总结:printf :把数据以格式化的形式打印在标准输出流上
fprintf:把数据以格式化的形式打印在指定输出流上
sprintf:把格式化数据转化为字符串
scanf:从标准输入流上读取格式化的数据
fscanf:从指定的输入流上读取格式化的数据
sscanf:从字符串中读格式化的数据
5.3 fwrite
可以看到buffer指向的是我们想要写入的数据,size是我们写入的数据的类型大小,count是我们想
要写入多少个size类型的数据。
int main()
{
int arr[] = { 10,11,12,13,14,15 };
FILE* fp1 = fopen("datab.txt", "wb");//二进制的形式写
if (fp1 == NULL)
{
perror("fopen");
return 1;
}
int sz = sizeof(arr) / sizeof(arr[0]);
fwrite(arr, sizeof(int), sz, fp1);//以二进制方式写进去
fclose(fp1);
return 0.;
}
为了验证有没有写进去,我们再以二进制的方式读出来试试
5.4 fread
int main()
{
int arr1[10] = { 0 };
FILE* fp2 = fopen("datab.txt", "rb");//二进制的形式写
if (fp2 == NULL)
{
perror("fopen");
return 1;
}
fread(arr1, sizeof(int), 10, fp2);//以二进制方式写进去
for (int i = 0; i < 10; i++)
{
printf("%d ", arr1[i]);
}
fclose(fp2);
return 0.;
}
可以看到 是没问题的。
同时我们在这里可以看到,要求fread是读10个,但通过fwrite写进去的元素是没有10个的,但是
fread还是成功读取了,我们可以看到fread的返回值是size_t,所以我们可以这样写
int main()
{
int arr1[10] = { 0 };
FILE* fp2 = fopen("datab.txt", "rb");//二进制的形式写
if (fp2 == NULL)
{
perror("fopen");
return 1;
}
int i = 0;
while (fread(&arr1[i], sizeof(int), 1, fp2))//根据返回值进行判断
{
printf("%d ", arr1[i]);
i++;
}
fclose(fp2);
return 0.;
}
可以看到 也是没问题的。
注:文本形式与二进制形式不存在绝对的效率高低,只是提供更多选择
6. 文件的随机读写
6.1 fseek
这个参数的意思是从origin这个位置开始偏移多少(正数为向后偏移,负数为向前偏移)
这个参数有以下选择
int main()
{
FILE* fp = fopen("test.txt", "r");
if (fp == NULL)
{
perror("fopen");
return 1;
}
int ch = fgetc(fp);
printf("%c\n", ch);
fseek(fp, 4, SEEK_CUR);//从当前指针位置进行了偏移
ch = fgetc(fp);
printf("%c\n", ch);//看看偏移成功没
fseek(fp, 4, SEEK_SET);//从文件起始进行了偏移
ch = fgetc(fp);
printf("%c\n", ch);//看看偏移成功没
fseek(fp, -4, SEEK_END);//从文件尾进行了偏移
ch = fgetc(fp);
printf("%c\n", ch);//看看偏移成功没
fclose(fp);
return 0;
}
这是文件中存放的数据
可以看到是符合的
6.2 ftell
这个没啥好说的
当文件指针指向文件尾的时候返回的偏移量及字符个数
6.3 rewind
int main()
{
FILE* fp = fopen("test.txt", "r");
if (fp == NULL)
{
perror("fopen");
return 1;
}
int ch = fgetc(fp);
printf("%c\n", ch);
fseek(fp, 4, SEEK_CUR);//从当前指针位置进行了偏移
ch = fgetc(fp);
printf("%c\n", ch);//看看偏移成功没
fseek(fp, 4, SEEK_SET);//从文件起始进行了偏移
ch = fgetc(fp);
printf("%c\n", ch);//看看偏移成功没
fseek(fp, -4, SEEK_END);//从文件尾进行了偏移
ch = fgetc(fp);
printf("%c\n", ch);//看看偏移成功没
rewind(fp);
ch = fgetc(fp);
printf("%c\n", ch);
fclose(fp);
return 0;
}
返回后输出的就是起始的那个字符
7. 文件结束读取的判定
在我们打开一个流的时候,这个流上会有两个标记值
一个标记值是是否遇到文件末尾
另一个是是否发生错误
任意一个流上都有这两个标记值
7.1 feof
那么如何判断文件是否读取结束呢?
1.文本文件是否读取结束
Ⅰ fgetc如果在读取的时候遇到文件尾,或者发生错误,那么就返回EOF
Ⅱ fgets在读取的时候遇到文件尾,或者发生错误,那么就返回NULL
2.二进制文件是否读取结束
判断返回值是否是小于实际要读的个数
7.2 ferror
作用是:当文件读取结束的时候,判断读取结束的原因是否是:遇到错误结束。(检测是否设置
int main()
{
int c;
FILE* fp = fopen("test.txt", "r");
if (!fp)
{
perror("fopen");
return 1;
}
//fgetc 当读取失败的时候或者遇到⽂件结束的时候,都会返回EOF
while ((c = fgetc(fp)) != EOF) // 标准C I/O读取⽂件循环
{
putchar(c);
}
//判断是什么原因结束的
if (ferror(fp))
puts("I/O error when reading");
else if (feof(fp))
puts("End of file reached successfully");
fclose(fp);
return 0;
}
二进制例子
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);
return 0;
}