文章目录
C语言学习——对文件的输入输出
什么是文件?
- “文件”(file)就是指存储在外部存储器上的数据的集合。
- 程序文件和相关数据文件通常都存储在外部存储器上,只有在执行时,会被载入内存。
- 每个文件都有一个符号化的指代,这个符号化的指代就是文件名。
文件的命名
文件要有一个唯一的文件标识,以便用户识别和引用。
文件标识包括三个部分:
文件路径
文件路径表示文件在外部存储设备中的位置。
如:
D:\CC\temp\file1.dat
表示file1.dat文件存放在D盘中的CC目录下的temp子目录下面
- 文件名**主干** - 命名规则遵循标识符的命名规则 - 文件**后缀** - 一般不超过3个字母(doc、txt、dat、c、cpp、obj、exe、ppt、bmp等)
文件的分类
广义的文件分类
- 从操作系统的角度来看,可分为普通文件和设备文件
- 普通文件:驻留在磁盘或其他外部介质的一个有序数据集
- 设备文件:与主机相连的各种外部设备,如显示器、打印机、键盘等。这是一种逻辑上的文件。
- 设备只是逻辑上的文件,操作系统把各种设备都统一作为文件处理
- 每一个与主机相联的输入输出设备都看作是文件
- 键盘被定义为标准输出文件,在键盘上就键入数据就意味着从标准输入文件接收数据
- 显示屏被定义为标准输出文件,在屏幕上显示信息就是向标准输出文件输出信息
程序设计中的文件类型
从程序设计的角度来看,主要用到两种文件:
程序文件
- 源程序文件(后缀为.c)
- 目标文件(后缀为.obj)
- 可执行文件(后缀为.exe)
这种文件的内容是程序代码。
数据文件
文件的内容不是程序,而是供程序运行时读写的数据,如在程序运行过程中输出到磁盘(或其他外部设备)的数据,或在程序运行过程中供读入的数据。
- 如一批学生的成绩数据、货物交易的数据等。
本文主要讨论的是数据文件
数据文件的分类
- 根据数据的组织形式,数据文件可分为文本文件和二进制文件。
- 数据在内存中是以二进制形式存储的,如果不加转化把地输出到外存,就是二进制文件
- 文本文件又称ASCII文件,每一个字节放一个字符的ASCII代码
文件的存储方式
数据的存储方式
- 字符一律以ASCII形式存储,也可用二进制形式存储,如果二进制数据要求在外存上以ASCII代码形式存储,则需要在存储前进行转换
- 如有整数123,如果用ASCII码形式输出到磁盘,则在磁盘中占3个字节(每一个字符占一个字节),而用二进制short类型输出,则在磁盘上只占2个字节。
文件类型指针
每个被使用的文件都在内存中开辟一个相应的文件信息区,用来存放文件的相关信息(如文件的名字、文件状态及文件当前位置等),指向这片内存的指针就是文件类型指针。这些信息是保存在一个结构体变量中的。该结构体类型是由系统声明的,取名为
FILE
,相应的指针类型就是FILE*
。声明FILE
结构体类型的信息包含在头文件“stdio.h”
中。一般设置一个指向
FILE
类型变量的指针变量,然后通过它来引用这些FILE
类型变量。例如:
FILE f1, f2, f3; FILE* fp1 = &f1; FILE* fp2 = &f2; FILE* fp3 = &f3;
在C语言中的文件操作
在C语言中,对于文件的操作分为几个部分:
- 文件打开
- 文件数据读取
- 文件数据写入
- 文件关闭
文件的打开和关闭是对文件操作的前提!
文件数据的读和写是文件操作的核心部分
fopen函数:用于打开一个文件
fopen
函数的调用方式为:fopen(文件名,打开文件的方式);
例如:
fopen(“a1”, “r”)
表示要打开名为
“a1”
的文件,打开文件的方式为“读入”当打开的文件和执行程序在同一个目录时,文件的路径可以省略,否则,应该提供完整路径!!!
fopen
函数的返回值是指向a1文件的指针通常将
fopen
函数的返回值赋给一个指向文件的指针变量,如:
FILE* fp; fp = fopen("a1", "r");//此处把文件指针fp和文件a1相联系,即fp指向了a1文件
打开文件方式中不同字符的含义
r
(read):读w
(write):写b
(binary):二进制文件t
(text):文本文件,默认情况,可省略不写a
(append):追加+
:读和写注意,某些字符可以组合!!!
注意事项:
如果文件打开失败,
fopen
函数将会带回一个出错信息。fopen
函数将带回一个空指针值NULL
。故常用if(fp == NULL)
判断操作是否成功。常用下面的方法打开一个文件:
if((fp = fopen("file1", "r")) == NULL){ printf("can't open this file\n"); exit(0);//终止正在执行的程序 }
用fclose函数关闭数据文件
关闭文件用
fclose
函数。fclose
函数调用的一般形式为:fclose(文件指针);
例如:
fclose(fp);
其中fp
是一个FILE
类的指针,指向一个已打开的文件。所谓**”关闭“是指撤销文件信息区和文件缓冲区,若成功则
fclose
函数返回0
,否则返回EOF(-1)
。如果不关闭文件将会丢失**数据。
向文件读写字符
读写一个字符的两个函数:fgetc和fputc
函数名 调用形式 功能 返回值 fgetc fgetc(fp);
从 fp
指向的文件读入一个字符读成功,带回所读的字符,失败则返回文件结束标志 EOF(即-1)
fputc fputc(ch, fp);
把字符 ch
写到文件指针变量fp
所指向的文件中写成功,返回值就是输出的字符;输出失败,则返回 EOF(即-1)
用fgetc函数读入
打开文本文件并读取一个字符
”r“模式打开文件时发生了什么
文件从硬盘载入内存,占据一片内存空间
文件指针(即
fp
)指代整个文件,始终指向文件头文件位置指针(即位置标记)暂时指向文件头
执行一次fgetc()后发生了什么
文件里文件位置标记当前位置的字符被读取
文件位置标记往后移动一个字节,下次读/写将从这个位置开始
用fgetc读取整个文本文件的内容
文件末尾的检测:feof函数
一般调用形式为
feof(fp);
- 其中
fp
必须是一个有效的文件指针,而且该文件必须已经成功打开。- 作用时在对文件读/写操作的过程中,判断文件是否已经读/写结束,即测试文件位置指针是否达到文件尾。
- 若达到文件尾或发生错误则返回true(非0),其他情况返回false(0)
使用fgetc函数的注意事项
- 在
fgetc()
函数中,读取的文件必须是以读或者读/写的方式打开的- 读取字符的结果也可以不向字符变量赋值,但在这种情况下,读出的字符将不能被保存,如
fget(pf);
- 文件内部在进行读/写操作时,文件位置指针是移动的。在打开文件时,该指针总是指向文件的第一个字节。在使用
fget()
函数后,该位置指针往后移动一个字节。因此,在对文件进行读/写操作时,可以使用fgetc()
连续多次读取字符。
用fputc函数写入
打开文本文件并写入一个字符
“w”模式打开文件时发生了什么
- 若原文件存在,内容清空
- 文件指针(即
fp
)指代整个文件,始终指向文件头- 文件位置指针(即位置标记)暂时指向文件头
使用fputc函数的注意事项
- 被写入的文件可以用写、读/写、追加方式打开
- 用写或读/写方式打开一个已存在的文件时将清除原有的文件内容,写入的字符从文件首开始。
- 如需保留原有文件内容,希望写入的字符以文件末开始存放,必须以追加方式打开文件。
- 被写入的文件若不存在,则创建该文件。
- 每写入一个字符,文件位置指针向后移动一个字节
fputc()
函数有一个返回值,如写入成功则返回写入的字符,否则返回EOF
,可用此来判断写入是否成功
例题1:复制文本文件
将一个文本文件中的信息复制到另一个文本文件中。现在要求将上例的num.txt文件中的内容复制到另一个文本文件num.copy中。
多行文本文件的换行
提示: ( 0 D ) 16 (0D)_{16} (0D)16和 ( 0 A ) 16 (0A)_{16} (0A)16分别是回车符和换行符的ASCII码
注意:windows和unix情况不同,unix下只有换行符
用fgetc读取多行文本文件内容
怎样向文件读写一个字符串
读写一个字符串的函数fgets和fputs
函数名 调用形式 功能 返回值 fgets fgets(str,n,fp)
从 fp
指向的文件读入长度为(n-1)的字符串,存放到字符数组str
中读成功,返回地址 str
,失败则返回NULL
fputs fputs(str,fp)
str
所指向的字符串写到文件指针变量fp
所指向的文件中写成功,返回0;否则返回非0值
关于fgets函数的说明
fgets
函数的函数原型为:char* fgets(char* str, int n, FILE* fp);
- 其作用是从文件读入一个字符串
- 调用时可以写成:
fgets(str, n, fp);
fgets(str, n, fp);
中n是要求得到的字符个数,但实际上只读(n-1)个字符,然后在最后加一个\0
字符,这样得到的字符串共有n个字符,把它们放到字符数组str
中- 如果在读完(n-1)个字符之前遇到按行符
\n
或文件结束标志EOF
,读入即结束,但将所遇到的换行符\n
也作为一个字符读入- 执行
fgets
成功,返回str
数组首地址,如果一开始就遇到文件尾或读数据出错,返回NULL
用fgets读取一行文本文件的内容
用fgets读取少于一整行的内容
用fgets读取文本文件全部内容
关于fputs函数的说明
fputs
函数的函数原型为:int fputs(char* str, FILE* fp);
str
指向字符串输出到fp
所指向的文件中- 调用时可以写成:
fputs("China", fp);
fputs
函数中第一个参数可以是字符串常量、字符数组名或字符型指针- 字符串末尾的
\0
不输出- 输出成功,函数值为0;失败,函数值EOF
写入一个字符再写入一个字符串
用格式化的方式读写文本文件
用fprintf和fscanf函数读写文件
一般调用方式为:
fprintf(文件指针,格式字符串,输出表列);
fscanf(文件指针,格式字符串,输出表列);
如:
fprintf(fp,"Today is %d-%d-%d\n", 2024, 11, 24); fcanf(fp, "%d", &i);
优缺点
- 优:与
printf
和scanf
相仿,使用方便,容易理解- 缺:大部分情况下,输入时要将文件中的ASCII码字符转化为二进制形式保存在内存,输出时要将内存中的二进制形式转化成字符,要花费较多时间
用fscanf读取文本文件内容
把各种类型数据写入文本文件
用二进制方式读写文件
读写一组数据块的函数fread和fwrite
int fread(void* buffer, int size, int count, FILE* pfile);
int fwrite(void* buffer, int size, int count, FILE* pfile);
- 参数
buffer
是一个指针,用来指向一个数据内存区域,表示“读入/输出”数据在内存中的起始地址- 参数
size
表示要进行读/写的每个数据项的字节数- 参数
count
表示将要读/写多少个size
字节的数据项- 参数
pfile
时FILE类型指针,如果文本以二进制形式打开,那么用fread()
和fwrite()
可以读/写任何类型的信息,无论是数组还是结构体
fread函数的调用说明
一般调用形式为:
n = fread(buffer, size, count, fp);
- 执行
fread()
函数时,文件位置指针会随着数据的读取而向后移动,最后移动结束的位置等于实际读出的字节数。- 该函数执行结束后,将返回实际读出的数据元素个数,这个“个数”不一定等于设置的
count
,因为文件中没有足够的数据元素项或读取过程中出错,都会导致返回的“个数”小于count
。
fwrite函数的调用说明
一般调用形式为:
n = fwrite(buffer, size, count, fp);
fwrite()
函数从buffer
指针指向的内存区中取出count
个长度为size
字节的数据元素项,写入到指针pfile
所指的文件中。- 执行该操作后,文件位置指针将向后移动,移动的字节数等于写入文件的字节数目。
- 该函数操作完成后,返回成功写入的数据元素个数。
创建二进制文件并写入一个数组
从二进制文件读取内容存入数组
小结一下
四个顺序读取文件的函数
fgetc
:从指定文本文件读一个字符fgets
:从指定文本文件读一个字符串fscanf
:按格式读取文本文件,可以读取整数、浮点数、字符串等fread
:从二进制文件读出指定长度的二进制内容
四个顺序写文件的函数
fputc
:从指定文本文件写一个字符fputs
:从指定文本文件写一个字符串fprintf
:按格式写入文本文件,可以写入整数、浮点数、字符串等fwrite
:从二进制文件写入指定长度的二进制内容
C语言中的流
从C程序的角度来看,作为输入输出的各种文件或设备都是统一以逻辑数据流的方式出现的。
什么是“流”
- “流”(stream)是一种逻辑上的概念。
- C语言中,任何输入/输出的数据都视为流。
- 输入输出是数据传送的过程,数据如流水一样从一处流向另一处,因此常将输入输出形象地称为流,即数据流。流表示了信息从源到目的端的流动。
流是程序与运行环境之间的通道
- 操作系统为程序提供运行环境,无论是用Word打开或保存文件,还是C程序中的输入输出都是通过操作系统进行的。
- 输入操作时,数据从文件流向计算机内存;输出操作时,数据从计算机内存流向文件。
- “流”是一个传输通道,数据可以从运行环境流入程序中,或从程序流至运行环境。
流式文件与标准输入输出
C语言中,数据文件的特点:
- 由一连串的字符(或字节)组成,而不考虑行的界限
- 两行数据间不会自动加分隔符,对文件的存取是以字符(字节)为单位的,而不考虑记录的界限
- 输入输出数据流的开始和结束仅受程序控制而不受物理符号(如回车换行符)控制
这种文件称为流式文件
三个无需打开的特殊流式文件:
程序中可以使用3个标准的流文件:“标准输入流”、“标准输出流”、“标准出错输出流”。
- 系统已对这3个文件指定了与终端的对应关系
- 标准输入流(stdin)是从终端的输入
- 标准输出流(stdout)是向终端的输出
- 标准出错输出流(stderr)是当程序出错时将出错信息发送到终端
- 程序开始运行时系统自动打开这3个标准流文件。因此,程序编写者不需要在程序中用
fopen
函数打开它们
结构体声明揭示FILE真面目
一大波僵尸(例题)正在接近……
例题2:向文件写字符串
从键盘读入若干个字符串,对它们按字母大小的顺序排序,然后把排好序的字符串送到磁盘文件中保存。
思路:
- 从键盘读入n个字符串,存放在一个二维字符数组中,每一个一维数组存放一个字符串;
- 对字符数组中的n个字符串按字母顺序排序,排好序的字符串仍存放在字符数组中;
- 将字符数组中的字符串顺序输出。
例题3:从文件读字符串
从例题2文件string.dat读回字符串,并在屏幕上显示
使用fgets的注意事项
调用时可以写成:
fgets(str, n, fp);
fgets()
函数中的第一个参数可以是静态定义的字符数组,也可以是动态分配的字符数组。作为参数,数组不用写数组名称后面的中括号以及其中的数组长度。
- 例题中的
fgets(str[i], 10, fp)
是因为str
是一个二维数组!- 第二个参数是“字符串实际长度 + 1”,因为还有一个
\0
。规定的长度应该与第一个参数字符数组的长度相等,若过大,运行程序会有溢出的错误。fgets()
和gets()
不同,当读到\n
不停止,会把\n
作为一个字符读入。
例题4:写入学生数据文件
从键盘输入10个学生的有关数据,然后把它们转存到磁盘文件上去。
思路:
- 定义有10个元素的结构体数组,用来存放10个学生的数据
- 从
main
函数输入10个学生的数据- 用
save
函数实现向磁盘输出学生的数据- 用
fwrite
函数一次输出一个学生的数据
例题5:读取学生数据文件
为验证在磁盘文件“stu.dat”中是否已存在此数据,可以用下面的程序从“stu.dat”文件中读入数据,然后在屏幕上输出。
例题6:复制学生数据文件
从已有的二进制文件“stu.list”中,读入数据并输出到“stu.dat”文件中。
思路:
- 编写
lode
函数与save
函数main
函数中先调用load
函数,再调用save
函数
文本文件与二进制文件
文本文件与二进制
- 首先,应明确所有文件本质上都是二进制序列,只是文本文件的每个字节分别代表一个字符(ASCII码),所以是个字符流。
- 程序要使用文本文件中存储的数据计算,就需要把字符流转成二进制流,比如要把字符串“100”转换成整数100。而存成文本文件又需要逆过来再转一次。
二进制文件优缺点
- 优点:节约不必要的转换时间
- 不足:
- 一台计算机或一个操作系统上产生的二进制文件,另一台计算机或操作系统可能无法识别(因为不像ASCII码一样有统一的格式)
- 人们无法像在文本处理软件中检查和修改文本文件那样来处理二进制文件,因为无法直接理解
创建二进制文件并写入一个整数
读写二进制文件数据类型应一致
创建二进制文件并写入一个数组
读写类型不一致的后果
通常以写入格式读取二进制文件
创建一个二进制文件并写入一个结构体
回顾fopen不同字符代表的含义
r
(read):读w
(write):写b
(binary):二进制文件t
(text):文本文件,默认情况,可省略不写a
(append):追加+
:读和写
关于fopen(“f1”,”r”)的说明
用
“r”
方式打开的文件只能用于向计算机输入而不能用作向该文件输出数据,而且该文件应该已经存在,并存有数据,这样程序才能从文件中读取数据。
- 不能用
“r”
方式打开一个并不存在的文件,否则会出错
关于fopen(“f1”,”w”)的说明
用
“w”
方式打开的文件只能用于向该文件写数据(即输出文件),而不能用来向计算机输入。
- 如果原来不存在该文件,则在打开文件前新建立一个以指定的名字命名的文件。
- 如果原来已存在一个以该文件名命名的文件,则在打开文件前先将该文件删去,然后重新建立一个新文件。
关于fopen(“f1”,”a”)的说明
如果希望向文件末尾添加新的数据(不希望删除原有数据),则应用
“a”
方式打开。
- 但此时应保证该文件已存在,否则将得到错误信息
- 打开文件时,文件读写标记移到文件末尾
fopen中其他打开方式说明
用
r+
、w+
、a+
方式打开的文件既可以用来输入数据,也可以用来输出数据。
- 用
r+
方式时该文件应该已存在- 用
w+
方式则新建立一个文件,先向此文件写数据,然后可以读此文件中的数据- 用
a+
方式打开的文件,原来的文件不被删去,文件读写位置标记移到文件末尾,可以添加,也可以读
用“r+”模式打开文件有什么不同
随机读写
- 可以根据读写的需要,人为地移动了文件标记的位置。文件标记可以向前移、向后移,移到文件头或文件尾,然后对该位置进行读写——随机读写
- 随机读写可以在任何位置写入数据,在任何位置读取数据
用rewind移动文件位置标记
可以强制使文件位置标记指向文件头的位置
- 可以用
rewind
函数实现,函数原型:void rewind(FILE* pfile);
rewind
函数的作用是使文件标记重新返回文件的开头,此函数没有返回值- 使用后如果继续向文件中输入数据,将会覆盖原来文件开头的内容
强行把文件位置标记移动到文件头
例题7:显示然后复制文件
有一个磁盘文件,内有一些信息。要求第一次将它的内容显示在屏幕上,第二次把它复制到另一文件上。
思路:
- 因为在第一次读入完文件内容后,文件标记已指向文件的末尾,如果再接着读数据,就遇到文件结束标志,
feof
函数的值等于1(真),无法再读数据- 必须在程序中用
rewind
函数使位置指针返回文件的开头
补充说明:getc实现字符读入
函数原型:
int getc(FILE* pfile);
getc
的使用形式为:ch = getc(fp);
表示从
pf
指针所指向的文件中读取一个字符,如果操作成功返回该字符,如果文件结束或出错则返回EOF
实际上
putc
并不是一个函数……而是一个宏
用fseek移动文件位置标记
可以强制使文件标记指向指定的位置
可以用
fseek
函数实现,函数原型:int fseek(FILE* pfile, long offest, int base);
- 其中,
pfile
是一个文件类型指针;offset
是位移量,表示移动的字节数;base
是起始点,表示从何处开始计算位移量。
fseek函数的起始点参数
C语言规定起始点有三种:
0
代表“文件开始位置”;1
代表“当前位置”;2
代表“文件末尾位置”。C标准指定的名字如下表
起始点 名字 用数字代表 文件开始位置 SEEK_SET 0 文件当前位置 SEEK_CUR 1 文件末尾位置 SEEK_END 2
fseek函数的补充说明
位移量指以起始点位基点,向前移动的字节数。位移量应是
long
型数据(在数字末尾加一个字母L)。
fseek
函数一般用于二进制文件。因为文本文件需要字符转换,fseek
计算的位置往往会出错。下面是
fseek
函数调用的几个例子fseek(fp, 100L, 0);//从文件头向后移100字节 fseek(fp, 50L, 1);//从当前位置向后移50字节 fseek(fp, -10L, 2);//从文件尾向前移10字节
把文件位置标记移到指定位置
用fseek和ftell计算文件总长度
文本文件慎用fseek和ftell
因为:有些操作系统中文本文件行末既有回车符又有换行符,另一些操作系统中则只有换行符
“a+”模式的特别之处
”a“与”a+‘’模式注意事项
- 当使用
“a”
或“a+”
模式打开文件时,所有的写入操作都在文件结尾进行,可以使用fseek
和rewind
对文件指针进行重定位,但是写入操作会将文件指针重新移至文件结尾,因此,已经存在的数据不会被覆盖。- 当使用
“a+”
模式打开文件时,文件既可以读也可以写,如有需要,可以使用fseek
或者是rewind
来改变当前位置,但文件位置指针移动仅对读操作有效