一、文件概述
1.1 文件概念
文件通常是在磁盘或固态硬盘上一段已命名的存储区。C 把文件看作是一系列连续的字节,每个字节都能单独读取。C提供两种文件模式:文本模式和二进制模式。
1.2 文本模式和二进制模式
1.2.1 文本内容和二进制内容
所有文件的内容都是以二进制形式(0 或 1)存储。但是,如果文件最初使用二进制编码的字符(例如,ASCLL 或 Unicode)表示文本,该文件就是文本文件,其中包含文本内容;如果文件中的二进制代表机器语言代码或数值数据或图片或音乐编码,该文件就是二进制文件,其中包含二进制内容。
1.2.2 文本模式和二进制模式
为了规范文本文件的处理,C 语言提供两种访问文件的突进:二进制模式和文本模式。在二进制模式中,程序可以访问文件的每个字节;而在文本模式中,程序所见的内容和文件的实际内容并不相同。
程序以文本模式读写文件时,把本地环境表示的行末尾或文件结尾映射成 C 模式。例如,C 程序在旧式的 Maciontosh 中以文本模式读取文件时,把文件中的 \r 映射成 \n;以文本模式写入数据时,把 \n 转换成 \r。
程序以二进制模式读写文件时,不会程序会看到文件中的 \r 和 \n ,不会发生映射。
虽然 C 提供了二进制模式和文本模式,但是这两种模式的实现可以相同,例如 UNIX 和 Linux 实现这两种模式的方法完全相同。
1.3 I/O 级别
除了选择文件的模式,还可以选择 I/O 的两个级别:底层 I/O 和 标准高级 I/O。底层 I/O 是操作系统提供的基本I/O 服务;标准高级 I/O 使用 C 库的标准包和 stdio.h 头文件定义。
二、标准 I/O
2.1 标准文件
C 程序会默认打开 3 个文件:标准输入,标准输出和标准错误输出。
在默认情况下,标准输入是系统的普通输入设备,通常是键盘;标准输出通常是系统的普通输出设备,通常是显示屏。
例:
/*本例实在 linux 环境中运行的,open 为系统调用,头文件是 open 函数所需要的文件。*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(void)
{
int fd;
//获得文件描述符
fd = open("./array1.c", O_RDONLY);
printf("fd is: %d\n", fd);
return 0;
}
/*运行结果
fd is: 3
*/
分析结果:fd = 3,说明在 0,1,2 已经有文件被打开了,就是上述的标准输入、标准输出、标准错误输出。
2.2 exit() 函数
exit() 函数关闭所有打开的文件并结束程序,它的参数传递给一些操作系统,以供其他程序使用,如同在UNIX中会告诉父进程它是怎么没的。正常结束的程序传递 0,异常结束的程序传递非 0 值,不同的退出值对应不同的原因。
ANSI C 规定,在最初调用的 main() 中使用 return 与调用 exit() 的效果相同。
区别:
Ⅰ.如果在 main() 在一个递归程序中,exit() 会直接终止程序,而 return 会返回上一级递归。
Ⅱ. 在其它函数中,exit() 也可以终止整个程序。
2.3 标准 I/O 函数
2.3.1 fopen()
程序成功打开文件后,fopen() 返回文件指针。文件指针的类型是指向 FILE 的指针。
2.3.2 getc() 和 putc()
getc() 和 putc() 与 getchar() 和 putchar() 函数类似,不同的是,getc() 和 putc() 可以指定文件,而 getchar() 和 putchar() 的文件只能是 stdin 和 stdout。
例:
#include <stdio.h>
int main(void)
{
FILE *rfp; //读文件的指针
FILE *wfp; //写文件的指针
char ch;
rfp = fopen("./read", "r");
wfp = fopen("./write","w");
while((ch = getc(rfp)) != EOF)
{
putc(ch, wfp); //向 write.c 中写入
putc(ch, stdout); //向屏幕输出
}
//保存文件
if(fclose(wfp) != 0)
printf("Error in closing file write\n");
return 0;
}
2.3.3 fclose()
flcose() 函数关闭 fp 指定的文件,必要时刷新缓冲区。如果成功关闭, fclose() 返回 0,否则返回 EOF。
2.3.5 fscanf() 和 fprintf() (注:这个不是标准 I/O,是文件 I/O)
这两个函数类似于 scanf() 和 printf() 。
例:
/*fprintf() demo*/
#include <stdio.h>
int main(void)
{
FILE *file;
double d = 0.3333;
char *a = "aldskjf";
file = fopen("fprintf", "w"); //打开文件
fprintf(file, "%.2f\n%s\n", d, a); //写入 file 文件中
fprintf(stdout, "%.2f\n%s\n", d, a); //写入标准输出中
return 0;
}
/*fscanf() demo*/
#include <stdio.h>
int main(void)
{
FILE *file;
char words[50];
file = fopen("fprintf", "r");
while(fscanf(file, "%s", words) == 1) //从 file 文件中读取内容输出到屏幕
puts(words);
return 0;
}
2.3.6 fread() 和 fwrite()
前面提到的两个 I/O 函数都是面向文本的,用于处理字符和字符串。在保存数值时使用 % 来进行转化,但把数值转化成字符数据可能会改变值,这种改变是不可逆的。为保证数值在存储前后一致,最精确的做法是使用与计算机相同的位组合来存储。这个过程不存在从数字形式到字符串的转换过程。这种以程序所用的表示法把数据存储在文件的方法称为以二进制形式处理数据。
例:
/*fwrite() demo*/
#include <stdio.h>
#define SIZE 7
int main(void)
{
FILE *arrayfile;
//定义一个 double 数组
double array[SIZE] = {4, 5, 1, 1, 4, 6, 7};
arrayfile = fopen("array", "wb"); //以二进制的形式打开文件
fwrite(array, sizeof(double), 7, arrayfile); //把数组中内容写到 books 文件中
fclose(arrayfile);
return 0;
}
/*结果:
存在文件中的数据(二进制编码):
^@^@^@^@^@^@^P@^@^@^@^@^@^@^T@^@^@^@^@^@^@ð?^@^@^@^@^@^@ð?^@^@^@^@^@^@^P@^@^@^@^@^@^@^X@^@^@^@^@^@^@^\@
*/
/*fread() demo*/
#include <stdio.h>
#define SIZE 7
int main(void)
{
int i;
FILE *arrayfile; //文件指针
double array[SIZE];
arrayfile = fopen("array", "rb"); //以二进制的形式打开 array
fread(array, sizeof(double), SIZE, arrayfile); //把文件中的内容读到结构数组中
//输出
printf("array: \n");
for(i = 0; i < SIZE; i++)
printf("%.2f ", array[i]);
printf("\n");
return 0;
}
/*运行结果
array:
4.00 5.00 1.00 1.00 4.00 6.00 7.00
*/
2.3.7 缓冲区
标准 I/O 的第 1 步是调用 fopen() 函数。fopen() 不仅打开一个文件,还创建了一个缓冲区(在读写模式下创建两个缓冲区)以及要给包含文件和缓冲数据的结构。
文件输入。使用标准 I/O 的第 2 步是调用一个定义在 stdio.h 中的输入函数,如 fscanf() 、getc() 。一调用这些函数,文件中的缓冲大小数据块就被拷贝到缓冲区中。
文件输出。类似于文件输入的方式把数据写入缓冲区。当缓冲区被填满时,数据将被拷贝到文件中。
标准 I/O 把读写文件大幅度简化了,不用在程序中体现缓冲区,只需要直接写就可以了。
例:
/*Linux 的底层级别读写文件 */
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char * argv[])
{
int rfd; //读文件的 fd
int wfd; //写文件的 fd
char buf[512];
ssize_t n;
printf("%s\n%s\n", argv[1], argv[2]);
//打卡文件
rfd = open(argv[1], O_RDONLY); //读模式
wfd = open(argv[2], O_WRONLY|O_CREAT); //写模式,没有就创建一个
while((n = read(rfd, buf, sizeof(buf))) > 0) //读文件,linux 如果没有读到末尾,n>0
{
printf("%zd\n", n); //输出读了多少内容
write(wfd, buf, n); //写文件
}
close(rfd);
if(close(wfd) == 0) //保存文件,linux 中如果成功保存 close 返回 0
printf("write successfully\n");
return 0;
}
可以看出,使用底层级别 I/O 进行读写文件步骤较多,相比较,2.3.2 的程序简单很多。
参考书籍
C Primer Plus (第六版)中文版