咱们开始讲文件操作。这是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"三种模式都试一遍,观察文件内容的变化。亲手试一次,比看十遍都明白。