文件IO与标准IO探究及总结
库
本质:实现好的一组函数接口
好处:屏蔽底层细节,向上层提供统一的接口
Liunx 库 : xxx.so(动态库) , xxx.a(静态库)
windows 库 : xxx.dll
问题:所有的库函数都调用了系统调用接口
回答:不是,例如 strcpy ...
系统调用:操作系统给用户提供的一组函数接口,通过这一组函数接口可以从用户空间
进入内核空间,从而使用内核提供的服务
二 文件操作的系统调用函数接口
(1)文件打开
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
参数:
@pathname 需要打开的文件
@flags
O_RDONLY ,O_WRONLY ,O_RDWR ,O_CREAT(不存在,则创建) ,O_TRUNC(清空),O_APPEND (追加)
这些标志可以通过 "|" 一起使用
echo "hello" > log (O_WRONLY | O_CREAT |O_TRUNC)
echo "hello" >> log (O_WRONLY | O_CREAT |O_APPEND)
@mode
权限,当需要创建文件的(O_CREAT)时候,用来指定文件权限 (八进制表示:0666)
注意:创建实际权限 mode & ~umask
666 & ~002 => 664
返回值:
成功返回 文件描述符,失败返回-1
-----------------------------------------------------------------------------------------------------
文件描述符:它是一个整数,用来标示打开的文件,分配原则:最小未使用,可以使用的文件描述符(0-1023)
默认被使用文件描述符
0 : 标准输入 (键盘)
1 : 标准输出 (终端)
2 : 标准出错 (终端)
errno : 全局变量,用来标示错误的原因,它是一个数子值
strerror(errno) -> 描述错误信息的字符串
----------------------------------------------------------------------------------------------------
<1>以读写方式打开一个文件,文件不存在则创建,文件存在则清空
如:
(2)文件读写
int read(int fd, void *buf, int count);
功能:读文件
参数:
@fd 打开的文件
@buf 用来存放读取的数据内存首地址
@count 期望读取的字节数
返回值:
成功返回读取字节数,失败返回-1,如果读到文件尾部,返回0
int write(int fd, void *buf, int count);
功能:写文件
参数:
@fd 打开的文件
@buf 需要写入数据存放的首地址
@count 需要写入的字节数
返回值:
成功返回写入字节数,失败返回-1
(3)文件关闭
int close(int fd);
如何实现文件拷贝呢
提示:
./a.out src dest
src : O_RDONLY
dest: O_WRONLY | O_TRUNC | O_TRUNC
比较两个文件是否有差异:diff src dest
(4)文件定位
含义:修改内核空间文件表项中的offset值
long lseek(int fd, long offset, int whence);
参数:
@fd 打开的文件
@offset 偏移量 ( 正数 : 向后偏移 , 负数:向前偏移)
@whence SEEK_SET:文件开头 SEEK_CUR:当前位置 SEEK_END:文件尾部
返回值:
成功返回从头开始偏移的字节数(修改之后内核中文件表项中的offset值),失败返回 -1
lseek(fd,100,SEEK_SET); =>内核中offset = 100
lseek(fd,100,SEEK_CUR); =>内核中offset = offset + 100
lseek(fd,0,SEEK_END); =>内核中offset = 文件大小
输出一个指定文件的大小:
lseek(fd,0,SEEK_END)
tip:
在linux 下vi可以使用:
ctrl + ] 跳转
ctrl + t 返回
三 C标准库提供的文件操作的函数接口
使用C标准库文件操作函数的好处:
<1>编写的代码移植性好
<2>效率较高
特点:标准C库的函数在操作文件的时候,有一个缓存
1.打开文件
流 : FILE * ,打开文件的时候,返回描述打开文件信息(文件描述符 + 缓存的信息)的结构体首地址
FILE *fopen(const char *path, const char *mode);
参数:
@path 需要打开的文件
@mode
"r" 只读 -> O_RDONLY
"r+" 读写 -> O_RDWR
"w" 只写,文件不存在则创建 ,存在则清空 ->O_WRONLY | O_CREAT | O_TRUNC,0666
"w+" 读写,文件不存在则创建 ,存在则清空 ->O_RDWR | O_CREAT | O_TRUNC,0666
"a" 只写,文件不存在则创建 ,存在则追加写 ->O_WRONLY | O_CREAT | O_APPEND,0666
"a+" 读写,文件不存在则创建 ,存在则追加写 ->O_RDWR | O_CREAT | O_APPEND,0666
(注意:,没有进行写操作的时候,读操作是从头开始,写操作总是在末尾)
问题:新建文件权限是多少?
回答:默认指定的是0666,实际权限:666 & ~umask
返回值:
成功返回流指针,失败返回NULL
-------------------------------------------------------------
stdin ----> 0
stdout ----> 1
stderr ----> 2
类型: FILE *
-----------------------------------------------------------
2.格式化输出
int printf(const char *format, ...);
只能向标准输出输出
int fprintf(FILE *stream, const char *format, ...);
向指定的流输出
int sprintf(char *str, const char *format, ...);
向指定的地址输出
循环从从终端上输入字符串写到文件中去,如果输入的是"quit",则结束
3.缓存类型
<1>全缓存
满,调用fflush , 程序正常结束(调用了exit函数,或者从main函数返回)
<2>行缓存
遇到换行符('\n'),满,调用fflush , 程序正常结束(调用了exit函数,或者从main函数返回)
<3>不缓存
直接输出,标准出错 stderr
注意:默认打开的文件,它的缓存是全缓存,当一个流和终端设备关联的时候,它的缓存是行缓存类型(标准输出stdout)
4.单个字符的文件读写
int fgetc(FILE *stream);
功能:从一个流中读取一个字符
参数:
@stream 打开的文件
返回值:
成功返回读取的一个字符,失败或读到文件尾部-1
int fputc(int c, FILE *stream);
功能:将一个字符写入指定的流
参数:
@stream 打开的文件
返回值:
成功返回写入的一个字符,失败-1
5. 多个字符的读写
char *fgets(char *s, int size, FILE *stream);
功能:从文件中读取多个字符
参数:
@s 存放读取的字符
@size 每次读取大小
@stream 打开的文件
返回值:
成功返回s,读到文件尾部返回NULL,失败返回NULL
注意:
<1>每次最多读取size-1个字符
<2>每次读取结束后,都会自动添加'\0'
<3>每次读取结束的时刻:
[1]读到文件尾没有数据可读
[2]读到'\n'字符
[3]已经读取了size-1个字符
问题:如何使用fgets函数读取从键盘输入的字符串?
char buf[1024];
/ /输入:abc回车
fgets(buf,sizeof(buf),stdin);
// 其实得到:buf:abc\n\0
//去掉回车键
buf[strlen(buf) - 1] = '\0';
int fputs(const char *s, FILE *stream);
功能:将一个字符串写入一个流,不会写'\0'字符进去(写的是'\0'之前的字符)
参数:
@s 字符串
@stream 打开的文件
返回值:
成功返回大于0的数,失败返回-1(EOF)
注意:
bug 不能对二进制文件操作
6.二进制文件读写(按数据元素读写)
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
功能:从流中读取指定个数的数据元素
参数:
@ptr 存放读取的数据
@size 数据元素的大小
@nmemb 数据元素的个数
@stream 打开的文件
返回值:
成功返回读取的数据元素个数,失败返回-1 ,读到文件尾部返回0 ,没有数据元素可读
注意:
每次读取的字节数: 成功读取的元素个数 * 元素的大小
size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);
实例:
struct student{
char name[15];
int age;
int score;
};
struct student stu[2] = {{"xiaoming",10,20},{"xiaohong",20,30}};
将学生信息写入文件中
然后从文件中读取第一个学生的信息,然后输出。
7.文件定位
int fseek(FILE *stream, long offset, int whence);
功能:实现文件定位,就是在修改内核空间文件表项中的offset
参数:
@stream 打开的文件
@offset 偏移量
@whence SEEK_SET,SEEK_CUR,SEEK_END
返回值:
成功返回0,失败返回-1
long ftell(FILE *stream); //lseek(fd,0,SEEK_CUR);
功能:返回文件偏移量的值
void rewind(FILE *stream);//lseek(fd,0,SEEK_SET);
功能:定位到文件头
获取文件大小:
三 文件IO(系统调用函数接口) 和 标准IO(标准C库提供的函数接口)
1.标准IO提供的函数接口,绝大部分都是在系统调用之上实现的,它操作对象是流
2.标准IO带缓存的
3.使用标准IO代码移植性更好
API:
FILE *fopen(const char *pathname,char *mode);
int fclose(FILE *fp);
读写:
int fgetc(FILE *fp) / int fputc(int c,FILE *fp);
char *fgets(void *s,int size,FILE *fp) / int fputs(char *s,FILE *fp);
int fread(void *ptr,int size,int nmemb,FILE *fp) / fwrite
文件定位
int fseek(FILE *fp,int offset,int whence);
long ftell(FILE *fp);
void rewind(FILE *fp);
---------------------------------------------------------------------------
1.文件IO就是系统调用,它不带缓存,它的操作对象文件描述符
2.文件IO针对Linux操作系统,除了可以操作普通文件之外,还可以对设备文件读写
打开
int open(const char *pathname,int flag,int mode);
读写
int read(int fd,void *buf,int count);
int write(int fd,void *buf,int count);
关闭
close(int fd);