一、什么是文件 IO
1. 概念
又称为系统IO,是系统调用,是操作系统提供的函数接口。
posix中定义的一组用于输入输出的函数。
POSIX接口 (英语:Portable Operating System Interface)可移植操作系统接口
2. 特点
(1) 没有缓冲机制,每次调用都会引起系统调用。
(2) 围绕文件描述符进行操作,非负整数(>=0),依次分配
(3) 文件IO默认打开了三个文件描述符,分别是0(标准输入)、1(标准输出)、2(标准错误)
(4) 操作除了目录(d类型)文件的任意其他类型文件:b c - l s p
一个进程的文件描述符最大到1023(0-1023),最多能打开1024个文件描述符,最多能打开1024-3=1021个文件
二.、函数接口
1.打开关闭文件 open() close()
int open( const char *pathname, int flags);
功能:打开文件
参数:pathname:文件路径名
flags:打开文件的方式
O_RDONLY:只读
O_WRONLY: 只写
O_RDWR:可读可写
O_CREAT: 不存在创建
O_TRUNC:存在清空
O_APPEND:追加
返回值:成功:文件描述符
失败:-1
当第二个参数中有O_CREAT选项时,需要给open函数传递第三个参数,指定创建文件的权限
例如:O_WRONLY | O_CREAT | O_TRUNC ==>"w" 可写,不能存在时创建,存在则清空
int open( const char *pathname, int flags, mode_t mode);
最后权限=创建出来的文件指定权限值&(~umask)
int close( int fd);
功能:关闭文件
参数:fd:文件描述符
(1)文件IO和标准IO的打开方式的对应关系
标准IO | 文件IO |
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 可读可写,不存在创建,存在追加 |
注意:有O_CREAT的时候需要加第三个参数代表权限
2. 读写文件
(1) 读文件 read()
ssize_t read( int fd, void *buf, size_t count);
功能:从一个已打开的可读文件中读取数据
参数: fd 文件描述符
buf 存放位置
count 期望的个数
返回值:成功:实际读到的个数(小于期望值说明实际没这么多)
返回 0:表示读到文件结尾
返回 -1:表示出错,并设置 errno号
(2) 写文件 write()
ssize_t write( int fd, const void *buf, size_t count);
功能:向指定文件描述符中,写入 count个字节的数据。
参数:fd 文件描述符
buf 要写的内容
count 期望写入字节数
返回值:成功:实际写入数据的个数
失败 : -1
//返回值小于期望值是错误行为,可能磁盘满了无法再写。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
int fd;
char buf[32] = "";
// 打开read.txt,可读可写,不存在创建,追加 等同于标准IO的“a+”
fd = open("read.txt", O_RDWR | O_CREAT | O_APPEND, 0666);
if (fd < 0)
{
perror("open error");
return -1;
}
printf("open scuess,fd = %d\n", fd);
// 从fd中读取十个字符放入buf中
read(fd, buf, 10);
printf("%s \n", buf);
printf("\n");
// 从fd中拂去15个字符放入buf中
read(fd, buf, 15);
printf("%s \n", buf);
// 向fd中写入十个字符
write(fd, "yuanshenqidong", 10);
// 向fd中写入二十个字符
write(fd, "yuanshenqidong", 20);
close(fd);
return 0;
}
3. 文件定位操作 lseek()
off_t lseek( int fd, off_t offset, int whence);
功能:设定文件的偏移位置
参数:fd:文件描述符
offset: 偏移量
正数:向文件结尾位置移动
负数:向文件开始位置
whence: 相对位置
SEEK_SET 开始位置
SEEK_CUR 当前位置
SEEK_END 结尾位置
补充:和fseek一样其中 SEEK_SET, SEEK_CUR 和 SEEK_END和依次为0,1 和 2.
返回值:成功:文件的当前位置
失败:-1
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
int fd;
char buf[32] = "";
// 打开read.txt,可读可写,不存在创建,追加 等同于标准IO的“a+”
fd = open("lseek.txt", O_RDWR | O_CREAT | O_TRUNC, 0666);
if (fd < 0)
{
perror("open error");
return -1;
}
printf("open scuess,fd = %d\n", fd);
// 从开头向后移动十个位置,写入hello
lseek(fd, 10, 0);
write(fd, "hello", 5);
// 从当前位置向后移动五个位置,写入hello
lseek(fd, 5, 1);
write(fd, "hello", 5);
// 从结尾位置向后移动五个位置,写入hello
lseek(fd, 5, 2);
write(fd, "hello", 5);
// 计算当前位置
off_t n = lseek(fd, 0, 2);
printf("%ld\n", n);
close(fd);
return 0;
}
4. 标准IO和文件IO总结
标准IO | 文件IO | |
概念 | C库中定义的一组用于输入输出的函数 | posix中定义的一组输入输出的函数 |
特点 | 1. 有缓冲机制减少系统调用提高效率 2. 围绕流进行操作,FILE* 3. 默认打开三个流:stdin/ stdout/ stderr 4. 只能操作普通文件 5. 可移植性更强 | 1. 无缓冲机制 2. 围绕文件描述符操作,非负整数 3. 默认打开三个文件描述符:0/1/2 4. 可以操作除了目录以外任意类型文件 5. 可移植性较弱 |
函数 | 打开文件:fopen \ freopen 关闭文件:fclose 读文件:fgetc \ fgets \ fread 写文件:fputc \ fputs \ fwrite 定位操作:rewind \ fseek \ ftell | 打开文件:open 关闭文件:close 读文件:read 写文件:write 定位操作:lseek |
三.、获取文件信息
1. 获取文件属性 stat 函数
int stat( const char *path, struct stat *buf);
功能:获取文件属性
参数: path:文件路径名
buf:保存文件属性信息的结构体
返回值:成功:0
失败:-1
struct stat{
ino_t st_ino; /* inode号 ls -il */
mode_t st_mode; /* 文件类型和权限 */
nlink_t st_nlink; /* 硬链接数 */
uid_t st_uid; /* 用户ID */
gid_t st_gid; /* 组ID */
off_t st_size; /* 大小 */
time_t st_atime; /* 最后访问时间 */
time_t st_mtime; /* 最后修改时间 */
time_t st_ctime; /* 最后状态改变时间 */
};
2. 获取文件类型
S_IFMT是一个掩码,它的值是0170000(注意这里用的是八进制前缀为0,二进制为0b001111000000000000), 可以用来把 st_mode 位与上掩码过滤提取出表示的文件类型的那四位(15bit~12bit位),也就是这四位原样获取其他位清零。
这四位可以表示0b0000~0b1111(八进制表示:001~014)七个值,每个值分别对应不同的文件类型:套接字文件、符号链接文件、普通文件、块设备、目录、字符设备、管道。
通过man手册可以看出,判断一个文件是不是普通文件,首先通过掩码S_IFMT把其他无关的部分置0,再与表示普通文件的数值比较,从而判断这是否是一个普通文件:
3. 获取文件权限
0-8bit位每一位表示一个权限,所以只需要把这一位位与出来就可以判断是否有这个权限,为1说明有,为0说明没有。
比如判断个人权限是否有可读: st.st_mode & 0b0000100000000 (八进制:00400)
也就是利用宏: st.st_mode&S_IRUSR
stat/fstat/lstat的区别?
stat函数返回一个与此命名文件有关的信息结构
fstat函数获得已在描述符filedes上打开的文件的有关信息,也就是参数是文件描述符,其他与stat相同。
lstat函数类似于stat,但是当命名的文件是一个符号连接时,lstat返回该符号连接的有关信息,而不是由该符号连接引用的文件的信息.
四、目录操作
围绕目录流进行操作:DIR*
DIR *opendir( const char*name);
功能:获得目录流
参数:要打开的目录
返回值:成功:目录流
失败:NULL
struct dirent *readdir( DIR *dirp);
功能:读目录
参数:要读的目录流
返回值:成功:读到的信息
失败:NULL
返回值为结构体,该结构体成员为描述该目录下的文件信息
readir 读取目录流之后,指针自动向后移动
struct dirent{
ino_t d_ino;/* 索引节点号*/
off_t d_off;/*在目录文件中的偏移*/
unsignedshort d_reclen;/* 文件名长度*/
unsignedchar d_type;/* 文件类型 */
char d_name[256];/* 文件名 */
};
int closedir(DIR *dirp);
功能:关闭目录
参数:dir p:目录流
五、库
1.头文件
#include<stdio.h>
< > 代表去系统路径下查找头文件 /usr/include
#include"head.h"
" " 代表先去当前路径下查找,找不到再去系统路径下查找
2.源文件
包含 main函数的 xx.c
包含子函数的 xx.c,封装的函数需要在头文件中声明
库文件(不能包含main函数)
3. 库的定义
当使用别人的函数时除了包含头文件以外还需要有库
头文件也就是.h结尾的文件,其中包含:宏定义、结构体、联合体、枚举的定义、函数声明、重命名、其他头文件、条件编译、外部引用
库:把一些常用的函数的目标文件打包在一起,提供相应的函数接口,便于程序员使用。本质上来说库是一种可执行代码的二进制形式文件。
4. 库的分类
静态库和动态库,本质区别时代码载入的时刻不同。
静态库
静态库在程序编译时会被复制到目标代码中, 以 .a结尾。
优点:程序运行的时候不再需要静态库,运行时无需加载库,运行速度快,
缺点:静态库中的代码复制到了程序中,因此体积较大;静态库升级后,程序需要重新编译链接。
动态库
动态库是在程序运行时才被载入代码中。也叫共享库,以 .so结尾。
优点:程序在执行时加载动态库,代码体积小;程序升级更简单;
不同应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例。
缺点:运行时还需要动态库的存在,移植性较差。
5.静态库的制作
(1) 将源文件编译生成目标文件 xx.o
gcc -c fun.c -o fun.o
(2) 创建静态库用ar命令,将很多 .o文件转换成一个 .a文件
ar crs libmyfun.a fun.o
静态库文件名的命名规范是以 lib为前缀,紧接着跟静态库名,扩展名为 .a
(3) 测试使用静态库:
gcc main.c -L. -lmyfun //-L指定库的路径 -l 指定库名
执行:./a.out
6.动态库制作
(1) 用gcc创建共享库
gcc -fPIC -c fun.c -o fun.o // -fPIC创建与地址无关的编译程序
gcc -shared -o libfun.so fun.o
(2) 测试使用动态库
gcc main.c -L. -lfun
执行:./a.out
-L路径:指定库的路径
-l库名:指定库名
-I(大写i):指定头文件路径,默认查找路径/usr/include
#include <stdio.h> //从系统路径下查找头文件
#include "head.h" //从当前路径下查找此头文件
ldd可执行文件名:查看链接的动态库
问题
可以正常编译通过,但是运行时报错error while loading shared libraries: libmyadd.so: cannot open shared object file: No such file or directory
原因:当加载动态库时,系统会默认从/lib或/usr/lib路径下查找库文件,所以需要把库拷贝到/usr/lib或者lib目录下,编译时不用-L加路径了,直接gcc main.c -lfun就可以了
解决方法(有三种):
(1) 把库拷贝到/usr/lib和/lib目录下。(此方法编译时不需要指定库的路径)
(2) 在LD_LIBRARY_PATH环境变量中加上库所在路径。
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
(终端关闭,环境变量就没在了)
(3) 添加/etc/ld.so.conf.d/*.conf文件。把库所在的路径加到文件末尾,并执行 ldconfig刷新
sudo vi xx.conf
添加动态库存在的路径,如:
/home/hq/work/lib
7. 静态库与动态库总结
静态库:编译阶段,体积大,移植性好,升级麻烦
动态库:运行阶段,体积小,移植性弱,升级简单
可以看出静态库需要重新编译链接,升级麻烦
动态库只需要重新生成动态库,不需要重新编译,升级简单