文章目录
1 IO概述
IO是Input/Output的缩写,指的是输入/输出。在计算机科学和工程领域,I/O是指计算机系统与外部环境或内部组件之间进行数据交换的过程和机制。
(1)用户I/O:用户通过输入设备与计算机交互。例如,通过键盘输入文字、通过鼠标点击界面等;用户通过输出设备接收计算机的反馈。例如,显示器显示图像、打印机输出文件等。
(2)文件I/O:程序通过文件系统进行数据读写操作。例如,从硬盘读取文件数据、将数据写入文件等,计算机通过网络接口与其他计算机或设备进行数据交换。例如,通过互联网下载文件、发送和接收电子邮件等。
**(3)设备I/O:**计算机与各种外部设备之间进行数据传输。例如,读取USB存储设备的数据、与外部传感器通信等。
1.1 五层I/O模型概述
五层I/O模型是计算机系统中用于描述数据从应用程序到物理硬件的传输过程的模型。这个模型帮助理解数据在不同层次上的处理方式,以及每一层在I/O操作中的角色。
(1)应用层(Application Layer);
(2)系统调用层(System Call Layer);
(3)文件系统层(File System Layer);
(4)设备驱动层(Device Driver Layer);
(5)硬件层(Hardware Layer)。
2 IO函数的定义
2.1 标准IO函数
(1)标准IO函数根据ANSI标准,对文件IO函数进行二次封装。例如:printf函数,scanf函数;
(2)标准IO函数最终依然会去调用文件IO函数实现数据的输入输出,提高了代码的移植性;
(3)标准I/O函数通常使用缓冲区来临时存储数据。这些缓冲区可以是针对文件、标准输入、标准输出等的内存区域,用于暂时存储数据,以减少与文件或设备的直接交互次数,从而提高效率。
2.2 文件IO函数
(1)文件IO函数由操作系统提供的基本函数,与操作系统绑定,可移植性较低,又称为系统调用;
(2)例如:读函数在Windows为ReadFile,在Linux中为read;
(3)文件IO函数涉及到用户空间到内核空间切换,cpu模式切换,C代码调用汇编指令等等,属于一种耗时操作。应该尽量减少对文件IO函数的使用。**
若要直接操作内核,则必须要使用对应操作系统提供的系统调用函数。
3 标准IO函数
3.1 流和指针
流是一个数据传输通道,用于在程序和外部资源(如文件、网络、设备)之间移动数据。流以连续字节流的形式表示数据的输入和输出。
3.1.1 流的分类
(1)标准流
①stdin:标准输入流,用于从键盘或另一个程序的输出中读取数据。scanf,getchar,gets默认使用的都是stdin对应的缓冲区;
②stdout:标准输出流,用于向屏幕或另一个程序的输入中写入数据。puts,printf,putchar默认使用的都是stdout对应的缓冲区;
③stderr:标准错误流,用于输出错误消息到屏幕或日志文件。
(2)文件流:通过fopen()函数打开文件时,会创建一个文件流,提供与文件的接口,用于进行文件的读写操作。
3.1.2 流指针
(1)流指针是一个指向FILE结构体的指针,用于标识和操作文件流。FILE结构体在C标准库中定义,包含有关文件流的信息,如文件描述符、缓冲区、当前位置等;
(2)FILE结构体由系统定义并维护,包含文件流的状态信息。程序员无需手动管理FILE结构体,只需通过标准库函数进行访问和操作。
3.2 FILE结构体
ctags 是一个用于生成 C/C++ 等语言的标记文件的工具,这个文件可以帮助代码编辑器快速跳转到函数、变量、结构体的定义。
sudo apt-get install ctags
cd /usr/include
sudo ctags -R
在 ~/.vimrc 文件中添加以下配置,以便 vim 使用生成的标记文件。
set tags+=/usr/include/tags
查找;
vi -t FILE
输入对应的数字并回车以查看 typedef struct _IO_FILE FILE; 的定义。
导航代码:
方法 1:将光标停留在要追踪的符号上,按下 Ctrl + ] 跳转到定义处。
方法 2:按下 Ctrl + 鼠标左键点击要追踪的内容。
返回上层:
方法 1:按下 Ctrl + t 返回到之前的代码位置。
方法 2:按 Ctrl + 鼠标右键。
查找结果。
3.3 man手册
(1)man命令用于查看Linux系统中命令、函数和系统调用的详细文档,包括功能、参数、返回值及其他相关信息。
(2)第一章:可执行程序或Shell命令;第二章:系统调用(内核提供的函数),如文件IO函数;第三章:库调用(程序库中的函数),如标准IO函数,范例:
man 3 fwrite //在man手册的第二章查找fwrite函数
3.4 标准流指针
在main函数运行之前,操作系统为我们定义好了三个标准流指针,用于处理标准输入、输出和错误输出。
标准流指针 | FILE* stdin | FILE* stdout | FILE* stderr |
---|---|---|---|
定义 | 标准输入流指针,通常与键盘输入缓冲区相关联。 | 标准输出流指针,通常与屏幕输出缓冲区相关联。 | 标准错误输出流指针,通常与屏幕输出缓冲区相关联。 |
功能 | 向终端输入的数据会放入到stdin对应的缓冲区中,可以通过stdin读取终端数据。 | 希望输出到终端的数据会放入到stdout对应的缓冲区中,可以通过stdout将数据输出到终端。 | 标准错误输出流指针是无缓冲的。缓冲区大小是1字节,因此无需任何刷新方式。 |
默认使用 | scanf、getchar、gets默认使用的都是stdin对应的缓冲区。 | printf、putchar、puts默认使用的都是stdout对应的缓冲区。 | 一般用于打印错误信息。防止缓冲区被错误阻塞,导致数据无法输出。perror函数通常使用stderr。 |
3.5 标准IO函数
(1)fopen
功能
fopen函数用于打开一个文件,并返回一个指向FILE类型的指针,用于后续的文件操作。
原型
#include <stdio.h>
FILE *fopen(const char *pathname, const char *mode);
参数
pathname:指定要打开的文件的路径及名称。
mode:指定打开文件的方式,决定文件的读写权限和状态。
文件打开模式
r:以只读的方式打开文件。
文件指针定位在文件开头。
r+:以读写的方式打开文件。
文件指针定位在文件开头。
w:以只写的方式打开文件。
文件指针定位在文件开头。
如果文件不存在,创建新文件。
如果文件存在,清空文件内容。
w+:以读写的方式打开文件。
文件指针定位在文件开头。
如果文件不存在,创建新文件。
如果文件存在,清空文件内容。
a:以追加的方式打开文件。
文件指针定位在文件末尾。
如果文件不存在,创建新文件。
数据将写入文件末尾。
a+:以读写追加的方式打开文件。
文件指针定位在文件末尾。读则在文件开头(初始位置)//读指针和写指针为同一个
如果文件不存在,创建新文件。
数据将写入文件末尾。
成功:返回指向FILE对象的指针。
失败:返回NULL,并设置errno以指示错误类型。
(2)perror
功能
根据errno打印对应的错误信息;
原型:
#include<stdio.h>
void perror(const char* s );
参数
char* s:用于提示的字符串;一般填写报错的函数名字。
(3)fclose
功能
关闭文件,释放各种资源:缓冲区资源,文件描述符资源
原型: #include<stdio.h>
int fclose(FILE *stream);
参数: FILE* stream指定要啊关闭的文件对应的流指针
返回值:
成功返回0
失败,返回EOF(-1),更新errno
(4)fprintf
功能
将数据格式化输出到指定文件中
原型 #include <stdio.h>
// int printf(const char *format, ...);
//printf("hello %d%c%f%s%g \n\t\b\r", 10, 'a', 3.14, "abc", 2.1);
int fprintf(FILE *stream, const char *format, ...);
参数:
FILE *stream:指定要输出到哪个文件中,就填上文件对应的流指针。该流指针用写的方式打开;
char *format:输入到文件中的内容;格式化字符串,其中可以填字符,可以填占位符,可以填转义字符
......:不定参数,不定数据类型,不定数据个数,跟着前面的参数来决定。
返回值:
成功,返回成功被打印的字符个数;
失败,返回负数。
1234567891011121314
(5)fscanf
功能
从指定文件中格式化读取数据
原型:#include<stdio.h>
int fscanf(FILE* stream,const char* format,...);
参数: FILE* stream 指定从文件中读取数据。
char *format:格式化字符串,指定如何读取输入数据,包含格式说明符。
...:可变参数列表,提供一个或多个指针,用于存储读取到的数据。
返回值:
>0,成功,返回成功读取到的字符个数
EOF,当前文件读取完成
EOF,当前函数运行失败,返回EOF,更新errno;
注意事项
在使用scanf和fscanf函数时,需要注意它们对空白字符的处理行为。默认情况下,格式说明符%d、%f和%s会忽略输入中的空格、制表符(\t)和换行符(\n),并在遇到空白字符时停止读取。
例如,%d和%f用于读取整数和浮点数时,会跳过所有初始空白字符,直到遇到有效的数字;%s用于读取字符串,会跳过初始空白字符并读取字符,直到遇到下一个空白字符停止。
而%c格式说明符则不同,它不会忽略空白字符,包括空格、制表符和换行符,所有字符都被视为有效输入。这意味着当使用%c读取字符时,即使输入是空白字符,程序也会将其读入。
因此,在读取单个字符时,尤其是在读取前有其他输入操作时,通常需要调用getchar()以吸收前一个输入中的换行符,防止影响字符的读取结果。了解这些细节有助于更准确地处理输入数据,避免在处理用户输入时出现意外行为。
(6)fputs和fputc
fputs功能
将字符串输出到指定的文件中;
fputc功能
将单个字符输出到指定的文件中;
原型:
#include <stdio.h>
int fputs(const char *s, FILE *stream); ---》不会自动换行
int fputc(int c,FILE *stream);
int puts(const char *s); ---》会自动打印换行
参数:
char *s:指定要输出的字符串的首地址;
FILE *stream:;
返回值:
成功,返回非负数;
失败,返回EOF;
fputs("abcd", fp);
fputs("1234", fp);
(7)fgets和fgetc
fgets功能
从指定文件中获取字符串
fgetc功能
从指定文件中获取单个字符,
会获取空格,会获取制表符,会获取\n字符,且拿完\n字符后会停止读取。
在fgets停止读取后,会在读取到的有效字符的后一个位置补\0
原型:
#include <stdio.h>
char *fgets(char *s, int size, FILE *stream);
int fgetc(FILE *stream); //成功则返回读取到的字符对应的整型形式,失败或读取完时,返回EOF
char *gets(char *s); char str[20]; gets(str);
参数:
char *s:该指针指向的空间中会存储获取到的字符串;
int size:指定要获取多少个字节; 最多只获取size-1个字节,
且在停止获取数据后会自动在获取到的最后一个字符后面补'\0',确保获取到的肯定是字符串。
FILE *stream: ;
返回值:
成功,返回存储字符串的空间地址。
失败,返回NULL;
文件读取完毕,且没有任何数据再被读出来时,返回NULL
(8)fwrite
功能
将数据的二进制形式写入到文件中.
二进制文件:将数据拆分成单个字节,将每个字节转换成对应的字符形式写入到文件中
原型:
#include <stdio.h>
size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);
参数:
void *ptr:指定要输出的数据的首地址。void*类型:万能指针,代表可以输出任意类型的数据。
size_t size:每个数据所占字节大小
size_t nmemb:共输出多少个数据。
总之,总输出的字节大小:size*nmemb;
int arr[3] ==> size:4 nmemb:3
1 12
12 1
FILE *stream:;
返回值:
成功,返回成功输出的数据个数;
失败,返回<nmemb,或者等于0
(9)fread
功能
从二进制文件中读取数据。还原回指定类型的数据
原型:
#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
参数:
void *ptr:该指针指向的内存空间存储读取到的数据;
size_t size:每个数据所占字节大小
size_t nmemb:共读取多少个数据。
总之,总读取的字节大小:size* nmemb;
建议:以1为单位读取n个数据,因为任何数都是1的倍数。
避免最后一次剩余的数据不是size的倍数,从而导致最后一次读取数据失败。如下图示例代码所示:
FILE *stream;
返回值:
成功,返回成功读取到的字节数;
失败,返回小于nmemb,或者等于0;
(10)fseek
功能
修改文件偏移量到指定位置;
原型:
#include <stdio.h>
int fseek(FILE *stream, long offset, int whence);
参数:
FILE *stream:指定要修改哪个文件偏移量;
long offset:距离whence偏移多少。向文件开头位置偏移,填负数。向文件结尾位置偏移填正数。
int whence:
SEEK_SET:文件开头位置,
SEEK_CUR:文件当前位置,
SEEK_END:文件结尾位置。
返回值:
成功,返回0;
失败,返回-1;
功能:将偏移量修改到开头。
void rewind(FILE *stream); ===> fseek(fp, 0, SEEK_SET);
long ftell(FILE *stream);
(11)ftell
功能
获取文件当前位置距离开头的偏移量;
原型:
#include <stdio.h>
long ftell(FILE *stream);
参数:
FILE *stream:;
返回值:
成功,返回文件当前位置距离开头的偏移量;
失败,返回-1,更新errno;
计算文件大小:
fseek(fp, 0, SEEK_END);
long size=ftell(fp);
3.6 缓冲区
只有标准IO函数有缓冲区。
3.6.1 全缓冲
(1)缓冲区操作对象: 手动用fopen函数打开,得到的缓冲区都是全缓冲。由FILE结构体指针维护;
(2)缓冲区大小 4096bytes = 4k;
fputc('a', fp);
//操作系统优化,如果只申请不使用,不会真正申请缓冲区
printf("%ld\n", fp->_IO_buf_end - fp->_IO_buf_base); //4096bytes
(3)刷新缓冲区方式;
i . 缓冲区满:需要多循环一次,才能将前4096个数据刷新出去。第4097个依然放在缓冲区中;
ii. 强制刷新缓冲区:fflush函数;
#include <stdio.h>
int fflush(FILE *stream);
fflush(fp);
iii.调用fclose关闭流指针;
iv.程序正常退出。main函数调用return退出;
v.调用exit函数退出程序。
功能
目前只需要知道该函数能够将整个程序结束即可。
#include <stdlib.h>
void exit(int status);
参数:
int status:目前随便填一个整数即可;
exit(0);
3.6.2 行缓冲
(1)缓冲区操作对象:stdin(标准输入流指针) stdout(标准输出流指针);
(2)缓冲区大小:1024bytes = 1k,b.printf(“%ld\n”, stdout->_IO_buf_end - stdout->_IO_buf_base);
(3)刷新缓冲区方式。
i .缓冲区满:需要多循环一次,才能将前1024个数据刷新出去。第1025个依然放在缓冲区中;
ii. 遇到\n字符刷新;
iii.强制刷新缓冲区:fflush函数;
#include <stdio.h>
int fflush(FILE *stream);
fflush(fp);
fflush(stdout);
iv.调用fclose关闭流指针;
vi.程序正常退出。main函数调用return退出;
vii.调用exit函数退出程序;
ix.输入输出转换。
功能
目前只需要知道该函数能够将整个程序结束即可。
#include <stdlib.h>
void exit(int status);
参数:
int status:目前随便填一个整数即可;
exit(0);
3.6.3 无缓冲
(1)缓冲区操作对象:a. stderr(标准错误输出流指针) -->perror函数
(2)缓冲区大小:1bytes。但是不需要任何刷新方式。
4 时间相关的函数
4.1 time
功能
获取1970-1-1日至今的秒数;
原型:
#include <time.h>
time_t time(time_t *tloc);
参数:
time_t *tloc:若该指针不为空,则返回值同样会被存储到该指针指向的内存空间中;
返回值:
返回1970-1-1日至今的秒数;
失败,返回((time_t) -1),更新errno;
方法1: time_t t;
time(&t); 可以从参数获取
方法2: time_t t2=time(NULL);
可以通过返回值获取
4.2 localtime
功能:将秒数转换成日历格式
原型:
#include <time.h>
struct tm *localtime(const time_t *timep);
参数:
time_t *timep:该指针指向的空间存储着要转换成日历格式的秒数;
返回值:
成功,返回struct tm*类型指针
失败,返回NULL;更新errno;
struct tm {
int tm_sec; /* Seconds (0-60) */ 秒
int tm_min; /* Minutes (0-59) */ 分
int tm_hour; /* Hours (0-23) */ 时
int tm_mday; /* Day of the month (1-31) */ 日
int tm_mon; /* Month (0-11) */
月=tm_mon+1
int tm_year; /* Year - 1900 */ 年=tm_year+1900
int tm_wday; /* Day of the week (0-6, Sunday = 0) */ 星期
int tm_yday; /* Day in the year (0-365, 1 Jan = 0) */ 一年的第几天
};
5 文件IO函数
文件IO由操作系统提供的基本函数,与操作系统绑定,又称之为系统调用,文件IO是没有缓冲区的,文件IO函数是通过文件描述符来维护一个文件的。
(1)文件描述符概念
当尝试打开一个文件的时候,系统会自动给这个文件分配一个编号,通过这个编号可以来描述这个文件。该编号称之为文件描述符。标准IO是对文件IO函数的二次封装,在文件IO的基础上封装了一个缓冲区,同时会将文件描述符及操作缓冲区的指针一起封装到FILE结构体中。标准IO通过流指针就可以操作文件。
文件IO函数,是通过文件描述符操作文件的。文件描述符的范围是**[0, 1023]**,共1024个,**文件描述符的总量是有上限的,在不使用的情况下一定要关闭!!!**文件描述符的总量是可以通过修改Linux的配置文档进行修改的,但是一般不动。文件描述符分配原则:从小到大,依次遍历没有被占用的文件描述符。
(2)特殊的文件描述符
特殊的流指针(FILE*) | 特殊的文件描述符 | |
---|---|---|
FILE* stdin | stdin->_fileno | 0 |
FILE* stdout | stdout->_fileno | 1 |
FILE* stderr | stderr->_fileno | 2 |
(3)文件描述符个数的计算
getdtablesize()函数可以直接计算:
5.1 文件IO函数的使用
(1)open
功能:打开一个文件,获得文件描述符;
原型:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
参数:
char *pathname:指定要打开的文件对应的路径以及名字;
int flags:打开方式
O_RDONLY:只读方式,
O_WRONLY:只写,
O_RDWR:读写
----- 以上三种必须,且只能包含一种----
O_CREAT:创建,若文件不存在,则创建一个普通文件
O_TRUNC:清空,若文件存在,且是一个普通文件,且用写的方式打开文件,则会清空文件
O_APPEND:追加。
w:O_WRONLY|O_CREAT|O_TRUNC
mode_t mode:指定文件创建时候的权限。
如果flags中没有O_CREAT或者O_TEMPFILE则mode选项忽略。
返回值:
成功,返回新的文件描述符;
失败,返回-1,更新errno;
(2)close
功能:关闭文件
原型:
#include <unistd.h>
int close(int fd);
参数:
int fd:指定要关闭的文件对应的文件描述符;
返回值:
成功,返回0;
失败,返回-1,更新errno;
(3)write
功能:将数据的二进制形式写入到文件中。例如int a=97,写入到文件中a^@^@^@
原型:
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
参数:
int fd:指定要将数据写入到哪个文件中,就填那个文件对应的文件描述符;
void *buf:指定要输出的数据的首地址。void*类型:可以输出任意类型数据;
size_t count:指定要输出的数据总大小,以字节为单位。
返回值:
成功,返回成功输出的字节数,ssize_t--》long int
失败,返回-1,更新errno;
(4)read
功能:从指定文件中读取数据; read函数不会自动补充\0。
原型:
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
参数:
int fd:指定从哪个文件中读取数据,就填那个文件对应的文件描述符;
void *buf:指定要将读取到的数据存储到什么位置。void*类型:可以读取任意类型数据;
size_t count:指定要读取的数据总大小,以字节为单位;
返回值:
>0, 成功读取到的数据个数;
=0, 文件读取完毕;
=-1,函数运行失败,更新errno;
(5)lseek
功能
修改文件偏移量,同时获取修改后的位置距离文件开头的偏移量。
头文件
#include <sys/types.h>
#include <unistd.h>
函数原型
off_t lseek(int fd, off_t offset, int whence);
参数
int fd: 文件描述符。
off_t offset: 距离 whence 指定的位置偏移多少个字节;往文件结尾方向偏移,填正数。往文件开头方向偏移填负数。
int whence: 位置参数,可以是以下之一:
SEEK_SET: 文件开头。
SEEK_CUR: 文件当前位置。
SEEK_END: 文件结尾。
返回值
成功:返回修改后的位置距离文件开头的偏移量。
失败:返回 (off_t) -1,并更新 errno。
5.2 文件属性相关函数
头文件
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
原型
int stat(const char *path, struct stat *buf);
*const char path: 文件路径。
*struct stat buf: 存储文件属性信息的结构体指针。
返回值
成功:返回 0。
失败:返回 -1,并设置 errno。
struct stat 结构体(用vi -t 查);
struct stat {
dev_t st_dev; // 文件所在设备的ID
ino_t st_ino; // 节点号
mode_t st_mode; // 文件的类型和权限
nlink_t st_nlink; // 硬链接数
uid_t st_uid; // 所有者的用户ID
gid_t st_gid; // 所有者的组ID
dev_t st_rdev; // 设备ID(如果是特殊文件)
off_t st_size; // 文件大小(以字节为单位)
blksize_t st_blksize; // 文件系统I/O块大小
blkcnt_t st_blocks; // 分配给文件的块数
time_t st_atime; // 最近一次访问时间
time_t st_mtime; // 最近一次修改时间
time_t st_ctime; // 最近一次状态改变时间
};
文章已获作者授权转载,版权归原作者所有,如有侵权,与本账号无关,可联系删除。 原文作者:每天的积累
原文链接:https://blog.csdn.net/m0_51830537/article/details/139488592