第9章 文件操作
9.1 文件操作概述
在程序运行时,数据通常存储在内存中,当程序结束运行后,内存中的数据就会丢失。为了长期保存数据,我们需要将数据存储到文件中。C语言提供了丰富的文件操作函数,用于对文件进行读写、打开关闭等操作。
文件的分类:
- 文本文件:以字符形式存储数据,每个字符用ASCII码表示,可读性强,但占用空间较大。
- 二进制文件:以二进制形式存储数据,直接存储数据的二进制表示,占用空间较小,但可读性差。
9.2 文件指针
在C语言中,文件操作是通过文件指针来进行的。文件指针是一个指向FILE类型结构体的指针,FILE结构体包含了文件的相关信息,如文件名、文件状态、文件位置等。
文件指针的定义:
FILE *文件指针名;
示例:
FILE *fp; // 定义一个文件指针fp
9.3 文件的打开与关闭
9.3.1 文件的打开
在对文件进行操作之前,必须先打开文件。C语言中使用fopen()函数来打开文件。
fopen()函数的原型:
FILE *fopen(const char *filename, const char *mode);
参数说明:
- filename:要打开的文件名(包括路径)
- mode:文件的打开模式
返回值:
- 成功:返回指向该文件的文件指针
- 失败:返回NULL
常用的文件打开模式:
| r | 以只读方式打开文件,文件必须存在 | 文本文件/二进制文件 |
| w | 以只写方式打开文件,如果文件不存在则创建,如果文件存在则清空原有内容 | 文本文件/二进制文件 |
| a | 以追加方式打开文件,如果文件不存在则创建,写入的数据会添加到文件末尾 | 文本文件/二进制文件 |
| r+ | 以读写方式打开文件,文件必须存在 | 文本文件/二进制文件 |
| w+ | 以读写方式打开文件,如果文件不存在则创建,如果文件存在则清空原有内容 | 文本文件/二进制文件 |
| a+ | 以读写方式打开文件,如果文件不存在则创建,写入的数据会添加到文件末尾 | 文本文件/二进制文件 |
| rb | 以二进制只读方式打开文件,文件必须存在 | 二进制文件 |
| wb | 以二进制只写方式打开文件,如果文件不存在则创建,如果文件存在则清空原有内容 | 二进制文件 |
| ab | 以二进制追加方式打开文件,如果文件不存在则创建,写入的数据会添加到文件末尾 | 二进制文件 |
示例:
// 以只读方式打开文本文件
FILE *fp1 = fopen("test.txt", "r");
// 以只写方式打开文本文件
FILE *fp2 = fopen("test.txt", "w");
// 以二进制只读方式打开文件
FILE *fp3 = fopen("data.bin", "rb");
注意事项
- 在使用fopen()函数打开文件后,一定要检查返回值是否为NULL,以判断文件是否成功打开。
- 如果打开文件失败,可能的原因有:文件不存在、权限不足、磁盘空间不足等。
9.3.2 文件的关闭
在对文件操作完成后,必须关闭文件,以释放文件资源。C语言中使用fclose()函数来关闭文件。
fclose()函数的原型:
int fclose(FILE *stream);
参数说明:
- stream:要关闭的文件指针
返回值:
- 成功:返回0
- 失败:返回EOF(-1)
示例:
FILE *fp = fopen("test.txt", "r");
if (fp != NULL) {
// 对文件进行操作
fclose(fp); // 关闭文件
}
注意事项
- 在程序结束前,一定要关闭所有打开的文件。
- 关闭文件后,文件指针将不再指向该文件,不能再通过该指针操作文件。
章节练习:文件的打开与关闭
编写一个程序,尝试打开一个名为"test.txt"的文件,如果文件存在则输出"文件打开成功",然后关闭文件;如果文件不存在则输出"文件不存在"。
疑难解析:为什么必须关闭文件?
在C语言中,打开文件后必须关闭文件,主要有以下几个原因:
- 释放资源:文件是一种系统资源,操作系统对同时打开的文件数量有一定限制。关闭文件可以释放文件描述符,避免资源泄漏。
- 刷新缓冲区:当向文件写入数据时,数据通常会先存储在缓冲区中,只有当缓冲区满或关闭文件时才会将数据写入磁盘。关闭文件可以确保所有缓冲的数据都被写入磁盘。
- 确保数据完整性:关闭文件时,操作系统会更新文件的元数据(如文件大小、修改时间等),确保文件系统的一致性。
- 避免文件锁定:某些操作系统会在文件打开时对文件进行锁定,防止其他进程修改。关闭文件可以释放文件锁,允许其他进程访问该文件。
易错部分:忘记检查文件是否打开成功
在使用fopen()函数打开文件后,必须检查返回值是否为NULL,否则当文件打开失败时,程序会继续执行,导致使用无效的文件指针,最终程序崩溃。例如:
// 错误:未检查文件是否打开成功
FILE *fp = fopen("test.txt", "r");
fclose(fp); // 如果fp为NULL,会导致程序崩溃
// 正确:检查文件是否打开成功
FILE *fp = fopen("test.txt", "r");
if (fp != NULL) {
fclose(fp);
} else {
printf("无法打开文件\\n");
}
章节趣事:文件操作的历史
文件操作的概念可以追溯到早期的计算机系统,当时的数据存储在纸带、卡片或磁带上。随着磁盘存储技术的发展,文件系统逐渐成熟,为了方便程序员操作文件,各种编程语言都提供了文件操作的API。C语言的文件操作函数是基于标准I/O库实现的,它提供了一套统一的接口,使程序员可以在不同的操作系统上编写可移植的文件操作代码。
9.4 文件的读写操作
9.4.1 字符读写函数
1. 字符读取函数fgetc()
fgetc()函数用于从文件中读取一个字符。
函数原型:
int fgetc(FILE *stream);
参数说明:
- stream:文件指针
返回值:
- 成功:返回读取到的字符(转换为int类型)
- 失败或到达文件末尾:返回EOF(-1)
示例:
#include <stdio.h>
int main() {
FILE *fp;
char ch;
// 打开文件
fp = fopen("test.txt", "r");
if (fp == NULL) {
printf("无法打开文件\\n");
return 1;
}
// 读取文件中的字符并输出
while ((ch = fgetc(fp)) != EOF) {
putchar(ch);
}
// 关闭文件
fclose(fp);
return 0;
}
2. 字符写入函数fputc()
fputc()函数用于向文件中写入一个字符。
函数原型:
int fputc(int c, FILE *stream);
参数说明:
- c:要写入的字符(int类型,会被转换为unsigned char类型写入)
- stream:文件指针
返回值:
- 成功:返回写入的字符(转换为int类型)
- 失败:返回EOF(-1)
示例:
#include <stdio.h>
int main() {
FILE *fp;
char ch;
// 打开文件
fp = fopen("test.txt", "w");
if (fp == NULL) {
printf("无法打开文件\\n");
return 1;
}
// 向文件中写入字符
for (ch = 'A'; ch <= 'Z'; ch++) {
fputc(ch, fp);
}
// 关闭文件
fclose(fp);
return 0;
}
9.4.2 字符串读写函数
1. 字符串读取函数fgets()
fgets()函数用于从文件中读取一个字符串。
函数原型:
char *fgets(char *s, int size, FILE *stream);
参数说明:
- s:存储读取到的字符串的字符数组
- size:要读取的最大字符数(包括终止符'\\0')
- stream:文件指针
返回值:
- 成功:返回指向字符数组s的指针
- 失败或到达文件末尾:返回NULL
示例:
#include <stdio.h>
int main() {
FILE *fp;
char buffer[100];
// 打开文件
fp = fopen("test.txt", "r");
if (fp == NULL) {
printf("无法打开文件\\n");
return 1;
}
// 读取文件中的字符串并输出
while (fgets(buffer, sizeof(buffer), fp) != NULL) {
printf("%s", buffer);
}
// 关闭文件
fclose(fp);
return 0;
}
2. 字符串写入函数fputs()
fputs()函数用于向文件中写入一个字符串。
函数原型:
int fputs(const char *s, FILE *stream);
参数说明:
- s:要写入的字符串
- stream:文件指针
返回值:
- 成功:返回非负值
- 失败:返回EOF(-1)
示例:
#include <stdio.h>
int main() {
FILE *fp;
char *str[] = {"Hello, World!", "C语言文件操作", "字符串写入示例"};
int i;
// 打开文件
fp = fopen("test.txt", "w");
if (fp == NULL) {
printf("无法打开文件\\n");
return 1;
}
// 向文件中写入字符串
for (i = 0; i < 3; i++) {
fputs(str[i], fp);
fputs("\\n", fp); // 写入换行符
}
// 关闭文件
fclose(fp);
return 0;
}
9.4.3 格式化读写函数
1. 格式化读取函数fscanf()
fscanf()函数用于从文件中读取格式化的数据。
函数原型:
int fscanf(FILE *stream, const char *format, …);
参数说明:
- stream:文件指针
- format:格式化字符串
- …:变量地址列表
返回值:
- 成功:返回实际读取到的数据项数
- 失败或到达文件末尾:返回EOF(-1)
示例:
#include <stdio.h>
int main() {
FILE *fp;
int id;
char name[20];
float score;
// 打开文件
fp = fopen("students.txt", "r");
if (fp == NULL) {
printf("无法打开文件\\n");
return 1;
}
// 读取文件中的格式化数据
printf("学号\\t姓名\\t成绩\\n");
while (fscanf(fp, "%d %s %f", &id, name, &score) != EOF) {
printf("%d\\t%s\\t%.1f\\n", id, name, score);
}
// 关闭文件
fclose(fp);
return 0;
}
2. 格式化写入函数fprintf()
fprintf()函数用于向文件中写入格式化的数据。
函数原型:
int fprintf(FILE *stream, const char *format, …);
参数说明:
- stream:文件指针
- format:格式化字符串
- …:要写入的数据
返回值:
- 成功:返回写入的字符数
- 失败:返回负数
示例:
#include <stdio.h>
int main() {
FILE *fp;
int id;
char name[20];
float score;
// 打开文件
fp = fopen("students.txt", "w");
if (fp == NULL) {
printf("无法打开文件\\n");
return 1;
}
// 向文件中写入格式化数据
fprintf(fp, "%d %s %.1f\\n", 1001, "张三", 90.5);
fprintf(fp, "%d %s %.1f\\n", 1002, "李四", 88.0);
fprintf(fp, "%d %s %.1f\\n", 1003, "王五", 92.5);
// 关闭文件
fclose(fp);
return 0;
}
9.4.4 二进制读写函数
1. 二进制读取函数fread()
fread()函数用于从二进制文件中读取数据。
函数原型:
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
参数说明:
- ptr:存储读取到的数据的指针
- size:每个数据项的大小(字节数)
- nmemb:要读取的数据项数
- stream:文件指针
返回值:
- 成功:返回实际读取到的数据项数
- 失败或到达文件末尾:返回小于nmemb的值或0
2. 二进制写入函数fwrite()
fwrite()函数用于向二进制文件中写入数据。
函数原型:
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
参数说明:
- ptr:要写入的数据的指针
- size:每个数据项的大小(字节数)
- nmemb:要写入的数据项数
- stream:文件指针
返回值:
- 成功:返回实际写入的数据项数
- 失败:返回小于nmemb的值或0
示例:
#include <stdio.h>
struct Student {
int id;
char name[20];
float score;
};
int main() {
FILE *fp;
struct Student stu[3] = {
{1001, "张三", 90.5},
{1002, "李四", 88.0},
{1003, "王五", 92.5}
};
struct Student read_stu[3];
int i;
// 写入二进制文件
fp = fopen("students.bin", "wb");
if (fp == NULL) {
printf("无法打开文件\\n");
return 1;
}
fwrite(stu, sizeof(struct Student), 3, fp);
fclose(fp);
// 读取二进制文件
fp = fopen("students.bin", "rb");
if (fp == NULL) {
printf("无法打开文件\\n");
return 1;
}
fread(read_stu, sizeof(struct Student), 3, fp);
fclose(fp);
// 输出读取到的数据
for (i = 0; i < 3; i++) {
printf("学号:%d, 姓名:%s, 成绩:%.1f\\n",
read_stu[i].id, read_stu[i].name, read_stu[i].score);
}
return 0;
}
9.5 文件的定位操作
文件的定位操作是指移动文件指针到指定的位置,以便在该位置进行读写操作。C语言提供了多种文件定位函数。
9.5.1 获取当前文件位置ftell()
ftell()函数用于获取文件指针当前的位置。
函数原型:
long int ftell(FILE *stream);
参数说明:
- stream:文件指针
返回值:
- 成功:返回文件指针当前的位置(相对于文件开头的字节数)
- 失败:返回-1L
9.5.2 移动文件指针fseek()
fseek()函数用于将文件指针移动到指定的位置。
函数原型:
int fseek(FILE *stream, long int offset, int whence);
参数说明:
- stream:文件指针
- offset:偏移量,正数表示向后移动,负数表示向前移动
- whence:起始位置,有三个取值:
- SEEK_SET:文件开头(0)
- SEEK_CUR:当前位置(1)
- SEEK_END:文件末尾(2)
返回值:
- 成功:返回0
- 失败:返回非0值
示例:
#include <stdio.h>
int main() {
FILE *fp;
char ch;
fp = fopen("test.txt", "r");
if (fp == NULL) {
printf("无法打开文件\\n");
return 1;
}
// 读取第一个字符
ch = fgetc(fp);
printf("第一个字符:%c\\n", ch);
// 移动到文件开头
fseek(fp, 0, SEEK_SET);
// 读取第一个字符
ch = fgetc(fp);
printf("第一个字符:%c\\n", ch);
// 向后移动3个字符
fseek(fp, 3, SEEK_CUR);
ch = fgetc(fp);
printf("当前字符:%c\\n", ch);
// 向前移动2个字符
fseek(fp, -2, SEEK_CUR);
ch = fgetc(fp);
printf("当前字符:%c\\n", ch);
// 移动到文件末尾前1个字符
fseek(fp, -1, SEEK_END);
ch = fgetc(fp);
printf("最后一个字符:%c\\n", ch);
fclose(fp);
return 0;
}
9.5.3 重置文件指针到开头rewind()
rewind()函数用于将文件指针重置到文件开头。
函数原型:
void rewind(FILE *stream);
参数说明:
- stream:文件指针
示例:
#include <stdio.h>
int main() {
FILE *fp;
char ch;
int count = 0;
fp = fopen("test.txt", "r");
if (fp == NULL) {
printf("无法打开文件\\n");
return 1;
}
// 统计文件中的字符数
while ((ch = fgetc(fp)) != EOF) {
count++;
}
printf("文件中的字符数:%d\\n", count);
// 重置文件指针到开头
rewind(fp);
// 再次读取文件
printf("文件内容:\\n");
while ((ch = fgetc(fp)) != EOF) {
putchar(ch);
}
fclose(fp);
return 0;
}
9.6 文件操作的错误处理
C语言提供了一些函数来处理文件操作中的错误,确保文件操作的安全性和可靠性。
9.6.1 错误信息函数ferror()
ferror()函数用于检查文件操作是否发生错误。
函数原型:
int ferror(FILE *stream);
参数说明:
- stream:文件指针
返回值:
- 0:没有发生错误
- 非0:发生了错误
9.6.2 文件结束检查函数feof()
feof()函数用于检查文件指针是否到达文件末尾。
函数原型:
int feof(FILE *stream);
参数说明:
- stream:文件指针
返回值:
- 非0:到达文件末尾
- 0:未到达文件末尾
9.6.3 清除错误标志函数clearerr()
clearerr()函数用于清除文件的错误标志和文件结束标志。
函数原型:
void clearerr(FILE *stream);
参数说明:
- stream:文件指针
9.6.4 错误处理最佳实践
在文件操作中,良好的错误处理是非常重要的。以下是一些最佳实践:
- 总是检查fopen()函数的返回值
- 在读取文件时,同时使用feof()和ferror()检查结束原因
- 使用perror()函数输出系统错误信息
- 在发生错误时,及时关闭已打开的文件
- 使用errno变量获取详细的错误代码
示例:完整的错误处理
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main() {
FILE *fp;
char ch;
fp = fopen("test.txt", "r");
if (fp == NULL) {
printf("无法打开文件: %s\\n", strerror(errno));
perror("fopen");
return 1;
}
// 读取文件
while (1) {
ch = fgetc(fp);
if (feof(fp)) {
printf("\\n文件读取完成\\n");
break;
}
if (ferror(fp)) {
printf("\\n文件读取错误: %s\\n", strerror(errno));
perror("fgetc");
clearerr(fp);
break;
}
putchar(ch);
}
fclose(fp);
return 0;
}
9.7 二进制文件详解
9.7.1 二进制文件的特点
二进制文件是直接以数据的二进制形式存储的文件,与文本文件相比具有以下特点:
| 存储方式 | 直接存储二进制数据 | 将数据转换为ASCII字符存储 |
| 存储空间 | 较小 | 较大 |
| 读写速度 | 较快 | 较慢 |
| 可读性 | 差,需要特定程序解析 | 好,可以直接用文本编辑器查看 |
| 适用场景 | 存储图像、音频、视频、结构体等复杂数据 | 存储文本、配置文件、日志等 |
9.7.2 二进制文件的高级操作
1. 结构体的二进制读写
二进制文件非常适合存储结构体数据,因为它可以直接保存结构体的内存表示。
示例:复杂结构体的二进制读写
#include <stdio.h>
#include <string.h>
// 定义日期结构体
typedef struct {
int year;
int month;
int day;
} Date;
// 定义学生结构体
typedef struct {
int id;
char name[20];
Date birthday;
float scores[3];
float average;
} Student;
// 计算平均分
void calculate_average(Student *stu) {
float sum = 0;
for (int i = 0; i < 3; i++) {
sum += stu->scores[i];
}
stu->average = sum / 3;
}
int main() {
Student students[2] = {
{
1001, "张三", {2005, 9, 1}, {85.5, 90.0, 88.5}, 0
},
{
1002, "李四", {2006, 3, 15}, {92.0, 87.5, 90.5}, 0
}
};
Student read_students[2];
// 计算平均分
for (int i = 0; i < 2; i++) {
calculate_average(&students[i]);
}
// 写入二进制文件
FILE *fp = fopen("students.dat", "wb");
if (fp == NULL) {
perror("fopen");
return 1;
}
size_t written = fwrite(students, sizeof(Student), 2, fp);
printf("成功写入 %zu 个学生记录\\n", written);
fclose(fp);
// 读取二进制文件
fp = fopen("students.dat", "rb");
if (fp == NULL) {
perror("fopen");
return 1;
}
size_t read = fread(read_students, sizeof(Student), 2, fp);
printf("成功读取 %zu 个学生记录\\n\\n", read);
fclose(fp);
// 输出读取的数据
for (int i = 0; i < 2; i++) {
printf("学号:%d\\n", read_students[i].id);
printf("姓名:%s\\n", read_students[i].name);
printf("生日:%d-%d-%d\\n",
read_students[i].birthday.year,
read_students[i].birthday.month,
read_students[i].birthday.day);
printf("成绩:%.1f, %.1f, %.1f\\n",
read_students[i].scores[0],
read_students[i].scores[1],
read_students[i].scores[2]);
printf("平均分:%.2f\\n\\n", read_students[i].average);
}
return 0;
}
2. 二进制文件的随机访问
二进制文件非常适合随机访问,因为每个记录的大小固定,可以通过fseek()快速定位到任意记录。
示例:二进制文件的随机访问
#include <stdio.h>
typedef struct {
int id;
char name[20];
float score;
} Student;
int main() {
Student students[5] = {
{1001, "张三", 90.5},
{1002, "李四", 88.0},
{1003, "王五", 92.5},
{1004, "赵六", 85.0},
{1005, "孙七", 89.5}
};
Student stu;
FILE *fp;
int record_size = sizeof(Student);
// 写入二进制文件
fp = fopen("students.bin", "wb");
fwrite(students, record_size, 5, fp);
fclose(fp);
// 随机读取第3个记录(索引为2)
fp = fopen("students.bin", "rb");
fseek(fp, 2 * record_size, SEEK_SET);
fread(&stu, record_size, 1, fp);
printf("第3个记录:学号=%d, 姓名=%s, 成绩=%.1f\\n",
stu.id, stu.name, stu.score);
// 随机读取最后一个记录
fseek(fp, -1 * record_size, SEEK_END);
fread(&stu, record_size, 1, fp);
printf("最后一个记录:学号=%d, 姓名=%s, 成绩=%.1f\\n",
stu.id, stu.name, stu.score);
// 修改第2个记录(索引为1)
fseek(fp, 1 * record_size, SEEK_SET);
stu.id = 2002;
strcpy(stu.name, "李四修改");
stu.score = 95.5;
fwrite(&stu, record_size, 1, fp);
// 验证修改结果
fseek(fp, 1 * record_size, SEEK_SET);
fread(&stu, record_size, 1, fp);
printf("修改后的第2个记录:学号=%d, 姓名=%s, 成绩=%.1f\\n",
stu.id, stu.name, stu.score);
fclose(fp);
return 0;
}
9.8 文件锁与并发访问
9.8.1 文件锁的概念
文件锁是用于控制多个进程对同一文件的并发访问的机制,防止数据不一致和竞争条件。
文件锁主要分为两种类型:
- 共享锁(读锁):允许多个进程同时读取文件,但不允许写入
- 排他锁(写锁):只允许一个进程写入文件,其他进程不能读取或写入
9.8.2 使用flock()实现文件锁
flock()函数用于在文件上设置锁,其原型如下:
#include <sys/file.h>
int flock(int fd, int operation);
参数说明:
- fd:文件描述符(可以通过fileno()函数从FILE*获取)
- operation:锁操作,包括:
- LOCK_SH:请求共享锁
- LOCK_EX:请求排他锁
- LOCK_UN:释放锁
- LOCK_NB:非阻塞模式(与上述选项配合使用)
示例:使用flock实现文件锁
#include <stdio.h>
#include <sys/file.h>
#include <unistd.h>
int main() {
FILE *fp;
int fd;
fp = fopen("shared.txt", "a+");
if (fp == NULL) {
perror("fopen");
return 1;
}
fd = fileno(fp);
// 请求排他锁
if (flock(fd, LOCK_EX) == -1) {
perror("flock");
fclose(fp);
return 1;
}
printf("获得排他锁,开始写入…\\n");
// 写入数据
fprintf(fp, "进程 %d 写入的数据\\n", getpid());
fflush(fp);
// 模拟长时间操作
sleep(5);
// 释放锁
if (flock(fd, LOCK_UN) == -1) {
perror("flock");
} else {
printf("释放排他锁\\n");
}
fclose(fp);
return 0;
}
9.8.3 文件并发访问的最佳实践
- 总是在访问共享文件时使用适当的锁
- 尽量缩短持有锁的时间
- 使用非阻塞模式避免死锁
- 在写入前总是先获取排他锁
- 在读取共享文件时使用共享锁
- 确保在所有退出路径上都释放锁
9.9 高级文件操作
9.9.1 文件缓冲区控制
C语言中的文件操作默认是缓冲的,可以使用以下函数控制缓冲区:
1. 刷新缓冲区fflush()
函数原型:
int fflush(FILE *stream);
将缓冲区中的数据立即写入文件。
2. 设置缓冲区setbuf()和setvbuf()
函数原型:
void setbuf(FILE *stream, char *buf);
int setvbuf(FILE *stream, char *buf, int mode, size_t size);
用于设置文件的缓冲区类型和大小。
示例:缓冲区控制
#include <stdio.h>
int main() {
FILE *fp;
char buf[BUFSIZ];
fp = fopen("test.txt", "w");
if (fp == NULL) {
perror("fopen");
return 1;
}
// 设置自定义缓冲区
setbuf(fp, buf);
// 写入数据(先进入缓冲区)
fprintf(fp, "Hello, ");
printf("写入\\"Hello, \\"到缓冲区\\n");
// 刷新缓冲区,将数据写入文件
fflush(fp);
printf("刷新缓冲区,数据写入文件\\n");
fprintf(fp, "World!\\n");
// 关闭文件时会自动刷新缓冲区
fclose(fp);
printf("关闭文件,缓冲区数据写入文件\\n");
return 0;
}
9.9.2 临时文件管理
C语言提供了创建临时文件的函数,用于存储临时数据:
- tmpfile():创建一个临时文件,关闭时自动删除
- tmpnam():生成一个唯一的临时文件名
- mkstemp():创建一个唯一的临时文件
示例:临时文件使用
#include <stdio.h>
int main() {
FILE *temp_fp;
char temp_name[L_tmpnam];
// 使用tmpfile()创建临时文件
temp_fp = tmpfile();
if (temp_fp == NULL) {
perror("tmpfile");
return 1;
}
// 写入数据
fprintf(temp_fp, "这是临时文件中的数据\\n");
// 读取数据
rewind(temp_fp);
char buffer[100];
fgets(buffer, sizeof(buffer), temp_fp);
printf("临时文件内容:%s", buffer);
fclose(temp_fp); // 关闭时自动删除
// 使用tmpnam()生成临时文件名
if (tmpnam(temp_name) == NULL) {
perror("tmpnam");
return 1;
}
printf("生成的临时文件名:%s\\n", temp_name);
return 0;
}
9.9.3 目录操作
C语言提供了一系列目录操作函数:
- opendir():打开目录
- readdir():读取目录项
- closedir():关闭目录
- mkdir():创建目录
- rmdir():删除目录
- chdir():改变当前工作目录
- getcwd():获取当前工作目录
示例:目录遍历
#include <stdio.h>
#include <dirent.h>
int main() {
DIR *dir;
struct dirent *entry;
// 打开当前目录
dir = opendir(".");
if (dir == NULL) {
perror("opendir");
return 1;
}
// 遍历目录
printf("当前目录内容:\\n");
while ((entry = readdir(dir)) != NULL) {
printf("%s\\n", entry->d_name);
}
closedir(dir);
return 0;
}
9.10 文件系统操作
9.10.1 文件属性获取
使用stat()函数可以获取文件的详细属性:
示例:获取文件属性
#include <stdio.h>
#include <sys/stat.h>
#include <time.h>
int main() {
struct stat st;
if (stat("test.txt", &st) == -1) {
perror("stat");
return 1;
}
printf("文件大小:%ld字节\\n", st.st_size);
printf("文件类型:");
if (S_ISREG(st.st_mode)) printf("普通文件\\n");
else if (S_ISDIR(st.st_mode)) printf("目录\\n");
else if (S_ISLNK(st.st_mode)) printf("符号链接\\n");
else printf("其他类型\\n");
printf("文件权限:%o\\n", st.st_mode & 0777);
printf("所有者ID:%d\\n", st.st_uid);
printf("组ID:%d\\n", st.st_gid);
printf("最后访问时间:%s", ctime(&st.st_atime));
printf("最后修改时间:%s", ctime(&st.st_mtime));
printf("最后状态变化时间:%s", ctime(&st.st_ctime));
return 0;
}
9.7 综合实例
下面是一个综合实例,演示了文件的创建、写入、读取和关闭操作。
#include <stdio.h>
#include <stdlib.h>
struct Student {
int id;
char name[20];
float score;
};
// 函数声明
void write_to_file(const char *filename, struct Student *stu, int count);
void read_from_file(const char *filename);
int main() {
struct Student stu[3] = {
{1001, "张三", 90.5},
{1002, "李四", 88.0},
{1003, "王五", 92.5}
};
// 写入文件
write_to_file("students.txt", stu, 3);
// 读取文件
read_from_file("students.txt");
return 0;
}
// 写入文件函数
void write_to_file(const char *filename, struct Student *stu, int count) {
FILE *fp;
int i;
fp = fopen(filename, "w");
if (fp == NULL) {
printf("无法打开文件 %s\\n", filename);
exit(1);
}
for (i = 0; i < count; i++) {
fprintf(fp, "%d %s %.1f\\n", stu[i].id, stu[i].name, stu[i].score);
}
fclose(fp);
printf("数据已成功写入文件 %s\\n", filename);
}
// 读取文件函数
void read_from_file(const char *filename) {
FILE *fp;
int id;
char name[20];
float score;
fp = fopen(filename, "r");
if (fp == NULL) {
printf("无法打开文件 %s\\n", filename);
exit(1);
}
printf("文件 %s 的内容:\\n", filename);
printf("学号\\t姓名\\t成绩\\n");
while (fscanf(fp, "%d %s %f", &id, name, &score) != EOF) {
printf("%d\\t%s\\t%.1f\\n", id, name, score);
}
fclose(fp);
}
练习题
学习建议
- 理解文件操作的基本概念和流程。
- 掌握文件指针的定义和使用。
- 学会使用fopen()和fclose()函数打开和关闭文件。
- 掌握不同类型的文件读写函数,包括字符读写、字符串读写、格式化读写和二进制读写。
- 学会使用文件定位函数,实现文件的随机访问。
- 了解文件操作的错误处理方法。
- 多做练习,熟练掌握文件操作的各种功能。


