IO小结
学习IO的目的是为了将数据存储以及对文件的操作
文件:在linux下普通文件或硬件设备均为文件
linux下的7种文件属性
(1)普通文件 -
(2)目录文件 d
(3)符号链接文件即软连接 I
(4)字符设备文件 c
(5)块设备文件 b
(7)套接字文件 s
IO
一、IO的分类
第一类:标准IO(高级磁盘IO)
第二类:文件IO(低级磁盘IO)
二、标准IO和文件IO的区别
1.标准IO
- 只要系统支持c库,就可以使用这一类标准IO提供的函数,移植性较高
- 操作的文件一般是普通文件
- 属于高级磁盘IO,即存在缓冲区,减少了用户态切换至内核态,最后由返回永辉态的频繁操作,减少系统开销
- 通过_文件流(FILE *)_操作文件
(打开文件时,系统会自动将该文件的信息定义为结构体atruct FILE来进行存储可以通过FILE *文件指针来操作文件)
2.文件IO
- 只能应用于UNIX系统(一般支持POSIX的标准),移植性不高
- 操作的文件可以是普通文件或设备文件(硬件)
- 属于低级磁盘IO,没有缓冲区,每一次都是系统调用,都会存在用户空间和内核空间的频繁切换工作,所以可以_直接对设备进行读写操作_
- 通过文件描述符_(非负的数字)_操作文件
三、系统调用和缓冲机制
标准IO
一.流
1.概念
文件被打开时,创建的结构体名为FILE的结构体指针,形象的称为流
- 结构体指针被称为流的原因
因为标准IO存在缓冲区,所以每一次向缓冲区不断放入数据,每一次的放入的数据均是需要通过文件指针来进行读写指向的文件
- 存在三个特点
1)有源头:APP
2)有目的:缓冲区
3)持续性:不断放入数据到缓冲区
具备以上三个特点就会形成流,所以通过文件指针操作文件可以理解未是通过操作流来操作文件
二、流的分类
文件被打开时会默认具备3个类:
- stdin:标准输入(键盘) --> 0
- stdout:标准输出(终端) --> 1
- stderr:标准出错(会向终端打印,不带缓冲区,即每次出错会立即刷新缓冲区) --> 2
因此,当用户想要操作文件时,需要先打开文件,此时会拥有一个文件流指针,以后通过该文件指针就可以操作文件
三、缓冲机制
- 名词解释:
缓冲区被放满
#include<stdio.h>
#include<unistd.h>
int main(int argc, const char *argv[])
{
while(1)
{
printf("hello");
sleep(1);//延时函数
}
return 0;
}
此时处于不断向缓冲区放hello,延时时间为1s放一次,因此当缓冲区没有被放满时,不会刷新缓冲区,也就喊不到hello
程序结束
return 0;//程序结束引起的刷新
强制刷新
#include<stdio.h>
int fflush(FILLE *stream);
1.全缓冲
缓冲区被放满,程序结束,强制刷新,则会引起缓冲区的刷新
2.行缓冲
缓冲区被放满,程序结束,强制刷新,遇到换行符,则会引起缓冲区的刷新
3.不带缓冲区
不存在缓冲区的概念,每一次读写都是直接输出stderr
四、操作文件
1.打开文件
FILE *fopen(const char *path, const char *mode);
//参数1:需要打开文件的名字(包含路径、指针类型)
//参数2:打开文件的方式(指针类型)
//返回值:打开文件成功之后的文件流指针
// 失败为NULL
标准IO-fopen()-mode参数
r或rb:打开只读文件,该文件必须存在
r+
w
w+
a
a+
打开文件代码
2.操作文件 读写文件
2.1按照字符操作
#include<stdio.h>
int fgetc(FILE *stream);
//从指定的文件流中获取一个字符
//参数1:制定获取一个字符所处文件的文件流
//返回值:成功返回获取到的字符值
// 读取到文件末尾返回EOF(-1)
// 操作中失败返回负数
#include<stdio.h>
int fputc(int c, FILE *stream);
//向指定的文件流中输出一个字符
//参数1:需要输出的指定字符(字符被称为单字节的整型)
//参数2:指定输出字符到文件对应的文件流
//返回值:成功返回刚写入的字符值
// 失败返回EOF(-1)
2.2按照行操作
标志:寻找’\n’
//参数1:即将存储内存空间首地址
//参数2:存储内容空间的大小(可以sizeof()测得)
//参数3:指定的文件流
//返回值:成功返回存储内容空间首地址
// 失败返回BULL
char *fgets(char *s, int size FILE *stream);
fgets什么时候会返回
- 第一次遇到’\n’就会返回
- 该行较长时,需要多次读取才能读完,所以位于读到’\n’之前的多次都是读取到size-1就返回
2.3按照块操作
//参数1:存储读取一块内容之后的空间地址
//参数2:块的大小,建议给1
//参数3:块的个数,建议给sizeof()
//参数4:要读取的文件对应的文件流
//返回值:成功代表读取的块的个数
// 失败返回0
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
//参数1:需要写入的内容的空间地址
//参数2:块的大小,建议给1
//参数3:块的个数,建议给sizeof()
//参数4:要写入的文件对应的文件流
//返回值:成功代表写入的块的个数
// 失败返回0
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
3.关闭文件
#include<stdio.h>
int fclose(FILE *stream);
因此,打开文件和关闭文件在标准IO下都是使用同一个接口,只是在操作文件的方式上存在选择
五、文件流的定位(文件指针的指示位置)
定位流
#include<stdio.h>
//用户设定stream流的文件位置指示
//返回值:成功返回0
// 失败返回-1,并设置errno
//whence参数:SEEK_SET/SEEK_CUR/SEEK_END
int fseek(FILE *stream, long offset, int whence);
//用于取得当前的文件位置
//返回值:调用成功返回当前文件位置指示
// 出错返回-1
long ftell(FILE *stream);
//用于设定流的文件位置指示
//调用成功无返回值
//rewind()等价于(void)fseek(stream, 0, SEEK_SET)
void rewind(FILE *stream);
六、特性函数
1. perror(“string”);
可以输出错误的原因
2. feof(FILE *Stream);
作用:判断文件是否抵达末尾(不管是文本文件还是二进制文件,都可以判断)
返回值:抵达文件末尾,返回非0;未抵达文件末尾,返回0
文件IO
一、操作文件IO
头文件
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
1.打开文件
//功能:打开指定的文件
//参数1:所需打开文件的名字(包含路径)
//参数2:打开文件的方式为:主标志|副标志
//返回值:成功代表一个大于0的数字(文件描述符)
// 失败返回-1(errno ie set)
int open(const char *pathname, int flags);
//功能:打开指定的文件
//参数1:所需打开文件的名字(包含路径)
//参数2:打开文件的方式为:主标志|副标志
//参数3:当需要一个O_CREAT时,就要写该参数来为创建的新文件指明权限(8进制)
//返回值:成功代表一个大于0的数字(文件描述符)
// 失败返回-1(errno ie set)
int open(const char *pathname, int flags, mode_t )
- 主标志
O_RONLY:只读方式打开文件
O_WRONLY:可写方式打开文件
O_RDWR:读写方式打开文件 - 副标志
O_CREAT:如果该文件不逊在,就创建一个新文件,并用第三的参数为其设置权限
O_EXCL:如果使用O_CREAT时文件存在,则可返回错误消息。这一参数可测试文件是否存在
O_NOCTTY:使用本参数时,若文件为终端,那么终端不可以作为调用open()系统调用的那个进程的控制终端
O_TRUNC:如文件已经存在,那么打开文件时先删除文件中原有数据
O_APPEND:以添加的方式打开文件,所以对文件的写操作都在文件的末尾进行
2.关闭文件
#include <unistd.h>
//功能:关闭一个文件描述符
//参数:打开文件成功之后的文件描述符
int close(int fd);
3.写文件
#include<unistd.h>
//功能:向指定的文件描述符的文件内写入内容
//参数1:打开文件之后的文件描述符
//参数2:所需写入的内容所在空间首地址
//参数3:需要写入的字节数
//返回值:成功返回写入的字节数
// 返回0代表没有写入任何内容
// 返回-1代表出错(errno ie set)
size_t write(int fd, const void *buf, size_t count);
4.读文件
#include<unistd.h>
//功能:从指定的文件描述符的文件中读取内容
//参数1:打开文件之后的文件描述符
//参数2:存储读取到结果的空间首地址
//参数3:可以读取的字节数,用sizeof测得
//返回值:成功返回写入的字节数
// 返回0代表没有写入任何内容
// 返回-1代表出错(errno ie set)
ssize_t read(int fd, void *buf, size_t count);
5.文件IO中文件指定位置
#include<sys/types.h>
#include<unistd.h>
//参数1:文件描述符
//参数2:偏移量,每一读写操作所需要移动的距离,单位是字节的数量,可正可负(向前移,向后移)
//参数3:当前位置基点
//返回值:成功返回文件的当前位移
// 出错返回-1
off_t lseek(int fildes, off_t offset, int whence);
- SEEK_SET:当前位置为文件的开头,新位置为偏移量的大小
SEEK_CUR:当前位置为文件指针的位置,新位置为当前位置加上偏移量
SEEK_END:当前位置为文件的结尾,新位置为文件的大小加上偏移量的大小
二、空洞文件
三、操作目录
1.打开目录
#include<sys/types.h>
#include<dirent.h>
//返回值:返回一个目录流指针,可以作为readdir的参数进行绑定需要读取目录的信息
DIR *opendir(const char *name);
2.操作目录
#include<dirent.h>
//返回值:代表需要读取的目录信息
struct dirent *readdir(DIR *dirp);
struct dirent
{
ino_t d_uno;
off_t d_off;
unsigned short d_reclen;
unsigned char d_type;
char d_name[256]; //文件名
}
3.关闭目录
#include<sys/types.h>
#include<dirent.h>
int closedir(DIR *dirp);//DIR *为opendir函数的返回值
4.测试文件属性
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
//穿透函数(追踪函数):stat
int stat(const char *pathname, struct stat *statbuf);
int fstat(int fd, struct stst *statbuf);
//非穿透函数lstat
int lstat(const char *pathname, struct stat *statbuf);
struct stat
{
}
穿透函数:当测试的文件是软链接文件时,此时测得属性是属于软链接所指向的源文件的属性
非穿透函数:当测试文件时软链接文件时,此时测得属性是软链接文件自身的属性
四、制作静态库和动态库
1.什么是库
- 本质上:库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。而windows和linux的本质不同,所以二者库的二进制是不兼容的
- linux下的库有两种,静态库和动态库,其不同点在于代码被载入的时刻不同
- 静态库在编译时会被连接到目标代码中,程序运行时将不再需要该静态库,所以体积较大
- 动态库在编译时不会被连接到目标代码中,而是在程序运行时才被载入,因此程序运行时还需要动态库存在,所以代码体积较小
2.库存在的意义
- 库是别人写好的、现有的、成熟的、可以复用的代码,你可以使用但要遵循许可协议
- 现实中每个程序都要依赖很多基础的底层库,不可能每个人的代码都从0开始,因此库的存在意非同寻常
- 共享库的好处是,不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例
3.静态库
3.1相关概念
- 静态库对函数库的链接是放在编译时期完成的
- 程序在运行时与函数库再无瓜葛
- 浪费空间和资源,因为所有相关的对象文件(object file)与牵涉到的函数库(library)被链接合成一个可执行文件(executable file)
- 编译时把静态库中的相关代码复制到可执行程序中
- 优点:程序运行时,无需加载库运行速度更快
- 缺点:占用更多磁盘和内存空间,静态库升级后,需要重新编译链接
3.2如何创建静态库
-
编写源代码
只要是一个文件,文件的内容是一个子函数即可(目标是为了生成库)
vim file_length.c / vim file_Line.c -
生成相应的.o文件
gcc -c file_length.c -o file_length.o
gcc -c file_Line.c -o file_Line.o -
创建成相应的静态库
静态库的命名方式:lib + 库名.a
ar acs libfile.a *.o(注意:libfile.a中的file是库名)- ar:用来创建静态库
- r:在库中插入模块
- c:创建一个库,不管库是否存在,都将创建
- s:创建目标文件索引,这在创建较大的库时能加快时间
-
编译时链接静态库
eg:gcc test.c -o test -L -lfile (注意:-L. 中的点. 代表当前路径(库的位置)) -
运行
eg:./ 可执行文件名
4.动态库
4.1概念 共享库(动态库)
- 动态库吧一些库函数的链接载入推迟到程序运行的时期(runtime)
- 可以实现进程之间的资源共享
- 将一些程序升级变得简单
- 甚至可以真正做到链接载入完全由程序员在程序代码中控制
- 编译时仅记录用到哪个共享库中的哪个符号,不复制共享库中的相关代码
- 优点:程序不包含库中代码,体积较小,库升级方便,无需重新编译
- 缺点:在运行时需要加载共享库
4.2如何创建动态库
- 编写源代码
- vim file_length.c / vim file_Line.c
- 分别生成响应的.o文件
- gcc -c -fPIC file_length.c -o file_length.o
- gcc -c -fPIC file_Line.c -o file_Line.o
- 注意:-fPIC要生成与位置无关的代码,可以在任何位置执行
- 生成动态库(共享库)动态库的命名方式:lib + 库名.so.版本号(用数字来表示版本号)
- gcc -sheared *.o -o libfile.so.l
- 为共享库文件创建软链接文件,创建软连接文件的目的是为了能够让我们编译时找到共享库
-
ln -s libfile.so l(绝对路径) libfile.so
-
软链接文件的取名方式一般就是去掉版本号即可
-
- 拷贝链接文件到指定的目录/use/lib下
- sudo cp libfile.so /usr/lib
- 编译并链接共享库
- gcc main_test.c -o myApp -lfile
- 此时动态库的软链接必须存在于/usr/lib目录下方可成功找到共享库
- 运行myApp可执行文件即可
4.3用gcc创建共享库
- #gcc -fPIC -Wall -c hello.c
- #gcc -shared -o libmyhello.so hello.o
-fPIC创建与地址无关的编译程序
总结
对文章进行总结: