文件和文件的输入与输出
什么是文件
文件有不同的类型,在程序设计中,主要用到两种文件:
程序文件:包括源程序文件(后缀为 .c),目标文件(后缀为 .o)以及可执行文件等。这种文件里的内容是程序代码。
数据文件:文件的内容不是程序,而是供程序运行时读写的数据,包括在程序运行过程中输出到磁盘的数据和在程序运行过程中供读入的数据,如全班学生的成绩数据、货物交易的数据等。
我们本节讨论的是数据文件。为了简化用户对输入输出设备的理解,用户不必区分各种输入输出设备之间的区别,操作系统把各种设备都统一作为文件来处理。例如终端键盘是输入文件,显示屏和打印机是输出文件。
文件的分类
根据数据的组织形式,数据文件可分为 ASCII 文件和二进制文件。数据在内存中都是以二进制形式存储的,如果不加转换地输出到外存,就是二进制文件,可以认为就是内存数据的映像,称为映像文件。如果要求在外存中以 ASCII 码形式存储,则需要在存储前进行转换。ASCII 文件又称为文本文件,每一个字节放一个字符的 ASCII 码。
用 ASCII 码形式输出时字节与字符一一对应,一个字节代表一个字符,因而便于对字符进行逐个处理,也便于输出字符。但一般占存储空间较多,而且要花费转换时间。用二进制形式输出数值,可以节省外存空间和转换时间,把内存中的存储单元中的内容原封不动地输出到外存磁盘上,此时一个字节并不一定代表一个字符。
打开和关闭文件
对文件读写之前应该“打开”该文件,使用结束之后“关闭”文件。实际上,所谓的打开文件是指为文件建立相应的信息区(用来存放有关文件的信息)和文件缓冲区(用来暂时存放输入输出的数据)。
fopen() 函数打开数据文件
fopen() 函数的调用方式为 **fopen(文件名,使用文件的方式);**。
例如:
fopen("a1","r");
表示要打开名字为“ a1 ”的文件,使用文件的方式为“读入”(r 代表 read,即读入)。fopen 函数的返回值是指向 a1 文件的指针(即 a1 文件信息区的起始地址)。通常将 fopen 函数的返回值赋给一个指向文件的指针变量。如:
FILE *fp;
fp = fopen("a1","r");
这样 fp 就和文件 a1 相联系了,或者说 fp 指向了 a1 文件。
fclose() 函数关闭数据文件
在使用完一个文件后应该关闭它,以防止它被误用。“关闭”就是撤销文件信息区和文件缓冲区,使文件指针变量不再指向该文件。
fclose() 函数的调用方式:**fclose(文件指针);**。
例如:
fclose(fp);
如果不关闭文件将会丢失数据,应当养成在程序终止之前关闭所有文件的习惯。
顺序读写数据文件
文件打开之后,就可以对它进行读写了。在顺序写时,先写入的数据存放在文件中前面的位置,后写入的数据存放在文件中后面的位置。在顺序读时,读数据的顺序和数据在文件中的物理顺序是一致的。顺序读写需要用库函数实现。
对文本文件读入或输出一个字符的函数见表:
函数名 | 调用形式 | 功能 | 返回值 |
fgetc | fgetc(fp) | 从 fp 指向的文件读入一个字符 | 成功,带回所读的字符;失败则返回文件结束标志 EOF(即 -1) |
fputc | fputc(ch,fp) | 把字符 ch 写到文件指针变量 fp 所指向的文件中 | 输出成功,返回值就是输出的字符;输出失败,则返回 EOF(即 -1) |
说明
fgetc 的第一个字母 f 代表文件(file),中间的 get 表示获取,最后一个 c 表示字符 char。fputc 也类似。
从键盘输入一些字符,逐个把它们送到磁盘上去,直到用户输入一个“ # ”为止。
解题思路
用 fgetc 函数从键盘逐个输入字符,然后用 fputc 函数写到磁盘文件即可。
创建 12-1.c 文件并输入以下代码:
#include<stdio.h>
#include<stdlib.h> // stdlib 头文件即 standard library 标准库头文件
int main(){
FILE * fp;
char ch,filename[10];
printf("Please enter the file name:");
scanf("%s",filename);
// 如果文件不存在,fopen 会建立该文件
if((fp=fopen(filename,"w"))==NULL){ // 打开输出文件并使 fp 指向此文件
printf("Unable to open this file\n"); // 如果打开出错,就输出“打不开”的信息
exit(0); // 终止程序
}
ch=getchar(); // 用来接收最后输入的回车符
printf("Please enter a string in the disk(Ends with a #):");
ch=getchar(); // 接收从键盘输入的第一个字符
while(ch!='#'){ // 当输入 # 时结束循环
fputc(ch,fp);
putchar(ch);
ch=getchar();
}
fclose(fp);
putchar(10);
return 0;
}
输入以下命令编译并运行:
gcc -o 12-1 12-1.c
./12-1
输入以下命令查看文件 file_name 的内容:
cat /home/project/file_name
程序运行结果如下:
程序分析:
- exit 存在于标准 C 的库函数中,作用是使程序终止,用此函数时在程序的开头应包含 stdlib.h 头文件。
- 执行过程如下:先从键盘读入一个字符,检查它是否是“ # ”,如果是,表示字符串结束,不执行循环体。如果不是,则执行循环体,将该字符输出到 file.date 。然后在屏幕上显示出该字符,接着再从键盘读入一个字符。如此反复,直到出现“ # ”字符为止。这时程序已经将“ hello shiyanlou ”写到以“ file.date ”命名的文件中。
随机读写数据文件
对文件进行顺序读写比较容易理解,也容易操作,但是效率不高。比如文件中存放了一个城市几百万人的资料,我们想要查找某一个人,按照顺序读写需要从第一个数据逐个读入,等待的时间是不能忍受的。
为了解决这个问题,可移动文件内部的位置指针到需要读写的位置,再进行读写,这种读写被称为随机读写。随机访问不是按数据在文件中的物理位置次序进行读写,而是可以对任何位置上的数据进行访问,显然这种方法比顺序访问的效率高得多。
实现随机读写的关键是要按要求移动位置指针,也就是文件的定位。
移动文件内部的位置指针的函数主要有两个,即 rewind() 和 fseek()。
rewind() 函数的调用形式为:**rewind(文件指针);**,它的功能是把文件内部的位置指针移到文件开头。
下面主要介绍 fseek 函数。fseek 函数用来移动文件内部的位置指针,其调用形式为:**fseek(文件指针,位移量,起始点);**。
其中:“文件指针”指向被移动的文件;“位移量”表示移动的字节数,要求位移量是 long 型数据,以便在文件长度大于 64KB 时不会出错,当用常量表示位移量时,要求加后缀“ L ”;“起始点”表示从何处开始计算位移量,规定的起始点有三种:文件首,当前位置和文件尾。
其表示方法如下表:
起始点 | 表示符号 | 数字表示 |
文件首 | SEEK_SET | 0 |
当前位置 | SEEK_CUR | 1 |
文件末尾 | SEEK_END | 2 |
例如:
fseek(fp,100L,0);
其意义是把位置指针移到离文件首 100 个字节处。
还要说明的是 fseek 函数一般用于二进制文件。在文本文件中由于要进行转换,故往往计算的位置会出现错误。
作业
复制文件内容
介绍
实现一个 C 语言程序,可以支持最简单的复制功能。
知识点
- Linux 下 C 语言程序编写
- C 语言基础
- C 程序输入与输出处理
- C 语言文件操作
例如复制一份 /etc/protocols 文件到 /tmp 目录,则程序编译后执行的效果如下所示:
# 编译
$ gcc copy.c -o copy
# 运行
$ ./copy /etc/protocols /tmp/protocols
注意拷贝的文件和目标文件要做为编译后的可执行文件的参数传入,而不是 scanf()
读取,如果使用 scanf()
读取则会导致测试系统等待超时。
程序参数处理的片段如下:
#include <stdlib.h>
#include <stdio.h>
int main(int argc, char *argv[]){
//...
}
目标
- 在右边的实验楼环境 WebIDE 中新建一个文档,命名为 copy.c;
- 将程序片段复制到文档 copy.c 中,并补充文档;
- 使用一个文件完成,不要使用单独的头文件;
- copy.c 可以在环境中编译运行,编译后生成的可执行文件接受两个参数,分别为拷贝的来源和目标文件路径;
- 需要处理两种错误:拷贝的来源文件不存在,或拷贝的目标文件已经存在。出现错误的时候需要打印一个带有 error 的输出信息。
参考答案
#include<stdio.h>
#include<stdlib.h>
int main(int argc,char **argv){ //tips: main 函数要能在命令行里接收参数
if(argc<3)
return 1;
// 获取文件名
char* file1 = argv[1];
char* file2 = argv[2];
FILE *fp1,*fp2;
char ch;
// 读取源文件
if((fp1=fopen(file1,"r"))==NULL)
printf("File isn't exist!\n");
// 创建目的文件
if((fp2=fopen(file2,"w"))==NULL)
printf("File can't be created!\n");
// 读取并写入文件
while((ch=fgetc(fp1))!=EOF)
fputc(ch,fp2);
fclose(fp1);
fclose(fp2);
return 0;
}
copy