文章目录
1、为什么使用文件
2、什么是文件
3、文件的打开和关闭
文件的打开
文件的关闭
4、文件的顺序读写
4.1文件读写的特点
4.2fputc、fgetc函数
4.3fgets、fputs函数
4.4fscanf、fprintf函数
5、标准输入输出流stdin、stdout
在编写例如通讯录、图书管理系统等程序的时候,所记录的数据,只有程序运行的时候才会有,但是如果结束运行,之前的数据全部没有了,又要重新输入、操作。这样子就很难受,我们在使用类似程序的时候,应该要把数据记录下来,只有自己选择删除的时候,对应信息才会被删除,这就涉及到了数据持久化问题。
要实现数据持久化可以 把数据存放在磁盘文件中,或者 存放到数据库里面。C语言里使用文件操作,我们就可以把数据放在磁盘文件中,实现数据持久化。
在程序设计中,从文件功能来分类,我们一般谈的文件有两种:程序文件、数据文件。
程序文件:包括源程序文件(后缀为.c),目标文件(windows环境后缀为.obj),可执行程序(windows环境后缀为.exe)。
数据文件:文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件。
在这里,显而易见,我们要讨论的是数据文件。
一个文件要有一个唯一标识,以便于用户识别和引用。这个标识俗称“文件名”。文件名包含三个部分:文件路径+文件名主干+后缀。例如:c:\code\test.c 这个文件名里面,文件路径是c:\code\ 文件名主干是test 后缀是.c
我们要对文件进行操作,首先就要打开文件,然后在文件里面进行数据的增删查改,用完之后关闭文件。下面一行是C语言内置的打开文件的函数,从中可以得知,该fopen函数有两个参数,返回值是FILE*。两个参数里面,第一个参数是文件名,第二个参数是打开文件的方式。
FILE * fopen ( const char * filename, const char * mode );
根据需求不同,打开文件也有不同的方式,比如我只需要知道这个文件里有什么数据,那么可以用“只读”方式打开文件,打开文件之后,文件里的内容不可更改。又或者我想要在这个文件后面新增内容,原有数据不变,那么可以用“追加”方式打开文件……
比如下方,我用“只读”方式打开文件(注意fopen的两个参数都是用双引号)。在这里设计FILE* 类型的指针pf来接收fopen函数的返回值,并且要判断pf指针是否为空。
这里fopen函数的第一个参数,发现并不是完整的文件名,而是 文件名主干+后缀 ,这是因为test.txt文件和这个C语言程序是在一个文件夹下面的,如下图。 如果不是在一个文件夹下面,那么就要用完整的文件名:文件路径+文件名主干+后缀 。前者叫相对路径,后者叫绝对路径。
#include
int main()
{
FILE* pf = fopen("test.txt", "r");//相对路径
if (pf == NULL)
{
perror("fopen");
return 1;
}
return 0;
}
如下,文件关闭使用fclose()函数,该函数返回值是int类型,有一个参数。关闭成功,返回0,否则返回EOF(-1)。
int fclose ( FILE * stream );
如下代码,文件关闭在最后几行代码,用fclose()函数,该函数里面的参数是文件指针。这个函数执行后,test.txt文件被关闭,但是pf指针并不为空,为了避免野指针的情况,把pf指针置为空。
#include
int main()
{
FILE* pf = fopen("test.txt", "r");//相对路径
if (pf == NULL)
{
perror("fopen");
return 1;
}
//文件关闭
fclose(pf);
pf=NULL;
return 0;
}
文件的读写和之前我们所使用的scanf、printf不一样。从下面图片可以看出。之前我们都是通过scanf函数,把数据从键盘输入到内存里面。然后利用printf函数,把内存里的数据输出到显示器里以方便我们看到。
但是在文件操作里面。输入、输出函数,一方面有多个;另一方面,打开文件后,对于输入函数,数据是从文件输入到内存里面,对于输出函数,数据是从内存里输出到文件内。
在这里相信会有一个小疑问:为什么进行文件操作的时候,要打开、关闭文件,而之前的程序使用scanf、printf都不需要打开键盘、显示器,关闭键盘、显示器呢? 这是因为,每一个程序运行的时候,都默认打开了三个流。如下,键盘和屏幕都是默认打开、关闭的,不需要我们手动打开关闭。
stdin 标准输入流 --键盘
stdout 标准输出流 --屏幕
stderr 标准错误流 --屏幕
fputc函数
fgetc是字符输出函数,一次向文本输出一个字符,适用于所有输出流。
如下,fputc函数有一个int类型的返回值,以及两个参数。第一个参数是要输入文件的那个字符的ASCLL码,第二个参数是文件指针。这个函数的结果是:字符被写入流的内部位置指示器指示的位置,然后自动前进一。指示器可以理解为文件中的“光标”,写入一个字符之后,光标前进1个位置。
int fputc ( int character, FILE * stream );
比如下面代码,在 “//使用” 段里面的循环内容,依次把 'a' - 'z' 放到文件 test.txt 里面。进行第一次循环之前,和下方图片最左边的一样,文件里没有任何内容,光标在首位。执行第一次循环,把字符‘a’写入文件光标所在位置,光标后移一位到‘a’的后面。执行第二次循环,把字符‘b’写入光标所在处,光标后移一位到‘b’的后面……执行完毕后,光标在‘z’后面。
#include
int main()
{
FILE* pf = fopen("test.txt", "w");//相对路径
if (pf == NULL)
{
perror("fopen");
return 1;
}
//使用
for (int i = 0;i < 26;i++)
{
fputc('a' + i, pf);
}
//关闭
fclose(pf);//此时pf并不指向NULL
pf = NULL;
return 0;
}
fgetc函数
fuptc是字符输入函数,一次向内存输入一个字符,适用于所有输入流。
如下,fgetc函数只有一个参数,返回值是int类型。该函数作用:返回指定流的内部文件位置指示器当前所指向的字符。然后,内部文件位置指示器将前进到下一个字符。
int fgetc ( FILE * stream );
如下代码和运行结果(test.txt是顺着上一段代码的,现在该文件里面是'a' - 'z'),虽然fgetc函数返回的是一个字符,但是其返回值是int类型,所以也要定义一个int类型的变量ch来接收该返回值。输出的时候用字符的输出格式即可。
在这里,由于只需要读取文件内的数据,所以用“只读”方式打开文件。打开文件后,光标是在首位,而不是在末尾,如下图。fgetc执行一次,光标后移一位。
#include
int main()
{
FILE* pf = fopen("test.txt", "r");//相对路径
if (pf == NULL)
{
perror("fopen");
return 1;
}
int ch = 0;
while ((ch=fgetc(pf)) != EOF)
{
printf("%c ", ch);
}
//关闭
fclose(pf);//此时pf并不指向NULL
pf = NULL;
return 0;
}
fputs函数
fgets函数是文本行输出函数,一次向文本输出一串字符,适用于所有输出流。
如下,该函数有两个参数,返回值是int类型。第一个参数是要写入文件的字符串的指针,第二个参数是文件指针。该函数作用是:从指定的地址 (str) 开始复制,直到达到终止空字符 ('\0')。此终止空字符不会复制到流中。
int fputs ( const char * str, FILE * stream );
如下代码和运行结果,值得注意的是,换行符要手动添加,否则会一直在某行输入直到该行满了。比如这段代码,连续用了两次fputs,第一次输入hello,第二次输入leo,但是两个字符串是在同一行的。只有手动输入\n才会换行。
#include
int main()
{
FILE* pf = fopen("test1.txt", "w");//相对路径
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写入文件,一行一行写
fputs("hello", pf);
fputs("leo",pf);
//这里并不会换行,所以要换行的话,手动加\n
fputs("\nnextline", pf);
//关闭
fclose(pf);//此时pf并不指向NULL
pf = NULL;
return 0;
}
fgets函数
fgets函数是文本行输入函数,一次向内存写入一串字符,适用于所有输入流。
如下,该函数返回值是char*类型,含有三个参数。该函数第一个参数是指向在其中复制字符串读取的字符数组的指针,第二个函数是读取字符的个数,第三个函数是文件指针。如果读取成功,返回str,否则返回NULL。该函数作用是:从流中读取字符并将其作为 C 字符串存储到 str 中,直到读取 (num-1) 个字符或到达换行符或文件结尾,以先发生者为准。
char * fgets ( char * str, int num, FILE * stream );
如下,这段代码也是基于上一段的,所以test1.txt文件里面的内容就是上面一张图片的内容。该程序运行的内存监视如下第一张图。成功从pf指向的文件中,读取4个字符,输出到arr数组中。并且第5个字符的位置,放的是'\0'。
将 “//fgets(arr,9,pf);” 这段取消注释之后,监视内存,发现结果如下第二张图片,读取了"oleo\n"这一串字符,然后结尾处加了'\0’。但是这段本意是想从文件读取8个字符写入arr,现在却只读取了5个字符。这是因为:到达换行符比读取8个字符先发生。见上面黄色背景的该函数用法。
#include
int main()
{
FILE* pf = fopen("test1.txt", "r");//相对路径
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件,一行一行读
char arr[20] = "123456789";
fgets(arr, 5, pf);//读 5-1 = 4 个,最后加的\0,并且最多只读一行。 如果一次未读完一行,那么下次接着读
//fgets(arr,9,pf);
printf(arr);
//关闭
fclose(pf);//此时pf并不指向NULL
pf = NULL;
return 0;
}
fprintf函数
fprintf是格式化输出函数,一次向文件输出任意个数的字符,适用于所有输出流。
如下,该函数返回值是int类型,有三个参数。其第一个参数是文件指针,后面两个参数和printf的格式一摸一样。函数执行成功,返回输出字符的个数,否则返回负数。
int fprintf ( FILE * stream, const char * format, ... );
如下函数和运行结果,文件里面的内容和预期一样。同样的,换行等内容也需要自己手动添加。
#include
struct S
{
char name[20];
int age;
};
//写入结构体数据
int main()
{
struct S a = { "zhangsan",20 };
FILE* pf = fopen("test2.txt", "w");//相对路径
if (pf == NULL)
{
perror("fopen");
return 1;
}
//格式化输出
fprintf(pf, "struct S a:%s %d", a.name, a.age);
//关闭
fclose(pf);//此时pf并不指向NULL
pf = NULL;
return 0;
}
fscanf函数
fprintf是格式化输入函数,一次向内存输入任意个数的字符,适用于所有输入流。
和fpritnf类似,fscanf也有一个int类型的返回值,三个参数。其中,第一个参数是文件指针,后面两个参数和scanf函数的的格式一样。如果函数执行成功,返回输入的个数。
int fscanf ( FILE * stream, const char * format, ... );
如下,没有太大问题。
#include
struct S
{
char name[20];
int age;
};
int main()
{
struct S b = { 0 };
FILE* pf = fopen("test2.txt", "r");//相对路径
if (pf == NULL)
{
perror("fopen");
return 1;
}
//格式化
fscanf(pf,"%s %d",b.name,&(b.age));
printf("%s %d", b.name, b.age);
//关闭
fclose(pf);//此时pf并不指向NULL
pf = NULL;
return 0;
}
对于流(stream),上面六个函数,第一句话最后总会加上一个“适用于所有输入/输出流”,而上文4.1的内容,也提到了,stdin是标准输入流,stdout是标准输出流。那么stdin、stdout也适用于上面的所有函数。如下:
1、int fputc ( int character, FILE * stream ); fputc函数第二个参数是一个流,使用标准输出流stdout,那么则向标准输出流——屏幕输出字符'a',其运行结果如下图,不过这个一般而言,用处不大。
#include
int main()
{
fputc('a', stdout);
return 0;
}
2、char * fgets ( char * str, int num, FILE * stream ); 如下,fgets函数第三个参数是一个流,使用标准输入流stdin的时候,就是从键盘输入到str里面。只读取4个字符到temp指向的空间。
#include
int main()
{
FILE* pf = fopen("test5.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
char temp[20] = { 1 };
fgets(temp, 5, stdin);
printf("%s", temp);
fclose(pf);
pf = NULL;
return 0;
}
3、例如fprintf,也适用于所有流,所以标准输出流也适用,如下代码和运行结果。
#include
int main()
{
FILE* pf = fopen("test5.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
char name[20] = "zhangsan";
int age = 20;
fprintf(pf, "My name is :%s ,I am %d years old.",name,age);
fclose(pf);
pf = NULL;
return 0;
}