知识点 12 · 文件操作¶
本页最后更新于 ,内容可能过时
速查表 · 文件函数与打开模式¶
| 函数 | 用途 | 关键点 |
|---|---|---|
fopen(name, mode) |
打开文件 | 返回 FILE *,失败返回 NULL(必须判空!) |
fclose(fp) |
关闭文件 | 不关会丢数据 |
fgetc(fp) / fputc(c, fp) |
读写一个字符 | 读到文件尾返回 EOF |
fgets(s, n, fp) |
读一行(最多 n-1 字符) |
保留换行符 |
fscanf(fp, fmt, ...) |
格式化读 | 返回成功读入的项数,常用于 while(fscanf(fp,"%d",&x)==1) |
fprintf(fp, fmt, ...) |
格式化写 | |
fread / fwrite |
二进制块读写 | 文本文件也能用 fread |
fseek(fp, offset, whence) |
重新定位文件流位置 | SEEK_SET/SEEK_CUR/SEEK_END |
ftell(fp) |
当前位置 | |
rewind(fp) |
回到开头 | |
feof(fp) |
是否到文件尾 |
打开模式:
| 模式 | 能不能读 | 能不能写 | 文件不存在时 | 文件已存在时 |
|---|---|---|---|---|
"r" |
✅ | ❌ | 打开失败,返回 NULL |
从头读,不清空 |
"w" |
❌ | ✅ | 新建 | 先清空!(内容全丢) |
"a" |
❌ | ✅ | 新建 | 不清空,写在末尾(追加) |
"r+" |
✅ | ✅ | 打开失败,返回 NULL |
不清空,可读可写(就地修改用它) |
"w+" |
✅ | ✅ | 新建 | 先清空 |
"a+" |
✅ | ✅ | 新建 | 不清空,读从头、写只能在末尾 |
"rb" / "wb" |
同 "r" |
同 "w" |
同 "r" / "w" |
二进制模式,不做换行符转换 |
一句话记忆:
r系列要文件已存在、w/a系列会自动建;只有带w的会清空。 「就地改写文件里已有的内容」→ 只有"r+"能用("w"一开就把内容清光了,没得改)。
更新流("r+"/"w+"/"a+")的硬规矩
写之后要读、读之后要写,中间必须插一个 fseek/rewind/fflush,否则行为未定义。
1 · 文件读写函数¶
以下文件操作中,哪个选项是正确的?
- A.
fread()函数只能用于文本文件 - B.
fseek()函数用于重新定位文件流的位置 - C.
fopen()函数返回整型值表示文件描述符 - D.
fprintf()函数不支持格式化输出
答案
B
解析
- A 错:
fread/fwrite是二进制块读写,主要用于二进制文件,但文本文件同样能用。反过来说"只能用于文本文件"更是错的。 - B 对:
fseek就是用来移动文件位置指针的(SEEK_SET/SEEK_CUR/SEEK_END三个基准)。 - C 错:
fopen返回的是FILE *(文件流指针),不是整型文件描述符(整型 fd 是 Linux 系统调用open的返回值)。 - D 错:
fprintf里的f就是 "formatted",恰恰专门用于格式化输出(与printf用法一致,只是多了个文件指针参数)。
易错:C 的命名规律可以背下来——看到中间带
f的就是"文件版printf/scanf":fprintf、fscanf、fgetc、fputc、fgets、fputs。本题四个选项全在速查表里,背熟表格即可秒杀。
2 · 文件读写小工具 · 逐字符统计¶
说明下面程序的功能。
#include <stdio.h>
FILE *fp;
int main(){
int count=0, ch;
char fname[1024];
printf("输入文件名!\n");
scanf("%s%*c", fname);
if ((fp = fopen(fname, "r")) == NULL){
printf("Can not open %s file.\n", fname);
return 1;
}
while((ch = fgetc(fp)) != EOF)
if ((ch>='A'&&ch<='Z')||(ch>='a'&&ch<='z'))
count++;
fclose(fp);
printf("%d\n",count);
return 0;
}
答案
程序功能:从键盘输入一个文件名,以只读方式打开该文本文件,统计文件中英文字母(
A–Z和a–z)的总个数并输出。
解析
逐句拆解——
scanf("%s%*c", fname):读入文件名到fname。%*c里的*表示"读入但丢弃"(这里用来吃掉输入缓冲区里残留的换行符)。if ((fp = fopen(fname, "r")) == NULL):打开文件并判空——这是文件操作必须做的检查,否则后面fgetc(NULL)直接崩溃。"r"模式要求文件必须已存在,不存在时fopen返回NULL,程序打印Can not open ... file.后return 1退出。while((ch = fgetc(fp)) != EOF):逐字符读取,直到文件末尾。fgetc读到文件尾返回EOF,这是"读到尾"的标准写法。注意
ch声明成int而不是char:EOF是负值(通常是 -1),char可能装不下,会死循环。- 内层
if:判断该字符是否是大小写英文字母,是则count++。 fclose(fp);关闭文件,最后printf输出计数。
易错:把功能说成"统计字符个数"或"统计字母和数字"。必须精确到"英文字母(大小写)",这是评分点。
3 · 读写交替与 fseek · 就地改写文件¶
说明下面程序的功能。
#include <stdio.h>
int main(void) {
char filename[1024];
printf("输入文件名!\n");
scanf("%s%*c", filename);
FILE *file = fopen(filename, "r+");
if (!file) {
perror("打开文件失败");
return 1;
}
fseek(file, 0, SEEK_SET);
int ch;
long pos = 0;
while ((ch = fgetc(file)) != EOF) {
if (ch >= 'A' && ch <= 'Z') {
fseek(file, pos, SEEK_SET);
fputc(ch + ('a' - 'A'), file);
fseek(file, pos + 1, SEEK_SET);
}
pos++;
}
fclose(file);
return 0;
}
答案
程序功能:从键盘读入一个文件名,把该文本文件中所有大写字母就地改写成对应的小写字母(其余字符不变)。
解析
几个关键点——
fopen(filename, "r+"):"r+"表示"可读可写,且要求文件已存在",这是"就地修改"的必要条件。对比记忆:
"w"一打开就把文件清空,还改什么?"a"只能往末尾追加,改不了中间已有的字符;"r"只能读,写不进去。只有"r+"(或"w+",但它会先清空)能在原地又读又写。scanf("%s%*c", filename):%*c中的*表示"读入但丢弃",用来吃掉残留的换行符。pos记录当前字符在文件中的字节偏移,用来在改写时定位。- 改写的三步走:
第 ③ 步可以省,但建议留着:
fseek(file, pos, SEEK_SET); // ① 回到该字符位置 fputc(ch + ('a' - 'A'), file); // ② 写入小写字母('A'→'a' 偏移量是 32) fseek(file, pos + 1, SEEK_SET); // ③ 把读位置拨到下一个字符fputc写完本来就把位置指针停在pos+1,所以这一行是冗余的(实测去掉后,穷举 3900+ 组输入 + 200 组随机输入,输出逐字节完全相同)。但 C 标准要求更新流("r+")在"写之后、读之前"必须插入一个定位函数,保留它最稳妥。 ch + ('a' - 'A')就是ch + 32,把小写化。用('a'-'A')而不是魔法数字 32,可读性更好,是加分项。
易错:把功能说成"统计大写字母"或"大小写互换"。必须是"把大写改成小写"(题干里没有反向操作)。 另一个坑:
fgetc的返回值必须存进int(存char就没法和EOF比较)——本题int ch;就写对了。
4 · 文件打开模式与输出¶
下列程序的功能是把带辅助表元的链表中的词汇(word)和数量(number)信息输出到文件中,每个表元的信息占一行,每个单词长度不超过 100。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
________(4)________
struct Node{
char word[MAXN];
int number;
struct Node *next;
};
void printDict(struct Node *h, char *fname){
FILE * fp;
struct Node * p;
if((fp=fopen(fname,"w"))==NULL){
printf("Can't open %s\n",fname);
________(5)________
}
for (________(6)________; p; p=p->next){
fprintf(fp, "%s\t%d\n", p->word, p->number);
/*函数 fprintf 为文件的格式化输出函数*/
}
________(7)________
}
答案
| 空 | 答案 |
|---|---|
| (4) | #define MAXN 101 |
| (5) | return; |
| (6) | p = h->next |
| (7) | fclose(fp); |
解析
- (4):题目说"单词长度不超过 100",而 C 的字符串还需要一个
'\0',所以数组要开 101。这是"长度 + 1"的经典考点。写成
100会导致最长的单词存不下结束符。 这里同时考了宏定义:#define MAXN 101(不用分号!#define MAXN 101;会把分号也替换进去)。 - (5):打开文件失败,打印提示后必须返回,否则
fp是NULL,接下来的循环会向空指针写入→ 崩溃。fopen判空 + 失败即返回,是文件操作的固定套路(对比第 2、3 题里return 1的写法,本质相同)。 - (6):链表带辅助表元,第一个真正的数据结点是
h->next。循环条件
p等价于p != NULL;循环体里已经p = p->next了,所以初始化只需管好起点。 若写成p = h,辅助表元也会被输出一行(word是垃圾值)——这是本空的主要陷阱。 - (7):
fclose(fp)不能省——不关闭文件,缓冲区里的内容可能还没落盘,数据会丢。关于这里的打开模式
"w":输出到文件用"w"(不存在就新建,已存在就清空重写)是标准选择。 注意它不要求文件已存在,这跟本题另一处会考的"r"正好相反。
易错汇总:① 数组长度要 +1;②
#define后面不加分号;③ 带辅助表元的链表从h->next开始;④fclose不能忘。
5 · 文件打开模式的选择¶
要打开一个已经存在的用于修改的非空二进制文件 "hello.txt",正确的语句是__。
- A.
fp=fopen("hello.txt","r") - B.
fp=fopen("hello.txt","ab+") - C.
fp=fopen("hello.txt","w") - D.
fp=fopen("hello.txt","r+")
答案
D
解析
题干给了三个约束——文件已存在、要修改、非空,只有 "r+" 全部满足:可读可写,且文件不存在时直接返回 NULL,正好逼你确认"已存在"。
A 的 "r" 只能读、写不进去;C 的 "w" 一打开就把文件清空,"非空"的内容当场全没了;B 的 "ab+" 虽然可读可写,但写入位置被强制移到末尾,改不了中间已有的数据。
记忆:要"就地改",只有 "r+"("w+" 也行,但它会先清空)。
6 · 文件写入代码片段的正误判断¶
以下代码片段打开文件并写入内容,哪一项是正确的?
- A. 文件 file.txt 不会被覆盖,内容会被写入
- B. 文件 file.txt 如果不存在,会创建一个空文件
- C.
fprintf用于将文本写入文件 - D. 以上都是
答案
B 【存疑】
解析
材料里这道题没有一个唯一答案
按标准 C,B 和 C 都是正确的陈述——B 说的是 "w" 模式在文件不存在时会新建文件,C 是对 fprintf 的一般性描述;A 是错的——"w" 一打开就会清空已有文件,所谓"不会被覆盖"不成立;因此"以上都是"的 D 也不成立。
也就是说,这道题在材料里没有一个唯一答案:可能是原卷就这么出的,也可能是扫描时把某个选项改掉了字。
按命题意图应选 B(材料给的答案):题干问的是"该代码片段"执行后文件的状态,"w" 遇文件不存在时会新建一个空文件,这正是 A 的反面。
不论选哪个,这两个知识点都要记牢:"w" = 不存在则新建、已存在则清空重写;fprintf 就是把格式化文本写进 FILE * 流的函数(f 即 "formatted")。