输入:从文件到内存。
输出:从内存到文件。
标准IO
C语言标准的一系列用来进行输入和输出的函数。
- 可跨平台。
- 标准IO通过缓冲机制,减少系统调用。实现最大化提升效率。
- 文件流指针。通过一个结构体来操作。
文件的基本概念
- 一组相关数据的有序集合。(视频、音频、图片)
- 文件名:这组数据集合的名字。
- 文件的类型:
-
- 普通文件。
- ASCII码文件。(文本文件)
- 二进制文件。
- d 目录文件。
- c 字符设备文件。
- b 块设备文件。
- l 链接文件。
- p 管道文件。
- s 套接字文件。(socket)
-
- 系统调用:操作系统提供给应用程序的接口。
- 缓冲区:减少系统调用的次数,减少系统的开销。
- FILE指针:结构体类型的别名。(标准IO中已有的结构体)
- 流:打开文件时就会有缓冲区。(存在与内存和缓存之间)对文件的操作就是对流的操作。标准IO就是通过流来操作文件。
流
- 文本流:ASCII码文本文件。
- 二进制流:二进制文件。
缓冲区
- 全缓存:当缓冲区满了(一般为1024字节)或者程序运行结束,再或者强制刷新缓冲区(fflush)时才会刷新缓存。
- 行缓存:当缓冲区满了或者程序运行结束,再或者强制刷新缓冲区,再或者遇到换行符时会刷新缓存。
- 不缓存:没有缓冲区,直接输出。当程序运行起来时,有三个文件默认打开(标准输入stdin、标准输出stdout、标准出错stderr)stderr是没有缓冲区的。
相关函数
- fopen:FILE *fopen(const char *open, const char *mode)
-
功能:打开一个流。
-
参数1:要打开的文件的路径。
-
参数2:打开方式。(r:以只读方式打开,该文件必须存在。
r+:可读可写方式打开,该文件必须存在。
w:以只写的方式打开文件,若文件存在清空,不存在则新建。
w+:可读可写方式打开,文件不存在新建,文件存在清空文件。
a:只写,文件不存在新建,存在追加在末尾。
a+:可读可写,文件存在追加在末尾,不存在新建。) -
返回值:成功返回流指针,失败返回NULL。
-
- fclose:FILE *fclose(FILE *fp);
- 功能:关闭一个流。
- 参数:要关闭的流指针。
- 返回值:成功返回1,失败返回-1(EOF)
- 注意:EOF只能判断是否到文本文件的末尾,不能判断二进制文件。
读写操作
- fgetc:int fgetc(FILE *Stream)
- 功能:从指定的流中读取数据。
- 参数:需要操作的流指针。
- 返回值:成功返回读到的字符ASCII码值,失败返回-1。
- fputc:int fputc(int c, FILE *Stream)
- 功能:从指定流中写入数据。
- 参数1:要输出的字符。
- 参数2:需要输出的流指针。
- 返回值:成功返回输出的字符ASCII码值,失败返回-1。
- feof:int feof(FILE * Stream);(这里注意避坑。feof的非0值返回在末尾标记的下一个)
- 功能:判断是否到文件的末尾。判断文本文件的同时也可用于二进制文件。
- 参数:要操作的流。
- 返回值:为0表示没有到达文件末尾,非0表示到达文件末尾。
- fgets:char* fgets(char* s, int size, FILE* Stream);
- 功能:按行读文件。
- 参数1:读到空间的首地址。
- 参数2:读的大小。
- 参数3:读哪个流。
- 返回值:成功返回读到的字符串的首地址,失败或读完返回NULL。
- fputs:int fputs(const char* s, FILE* Stream);
- 功能:向文件输出一行字符串。
- 参数1:需要输出的字符串首地址。
- 参数2:要输出的流。
- 返回值:成功返回非负数,失败返回-1。
- fread:size_t fread(void* ptr, size_t size, size_t number, FILE* stream);
- 功能:按块读。
- 参数1:读到内存空间的首地址。
- 参数2:一个块的大小。(一般传1不会漏数据)
- 参数3:块的数量。(一般传sizeof(ptr))
- 参数4:要操作的流。
- 返回值:成功返回读到的块数(向下取整,其余的不要),失败返回-1。
- fwrite:size_t fwrite(const void* ptr, size_t size, size_t number, FILE* stream);
- 功能:往流中写。
- 参数1:需要写入内容的内存空间的首地址。
- 参数2:一个块的大小。(一般传1不会漏数据)
- 参数3:块的数量。(一般传sizeof(ptr))
- 参数4:要写入哪个流。
- 返回值:成功返回读到的块数(向下取整,其余的不要),失败返回-1。
- fseek:int fseek(FILE *stream,long offset,int origin);
- 功能:自定义文件位置。
- 参数1:正在操作的流。
- 参数2:偏移量。(正数以参照物为基准向后,负数向前)
- 参数3:参照物。
- 返回值:
origin有三种位置,一种是起始位置:SEEK_SET,第二种是当前位置:SEEK_CUR,第三种是末尾位置:SEEK_END。
fseek(fr, 0, SEEK_SET) //开头光标为参照点,光标走到文件开头
fseek(fr, -len, SEEK_CUR) //当前光标开始向前偏移
fseer(fr, -len, SEEK_END) //从末尾开始向前偏移len
- ftell:int ftell(FILE *stream);
- 功能:返回文件流中当前光标位置。
以上标准 IO打开文件自带缓冲区所以默认会打开流,下面的文件 IO操作系统不带缓冲区将不会自动开启流
文件IO
- 不带缓冲区,每次读写都由系统调用。
- 文件描述符。(非负的整数)
- 一般用于设备文件的操作。
相关函数
- open: 头文件:<sys/types.h><sys/stat.h><fcntl.h>
-
int open(const char* pathname, int flags);(文件必须存在)
-
int open(const char* pathname, int flags, mode_t mode);
这里mode 权限给了 0777 但是实际却不是0777权限,这里有个文件掩码 可以通过 umask 查看文件掩码 最终的权限0777与0002在先取反再与操作得到真正的文件权限 0775
-
打开方式(flags),以下参数必选其一
1.O_RDONLY:只读打开。
2.O_WRONLY:只写打开。
3.O_RDWR:可读可写打开。
其余的与上面参数进行或运算:
4.O_CREAT:文件不存在则新建,但必须使用前3个参数其中一个和creat进行或操作。
5.O_EXCL:文件存在返回错误信息。
6.O_NOCTTY:若文件为终端,那么终端不可以作为这个进程的控制终端。(阻塞)
7.O_TRUNC:文件存在清空。
8.O_APPEND:文件存在追加到末尾。 -
mode:权限,一般用8进制表示。
-
返回值:打开成功文件的描述符。
-
- close:头文件:<unistd.h>
- int close(int fd); 入参为open打开的文件描述符。
- 成功返回0,失败-1。
读写操作
头文件:<unistd.h>
- read:ssize_t read(int fd, void* buf, size_t count);
- 参数1:文件描述符。
- 参数2:指定存储器读出数据的缓冲区。
- 参数3:指定读出的字节数。
- 返回值:成功返回读到的字节数,0表示已到达末尾,-1出错。
- write:ssize_t write(int fd, void *buf, size_t count);
- 参数1:文件描述符。open的返回值。
- 参数2:指定存储器写入数据的缓冲区。
- 参数3:指定写入的字节数。
- 返回值:成功返回写的字节数,0表示到达文件末尾,-1出错。
相关函数
- lseek:off_t lseek(int fd, off_t offset, int whence);
- 功能:创建空洞文件,实现光标的移动。
- 参数1:文件描述符。
- 参数2:偏移量。
- 参数3:起始位置。同fseek一样的宏
- 返回值:成功当前读写位置,失败-1。
- 空洞文件:在未下载完成时就已经占据了文件的全部大小的文件。
- 空洞文件的创建:
- 只写方式打开文件。
- 使用lseek改变文件下标位置。
- 向该标记末尾添加\0
- 关闭文件。
目录相关操作
打开目录
头文件:<sys/types.h> <dirent.h>
- opendir:DIR* opendir(const char* name);
- 参数:路径。
- 返回值:成功返回DIR*类型的流。失败返回NULL。
关闭目录
头文件:<sys/types.h> <dirent.h>
- closedir:int closedir(DIR* dir);
- 参数:目录流,opendir的返回值。
目录操作
头文件:<dirent.h>
- struct dirent* readdir(DIR* dir);
- 打开目录文件。
- 返回值:成功返回dirent结构体,失败返回NULL。
- struct dirent {
ino_t d_ino; /* Inode数*/
off_t d_off; /* 不透明值 */
unsigned short d_reclen; /* 记录的长度 */
unsigned char d_type; /* 文件类型 */
char d_name[256]; /* 文件名 */
};
静态库与动态库的区别
库:是一个二进制文件,包含的代码可以被程序调用。
静态库的特点
- 编译时把静态库中相关二进制文件拷贝到可执行文件中,运行时不需要链接库。
- 程序运行时无需加载库,运行速度快。(优点)
- 占用更多的内存空间。静态库升级后程序需要重新编译。(缺点)
动态库的特点
- 编译时仅记录使用哪个共享库(动态库)中的哪个符号(函数),不复制库中相关代码,运行时加载共享库。
- 程序不包含库中的代码,代码尺寸比较小。(优点)
- 库加载方便,升级后无需重新编译。
- 使用较多。
静态库的创建
- 编写源代码。(去掉main函数)
- 将源代码生成对应的.o文件。
- ar crs 命令 生成静态库。
- 静态库的命名规范:lib+库名.a
- ar:库文件维护程序的名称。
- c:创建一个库,不管是否存在,都创建。
- r:在库中插入模块。
- s:创建目标文件索引,这个参数在创建较大的库时能加快时间。
- 编译时使用 -L+静态库的目录 -l+库名 完成链接。
动态库的创建
-
编写源代码。(去掉main函数)
-
将源代码生成对应的.o文件。(这里需要多使用 -fPIC 参数生成与位置无关的代码,可以在任何位置执行)
-
创建共享库。(gcc -shared)
-
共享库的命名规范:lib+库名.so.版本号
-
共享库创建软连接。(为了让编译器编译的时候方便找到自己写的共享库)
-
链接共享库。(注意:此时是在系统库目录下找自己写的共享库是找不到的)
-
将自己写的共享库软连接复制到系统库路径下。/usr/lib 或者 lib
添加到系统库路径下后,编译链接时就不用 -L参数
-
运行