赞
踩
在计算机世界中,文件是存储数据的基本单位。从文本文档到图像、音频或视频,无论是持久保存数据还是临时存储信息,文件都扮演着重要的角色。对于初学C语言的学生来说,掌握如何通过C语言进行文件操作是一个非常实用且必要的技能。本文将引导你理解C语言中的文件概念,并通过示例学习如何执行基本的文件操作。
如果没有文件,我们写的程序的数据是存储在电脑的内存中,如果程序退出,内存回收,数据就丢失了,等再次运行程序,是看不到上次程序的数据的,如果要将数据进行持久化的保存,我们可以使用文件。
磁盘上的文件是文件。但是在程序设计中,我们一般谈的文件有两种:程序文件、数据文件(从文件功能的角度来分类的)。
程序文件包括源程序文件(后缀为.c),目标文件(windows环境后缀为.obj),可执行程序(windows环境后缀为.exe)。
文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件。
本章讨论的是数据文件。
在以前各章所处理数据的输入输出都是以终端为对象的,即从终端的键盘输入数据,运行结果显示到显示器上。其实有时候我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使用,这里处理的就是磁盘上文件。
一个文件要有一个唯一的文件标识,以便用户识别和引用。
文件名包含3部分:文件路径+文件名主干+文件后缀
例如:
c:\code\test.txt
为了方便起见,文件标识常被称为文件名。
根据数据的组织形式,数据文件被称为文本文件或者二进制文件。
数据在内存中以二进制的形式存储,如果不加转换的输出到外存,就是二进制文件。
如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCll字符的形式存储的文件就是文本文件。
一个数据在内存中是怎么存储的呢?
字符一律以ASCI形式存储,数值型数据既可以用ASCI形式存储,也可以使用二进制形式存储。
在C语言中,文件被视为一个长序列的字节,无论它是文本文件还是二进制文件。C标准库提供了一系列的函数,允许我们在程序中打开、读写、搜索和关闭文件。当我们在C程序中操作文件时,实际上是在操作文件指针,即FILE
类型的指针。这个指针指向一个类型为FILE
的结构体,该结构体包含了关于文件的所有信息,比如文件名、打开模式以及当前的读写位置等。
在C语言中,标准流(stdin、stdout、stderr)是程序与其执行环境交互的基础。stdin
用于输入,通常连接到键盘;stdout
用于正常输出,通常显示在终端或屏幕上;stderr
专门用于错误输出,即使在stdout
被重定向时,也能让错误信息显示给用户。这些流自动打开、无需手动管理。通过重定向,程序可以将输入从文件读取,或将输出写入文件,提高了程序与外部世界交互的灵活性和效率。
在C语言中,文件指针是FILE
类型的指针,用于访问文件。它指向一个包含了文件相关信息的结构体,如文件状态和位置。通过这个指针,程序可以进行文件的打开、读写和关闭操作。文件指针是操作文件的关键,使得程序能够在文件中定位、读取数据、写入数据,以及执行其他文件相关操作。
例如,VS2013编译环境提供的stdio.h头文件中有以下的文件类型申明:
- struct _iobuf {
- char *_ptr;
- int _cnt;
- char *_base;
- int _flag;
- int _file;
- int _charbuf;
- int _bufsiz;
- char *_tmpfname;
- };
- typedef struct _iobuf FILE;
在C语言中,我们使用fopen
函数来打开一个文件。fopen
需要两个参数:文件路径和打开模式。打开模式决定了文件如何被访问,例如只读(r
)、写入(w
)、追加(a
)等。
- FILE *fp;
- fp = fopen("example.txt", "r");
- if (fp == NULL) {
- perror("Error opening file");
- return(-1);
- }
mode 有下列几种形态字符串:
- | 打开模式 | 描述 |
- |----------|--------------------------------------------------------------------------------------------------------------------------------------------------|
- | r | 以只读方式打开文件,该文件必须存在。 |
- | r+ | 以读/写方式打开文件,该文件必须存在。 |
- | rb+ | 以读/写方式打开一个二进制文件,只允许读/写数据。 |
- | rt+ | 以读/写方式打开一个文本文件,允许读和写。 |
- | w | 打开只写文件,若文件存在则文件长度清为零,即该文件内容会消失;若文件不存在则创建该文件。 |
- | w+ | 打开可读/写文件,若文件存在则文件长度清为零,即该文件内容会消失;若文件不存在则创建该文件。 |
- | a | 以附加的方式打开只写文件。若文件不存在,则会创建该文件;如果文件存在,则写入的数据会被加到文件尾后,即文件原先的内容会被保留(EOF 符保留)。 |
- | a+ | 以附加方式打开可读/写的文件。若文件不存在,则会创建该文件,如果文件存在,则写入的数据会被加到文件尾后,即文件原先的内容会被保留(EOF符不保留)。 |
- | wb | 以只写方式打开或新建一个二进制文件,只允许写数据。 |
- | wb+ | 以读/写方式打开或新建一个二进制文件,允许读和写。 |
- | wt+ | 以读/写方式打开或新建一个文本文件,允许读和写。 |
- | at+ | 以读/写方式打开一个文本文件,允许读或在文本末追加数据。 |
- | ab+ | 以读/写方式打开一个二进制文件,允许读或在文件末追加数据。 |
读取文件内容可以使用多种函数,如fscanf
、fgets
或fread
,取决于你的需求。例如,fgets
可以按行读取文本文件:
- char buffer[255];
- while(fgets(buffer, 255, fp) != NULL) {
- printf("%s", buffer);
- }
向文件写入内容,可以使用fprintf
、fputs
或fwrite
等函数。例如,使用fprintf
向文件写入格式化文本:
fprintf(fp, "Learning C is fun!\n");
完成文件操作后,应该使用fclose
函数关闭文件。这是一个好习惯,可以释放文件资源,避免数据丢失或文件损坏:
fclose(fp);
下面是一个简单的程序示例,展示了如何在C语言中创建一个文件,向其中写入一些文本,然后读取并打印出来:
- #include <stdio.h>
-
- int main() {
- FILE *fp;
- char buffer[255];
-
- // 打开文件进行写入
- fp = fopen("example.txt", "w+");
- if (fp == NULL) {
- perror("Error opening file");
- return(-1);
- }
- fprintf(fp, "Hello, C Programming!\n");
-
- // 定位到文件开头
- rewind(fp);
-
- // 读取并显示数据
- while(fgets(buffer, 255, fp) != NULL) {
- printf("%s", buffer);
- }
-
- // 关闭文件
- fclose(fp);
- return 0;
- }
函数名 | 功能 | 适用于 |
fgetc | 字符输入函数 | 所有输入流 |
fputc | 字符输出函数 | 所有输出流 |
fgets | 文本行输入函数 | 所有输入流 |
fputs | 文本行输出函数 | 所有输出流 |
fscanf | 格式化输入函数 | 所有输入流 |
fprintf | 格式化输出函数 | 所有输出流 |
fread | 二进制输入 | 文件 |
fwrite | 二进制输出 | 文件 |
上面说的适用于所有输入流一般指适用于标准输入流和其他输入流(如文件输入流);所有输出流一般指适用于标准输出流和其他输出流(如文件输出流)。
fseek
是一个在C语言中用于文件操作的函数,它允许你在文件中移动读写位置的指针。fseek
函数需要三个参数:一个文件指针,一个偏移量(从文件开头计算的字节数),以及一个起点位置(如SEEK_SET
、SEEK_CUR
或SEEK_END
)。通过使用fseek
,你可以向前或向后移动文件内的位置,实现随机访问文件中的数据。这对于读取或修改文件中特定部分的内容非常有用。
int fseek ( FILE * stream, long int offset, int origin );
例子
- #include <stdio.h>
-
- int main() {
- FILE *file;
- file = fopen("example.txt", "r+"); // 打开文件用于读写
- if (file == NULL) {
- perror("文件打开失败");
- return -1;
- }
-
- // 将文件指针移动到文件的开头
- fseek(file, 0, SEEK_SET);
- // 写入数据到文件开头
- fprintf(file, "Hello, World!");
-
- // 将文件指针移动到文件的第10个字节
- fseek(file, 10, SEEK_SET);
- // 在文件的第10个字节处写入数据
- fprintf(file, "C语言");
-
- fclose(file); // 关闭文件
- return 0;
- }
这个例子首先打开了一个文件用于读写,然后使用fseek
函数移动文件指针到文件开头和指定位置,最后在这些位置写入数据。
ftell
函数用于C语言中的文件操作,它返回一个
long
类型的值,表示当前文件指针的位置,即从文件开头到当前位置的字节数。这个函数非常有用,可以用来确定当前读写位置或计算文件的总大小。使用
ftell
可以帮助实现更复杂的文件操作,如确定文件的剩余大小或实现文件内容的复制。
long int ftell ( FILE * stream );
例子
- #include <stdio.h>
-
- int main() {
- FILE *file;
- file = fopen("example.txt", "r"); // 打开文件用于读取
- if (file == NULL) {
- perror("文件打开失败");
- return -1;
- }
-
- // 移动文件指针到文件的末尾
- fseek(file, 0, SEEK_END);
-
- // 使用ftell获取当前位置,即文件的总大小
- long size = ftell(file);
- printf("文件大小为:%ld 字节\n", size);
-
- fclose(file); // 关闭文件
- return 0;
- }
这个例子中,首先打开了一个文件用于读取,然后通过将文件指针移动到文件的末尾,并使用ftell
函数获取当前指针的位置,从而得到了文件的总大小。
rewind
函数在C语言中用于将文件指针重置回文件的开始位置。它是对fseek
函数的简化调用,使得文件指针移动到文件的开头,而不需要指定偏移量和起始位置。rewind
不返回值,也不考虑任何由于移动文件指针而引发的错误。这个函数特别适用于需要多次从文件开始处读取数据的场景。
void rewind ( FILE * stream );
例子
- #include <stdio.h>
-
- int main() {
- FILE *file;
- file = fopen("example.txt", "r"); // 打开文件用于读取
- if (file == NULL) {
- perror("文件打开失败");
- return -1;
- }
-
- // 假设进行了一些文件操作,如读取数据
-
- // 使用rewind重置文件指针到文件开头
- rewind(file);
-
- // 之后可以从文件开头开始读取或进行其他操作
-
- fclose(file); // 关闭文件
- return 0;
- }
在这个例子中,rewind
函数被用来将文件指针移动回文件的开始位置,这样可以方便地重新开始读取文件,而不需要关闭并重新打开文件。
在C语言中,文件读取结束通常通过检查读取函数的返回值来判定。对于fscanf
、fgets
和fread
等函数,当达到文件末尾(EOF)时,这些函数会返回特定的值。
例如,fgets
和fscanf
在读取失败或到达EOF时返回NULL
,fread
返回读取的元素个数,如果这个数小于请求读取的个数,则可能已经到达文件末尾。
另外,feof
函数用于显式检查是否达到文件末尾,如果到达文件末尾,feof
会返回非零值。正确使用这些函数和返回值是判定文件读取结束的关键。
牢记:在文件读取过程中,不能用feof函数的返回值直接来判断文件的是否结束。
feof的作用是:当文件读取结束的时候,判断是读取结束的原因是否是:遇到文件尾结束。
文本文件的例子
- #include <stdio.h>
- #include <stdlib.h>
- int main(void)
- {
- int c; // 注意:int,⾮char,要求处理EOF
- FILE* fp = fopen("test.txt", "r");
- if(!fp) {
- perror("File opening failed");
- return EXIT_FAILURE;
- }
- //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);
- }
二进制文件的例子
- #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);
- }
ANSIC标准采用“缓冲文件系统”处理的数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的大小根据C编译系统决定的。
- #include <stdio.h>
- #include <windows.h>
- //VS2019 WIN11环境测试
- int main()
- {
- FILE*pf = fopen("test.txt", "w");
- fputs("abcdef", pf);//先将代码放在输出缓冲区
- printf("睡眠10秒-已经写数据了,打开test.txt⽂件,发现⽂件没有内容\n");
- Sleep(10000);
- printf("刷新缓冲区\n");
- fflush(pf);//刷新缓冲区时,才将输出缓冲区的数据写到⽂件(磁盘)
- //注:fflush 在⾼版本的VS上不能使⽤了
- printf("再睡眠10秒-此时,再次打开test.txt⽂件,⽂件有内容了\n");
- Sleep(10000);
- fclose(pf);
- //注:fclose在关闭⽂件的时候,也会刷新缓冲区
- pf = NULL;
- return 0;
- }
这里可以得出一个结论:
因为有缓冲区的存在,C语言在操作文件的时候,需要做刷新缓冲区或者在文件操作结束的时候关闭文件。
如果不做,可能导致读写文件的问题。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。