IO 小结

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

  1. 只要系统支持c库,就可以使用这一类标准IO提供的函数,移植性较高
  2. 操作的文件一般是普通文件
  3. 属于高级磁盘IO,即存在缓冲区,减少了用户态切换至内核态,最后由返回永辉态的频繁操作,减少系统开销
  4. 通过_文件流(FILE *)_操作文件
    (打开文件时,系统会自动将该文件的信息定义为结构体atruct FILE来进行存储可以通过FILE *文件指针来操作文件)

2.文件IO

  1. 只能应用于UNIX系统(一般支持POSIX的标准),移植性不高
  2. 操作的文件可以是普通文件或设备文件(硬件)
  3. 属于低级磁盘IO,没有缓冲区,每一次都是系统调用,都会存在用户空间和内核空间的频繁切换工作,所以可以_直接对设备进行读写操作_
  4. 通过文件描述符_(非负的数字)_操作文件

三、系统调用和缓冲机制

标准IO

一.流

1.概念

文件被打开时,创建的结构体名为FILE的结构体指针,形象的称为流

  • 结构体指针被称为流的原因

因为标准IO存在缓冲区,所以每一次向缓冲区不断放入数据,每一次的放入的数据均是需要通过文件指针来进行读写指向的文件

  • 存在三个特点

1)有源头:APP
2)有目的:缓冲区
3)持续性:不断放入数据到缓冲区

具备以上三个特点就会形成流,所以通过文件指针操作文件可以理解未是通过操作流来操作文件

二、流的分类

文件被打开时会默认具备3个类:

  1. stdin:标准输入(键盘) --> 0
  2. stdout:标准输出(终端) --> 1
  3. 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什么时候会返回

  1. 第一次遇到’\n’就会返回
  2. 该行较长时,需要多次读取才能读完,所以位于读到’\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相关概念
  • 静态库对函数库的链接是放在编译时期完成的
    1. 程序在运行时与函数库再无瓜葛
    2. 浪费空间和资源,因为所有相关的对象文件(object file)与牵涉到的函数库(library)被链接合成一个可执行文件(executable file)
  • 编译时把静态库中的相关代码复制到可执行程序中
    1. 优点:程序运行时,无需加载库运行速度更快
    2. 缺点:占用更多磁盘和内存空间,静态库升级后,需要重新编译链接
3.2如何创建静态库
  1. 编写源代码
    只要是一个文件,文件的内容是一个子函数即可(目标是为了生成库)
    vim file_length.c / vim file_Line.c

  2. 生成相应的.o文件
    gcc -c file_length.c -o file_length.o
    gcc -c file_Line.c -o file_Line.o

  3. 创建成相应的静态库
    静态库的命名方式:lib + 库名.a
    ar acs libfile.a *.o(注意:libfile.a中的file是库名)

    • ar:用来创建静态库
    • r:在库中插入模块
    • c:创建一个库,不管库是否存在,都将创建
    • s:创建目标文件索引,这在创建较大的库时能加快时间
  4. 编译时链接静态库
    eg:gcc test.c -o test -L -lfile (注意:-L. 中的点. 代表当前路径(库的位置))

  5. 运行
    eg:./ 可执行文件名

4.动态库

4.1概念 共享库(动态库)
  • 动态库吧一些库函数的链接载入推迟到程序运行的时期(runtime)
    1. 可以实现进程之间的资源共享
    2. 将一些程序升级变得简单
    3. 甚至可以真正做到链接载入完全由程序员在程序代码中控制
  • 编译时仅记录用到哪个共享库中的哪个符号,不复制共享库中的相关代码
    1. 优点:程序不包含库中代码,体积较小,库升级方便,无需重新编译
    2. 缺点:在运行时需要加载共享库
4.2如何创建动态库
  1. 编写源代码
    • vim file_length.c / vim file_Line.c
  2. 分别生成响应的.o文件
    • gcc -c -fPIC file_length.c -o file_length.o
    • gcc -c -fPIC file_Line.c -o file_Line.o
    • 注意:-fPIC要生成与位置无关的代码,可以在任何位置执行
  3. 生成动态库(共享库)动态库的命名方式:lib + 库名.so.版本号(用数字来表示版本号)
    • gcc -sheared *.o -o libfile.so.l
  4. 为共享库文件创建软链接文件,创建软连接文件的目的是为了能够让我们编译时找到共享库
    • ln -s libfile.so l(绝对路径) libfile.so

    • 软链接文件的取名方式一般就是去掉版本号即可

  5. 拷贝链接文件到指定的目录/use/lib下
    • sudo cp libfile.so /usr/lib
  6. 编译并链接共享库
    • gcc main_test.c -o myApp -lfile
    • 此时动态库的软链接必须存在于/usr/lib目录下方可成功找到共享库
  7. 运行myApp可执行文件即可
4.3用gcc创建共享库
  1. #gcc -fPIC -Wall -c hello.c
  2. #gcc -shared -o libmyhello.so hello.o
    -fPIC创建与地址无关的编译程序

总结
对文章进行总结:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值