一、 输入输出的基本概念
数据在外部设备和内存之间的传递,我们称之为输入输出。
二、文件概念
在C语言中,“文件”的概念具有广泛的意义,它把和主机进行数据交换的输入输出设备都看作一个文件。即把实际的物理设备抽象为逻辑文件,它们也被称为设备文件。
如:
- 键盘作为标准输入文件,文件名
stdin
- 显示器作为标准输出设备,文件名
stdout
,标准错误输出文件stderr
- 打印机也可以作为输出文件,文件名
PRN
注:数值12345在ASCII文件中和二进制文件中的存放形式不同,整数 12345
i. 若存于文本文件中,占 5 个字节。
即存放 5 个 ASCII 字符(看成字符串),
本质上存储的是二进制值。
ii. 若存于二进制文件中,占 4 个字节(即内存映象)。
因为一个整数在内存中占 4 个字节,
本质上存储的仍是二进制值。
三、 缓冲
引入缓冲的目的是解决CPU的运行速度和外部设备操作速度不匹配的矛盾。提高计算机系统运行速度。以输出操作为例,在内存中开辟一个输出缓冲区,程序中的每次输出,不是立刻直接驱动并输出到外部设备上的,而是首先输出到缓冲区中,当缓冲区被写满或缓冲区不满但程序干涉时,才将缓冲区中的数据成批送往外部设备,这样可以减少对外部设备的驱动次数,提高输出速度。
四、文件的读写过程
(一) 预备知识
- 无论是文本文件还是二进制文件,文件都可以看成数据流,数据流由若干字节构成,以文件结束符EOF结尾。
- 在程序中要读写一个文件时,必须要通过指向文件的指针进行。
- 需知道待读写文件的文件名、文件类型、文件的操作方式(即读写方式)以及文件的读写位置指针等信息。
- 信息一般存放在一个文件信息区中,文件信息区的本质是一个结构体变量,其各个成员存放上述信息。
- 在缓冲文件系统中,对每个正在使用的文件,都要使用一个FILE类型的结构变量,该结构变量用于存放文件的有关信息,如:文件名、文件状态等。
- 在C语言中,无论是一般磁盘文件还是设备文件,都要通过文件结构的相应成员数据进行输入输出处理。
- 文件结构不需要用户定义,由系统事先定义好。
- 文件结构已由系统定义,在程序中若想读写一个文件,则首先必须为该文件建立一个信息区,用一个指针指向该信息区。
- 定义一个FILE类型(文件型)文件信息区指针(简称文件指针):
FILE * 文件型指针变量名;
例如: FILE *fp;
fp是一个指针变量,指向文件结构。如要同时使用多个文件时,必须有多个不同的文件指针。程序运行过程中,文件指针是指定待读写文件的唯一标识。
(二) 读写过程
FILE *file;
file=fopen(" "," ");
(其中第一个双引号里是文件地址,文件地址必须用 \ \ 隔开,不然会出错,第二个是打开类型)
打开类型汇总:
r :以只读方式打开文件,该文件必须存在。
r+ :以可读写方式
打开文件,该文件必须存在。
w :打开只写文件,若文件存在则文件长度清为0,即该文件内容会消失。若文件不存在则建立该文件。
w+: 打开可读写文件,若文件存在则文件长度清为零,即该文件内容会消失。若文件不存在则建立该文件。
a :以附加的方式打开只写文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留。(EOF符保留)
a+: 以附加方式打开可读写的文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾后,即文件原先的内容会被保留。 (原来的EOF符不保留)
wb :只写打开或新建一个二进制文件;只允许写数据
wb+ :读写打开或建立一个二进制文件,允许读和写。
ab+ :读写打开一个二进制文件,允许读或在文件末追加数据。
rb+ :读写打开一个二进制文件,允许读写数据,文件必须存在。
一张好图:
注意:
1.当程序开始运行时,系统自动打开三个标准文件:
标准输入(文件指针stdin )
标准输出(文件指针stdout )
标准出错输出(文件指针stderr )
2.如果程序从stdin输入数据,则从键盘输入数据。
3.如果程序向stdout写数据,则写到屏幕上。
(三) 文件的打开与关闭
1.打开文件
——建立“文件信息区”,返回一个文件指针。
即通过指针建立用户程序与文件的联系,为文件开辟缓冲区。
FILE *fp;
fp=fopen(文件名,文件的使用方式);
文件名——字符串,可带路径
文件的使用方式——字符串
例:
fp = fopen("data.dat ","r");
fp= fopen("d:\\c\\data.dat ","r");
//以读方式打开正文文件 data.dat, 它事前应存在。若打开不成功,则 fopen( ) 函数返回空指针 NULL 。
一般以如下方式处理
#include <stdio.h>
#include<stdlib.h>
int main()
{
//一般如下处理
FILE*fp;
fp=fopen("d:\\data.dat","r");//以只读方式打开数据文件
if (fp==NULL)
{
printf("打不开");
exit(1);//exit是系统函数,表示终止程序的运行
}}
2.关闭文件
对文件读写完毕,要关闭该文件,释放信息区。
调用格式 fclose(fp); /* fp是文件指针 */
即切断文件和程序的联系,将文件缓冲区的内容写入磁盘,并释放文件指针。
注意:若文件关闭成功,则 fclose( ) 函数返回返回值为 0 。
若文件关闭失败,则返回非0值。
综上标准操作如下:
#include <stdio.h>
#include<errno.h>
#include<string.h>
int main()
{
FILE*p=fopen("Contact.txt","r"); //打开文件输入文件地址,和打开方式
if(p==NULL) //如果传入空指针则会报错
{
printf("%s", strerror(errno));//printf("%s", strerror(errno)) 用于输出当前的错误描述。
//errno 是一个全局变量,它保存了最近一次发生的错误代码。strerror(errno) 将根据这个错误代码返回相应的错误描述字符串。
}
fclose(p); //关闭文件
p=NULL; //防止变为野指针
return 0;
}
五、 文件的读写
1. 文件顺序操作
对文件的操作(文件读写)必须按文件中字符的先后顺序进行,只能在操作了第i个字符后,才能操作第i+1个字符。
在对文件操作时,文件的位置指针由系统自动向后(文件尾方向)移动。
2. 文件读写函数
-
读写一个字符:fgetc函数和fputc函数。
-
读写一行字符串:fgets函数和fputs函数。
-
格式化读写:fscanf函数和fprintf函数。
-
“块”读写:fread函数和fwrite函数。
-
“字”(整数)读写:getw函数和putw函数。
-
ungetc函数用于退回一个字符到输入缓冲区中。
-
feof函数用于在文件的读写过程中,检测是否到达文件结束
文件I/O 函数 | 标准终端I/O函数 | 作用 |
---|---|---|
fgetc | getchar | 输入一个字符 |
fputc | putchar | 输出一个字符 |
fgets | gets | 输入一行字符串 |
fputs | puts | 输出一行字符串 |
fscanf | scanf | 格式化输入函数 |
fprintf | printf | 格式化输出函数 |
(一) fgetc函数和fputc函数
用于读写一个字符,它们的函数原型是:
int fgetc(FILE *fp) ;
/* 功能:从fp指定的文件中读入一个字符 */
int fputc(int c, FILE *fp) ;
/* 功能:将字符c写入fp指定的文件中 */
(二)fgets函数和fputs函数
·fgets
原型为:
char * fgets(char *s, int n, FILE *fp) ;
/* 功能:从fp 指定的文件中读入一行字符串,
最多读取n-1个字符,并把它们存放到字符数组s中,若在读入n-1个字符之前遇到换行符 ‘\n’ 或文件结束符EOF,则结束读入。
·fputs
原型为:
int fputs(const char *s, FILE *fp) ;
/* 功能:将字符串s 写到fp指定的文件中 */
注意:
~fgets与gets的区别:
fgets将’\n’作为字符读入到字符串中,
而gets不把’\n’读入到字符串中。
~fputs与puts的区别:
fputs原样输出字符串,不增加输出’\n’;
而puts将字符串输出后,增加输出’\n’。
(三) fscanf函数和fprintf函数
fscanf 函数用于文件格式化的输入,原型为:
int fscanf(FILE *fp, const char *format, 输入量地址列表) ;
fprintf函数用于文件格式化输出,原型为:
int fprintf(FILE *fp, const char *format, 输出量列表) ;
参数fp
是文件指针,参数format
是格式控制字符串。
与标准格式化输入输出函数scanf
和printf
的原型比较,多了第一个参数——文件指针,用于指定输入输出的数据文件。
其他的使用方式和 scanf
和 printf
没有区别。
scanf(“%d”,&x) 等价于 fscanf(stdin, “%d”, &x)
(四) feof函数
函数原型:
int feof(FILE *fp) ;
/* 功能:判断fp指定的文件是否到达了文件结尾处 */
/* 如果到达,则函数返回 “真”;否则返回“假” */
feof函数常用于循环读取文件内容的场景中,通过检查文件流的结束标志来确定是否已到达文件末尾。
例:以下是一个使用feof函数的示例代码,用于逐行读取文件内容并输出到控制台
#include <stdio.h>
int main() {
FILE *fp;
char line[100];
fp = fopen("file.txt", "r");
if (fp == NULL) {
printf("无法打开文件\n");
return 1;
}
while (!feof(fp)) {
if (fgets(line, sizeof(line), fp) != NULL) {
printf("%s", line);
}
}
fclose(fp);
return 0;
}
(五)ungetc函数的使用
函数原型如下:
int ungetc(int c, FILE *fp) ;
功能:将c字符退回到fp指定的输入数据流中。
例:假定文本文件data.txt的内容是字符流"ABCDEFG…",下面的函数读入其前三个字符,按逆序退回这三个字符,然后再读入前三个字符。通过本例,读者可以学习ungetc函数的使用,同时能够体会到文件缓冲区的意义。
int main( )
{
FILE *fp;
char ch1, ch2, ch3;
if((fp = fopen("data.txt", "r"))==NULL)
{ printf("Can not open data.txt!\n");
exit(1);
}
ch1=fgetc(fp); ch2=fgetc(fp); ch3=fgetc(fp);
printf("%c%c%c,", ch1, ch2, ch3);
ungetc(ch1,fp); ungetc(ch2,fp); ungetc(ch3,fp);
ch1=ch2=ch3='X';
ch1=fgetc(fp); ch2=fgetc(fp); ch3=fgetc(fp);
printf("%c%c%c\n", ch1, ch2, ch3);
fclose(fp);
return 0;
}
例1
从键盘输入以回车结尾的一行字符,将其写入文本文件:
# include <stdio.h> /* 文件操作必须包含此头文件 */
# include <stdlib.h>
int main( )
{ FILE *fp;
char filename[30], ch; /* filename存放文件名 */
printf("Please enter the file name: ");
gets(filename); /* 用标准输入函数输入目标文本文件名 */
fp = fopen(filename, "w");
if(fp==NULL)
{ printf("Can not open file %s!\n", filename);
exit(1);
}
while((ch=getchar( ))!='\n') /* A */
fputc(ch, fp); /* B */
fclose(fp);
return 0;
}
例2
文本文件的复制
以下程序用于将一个源文件的内容复制到一个目标文件。
#include <stdio.h>
#include <stdlib.h>
int main( )
{
FILE *fp1, *fp2;
char infile[30], outfile[30], ch;
/* infile存放源文件名,outfile 存放目标文件 */
printf("Please enter an input file name: ");
gets(infile); /* 用标准输入函数输入源文件名 */
fp1 = fopen(infile, "r");
if(fp1==NULL)
{
printf("Can not open file %s!\n", infile);
exit(1);
}
printf("Please enter an output file name: ");
gets(outfile); /* 用标准输入函数输入目标文件名 */
fp2 = fopen(outfile, "w");
if(fp2==NULL)
{
printf("Can not open file %s!\n", outfile);
exit(2);
}
while((ch=fgetc(fp1))!=EOF)
fputc(ch, fp2);
fclose(fp1);
fclose(fp2);
return 0;
}
例3
从正文文件in.txt中读入数据到二维数组中,调用函数分别求该二维数组的主、辅对角线上的元素之和,然后将二维数组的数据以矩阵方式写入另一正文文件out.txt,最后写入求和结果。
int main( )
{
void sum(int a[4][4], int *, int *); /* 函数原型说明 */
FILE *fp1, *fp2;
int a[4][4], i, j, sum1, sum2;
if((fp1=fopen("dada.in", "r"))==NULL)
/* 打开输入数据文件 */
{
printf("Can'nt open dada.in!\n");
exit(1);
}
if((fp2=fopen("data.out", "w"))==NULL)
/* 打开输出数据文件 */
{
printf("Can'nt open data.out!\n");
exit(2);
}
for(i=0; i<4; i++)
for(j=0; j<4; j++)
fscanf(fp1, "%d", &a[i][j]); /* 从文件读元素值 */
sum(a, &sum1, &sum2); /* 调用函数计算对角线元素之和 */
for(i=0; i<4; i++)
{
for(j=0; j<4; j++)
fprintf(fp2, "%4d", a[i][j]); /* 写数组元素到文件 */
fprintf(fp2, "\n");
}
fprintf(fp2, " sum1=%d\n sum2=%d\n", sum1, sum2); /* 写求和结果到文件 */
fclose(fp1);
fclose(fp2);
return 0;
}
void sum(int a[4][4], int *sum1, int *sum2)
/* 通过指针带回多个计算结果 */
{
int i;
*sum1=*sum2=0;
for(i=0; i<4; i++)
{
*sum1 = *sum1 + a[i][i];
*sum2 = *sum2 + a[i][3-i];
}
}