【C语言-文件操作】1. 文件操作的基本概念——文件指针和打开关闭

开发技术 作者: 精灵星空 SKY315.COM 更新于: 浏览: 5
码字发文不易,转载请注明出处【精灵星空 SKY315.COM】

咱们开始讲文件操作。这是C语言里实用性最强的一块,学完了你写的程序就能“记住”东西了,不再是关掉就啥都没了。

但在动手写代码之前,咱们得先把几个基本概念搞清楚。磨刀不误砍柴工嘛。

什么是文件?

从程序的角度来说,文件就是存在硬盘上的一堆数据。它跟内存里的变量、数组有啥区别?区别大了:

  • 内存里的数据,程序一关就没了。

  • 硬盘上的文件,关机了还在,下次打开还能读到。

你用记事本写个txt,保存了,明天打开还在——这就是文件的作用。在C语言里,文件就是程序跟硬盘打交道的一个通道。你往文件里写东西,就是把数据从内存搬到硬盘上;你从文件里读东西,就是把数据从硬盘搬回内存里。

文本文件 vs 二进制文件

文件分两种:文本文件和二进制文件。这俩的区别,说白了就是“数据怎么存的”。

文本文件:里面存的是字符,人能直接看懂。你打开一个txt文件,看到的是“Hello World”,那就是文本文件。数字123在文本文件里存的是字符'1'、'2'、'3',每个字符占一个字节,一共3个字节。

二进制文件:里面存的是数据在内存里的原始二进制形式,人看不懂。整数123在二进制文件里就是它的二进制表示,一个int占4个字节(比如0x0000007B)。你用记事本打开二进制文件,看到的是一堆乱码。

举个例子你就明白了。你要存整数12345到文件里:

  • 文本文件:存的是字符'1'、'2'、'3'、'4'、'5',占5个字节。

  • 二进制文件:存的是12345这个整数的二进制形式,占4个字节。

文本文件的好处是通用——你用记事本能看,用别的软件也能看。缺点是占空间多(每个数字至少占1个字节),而且读写的时候需要转换(把数字转成字符再写,读的时候把字符转回数字)。

二进制文件的好处是读写快、占空间小、精度不丢失(浮点数用文本存可能丢精度)。缺点是通用性差,你得知道它存的是什么类型才能正确读取。

你平时写作业,用文本文件就够用了。 二进制文件主要用在存图片、音频、视频、数据库这些场景。

但是!你操作文件的方式其实是一样的,都是用那几个函数。区别只在于你写入的数据是什么类型。

文件指针 FILE*——操作文件的“把手”

你想操作一个文件,总得先“抓住”它吧?C语言里抓文件的工具就是文件指针。

FILE *fp;

FILE是一个结构体类型(在stdio.h里定义的),里面包含了操作文件所需的各种信息——文件在硬盘上的位置、当前读到哪儿了、缓冲区状态等等。你不需要知道它里面具体有什么,你只需要知道:FILE*就是操作文件的把手。

这个“把手”跟咱们之前学的指针不太一样。你别去想它指向哪块内存,你就把它想象成一个遥控器——你拿着遥控器,就能控制电视(文件)的开关、换台(定位)、调音量(读写)。

每次打开一个文件,系统就给你返回一个FILE*指针。后面你对文件的所有操作,都通过这个指针来进行。用完了,关掉它,遥控器交回去。

fopen——打开文件

想操作文件,第一件事就是打开它。用的函数是fopen:

FILE *fopen(const char *filename, const char *mode);

两个参数:

  • filename:文件名,可以带路径。比如"data.txt"或者"C:\\Users\\test\\data.txt"。

  • mode:打开模式,告诉系统你想对文件做什么。

返回值:成功的话返回一个FILE*,失败返回NULL。

用法:

FILE *fp = fopen("data.txt", "r");

这行代码的意思是:以“只读”模式打开当前目录下的data.txt文件,把文件指针赋给fp。

打开模式——你想干啥,就选哪个模式

打开模式是fopen的核心,你必须搞清楚每种模式的区别。咱们一个个说:

1. "r"——只读模式

打开一个已有的文件,只能读,不能写。

  • 文件必须存在,如果不存在,fopen返回NULL。

  • 文件存在的话,指针指向文件开头。

FILE *fp = fopen("data.txt", "r");
if (fp == NULL) {
    printf("文件不存在\n");
}

2. "w"——只写模式

打开一个文件用来写入。

  • 如果文件不存在,创建一个新文件。

  • 如果文件已经存在,把里面的内容全部清空,然后从头开始写。这个很要命——你本来想往文件里加点东西,结果一打开,原来的内容全没了。

  • 文件指针指向文件开头。

FILE *fp = fopen("output.txt", "w");

3. "a"——追加模式

打开一个文件,在末尾追加内容。

  • 如果文件不存在,创建一个新文件。

  • 如果文件存在,不会清空,文件指针指向文件末尾,你写的东西会接在原来内容的后面。

FILE *fp = fopen("log.txt", "a");

这个模式特别适合写日志——你每次运行程序往日志文件里追加一条记录,不会把之前的记录覆盖掉。

4. "r+"——读写模式

打开一个已有的文件,既能读也能写。

  • 文件必须存在。

  • 文件指针指向开头。

  • 写的时候会覆盖当前位置的内容,不会自动在后面追加。

5. "w+"——读写模式(清空)

打开一个文件,既能读也能写。

  • 文件不存在就创建。

  • 文件存在就清空。

6. "a+"——读写模式(追加)

打开一个文件,既能读也能写。

  • 文件不存在就创建。

  • 文件存在就在末尾追加,但你可以移动到任意位置读取。

这些模式看着多,其实核心就三组:

  • r系列:读为主,文件必须存在。

  • w系列:写为主,文件不存在就创建,存在就清空。

  • a系列:追加为主,文件不存在就创建,存在就在末尾加。

带+的表示既能读又能写,不带+的只能读或者只能写。

初学者最常用的就是"r"、"w"、"a"这三个。 你把这三个用熟了,其他的自然就理解了。

一个小细节:路径里的反斜杠

Windows的路径用反斜杠,比如C:\Users\test\data.txt。但在C语言字符串里,反斜杠是转义字符,所以你写路径的时候得写两个反斜杠:

FILE *fp = fopen("C:\\Users\\test\\data.txt", "r");

或者用正斜杠也行(Windows也认):

FILE *fp = fopen("C:/Users/test/data.txt", "r");

这个坑新手经常踩——你写"C:\Users\test\data.txt",\U和\t会被当成转义字符,路径就错了。

打开失败怎么办?

fopen有可能失败——文件不存在、没有权限、路径不对、硬盘满了等等。失败的时候返回NULL。

你必须检查返回值。 不检查的话,后面用这个NULL指针去读写,程序直接崩溃。

FILE *fp = fopen("data.txt", "r");
if (fp == NULL) {
    printf("打开文件失败\n");
    return 1;   // 或者用exit(1)
}

还可以用perror函数打印更详细的错误信息:

FILE *fp = fopen("data.txt", "r");
if (fp == NULL) {
    perror("打开文件失败");
    return 1;
}

perror会打印你给的提示信息,再加上系统给的错误原因。比如输出可能是打开文件失败: No such file or directory。比你自己猜原因方便多了。

fclose——关闭文件

文件用完了,必须关闭。用的函数是fclose:

int fclose(FILE *fp);

参数就是你打开文件时得到的那个FILE*。成功返回0,失败返回EOF(-1)。

fclose(fp);

为什么必须关闭?

第一,数据不会丢。你往文件里写的数据,系统会先放在缓冲区里(内存里的一块临时区域),等到缓冲区满了或者你关闭文件的时候,才真正写到硬盘上。你不关闭文件,最后那部分数据可能还在缓冲区里,程序一结束就没了。

