跳转至

知识点 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":fprintffscanffgetcfputcfgetsfputs

本题四个选项全在速查表里,背熟表格即可秒杀。


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;
}
答案

程序功能:从键盘输入一个文件名,以只读方式打开该文本文件,统计文件中英文字母(AZaz)的总个数并输出。

解析

逐句拆解——

  1. scanf("%s%*c", fname):读入文件名到 fname%*c 里的 * 表示"读入但丢弃"(这里用来吃掉输入缓冲区里残留的换行符)。
  2. if ((fp = fopen(fname, "r")) == NULL):打开文件并判空——这是文件操作必须做的检查,否则后面 fgetc(NULL) 直接崩溃。

    "r" 模式要求文件必须已存在,不存在时 fopen 返回 NULL,程序打印 Can not open ... file.return 1 退出。

  3. while((ch = fgetc(fp)) != EOF):逐字符读取,直到文件末尾。fgetc 读到文件尾返回 EOF,这是"读到尾"的标准写法。

    注意 ch 声明成 int 而不是 charEOF 是负值(通常是 -1),char 可能装不下,会死循环。

  4. 内层 if:判断该字符是否是大小写英文字母,是则 count++
  5. 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;
}
答案

程序功能:从键盘读入一个文件名,把该文本文件中所有大写字母就地改写成对应的小写字母(其余字符不变)。

解析

几个关键点——

  1. fopen(filename, "r+")"r+" 表示"可读可写,且要求文件已存在",这是"就地修改"的必要条件。

    对比记忆:"w" 一打开就把文件清空,还改什么?"a" 只能往末尾追加,改不了中间已有的字符;"r" 只能读,写不进去。只有 "r+"(或 "w+",但它会先清空)能在原地又读又写。

  2. scanf("%s%*c", filename)%*c 中的 * 表示"读入但丢弃",用来吃掉残留的换行符。
  3. pos 记录当前字符在文件中的字节偏移,用来在改写时定位。
  4. 改写的三步走:
    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+")在"写之后、读之前"必须插入一个定位函数,保留它最稳妥。
  5. 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):打开文件失败,打印提示后必须返回,否则 fpNULL,接下来的循环会向空指针写入→ 崩溃。

    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 · 文件写入代码片段的正误判断

以下代码片段打开文件并写入内容,哪一项是正确的?

FILE* fp = fopen("file.txt", "w");
fprintf(fp, "Hello, world!");
fclose(fp);
  • 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")。