目录
综述标准IO与文件IO
标准IO:
使用时在用户空间创建缓冲区,在合适的时机在通过系统调用访问实际的文件,从而减少了使用系统调用的次数。
缓存方式分三种:
行缓冲:\n
全缓冲:缓存区填满内容才会溢出
不缓存:stderr
流:
当使用标准IO打开一个文件时,就会创建一个FILE结构体描述该文件,我们把这个FILE结构体形象的称为流,标准IO函数都是基于流进行各种操作。
文件指针:
指向一个打开文件的指针(硬盘中的文件被拷贝到内存中之后,会以FILE结构体的形态存在,要操作该文件必须使用文件指针)
FILE:
C语言file类,在stdio.h 头文件中,FILE类是一个结构体:定义如下:
struct _iobuf {
char *_ptr;
int _cnt;
char *_base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char *_tmpfname;
};
typedef struct _iobuf FILE;
通过typedef定义了 文件类型 的别名: “FILE”,这样以后需要读写文件的时候直接就可以用FILE去定义。
文件IO:
1、不带缓存
2、通过文件描述符来访问文件
文件IO与标准IO的区别:
- 文件IO又被称为低级磁盘IO,遵循POSIX相关标准,任何兼容POSIX标准的操作系统上都支持文件IO。标准IO被称为高级磁盘IO,遵循ANSI C标准,只要开发环境中有标准C库,就可以使用标准IO。
- 文件IO读写文件时,每次操作都会执行相关的系统调用,这样可以直接读取实际文件,但是频繁的系统调用会增加系统的开销。标准IO可以看做是在文件IO的基础上封装了缓冲机制,从而减少了系统调用的次数。
- 文件IO中用文件描述符表示一个打开的文件,可以访问不同类型的文件。而标准IO使用流表示一个打开的文件,只能访问普通文件。
再来回顾一下各个文件类型
普通件 -
管道文件 p
链接文件 l
目录文件 d
套接字文件 s
块设备文件 b
字符设备文件 c
1、fopen 函数(打开一个文件)
FILE *fopen(const char *path, const char *mode)
FILE *fopen(要打开的文件路径及文件名, 以何方式打开 )
打开方式:r(只读)、w(只写)、a(追加写入)、r+(读写)、w+(读写)
返回值:成功,返回已经打开的文件流指针,失败返回NULL
2、fclose函数(关闭流)
int fclose(FILE *fp)
int fclose(已经打开的文件流指针)
返回值:
成功,返回0
失败,返回-1(EOF)(end of file,文件尾部的意思)
注意:EOF只能判断是否到文本文件的末尾,不能判断是否到二进制文件的末尾
判断是否关闭的代码
if (fclose(fp) != 0)
{
printf("Error in closing file %s\n", str1);
}
以下是使用fopen和fclose的案例:
说明:
../2.txt表示上一级目录下的2.txt文件,此处按照实际情况来写就行。
errno.h,该头文件定义了通过错误码来回报错误资讯的宏。errno 宏定义为一个 int 型态的左值, 包含任何函式使用errno功能所产生的上一个错误码。
所以这里用的是%d这个格式控制符。
3、读写函数
当程序运行起来时,有三个文件默认已经打开,标准输入、标准输出、标准出错,对应的流指针分别为:stdin、stdout、stderr,这几个都是从键盘输入,终端输出,此次我们重点介绍的是从文件中读取,以及写入文件中。
3.1按字符读写
getchar( )、putchar( ) 只能从键盘输入
fgetc( )、fputc( ) 从哪里输入都可以:键盘、文件都可输入
3.1.1 fgetc函数(从指定流中读取一个字符)
int fgetc(FILE *stream)
int fgetc(指定的流),参数是读取字符的流来源
这个流可以写stdin,就是从键盘流输入,如果要用其他的流,从文件中读取和写入,需自己定义
返回值:成功返回输出的字符的ASCII值,失败返回-1
这个函数的返回值,是返回所读取的一个字节。如果读到文件末尾或者读取出错时返回EOF
3.1.2 fputc函数(向流中输出一个字符)
int fputc(int c, FILE *stream);
int fputc(要输出的字符变量名, 要输出到哪的流?);
返回值:成功返回输出的字符的ASCII值,失败返回-1
案例:
说明:
这里用的stdin 是标准输入流、stdout是标准输出流这两个流,以及stderr在程序运行一开始就会自己开启,我们可以直接拿来用,体现的是一种键盘与终端的关系
3.1.3案例操作
用字符的读写模拟完成CP命令,如下:
3.2按行读写
3.2.1 fgets函数(按行读文件)
char *fgets(char *s, int size, FILE *stream)
char *fgets(读到内容放哪个地址, 能放的空间大小, 要操作的流(读它内容))
参数:
s: 字符型指针,指向存储读入数据的缓冲区的地址
size: 从流中读入n-1个字符
stream : 指向读取的流
返回值:
成功,返回读到缓冲区字符串的首地址,
失败,返回NULL,遇到文件结尾也是返回NULL
注意:1、最多读size-1个字节
因为它遇到换行符,会将换行符也读入
3.2.2 fputs函数(向文件中输出一串字符串)
int fputs(const char *s, FILE *stream)
int fputs(内存空间的首地址, 要操作的流)
返回值:
成功,返回非负整数
失败,返回-1
3.2.3 小结
总结:fgets和gets有什么区别
fgets比gets安全
fgets会读取换行符,而gets不会
puts和fputs的区别
puts会自动换行,而fputs不会
3.3按块读写
3.3.1 fread函数(按块读取内容)
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)
size_t fread(接收保存数据的地址, 要读出内容的单字节数, 要进行读出size字节的数据项的个数, 要操作的流(目标文件指针))
fread函数每次从stream中最多读取nmemb个单元,每个单元大小为size个字节,将读取的数据放到ptr,文件流的指针后移size*nmemb字节。
由于每次移动的是size*nmemb,可以想象成二维变动(有两个变量值),所以我们叫它块读取
fread函数,每一次若读取成功,返回的是nmemb数值(这个值是我们自己设置的),如果返回值比nmemb要小,则可能是出错或者读取结束。
他还说到,fread函数无法区分是出错或者读取结束,需要借助feof函数或者ferror函数去进一步判断。
经验证,fread函数的返回值成功都是返回设定的nmemb值大小,只在最后一次,剩余的量不足以够nmemb,所以会返回一个比nmemb要小的值
feof函数用于检测是否到达文件末尾,如果到末尾,则返回是一个非零数。
3.3.2 fwrite函数(按块向流中写入数据)
size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream)
size_t fwrite(内存空间首地址, 要写入内容的单字节数, 要进行写入size字节的数据项的个数,要操作的流)
size是sizeof(char),如果是char类型的,后一个参数是总的个数
返回值:
成功,返回写入的块数
失败,返回-1
3.3.3总结
此处用字符读写、按行读写、按块读写,模拟了虚拟机的CP命令,读者可以进行对比学习
文件操作基本步骤:
①打开文件
②操作文件
③关闭文件
#include<stdio.h>
#include<errno.h>
int main(int argc,char *argv[])
{
//入参检测
if(argc < 3)
{
printf("try again");
return -1;
}
//打开文件
FILE *fr=fopen(argv[1],"r");
FILE *fw=fopen(argv[2],"w");
if((NULL == fr) || (NULL == fw))
{
printf("open error:%d\n",errno);
return -1;
}
/*
//文件操作(模拟cp命令,通过字符读写)
char ch;
while(1)
{
ch = fgetc(fr);
if(EOF == ch)
{
break;
}
fputc(ch,fw);
}
//文件操作(模拟CP,通过行读写)
char buf[10]="0";
char *p=NULL;
while(1)
{
p=fgets(buf,10,fr);
if(NULL == p)
{
break;
}
fputs(buf,fw);
}
*/
//文件操作(模拟CP,通过块读写)
char buf[50]="0";
int test1,test2;
while(1)
{
test1=fread(buf,1,15,fr);
test2=feof(fr);
//printf("fread的返回值:%d\n",test1);
//printf("feof的返回值:%d\n",test2);
if(test1 < 0)
{
perror("fread error");
return -1;
}
else if(test2 != 0)
{
break;
}
fwrite(buf,1,15,fw);
}
//关闭文件
fclose(fr);
fclose(fw);
return 0;
}
4、perror函数
函数定义:
void perror(const char *s);
使用:
perror ("open_port");
包含头文件(不可以掉了这个头文件):
#include <stdio.h>//包含perror的头文件
功能:perror ( )用 来 将 上 一 个 函 数 发 生 错 误 的 原 因 输 出 到 标 准 设备 (stderr) 。参数 s 所指的字符串会先打印出,后面再加上错误原因字符串。
5、fseek、ftell、rewind函数
首先,我们需要知道一点,文件在被操作时,文件指针会在文件内部自己移动
这三个函数都包含于stdio.h头文件中
函数的形式分别为:
int fseek(FILE *stream ,long offset ,int whence);
long ftell(FILE *stream);
Void rewind(FILE *stream);
函数 | 功能 | 返回值 | |
fseek | 重定位流上的文件指针 | 成功返回0 | 失败返回-1 |
ftell | 获取当前文件指针位置 | 成功返回文件指针位置 | 失败返回-1 |
rewind | 将文件内部指针重新指向文件的开头 | 无返回值 |
fseek的 whence参数设置,可以写英文,也可以写0,1,2,分别依次对应
起始点 | 代表的文件位置 | 对应数字 |
SEEK_SET | 文件开头 | 0 |
SEEK_CUR | 文件当前位置 | 1 |
SEEK_END | 文件末尾 | 2 |