cp命令实现
lseek
标准io&&追代码
缓冲区
open
一 文件IO
1.1 什么是文件IO
所谓文件IO指的就是操作系统提供支持的API接口,以对文件的输入和输出
1.2 操作系统的分层
应用层
应用APP,bash脚本等
-----------------------------------
内核层
接收应用层发来的通知,去操作相应的硬件 (操作系统)
文件IO的接口都来自于内核层
-----------------------------------
硬件层
硬盘,鼠标,键盘,lcd,摄像头等
1.3 学习IO的前提
1> linux下一切皆文件
2> 文件IO是由操作系统提供的API接口,不同操作系统之间不通用
3> 由于文件IO直接由内核提供,所以效率比较高,但是频繁的调用
会极大的浪费CPU资源
4> linux :open write read
windows:_open _write _read
ios : Open Write Read
5> 文件描述符:是一个文件的代表,系统内部为结构体指针数组的下标
6> 文件描述符中:前三个已经被系统占用
0:标准输入 (从终端文件写入内存)
1:标准输出 (从内存输出到终端文件)
2:标准错误输出(从内存直接输出到终端)
7> errno:错误码(系统返回的错误码,其对应着一些错误原因)
错误原因可以使用perror函数进行打印
8> 文件掩码:umask(控制文件创建的权限)
1.4 文件IO的五大函数(linux)
1> 打开文件 (open)
2> 写入文件 (write)
3> 读取文件 (read)
4> 操作文件读写指针 (lseek)
5> 关闭文件 (close)
1.5 man手册
下表显示了手册的 章节 号及其包含的手册页类型。
1 可执行程序或 shell 命令
2 系统调用(内核提供的函数)
3 库调用(程序库中的函数)
4 特殊文件(通常位于 /dev)
5 文件格式和规范,如 /etc/passwd
6 游戏
7 杂项(包括宏包和规范,如 man(7),groff(7))
8 系统管理命令(通常只针对 root 用户)
9 内核例程 [非标准]
使用方法:man 章节数 查找目标
1.6 文件IO的操作
【1】open
头文件:#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
原型: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_CREAT,则必须加上第三个参数 mode)
O_APPEND: 以追加写的方式
O_TRUNC : 以清空的方式写入
O_NONBLOCK or O_NDELAY:以非阻塞的方式打开一个文件
mode :创建文件的权限(三位八进制数组成)
返回值:
成功:返回文件描述符
失败:返回-1
【2】read
原型:#include <unistd.h>
头文件:ssize_t read(int fd, void *buf, size_t count);
功能:从文件中获取一段内容写入到内存buf中
参数:
fd:目标文件描述符(从哪个文件中读)
buf:从文件中读取后写入内存的位置
count:写入文件的字节个数(写入内存的最大字节个数)
返回值:
成功:返回 读取到的字节个数
返回0:读到文件的末尾
失败:返回 -1
【3】write
头文件:#include <unistd.h>
原型:ssize_t write(int fd, const void *buf, size_t count);
功能:向文件中写入一段字符串
参数:
fd:目标文件描述符(向哪个文件写入内容)
buf:写入字符串的首地址
count:写入文件的字节的个数
注意:如果buf的长度小于count的值,则会产生空洞文件
如果buf的长度大于count的值,则写入count大小的字节
返回值:
成功:返回 实际写入文件的字节的个数
返回0:向文件中写入0个字节
失败:返回-1
作业:实现cp命令(cp 源文件路径 目标文件路径)
要求:可以支持拷贝图片(二进制文件)
提示:
1.源文件路径 目标文件路径 使用命令行参数实现
2.open函数打开两个文件(源文件,目标文件)
3.循环从源文件中读取内容,再循环写入新的文件中
【4】lseek
头文件:#include <sys/types.h>
#include <unistd.h>
原型:off_t lseek(int fd, off_t offset, int whence);
功能:操作文件读写指针的偏移
参数:
fd:目标文件描述符
offset:偏移基准
SEEK_SET
以文件开头为基准进行指针的偏移
SEEK_CUR
以当前读写指针的位置为基准进行偏移
SEEK_END
以文件末尾为基准进行读写指针的偏移
whence:偏移量
如果偏移量为负数,表示往前偏移whence个字节
如果偏移量为正数,表示往后偏移whence个字节
返回值:
成功:返回文件读写指针距离文件开头的偏移量
失败:返回 -1。
【5】close
头文件:#include <unistd.h>
原型:int close(int fd);
功能:关闭一个文件
参数:
fd:目标文件描述符
返回值:
成功:返回 0
失败:返回 -1
二 标准IO
2.1 什么是标准IO
所谓标准IO,就是在文件IO的基础上封装出来的一系列接口
主要目的就是为了其可移植性和减少内核的调用。
2.2 搜索引擎(追代码工具 ctag)
1> 找到搜索的路径
2> vi -t FILE
3> 输入对应的数字
4> ctrl + ] 进入下一级路径
5> ctrl + t 回到上一级路径
2.3 文件流指针(FILE*)
所谓文件流指针,就是在文件描述符的基础上,封装出来的一个
结构体指针,文件流指针就是标准IO中操作文件的指针
标准输入流指针:stdin
标准输出流指针:stdout
标准错误输出流指针:stderr
文件流指针结构体:
241 struct _IO_FILE {
242 int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
243 #define _IO_file_flags _flags
244
245 /* The following pointers correspond to the C++ streambuf protocol. */
246 /* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
247 char* _IO_read_ptr; /* Current read pointer */
248 char* _IO_read_end; /* End of get area. */
249 char* _IO_read_base; /* Start of putback+get area. */
250 char* _IO_write_base; /* Start of put area. */
251 char* _IO_write_ptr; /* Current put pointer. */
252 char* _IO_write_end; /* End of put area. */
253 char* _IO_buf_base; /* Start of reserve area. */
254 char* _IO_buf_end; /* End of reserve area. */
255 /* The following fields are used to support backing up and undo. */
256 char *_IO_save_base; /* Pointer to start of non-current get area. */
257 char *_IO_backup_base; /* Pointer to first valid character of backup area */
258 char *_IO_save_end; /* Pointer to end of non-current get area. */
259
260 struct _IO_marker *_markers;
261
262 struct _IO_FILE *_chain;
263
264 int _fileno; //文件描述符
265 #if 0
266 int _blksize;
267 #else
268 int _flags2;
269 #endif
270 _IO_off_t _old_offset; /* This used to be _offset but it's too small. */
271
272 #define __HAVE_COLUMN /* temporary */
273 /* 1+column number of pbase(); 0 is unknown. */
274 unsigned short _cur_column;
275 signed char _vtable_offset;
276 char _shortbuf[1];
277
278 /* char* _save_gptr; char* _save_egptr; */
279
280 _IO_lock_t *_lock;
281 #ifdef _IO_USE_OLD_IO_FILE
282 };
2.4 缓冲区分类
1> 行缓冲(标准输入(文件描述符:0)和标准输出(文件描述符:1))
(1)行缓冲的大小--->1024个字节
(2)刷新缓冲区的条件
<1> 遇到\n会刷新缓冲区
<2> 程序结束会刷新缓冲区
<3> 缓冲区满会刷新缓冲区
<4> 当标准输入和标准输出有一方需要使用缓冲区时
另一方需要让出缓冲区
<5> 程序员手动刷新缓冲区(fflush)
2> 全缓冲(除行缓冲和无缓冲外)
(1) 全缓冲的大小--->4096个字节
(2) 全缓冲刷新的条件
<1> 程序结束会刷新缓冲区
<2> 缓冲区满会刷新缓冲区
<3> 程序员手动刷新缓冲区(fflush)
3> 无缓冲(标准错误输出(文件描述符:2))
2.5 相关API接口
【1】fopen
头文件:#include <stdio.h>
原型:FILE *fopen(const char *path, const char *mode);
功能:打开一个文件
参数:
path:打开文件的路径
mode:
┌─────────────┬───────────────────────────────┐
│fopen() mode │ open() flags │
├─────────────┼───────────────────────────────┤
│ r │ O_RDONLY │
├─────────────┼───────────────────────────────┤
│ w │ O_WRONLY | O_CREAT | O_TRUNC │
├─────────────┼───────────────────────────────┤
│ a │ O_WRONLY | O_CREAT | O_APPEND │
├─────────────┼───────────────────────────────┤
│ r+ │ O_RDWR │
├─────────────┼───────────────────────────────┤
│ w+ │ O_RDWR | O_CREAT | O_TRUNC │
├─────────────┼───────────────────────────────┤
│ a+ │ O_RDWR | O_CREAT | O_APPEND │
└─────────────┴───────────────────────────────┘
返回值:
成功:返回一个文件流指针
失败:返回NULL
【2】fgetc
头文件:#include <stdio.h>
原型:int fgetc(FILE *stream);
功能:从文件中获取一个字符
参数:
stream:目标文件流指针
返回值:
成功:返回获取到的字符
读取到文件末尾,返回EOF
失败:EOF
【3】feof
头文件:#include <stdio.h>
原型:int feof(FILE *stream);
功能:检测文件是否已经到达末尾
参数:
stream:目标文件流指针
返回值:到达文件末尾:返回非0
未到达: 返回0
【3】fputc
头文件:#include <stdio.h>
原型:int fputc(int c, FILE *stream);
功能:向一个文件输出一个字符
参数:
c:想要写入文件的字符
stream:目标文件流指针
返回值:
成功:返回成功写入的字符的ascii
失败:返回EOF
练习:使用 fgetc和fputc实现cp命令。
【4】fgets(行读取)
头文件:#include <stdio.h>
原型:char *fgets(char *s, int size, FILE *stream);
功能:从指定的文件中获取一行字符串
参数:
s:从文件中获取内容后写入的位置
size:一次获取字符串的长度(最多读取一行,并且将\n也写入内存)
stream:目标文件流指针
返回值:
成功:返回读取到的字符串
读取到文件末尾:返回NULL
失败:返回NULL
【5】fputs
头文件:#include <stdio.h>
原型:int fputs(const char *s, FILE *stream);
功能:向指定的文件输出一个字符
参数:
s:想要向文件中写入的字符串
stream:目标文件流指针
返回值:
成功:返回一个非负数
失败:返回EOF
作业:1.通过以上函数计算文件的行数
2.通过以上函数计算文件的大小
3.员工管理系统,要求运行后,可以获取之前插入的数据(周一验收)
【6】fread(二进制读取)
头文件:#include <stdio.h>
原型:size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
功能:从指定的文件中读取 size *nmemb个字节大小的数据到内存ptr中
参数:
ptr :保存从文件中读取到内容的首地址
size: 读取文件的大小
nmemb:读取内容的个数
stream:目标文件流指针
返回值:
成功:返回读取到的项目(以size大小为单位)的个数
读到文件末尾:返回0
失败:返回0
【7】fwrite
头文件:#include <stdio.h>
原型:size_t fwrite(const void *ptr, size_t size, size_t nmemb,
FILE *stream);
功能:向指定的文件中写入size *nmemb个字节
参数:
ptr:内存的首地址
size:一个数据包的大小
nmemb:数据包的个数
stream:目标文件流指针
返回值:
返回成功写入文件中数据包的个数
失败:返回0
【8】fseek
头文件:#include <stdio.h>
原型:int fseek(FILE *stream, long offset, int whence);
功能:操作读写指针的偏移
参数:
stream:目标文件流指针
offset: 偏移的基准位置
SEEK_SET:以文件开头为基准进行偏移
SEEK_CUR:以文件当前位置为基准进行偏移
SEEK_END:以文件末尾为基准进行偏移
whence:偏移量
注意:负数表示向前偏移
返回值: 正数表示向后偏移
成功:返回0
注意:(使用ftell()函数获取偏移量.)
失败:返回-1
【9】sprintf
头文件:#include <stdio.h>
原型:int sprintf(char *str, const char *format, ...);
功能:向某一块内存按照格式输入字符串
参数:
str :字符串输入内存的首地址
format:格式化字符串
... : 可变参数
返回值: 成功输出的字符的个数
【10】fprintf
头文件:#include <stdio.h>
原型:int fprintf(FILE *stream, const char *format, ...);
功能:向固定的文件按照格式写入字符串
参数:
stream:目标文件流指针
format:格式化字符串
... : 可变参数
返回值: 成功写入文件的字符的个数
三 总结
1.标准IO和文件IO的区别?
标准IO是在文件IO的基础上,增加了缓冲区,封装出来的一系列接口
可移植性:标准IO的移植性更好
使用区别:当我们需要频繁的调用IO接口,但是又不着急立刻
读写文件内容,这个时候,选择标准IO
当需要高效的读取文件内容,且仅用于特定的环境,但是又不频繁调用
选择文件IO