c语言阶段学习文件操作复习
1 打开文件
FILE fopen(const char path, const char* mode)**
path:需要打开的文件路径,可以是绝对路径,也可以是相对路径
mode:以什么方式打开文件
- r:以读模式打开文件,如果文件不存在,则报错
- r+:以读写模式打开文件,如果文件不存在,则报错
- w:以写模式打开文件,如果文件不存在则创建文件。如果文件存在,则清空文件内容,文件流指针指向文件的头部。
- w+:以读写模式打开,如果文件不存在则创建文件。如果文件存在,则清空文件内容,文件流指向文件的头部。
- a:以追加模式打开,如果文件不存在则创建文件;并不能对文件进行读,只能在文件的末尾进行追加写
- a+:以追加模式打开,如果文件不存在则创建文件,支持读文件,在文件的末尾进行写。
返回值:返回了文件流指针
fclose(fp);//使用完文件指针函数后需要关闭文件流指针,否则会导致内存泄漏
2 读文件
**size_t fread(void ptr, size_t size, size_t nmemb, FILE stream);
ptr:将读到的内容保存在ptr当中
size:块的大小,单位是字节
nmemb:块的个数,期望读到的个数
size * nmemb,单位是字节,用来衡量总共读到的字节数量,如果size不为1,块的大小不为1,则不能返回值来讲读到了多少字节。
常见用法:将size置为1,每一块的大小就是1;要从文件中读多少字节,就设置多少块就可以了。
stream:文件流指针,从哪里读
返回值:返回成功读到的文件块的个数,实际读到的个数
3 写文件
**size_t fread(void ptr, size_t size, size_t nmemb, FILE stream);
ptr:要往文件当中写的内容
size:块的大小,单位是字节
nmemb:块的个数,期望读到个数,判断写了多少字节,size * nmemb
一般用法:将size设置为1,意味着块的个数就相当于写多少个字节。
stream:文件流指针,往哪里写
返回值:返回成功写入块的个数
系统调用的文件操作
1 open(const char* filename, int falgs, mode_t mode)
filename:待打开的文件名称
flags:以何种方式打开
- O_RDWR:以读写 模式打开
- O_RDONLY:以读模式打开
- O_ERONLY:以写模式打开
- O_CREAT:如果文件不存在,则创建文件
mode:对于新创建出来的文件,设置文件权限
返回值:返回文件描述符
查看进程pid:ps aux | grep [xxx] --> 进程PID
查看进程打开的文件资源:cd /proc/[pid]/fd -->打开的文件资源
实例代码如下:
== 问题1:文件描述符为啥是3?==
执行上述代码后,ps aux | grep sysfileoper查看当前进程的PID为13349
在命令行一栏输入:cd /proc/13349后,ls查看当前进程中文件资源如下
进入fd文件,ll查看进程状态:
其中上图中0代表标准输入,1代表标准输出,2代表标准错误。
也就是说启动任何一个程序,操作系统都会打开三个文件描述符,标准输入,标准输出,标准错误。所以,当程序中新打开的文件描述符,就会一次往下去排布(粗略理解)
== 问题2:打开模式,不同的宏在组合的时候为什么要按位或?==
进一步说明:
2 常用的系统调用函数
ssize_t write(int fd,const void buf,size_t count)*
fd:文件操作符
buf:写入的数据
count:写入的数据大小(单位是字节)
**
ssize_t read(int fd, void* buf, size_t count)**
fd:文件操作符,从哪里进行读
buf:读到哪里去(程序员在代码当中定义的缓冲区)
count:最大可以读多少个字节,注意要预留 \0 的位置,否则会导致在访问buf的时候可能会产生越界访问,导致程序崩溃。
off_t lseek(int fd, off_t offset, int whence)
fd:文件操作符
offset:偏移量
whence:偏移到哪
SEEK_SET:偏移量放到当前文件操作的首部
SEEK_CUR:偏移量放到当前文件操作的位置
SEEK_END:偏移量放到当前文件操作的尾部
int close(int fd)
如果一味的在程序中打开文件,而不去关闭文件,最终会受到open files的限制
文件描述符
文件描述符就是一个正整数
文件描述符就是内核当中维护fd_array数组下标,下标是从0开始的,所以文件描述符是一个正整数;
当程序员操作文件的时候,其实就是通过文件描述符,找到fd_array数组当中对应的元素,每一个元素对应一个文件信息,内核通过操作数组元素对应的文件信息,找到对应的文件,从而实现文件操作。
文件描述符的分配规则
创建一个进程就会默认打开标准输入、标准输出、标准错误着三个文件描述符。
用close(0)关闭0号位置的文件描述符,新打开文件描述符后占用的是0号位置。
使用规则:最小末占用规则,只要fd_array数组当中从小到大,哪个位置没有占用,则新打开的文件就会占用该位置。
文件描述符与文件指针的关系
fopen/fread/fwrite/fseek/fclose ==> 文件流指针 ==>C库当中的函数
open/read/write/lseek/close ==>文件描述符 ==>系统调用函数
文件流指针:typedef struct _10_FILE FILE,文件流指针本质是一个结构体,而这个结构体是struct _10_FILE,如下图所示。
- 之前在进程终止章节,刷新了缓冲区,其实刷新的是C库当中维护的缓冲区,并非是内核当中维护的。
exit() —> 刷新缓冲区 —> C库函数
_exit() —> 不会刷新缓冲区 —> 系统调用 - 为什么使用文件流指针的时候,打印内容的时候,如果不加上\n或者强制刷新缓冲区的操作,我们输出的内容就不会立即打印到屏幕上,因为不加上\n,输出的内容就一直停留在写缓冲区上。
- 当我们在使用文件流指针的时候,其实文件流指针对应的数据结构内容也保存了对应的文件描述符,当使用流指针进行读写的时候,其实也是C库当中操作文件描述符来完成读写的。
重定向
printf(“hello world\n”) ==> 标准输出
现在想让“hello world”字符串输出到文件当中
文件接口函数:int dup2(int oldfd, int newfd), oldfd和newfd都是文件描述符,其中newfd拷贝oldfd内容
eg:想要标准输出变成一个程序员打开的文件
dup2(3, 1);
注意:如果oldfd是一个无效的文件描述符,则dup2什么事情都不需要干,newfd还是指向原来的文件;如果newfd和oldfd是同样的值,则dup2什么事情都不干。错误,返回值小于1。
命令行当中重定向:
静态库和动态库
为了将完成将某项功能的函数,打包起来放到一起,提供给别人一起使用。为了保护商业秘密。
动态库
1 后缀:在win下:xxxx.dll 在Linux下:libxxxx.so
2 动态库如何去生成与使用
动态库的生成需要:
- 1 gcc/g++:编译器
- 2 命令行参数:-shared:生成共享库的命令行参数;-fPIC:产生位置无关的代码。
eg:创建一个新的test.c文件,使用 gcc -shared -fPIC test.c -o libtest.so, 生成动态库文件,ls查看后如下:
动态库的使用:
gcc/g++命令行当中
-L [path]:指定链接的库文件路径 -l [库名称(去掉前缀,也去掉后缀)] 指定可执行程序在编译过程中依赖的库文件
使用== gcc main.c -L . -ltest ==,虽然在mian.c中没有声明print()函数,但依旧可以用。
ldd a.out查看可执行程序依赖的库文件,可以看到有libtest.so这个动态库文件,如下:
以上操作都是动态库和可执行程序在一个文件下才可以实现,问题:无论将程序依赖的动态库放到哪里,都可以使程序找到自己依赖的动态库?
- 将动态库放到可执行程序的目录下,可执行程序会搜索当前的目录。
- 环境变量
LD_LIBRARY_PATH:保存动态库的搜索路径
首先查看一下当前的环境变量,此时动态库没有在当前路径下,在/home/wudu下:
在命令行中输入:export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/wudu,先将原来的环境变量加入,再将动态库的路径加到后面即可,加入后结果如下:
查看可执行程序的环境变量后,可以看到动态库添加成功:
LD_LIBRARY_PATH环境变量中有,PATH1:、PATH2:、PATH3:、…只需要将程序依赖的动态库所在的路径保存在该环境变量当中即可,可执行程序在执行的时候,能够通过LD_LIBRARY_PATH环境变量找到搜索到动态库在哪里,从而在可执行程序的时候,就能够找到动态库了。 - 不推荐使用!!!
将自己写的库文件或者第三方的库文件和操作系统以来的库文件放在一起,目录是/user/lib64
静态库
1 后缀:在win下:xxxx.lib,在win环境下编译一个依赖库的可执行程序的时候,编译阶段的时候依赖的静态库(.lib),程序运行阶段依赖是动态库(.dll)
在Linux下:libxxxx.a
2 静态库的生成和使用
静态库的生成:生成静态库的时候,需要使用.o文件来进行编译生成。
- 使用的参数是ar,命令行参数-rc,生成静态库如下:
静态库的使用:
gcc/g++命令行当中
-L [path]:指定链接的库文件路径 -l [库名称(去掉前缀,也去掉后缀)] 指定可执行程序在编译过程中依赖的库文件