c的标准IO
文件的概念
- 所谓文件是指一组相关数据的有序集合,这个数据集有一个名称,叫做文件名。如源程序文件,目标文件,可执行文件,头文件等
- 文件通常时驻留在外部介质(如磁盘等)上的,在使用时才调入内存中来
从用户角度看,文件可分为普通文件和设备文件两种
- 普通文件:普通文件是指驻留在磁盘或其他外部介质上的一个有序数据集,可以是源文件,目标文件,可执行程序等
- 设备文件:设备文件是指与主机相联的各种外部设备,如显示器,打印机,键盘等。在操作系统中,把外部设备也看作是一个文件来管理,把它们的输入,输出等同于磁盘文件的读和写
从文件编码的形式看,文件可分为文本文件和二进制文件两种
文本文件
- 以
ASCII
码格式存放,一个字节放一个字符。文本文件的每一个字节存放一个ASCII
码,代表一个字符。这便于对字符的逐个处理,但占用存储空间较多,转换为二进制速度慢,但直观易记。 - 如整数
1000 => 0011000100110000001100000011000000110000
- 以
二进制文件
- 以补码形式存放。二进制文件是把数据以二进制数的格式存放在文件中的,其占用存储空间极少,无需转换。
- 数据按其内存中的存储形式原样存放。一个字节不对应一个字符,故不能直接输出其字符样式。
- 如:整数
1000 => 0010011100010000
文件系统的分类
目前c语言所使用的磁盘文件系统分为缓冲文件系统和非缓冲文件系统
缓冲文件系统
- 系统自动地在内存区为每一个正在使用的文件开辟一个缓冲区。从磁盘向内存读入数据时,则一次从磁盘文件将一些数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送给变量。向磁盘文件输出数据时,先将数据送到内存中的缓冲区,装满缓冲区后才一起送到磁盘去
- 用缓冲区可以一次读入一批数据,或输出一批数据,而不是执行一次输入或输出函数就去访问一次磁盘,这样做的目的是减少磁盘的实际读写次数,提高读写的效率。
非缓冲的文件系统
- 不是有系统自动设置缓冲区,而由用户自己根据需要设置
- 用户在程序中为每个文件设定缓冲区
- 在传统的
UNIX
文件系统下,用缓冲文件文件系统来处理文本文件,用非缓冲文件系统处理二进制文件 - 1983年
ANSI C
标准决定不采用非缓冲文件系统,而只采用缓冲文件系统。即用缓冲文件系统处理文本文件,也用它来处理二进制文件。也就是将缓冲文件系统扩充为可以处理二进制文件。 - 一般把缓冲文件系统的输入输出称为标准输入输出(标准IO),非缓冲文件系统的输入输出称为系统输入输出(系统IO)。
- 在c语言中没有输入输出语句,对文件的读写都是用库函数来实现的。
ANSI C
规定了标准输入输出函数,用它们对文件进行读写,需要使用头文件stdio.h
。
文件流的概念
- 在c语言对文件的操作最终转化为对缓存中的数据进行操作,流实际上就是内存中的一种具有自动顺序操作的特殊数据,流如同流动的河水,有它的源和目的地。
流根据方向可分为输入流和输出流
输入流
- 从文件读取数据的流
输出流
- 将数据输出到文件的流
流根据数据内容可分为文本流和二进制流
文本流
- 二进制流就是流动着的字符序列
二进制流
- 二进制流就是流动着的二进制序列
三个标准流
- 标准输入流
stdin
(用数字0
表示):针对标准输入,键盘 - 标准输出流
stdout
(用数字1
表示):针对标准输出,屏幕 - 标准错误流
stderr
(用数字2
表示):针对标准输出,屏幕
- 标准输入流
- 上述所有的流都统称为
文件流
文件类型结构体FILE
和文件指针
文件类型结构体
FILE
- 缓冲文件系统为每个正使用的文件在内存开辟文件信息区
- 文件信息用系统定义的名为
FILE
的结构体描述 FILE
定义在stdio.h
中
文件指针的定义
FILE *指针变量名
- 文件指针实际上是一个文件类型结构体指针
- 通过文件指针即可找到存放某个文件信息的文件类型结构体变量,然后按结构体变量提供的信息找到该文件,实施对文件的操作
- 习惯上也笼统地把文件指针称为指向一个文件的指针或流指针
- 当文件打开时,系统会自动创建文件类型结构体变量,并把指向它的指针返回回来,程序通过这个指针获得文件信息并访问文件
- 文件关闭后,文件指针指向的结构体变量会被释放
文件打开
FILE *fopen(char *filename,char *mode);
- 功能:按指定方式打开文件
参数
- filename:为打开的文件路径(相对路径或绝对路径)
- mode:使用文件的方式
- 返回:正常打开,返回文件指针;打开失败,返回
NULL
- 标准输入,标准输出和标准错误是由系统打开的,可直接使用
- 示例
FILE *fp;
fp =fopen("test.txt","w");
使用文件的方式
文件使用方式 | 含义 |
---|---|
rt | 只读打开一个文本文件,只允许读数据 |
wt | 只写打开或建立一个文本文件,只允许写数据 |
at | 追加打开一个文本文件,并在末尾写数据 |
rb | 只读打开一个二进制文件,只允许写数据 |
wb | 只写打开或建立一个二进制文件,只允许写数据 |
ab | 追加打开一个二进制文件,并在末尾写数据 |
rt+ | 读写打开一个文本文件,允许读和写 |
wt+ | 读写打开或建立一个文本文件,允许读和写 |
at+ | 读写打开一个文本文件,允许读或在末尾追加数据 |
rb+ | 读写打开一个二进制文件,允许读和写 |
wb+ | 读写打开或建立一个二进制文件,允许读和写 |
ab+ | 读写打开一个二进制文件,允许读或在末尾追加数据 |
符号说明
- r(read):读
- w(write):写
- a(append):追加
- t(text):文本文件,可以省略不写
- b(binary):二进制文件
- +:读和写
文件使用的处理方式
mode | 处理方式 | 当文件不存在 | 当文件存在 | 写文件 | 读文件 |
---|---|---|---|---|---|
r | 读取 | 出错 | 打开文件 | 不能 | 能 |
w | 写入 | 建立新文件 | 覆盖原有文件 | 能 | 不能 |
a | 追加 | 建立新文件 | 在原有文件后追加 | 能 | 不能 |
r+ | 读取/写入 | 出错 | 打开文件 | 能 | 能 |
w+ | 读取/写入 | 建立新文件 | 覆盖原有文件 | 能 | 能 |
a+ | 读取/写入 | 建立新文件 | 在原有文件后追加 | 能 | 能 |
如果是二进制文件,在使用时只要在模式后添加字符b
即可,如rb
,rb+
分别表示读取二进制文件和以读取/写入方式打开二进制文件
文件关闭
int fclose(FILE *fp);
- 功能:关闭fp指向的文件,释放文件类型结构体和文件指针。
- 参数:fp打开文件时返回的文件指针
- 返回:成功返回
0
,失败返回-1
注意点:不关闭文件可能会丢失数据
- 向文件写数据时,是先将数据输出到缓冲区,待缓冲区充满后才正式输出给文件。如果当数据未充满缓冲区而程序结束允许,就会将缓冲区的数据丢失
- fclose先把缓冲区的数据输出到磁盘文件(刷新缓存),然后才会释放文件类型结构体和文件指针
测试文件读写位置
int ftell(FILE *fp);
- 功能:测试当前文件的读写位置
- 返回:测试成功返回文件位置指针所在的位置(当前读写位置距离文件开头的字节数),失败则返回
-1
常用的标准IO函数
getchar
int getchar();
- 功能:从标准输入读取一个字符
- 返回:成功返回读取的字符,否则返回
EOF
putchar
int putchar();
- 功能:将一字符ch写入到标准输出
fgetc
int fgetc(FILE *fp);
- 功能:从fp指向的文件中读取一个字符
- 返回:成功返回读取的字符,否则返回
EOF
(-1) - 可针对标准输入操作
fputc
int fputc(int ch,FILE *fp);
- 功能:把一字符ch写入fp指向的文件中
- 返回:成功返回写入的字符ch,失败返回
EOF
- 可针对标准输入输出操作
ungetc
int ungetc(int c,FILE *fp);
- 功能:撤销一个字符
fgets
char *fgets(char *str,int siez,FILE *fp);
- 功能:从fp指向的文件中至多读
size - 1
个字符,放到str指向的字符数组中,如果在读入size - 1
个字符结束前遇到换行符或EOF
,读入即结束,字符串读入后在最后加一个\0
- 返回:成功返回str字符串指针,失败返回
NULL
- 可针对标准输入操作
fputs
int fputs(char *str,FILE *fp);
- 功能:把str指向的字符串或字符数组写入fp指向的文件中
- 返回:成功返回0,出错返回
EOF
- 可针对标准输出操作
fscanf和fprintf
int fscanf(FILE *fp,const char *format,...);
int fprintf(FILE *fp,const char *format,...);
- 功能:按format格式对fp指向的文件进行IO操作
- 返回:成功返回IO字节数,失败或到文件末尾返回
EOF
- 可针对标准输入和输出操作
- 示例
#include <stdio.h>
#include <string.h>
int main(int argc,char *argv[])
{
if(argc < 2)
6 {
fprintf(stdout,"parameter error\n");
return 1;
}
FILE *fp1 = fopen("/etc/passwd","r");
FILE *fp2 = fopen(argv[1],"r");
char str[1024] = {'\0'},name[20] = {'\0'},passwd[20] = {'\0'},description[20] = {'\0'},home[20] = {'\0'},bash[20] = {'\0'};
int uid = 0,gid = 0;
// 这里读取之后,整行的内容都在name中
int flag = fscanf(fp1,"%[^:]:%[^:]:%d:%d:%[^:]:%[^:]:%[^\n]\n",name,passwd,&uid,&gid,description,home,bash);
while((flag = fscanf(fp1,"%[^:]:%[^:]:%d:%d:%[^:]:%[^:]:%[^\n]\n",name,passwd,&uid,&gid,description,home,bash))!= EOF)
{
printf("%d\n",flag); fprintf(stdout,"name=%s;passwd=%s;uid=%d;gid=%d;description=%s;home=%s;bash=%s\n",name,passwd,uid,gid,description,home,bash);
// description位置可能会为空,为空的话,文件指针的位置不会移动到下一行,这个需要处理下
if(0 == flag)
{
fscanf(fp1,"%*[^\n]\n");
// 清空description的数据
memset(description,'\0',sizeof(description));
//break;
}
}
return 0;
}
sscanf
int sscanf(char const *str,char const *format,...);
int sprintf(char const *str,char const *format,...);
- 功能:按format格式对str指向的字符数组进行IO操作
- 返回:成功返回
I/O
字节数,失败返回EOF
- 实例
#include <stdio.h>
int main(void)
{
char str1[20] = "666 jin";
int id = 0;
char name[10] = {'\0'};
// 这里不字符串666转换成了数字
sscanf(str1,"%d %s",&id,name);
printf("id = %d,name = %s\n",id,name);
// 不数字666转换成字符串666
char number[5] = {'\0'};
sprintf(number,"%d",id);
printf("number = %s\n",number);
return 0;
}
fread和fwrite
int fread(void *buffer,int num_bytes,int count,FILE *fp);
int fwrite(void *buffer,int num_bytes,int count,FILE *fp);
- 功能:读写数据块,一般用于二进制文件的输入输出
- 返回:成功返回读写的元素个数,失败或到文件末尾返回
EOF
参数:
- buffer:一个指向要进行输入输出数据存储区的通用指针
- num_bytes:每个要读写的元素的字节数
- count:要读写的元素个数
- fp:要读写的文件指针
fread和fwrite函数的使用注意点
- 用
fprintf
和fscanf
函数对磁盘读写,使用方便,容易理解,但由于在输入时要将ASCII
码转换成二进制形式,在输出时又要将二进制形式转换成字符,花费时间较多。 - 因此,在内存与磁盘频繁交换数据的情况下,最好不要用
fprintf
和fscanf
函数,而用fread
和fwrite
函数
- 用
- 实例
#include <stdio.h>
struct User
{
int id;
char name[20];
};
int main(int argc,char *argv[])
{
if(argc < 2)
{
printf("parameter error\n");
return 1;
}
FILE *fp1 = fopen("/etc/passwd","r");
FILE *fp2 = fopen(argv[1],"wb");
if(fp1 == NULL || fp2 == NULL)
{
printf("parameter error\n");
return 1;
}
struct User user;
while(EOF != fscanf(fp1,"%[^:]:%*[^:]:%d:%*[^\n]\n",user.name,&user.id))
{
if(EOF == fwrite(&user,sizeof(struct User),1,fp2))
{
printf("fail to write file\n");
return 1;
}
//printf("uid = %i;name = %s\n",user.id,user.name);
}
fclose(fp1);
fclose(fp2);
fp2 = fopen(argv[1],"rb");
/* 利用返回值判断是否到达文件尾
while(0 != fread(&user,sizeof(struct User),1,fp2))
{
printf("uid = %i;name = %s\n",user.id,user.name);
}
*/
fread(&user,sizeof(struct User),1,fp2);
// 使用feof判断是否是文件结尾
while(!feof(fp2))
{
printf("uid = %i;name = %s\n",user.id,user.name);
fread(&user,sizeof(struct User),1,fp2);
}
return 0;
}
fseek函数
int fseek(FILE *fp,long offset,int whence);
- 功能:使fp所指文件的位置指针重置到指定位置(从whence位置移动offset个字节)
- 返回:成功返回0,失败返回-1
- offset:位移量,表示移动的字节数
whence:
SEEK_SET
:文件首 0 offset非负SEEK_CUR
:文件当前读写位置 1 offset可正可负SEEK_END
:文件尾 2 offset可正可负
rewind函数
void rewind(FILE *fp);
- 功能:使文件位置指针重新返回文件首
remove函数
int remove(const char *filename);
- 功能:删除指定的文件
- 返回:成功返回0,失败返回-1
fflush函数
void fflush(FILE *fp);
- 功能:刷新缓冲区。如果打开文件进行读操作,该函数将清空文件的输入输出缓冲区,如果打开文件进行写操作时,该函数将文件的输出缓冲区内容写入文件中