第二,释放资源。系统能同时打开的文件数量是有限的。你打开一堆文件不关,后面再想打开就失败了。

第三,防止文件损坏。有些系统会锁定打开的文件,你不关闭,别的程序打不开。

养成习惯:打开的文件一定要关闭,fopen和fclose成对出现。 就像你进房间要开门,出房间要关门一样,天经地义。

一个完整的例子:打开文件,读一个字符,关闭

咱们写个最简单的例子,把今天学的串起来:

#include <stdio.h>

int main() {
    FILE *fp;
    int ch;

    // 打开文件
    fp = fopen("test.txt", "r");
    if (fp == NULL) {
        perror("打开文件失败");
        return 1;
    }

    // 从文件里读一个字符
    ch = fgetc(fp);
    if (ch != EOF) {
        printf("读到的字符:%c\n", ch);
    }

    // 关闭文件
    fclose(fp);

    return 0;
}

这个程序先打开test.txt,读第一个字符,打印出来,然后关闭。就这么简单。

再一个例子:写文件

#include <stdio.h>

int main() {
    FILE *fp;

    // 以写入模式打开文件
    fp = fopen("output.txt", "w");
    if (fp == NULL) {
        perror("打开文件失败");
        return 1;
    }

    // 往文件里写点东西
    fprintf(fp, "Hello, file!\n");
    fprintf(fp, "这是第二行\n");
    fprintf(fp, "数字:%d,小数:%.2f\n", 100, 3.14);

    // 关闭文件
    fclose(fp);

    printf("写入完成,去当前目录下找output.txt看看\n");

    return 0;
}

运行完这个程序,你去程序所在目录下找,会多出一个output.txt文件,用记事本打开就能看到刚才写的内容。

你还可以试试把"w"改成"a",再运行一次。你会发现新写的内容追加在原来内容的后面,没有覆盖。这就是"w"和"a"的区别。

几个常见的坑

坑1:忘了检查fopen的返回值

FILE *fp = fopen("data.txt", "r");
fprintf(fp, "hello");   // 如果fp是NULL,程序崩溃

一定要检查,一定。

坑2:用了"w"模式,把原来文件的内容清空了

你想给文件追加内容,结果用了"w",一打开原来的内容全没了。这是最惨的错误——数据没了就真没了,找不回来。追加用"a"。

坑3:忘了fclose

FILE *fp = fopen("data.txt", "w");
fprintf(fp, "hello");
// 没写fclose,程序结束了

有时候程序结束的时候系统会自动帮你关闭,但不保证。养成手动关闭的习惯。

坑4:路径写错

FILE *fp = fopen("C:\Users\test\data.txt", "r");   // 错误!反斜杠被转义了
FILE *fp = fopen("C:\\Users\\test\\data.txt", "r");  // 正确
FILE *fp = fopen("C:/Users/test/data.txt", "r");     // 也对

坑5:文件打开了没关,又去打开同一个文件

FILE *fp1 = fopen("data.txt", "w");
FILE *fp2 = fopen("data.txt", "w");   // 两个指针指向同一个文件,行为不确定

一个文件不要同时打开两次,除非你知道自己在干什么。

总结一下:

文件操作第一节的核心就两件事:打开文件和关闭文件。

  • 文件是硬盘上存数据的地方,分文本文件和二进制文件。

  • FILE*是操作文件的把手,所有文件操作都通过它进行。

  • fopen打开文件,必须指定模式:"r"只读、"w"写入(清空)、"a"追加。

  • fopen可能失败,返回NULL,必须检查。

  • fclose关闭文件,打开的文件一定要关闭,不然数据可能丢失。

把这几个知识点记住,你就算入门了。下节课咱们讲怎么往文件里写数据和从文件里读数据——那才是文件操作真正干活的地方。

回去自己动手,写几个程序:创建一个文件,写点东西进去,关掉;再打开读出来看看。把"r"、"w"、"a"三种模式都试一遍,观察文件内容的变化。亲手试一次,比看十遍都明白。