为什么使用文件
在之前的通讯录程序中,我们介绍了如何使用文件来实现数据的持久化。然而,我们只是创建了储存功能,但只限于程序运行时。在程序退出以后,数据就不存在了。为了解决这个问题,我们通常会将数据存储到磁盘文件或数据库中。
使用文件我们可以将数据存放到电脑硬盘上,做到数据持久化。
什么是文件
文件是指在磁盘或其他存储媒体上存储数据的一种数据结构。在程序设计中有两种文件:程序文件和数据文件。
程序文件
包括源程序(后缀位.c),目标文件(后缀为.obj),可执行文件(后缀为.exe)。
数据文件
文件的内容不一定是程序,而是程序运行时读写的数据。
文件名
文件名包括了三个部分:文件路径+文件名主干+文件后缀
eg:c:\code\test.txt#
文件的打开和关闭
文件指针
缓冲文件系统中关键概念是文件类型指针,简称文件指针。
每次被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件名,文件状态和文件的当前位置),这些信息保存在一个结构体变量中,该结构体变量由系统声明,取名:FILE。
在不同的编译环境下,FILE包含的内容大同小异。
每打开一个文件,系统会根据文件情况自动创建一个FILE,并填充其中信息
对一个程序员来说,一般通过FILE的指针来维护FILE结构变量。
文件的打开和关闭
文件在读写前应先打开文件,结束后应关闭文件。
在编写程序时,打开文件时会返回一个FILE的指针指向改文件,建立了指针与文件的关系。
ANIC规定用fopen打开文件fclose*关闭文件
fopen
FILE * fopen(const charfilename,const char mode)
filename:这是一个字符串,包含了您想要打开的文件的路径。这个路径可以是相对路径或绝对路径。
相对路径:相对路径(Relative Path)是相对于当前工作目录(或者当前文件的位置)的路径。它不包含从根目录(如 Unix 系统中的 /,Windows 系统中的 C:\)开始的全部信息,而是从当前目录开始到目标文件或目录的路径。
例如,如果您的当前工作目录是 /home/user/documents,那么相对路径 subfolder/file.txt 指的是 /home/user/documents/subfolder/file.txt。
绝对路径:绝对路径(Absolute Path)是一个完整的文件系统路径,它从文件系统的根目录开始,指定了文件或目录在文件系统中的确切位置。无论当前工作目录在哪里,绝对路径都能正确地定位到目标文件或目录。
mode:fopen 函数可以使用多种模式来打开文件。下面是所有有效的模式字符串列表:
“r”: 打开文件用于读取。如果文件不存在,fopen 返回 NULL。
“w”: 打开文件用于写入。如果文件存在,则清空内容;如果文件不存在,则创建新文件。
“a”: 打开文件用于追加。如果文件存在,数据将被追加到文件末尾;如果文件不存在,则创建新文件。
“r+”: 打开文件用于读写。如果文件不存在,fopen 返回 NULL。
“w+”: 打开文件用于读写。如果文件存在,则清空内容;如果文件不存在,则创建新文件。
“a+”: 打开文件用于读取和追加。如果文件存在,数据将被追加到文件末尾;如果文件不存在,则创建新文件。
“rb”: 打开二进制文件用于读取。
“wb”: 打开二进制文件用于写入。
“ab”: 打开二进制文件用于追加。
“rb+”: 打开二进制文件用于读写。
“wb+”: 打开二进制文件用于读写。
“ab+”: 打开二进制文件用于读取和追加。
此外,还可以在模式字符串中包含以下特殊字符:
“t”: 文本模式(默认值)。
“b”: 二进制模式。
“+”: 同时进行读写操作。
eg:
FILE* pf=fopen(“data.txt”,“r”);
尝试打开名为 data.txt 的文件,以便进行读取操作。如果文件存在且打开成功,fopen 函数将返回一个指向 FILE 对象的指针,该指针可以用来 subsequent I/O 操作。如果文件不存在或者由于其他原因无法打开,fopen 函数将返回 NULL。
fclose
int fclose(FILE *stream);
参数说明:
stream: 这是一个 FILE 类型的指针,它指向之前通过 fopen 函数打开的文件。
函数返回值:
如果关闭成功,fclose 函数返回 0。
如果发生错误,fclose 函数返回 EOF(-1 的宏定义)。
功能说明:
当您完成文件的读写操作后,应该使用 fclose 函数来关闭文件。这不仅会释放文件描述符和相关资源,还会确保所有的缓冲区内容都被刷新到文件中,从而确保数据的持久保存。
文件的顺序读写
函数名称 | 作用 | 原型 | 返回值 | 说明 |
---|---|---|---|---|
fgetc | 从文件中读取一个字符 | int fgetc(FILE *stream); | 读取的字符或 EOF | 每次调用读取文件的一个字符 |
fputc | 将一个字符写入文件 | int fputc(int c, FILE *stream); | 写入的字符或 EOF | 每次调用写入文件的一个字符 |
fgets | 从文件中读取一行文本 | char *fgets(char *str, int n, FILE *stream); | 读取的文本或 NULL | 每次调用读取文件的一行文本 |
fputs | 将一行文本写入文件 | int fputs(const char *str, FILE *stream); | 写入的字节数或 EOF | 每次调用写入文件的一行文本 |
fscanf | 读取文件并转换成变量 | int fscanf(FILE *stream, const char *format, ...); | 读取并转换的输入项数 | 根据格式字符串读取文件并转换成变量 |
fprintf | 将变量格式化后写入文件 | int fprintf(FILE *stream, const char *format, ...); | 写入的字节数 | 根据格式字符串将变量写入文件 |
fread | 从文件中读取数据 | size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); | 读取的字节数或 EOF | 每次调用读取指定数量的字节 |
fwrite | 将数据写入文件 | size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); | 写入的字节数 | 每次调用写入指定数量的字节 |
eg 1: 使用 fgetc 读取文件中的一个字符 |
#include <stdio.h>
int main() {
FILE *fp;
char ch;
fp = fopen("data.txt", "r"); // 打开文件用于读取
if (fp == NULL) {
perror("Error opening file");
return -1;
}
ch = fgetc(fp); // 读取文件中的第一个字符
printf("Read character: %c\n", ch);
fclose(fp); // 关闭文件
return 0;
}
eg 2: 使用 fgets
读取文件中的一行文本
#include <stdio.h>
int main() {
FILE *fp;
char buffer[100];
fp = fopen("data.txt", "r"); // 打开文件用于读取
if (fp == NULL) {
perror("Error opening file");
return -1;
}
fgets(buffer, 100, fp); // 读取文件中的一行文本
printf("Read line: %s\n", buffer);
fclose(fp); // 关闭文件
return 0;
}
eg 3: 使用 fwrite
写入数据到文件
#include <stdio.h>
int main() {
FILE *fp;
int data[] = {1, 2, 3, 4, 5};
size_t bytes_written;
fp = fopen("data.bin", "wb"); // 打开文件用于写入
if (fp == NULL) {
perror("Error opening file");
return -1;
}
bytes_written = fwrite(data, sizeof(int), 5, fp); // 写入数据到文件
printf("Written bytes: %zu\n", bytes_written);
fclose(fp); // 关闭文件
return 0;
}
每个例子都包含了打开文件、执行读写操作和关闭文件的步骤。这些例子展示了如何在 C 语言中使用这些基本的文件操作函数。
文件的随机读写
文件的随机读写是指可以读取或写入文件中的任意位置的数据,而不必按照文件的顺序进行。随机读写函数允许你直接跳转到文件中的特定位置,读取或写入一个或多个字节。以下是一些常用的文件随机读写函数。
在 C 语言中,文件的随机读写是指可以读取或写入文件中的任意位置的数据,而不必按照文件的顺序进行。随机读写函数允许你直接跳转到文件中的特定位置,读取或写入一个或多个字节。以下是一些常用的文件随机读写函数:
文件随机读取函数
fseek
int fseek(FILE *stream, long int offset, int whence);
fseek
函数用于设置文件的读/写位置。stream
参数是指向FILE
对象的指针,offset
是一个长整型数,表示从whence
指定的位置开始的偏移量。whence
参数可以是SEEK_SET
(文件开头)、SEEK_CUR
(当前位置)或SEEK_END
(文件末尾)。函数返回0
表示成功,或者返回-1
表示失败。ftell
long int ftell(FILE *stream);
ftell
函数用于获取当前文件的读/写位置。函数返回当前位置相对于文件开头的偏移量,如果出错,返回-1
。fgetc
(用于随机读取)int fgetc(FILE *stream);
fgetc
函数用于读取文件的一个字符。为了实现随机读取,你首先需要使用fseek
函数将文件指针移动到想要读取的位置。
文件随机写入函数
fputc
(用于随机写入)int fputc(int c, FILE *stream);
fputc
函数用于写入文件的一个字符。为了实现随机写入,你首先需要使用fseek
函数将文件指针移动到想要写入的位置。fwrite
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
fwrite
函数用于向文件中写入数据。为了实现随机写入,你首先需要使用fseek
函数将文件指针移动到想要写入的位置。
示例代码
以下是一个简单的示例,展示了如何使用 fseek
和 fwrite
函数进行文件的随机写入:
#include <stdio.h>
int main() {
FILE *fp;
int data[] = {1, 2, 3, 4, 5};
size_t bytes_written;
fp = fopen("data.bin", "w+b"); // 打开文件用于写入和读取
if (fp == NULL) {
perror("Error opening file");
return -1;
}
// 将文件指针移动到文件末尾
fseek(fp, 0, SEEK_END);
// 写入数据到文件末尾
bytes_written = fwrite(data, sizeof(int), 5, fp);
printf("Written bytes: %zu\n", bytes_written);
// 关闭文件
fclose(fp);
return 0;
}
在上面的示例中,我们使用了 fseek
函数将文件指针移动到文件末尾,然后使用 fwrite
函数向文件末尾写入数据。这样可以实现文件的随机写入操作。
rewind
函数
用于将文件指针重新定位到文件的开始位置。这个函数非常有用,当你想要从头开始重新读取一个文件时,而不想从头开始逐字节移动文件指针。以下是 rewind
函数的原型:
void rewind(FILE *stream);
rewind
函数接受一个指向 FILE
对象的指针 stream
,表示要重置的文件流。调用 rewind
函数后,文件指针将移动到文件的开头,就像文件刚刚被打开一样。
下面是一个使用 rewind
函数的示例:
#include <stdio.h>
int main() {
FILE *fp;
int ch;
fp = fopen("data.txt", "r"); // 打开文件用于读取
if (fp == NULL) {
perror("Error opening file");
return -1;
}
// 读取文件的一个字符
ch = fgetc(fp);
printf("Read character: %c\n", ch);
// 将文件指针重新定位到文件开始位置
rewind(fp);
// 重新读取文件的一个字符
ch = fgetc(fp);
printf("Rewinded and read character: %c\n", ch);
// 关闭文件
fclose(fp);
return 0;
}
在上述示例中,我们首先读取了文件 data.txt
的一个字符,然后调用了 rewind
函数将文件指针重新定位到文件的开头。之后,我们再次读取了文件的一个字符。由于 rewind
函数的作用,第二次读取的是文件开头的同一个字符。
rewind
函数通常用于数据处理任务,比如当你需要对文件进行多次读取操作,并且每次都需要从文件开始处读取时,使用 rewind
函数可以简化代码,避免手动移动文件指针的复杂性。
文件读取结束的判定
在 C 语言中,文件操作是编程中的一个常见任务。当我们读取文件时,我们需要一种方法来确定何时我们已经到达文件的末尾。这里,我们要讨论的是如何使用 feof
函数来帮助我们判断文件是否已经读取完毕。
首先,我们需要明确一点:feof
函数并不直接告诉我们文件是否结束,而是告诉我们文件指针是否已经到达文件末尾。这意味着,即使 feof
返回 1
,文件也可能还有数据,只是文件指针已经超出了数据区域。因此,我们不能仅仅依赖 feof
的返回值来判断文件是否结束。
正确的方法是,在每次读取操作之后,检查读取函数的返回值。例如,当我们使用 fgetc
或 fgets
读取文件时,如果文件已经读取完毕,fgetc
会返回 EOF
(End Of File),而 fgets
会返回一个空字符串(\0
)。这样,我们就可以确定文件是否已经读取完毕。
下面是一个示例,展示了如何正确使用 fgetc
和 feof
:
#include <stdio.h>
int main() {
FILE *fp;
int ch;
fp = fopen("data.txt", "r"); // 打开文件用于读取
if (fp == NULL) {
perror("Error opening file");
return -1;
}
// 逐字符读取文件
while ((ch = fgetc(fp)) != EOF) {
printf("Read character: %c\n", ch);
}
// 检查是否到达文件末尾
if (feof(fp)) {
printf("Reached end of file due to EOF.\n");
} else {
printf("Did not reach end of file.\n");
}
// 关闭文件
fclose(fp);
return 0;
}
在这个示例中,我们使用 while
循环逐字符读取文件。每次 fgetc
函数读取一个字符后,我们检查返回值是否为 EOF
。如果是,我们使用 feof
函数来确认是否已经到达文件末尾。如果 feof
返回 1
,我们输出 “Reached end of file due to EOF.”;如果返回 0
,我们输出 “Did not reach end of file.”。
这样,我们就通过正确的逻辑来判断文件是否读取结束,而不是直接依赖 feof
的返回值。这种方法可以确保我们在文件末尾正确地处理数据,而不会错误地认为文件中还有未读取的数据。