文件 IO 笔记

一、什么是文件 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. 静态库与动态库总结

静态库:编译阶段,体积大,移植性好,升级麻烦

动态库:运行阶段,体积小,移植性弱,升级简单

可以看出静态库需要重新编译链接,升级麻烦

动态库只需要重新生成动态库,不需要重新编译,升级简单

  • 22
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值