文件IO和标准IO的区别
- 遵循标准 :
- 文件IO遵循POSIX标准,主要在类UNIX环境下使用。
- 标准IO遵循ANSI标准,具有更好的可移植性,可以在不同的操作系统上重新编译后运行。
- 可移植性 :
- 文件IO的可移植性相对较差,因为它与系统底层紧密相关。
- 标准IO的可移植性较好,因为它是C语言标准库的一部分,可以在不同的操作系统上移植使用。
- 操作方式 :
- 文件IO通过系统调用来直接读写文件,每次操作都会触发系统调用。
- 标准IO在内部实现了缓冲机制,通过文件流指针来操作文件,减少了系统调用的次数,提高了性能。
- 缓冲机制 :
- 文件IO通常不带缓冲,每次读写操作都会直接与系统底层交互。
- 标准IO带缓冲,先将数据写入缓冲区,再选择合适时机将缓冲区中的数据写入文件或从文件中读取数据到缓冲区。
- 访问的文件类型 :
- 文件IO可以访问不同类型的文件,包括普通文件、设备文件、管道文件和套接字文件等。
- 标准IO主要用于访问普通文件,虽然也可以重定向到其他类型的设备,但其本质还是通过文件流指针来操作文件。
- 使用的接口 :
- 文件IO使用如open、read、write、close等系统调用接口。
- 标准IO使用如fopen、fread、fwrite、fclose等C标准库函数接口。
Linux标准文件描述符
我们使用printf和write都可以输出正常的字符串
当我们给程序加上一条死循环的程序,就会发现用printf()不能继续执行,而用write可以继续执行。
这个原因就是因为缓存问题,见下文
缓存的概念
1.我们的程序中的缓存,就是你想从内核读写的缓存(数组)----用户空间的缓存
2.每打开一个文件,内核在内核空间中也会开辟一块缓存,这个叫内核空间的缓存
文件IO中的写 即是将用户空间中的缓存写到内核空间的缓存中。
文件IO中的读 即是将内核空间的缓存写到用户空间中的缓存中。
3.标准IO的库函数中也有一个缓存,这个缓存称为----库缓存
C库缓存的特点:
1.遇到\n 时,会将库缓存的内容写到内核缓存中,即调用了系统调用函数。
2.库缓存写满时,会调用系统调用函数,将库缓存内容写到内核缓存中。当写满时,即1024字节
关于上面的问题,使用write函数就是文件IO,它可以直接将用户空间(我们给文件的数据)写道内存空间中,当我们使用C库printf时,他不会直接调用而是等待写满才会系统调用函数将库缓存写道内核缓存中,为了使能够输出我们给文件的数据(hello world),我们可以在输入后面加一个\n使其成功打印(还是和正常输出略有不同)
三类读写缓存
fputs函数
属于C语言标准库函数,用于将一个字符串写入到指的头文件中,写入成功后光标会移到写入的最后一个字符
函数原型
int fputs(const char *str, FILE *stream);
str
:指向要写入文件的字符串的指针。stream
:指向FILE
对象的指针,该FILE
对象标识了要写入数据的文件流。
fputs
函数将str
指向的字符串写入到stream
指定的文件流中,但不包括空字符('\0'
)。如果写入成功,fputs
返回一个非负整数;如果发生错误,则返回EOF
。
fgets函数
用于从指定的文件流中读取一行,使用 fgets
可以很方便地从文件或者标准输入(如键盘)读取字符串,直到遇到换行符或文件结束符,或者达到指定的字符数。
函数原型:
char *fgets(char *str, int n, FILE *stream);
str
:指向一个字符数组的指针,用来存储读取到的字符串。n
:指定最多读取的字符数,包括最后的空字符(\0
),因此实际上最多能读取n-1
个字符。stream
:指定从哪个文件流中读取数据。
返回值
- 成功时,返回
str
。 - 失败或到达文件末尾时,返回
NULL
。
注意文件的权限,应该是w+,可读可写!!!!
fflush函数
用于刷新缓冲区,
fflush
函数用于将给定的输出流或更新流stream
的缓冲区中的数据强制写入到对应的文件或设备中。如果stream
指向的是一个输出流或者是一个最近一次操作不是输入的更新流,则fflush
会将缓冲区中未写入的数据写入到流指向的文件或设备中。- 如果
stream
是空指针(NULL
),则fflush
会对所有打开的文件流执行刷新操作。
函数原型
int fflush(FILE *stream);
返回值:
- 成功时,
fflush
返回0
。 - 如果发生错误,则返回
EOF
,并且设置相应的错误标识符。
第一种:刷新标准输出缓冲区
fflush(stdout)
确保printf
函数输出的内容立即被写入到标准输出设备(通常是屏幕)上。
第二种:刷新文件流缓冲区
在这个例子中,fflush(file)
确保通过fprintf
函数写入到file
指向的文件中的数据被立即写入到磁盘上。注意,虽然在这个例子中fflush
的调用是可选的(因为关闭文件时会自动刷新缓冲区)
stderr无缓冲函数
stderr
是C语言标准I/O库中的一个特殊文件流,代表标准错误输出。与标准输出stdout
不同,stderr
通常是无缓冲的,这意味着写入stderr
的数据会立即输出,而不会被缓存在内存中。
由于stderr
是无缓冲的,因此它非常适合用于输出错误信息或日志,因为这些信息通常需要立即显示在屏幕上,以便用户或开发人员能够及时发现并处理问题。
stderr与stdout的区别
stdout
是标准输出流,它通常是行缓冲或全缓冲的。这意味着写入stdout
的数据可能会被缓存在内存中,直到遇到换行符或缓冲区满时才真正输出。而stderr
则不同,它无缓冲的特性确保了数据的即时输出。
在这个例子中,我们尝试打开一个不存在的文件。由于文件不存在,fopen
函数将返回NULL
,并且我们通过fprintf
函数将一条错误信息写入stderr
。由于stderr
是无缓冲的,这条错误信息将立即显示在屏幕上。
stderr
作为标准错误输出流,其无缓冲的特性使得它非常适合用于输出错误信息或日志。在使用时,我们只需要将错误信息或日志写入stderr
,它们就会立即显示在屏幕上,无需担心缓冲导致的问题。
rewind
也属于C语言标准库函数,该函数的主要作用是将文件内部的位置指针(也称为文件位置指示器)重新指向一个文件流(数据流/文件)的开头。
所以此函数的作用和fseek中的一个作用相同(SEEK_SET)
gets函数
函数原型
char *gets(char *s);
gets 与fgets的区别:
gets()时不能指定缓存的长度,这样就可能造成缓存越界(如若该行长于缓存长度),写到缓存之后的存储空 间中,从而产生不可预料的后果;
gets()只能从标准输入中读;
gets()与fgets()的另一个区别是:gets()并不将新行符存入缓存中, fgets 将新行符存入缓存中;
用gets容易越界,写入缓存时不会把换行符\n算进字符长度
fgets会把换行符算进字符长度,不会越界
puts函数
函数原型:
int puts(const char *str);
puts 与fputs的区别:
puts()只能向标准输出中写;
puts()与fputs()的另一个区别是: puts 输出时会添加一个新行符,fputs不会添加;
比gets多一个换行操作(\n)。
fgetc函数
从文件中读取一个字符
int fgetc(FILE *fp)
参数:字符流
返回值:正确为读取的字符文件结尾或出错返回-1。
fputc函数
功能:写一个字符到文件中
参数:第一个参数为要写的字符,第二个参数为文件流
返回值:成功则返回输入的字符,出错返回EOF。-1
fputc有缓存,但不是行缓存函数
当读到a时已经是最后一位了,所以再次读就会按照函数规则返回-1
行缓存函数
fprintf函数
通过文件流指针来控制输出的目标,通常用于将信息写入到指定的文件或标准输出流(如stdout或stderr)中。
fprintf的原型函数为:
int fprintf(FILE *stream, const char *format, ...);
FILE *stream
:指向输出流的文件指针,决定了数据将被写入哪个文件或输出流。
const char *format
:格式化字符串,用于指定后续参数如何被格式化和输出。
...:可变参数列表,包含了一系列将要被格式化和输出的数据。
fprintf函数的返回值是写入的字符数(不包括终止的空字符),如果发生错误则返回负值。
第一个fprintf是写入一行文本到example.txt这个文件中
第二个fprintf是写入一行文本到标准输出中,(通常是屏幕)。
sprintf函数
sprintf是C语言中的一个字符串格式化函数,其函数声明为:
int sprintf(char *str, const char *format, ...);
char *str
:指向一个字符数组的指针,该数组用于存储格式化后的字符串。const char *format
:格式化字符串,指定了如何将后续参数格式化为字符串。...
:可变参数列表,包含了一系列将要被格式化和插入到str
所指向字符串中的数据。
sprintf函数将格式化的数据写入到str
指向的字符串 中,并返回写入的字符数(不包括终止的空字符 )。需要注意的是,使用sprintf时需要确保目标字符串str
有足够的空间来存储格式化后的结果,否则可能会导致缓冲区溢出。
一般情况下,sprintf函数和printf函数一起使用,sprintf是用来把数据写入到字符串当中,并不起到输出作用
cat指令的实现
用fgetc和fputc
通过一个个字符的读写从而使cat查看指令实现
注:feof函数用来判断是否已经到文件结束(如果文件结束则返回非0,没有则返回0)
if(argv!=2)表示若不是只有一个命令行,则操作失败
三个判断函数
int feof(FILE *stream);
功能:判断是否已经到文件结束
参数:文件流
返回值:到文件结束,返回为非0,没有则返回0
int ferror(FILE *stream);
功能:判断是否读写错误
参数:文件流 返
回值:是读写错误,返回为非0,不是则返回0
void clearerr(FILE *stream);
功能:清除流错误
参数:文件流
清除错误标志(通常是通过调用clearerr
函数)对程序的影响主要体现在以下几个方面:
-
恢复文件流状态 :当文件流(如通过
FILE *
类型表示的文件指针)遇到错误(如读写错误)或到达文件末尾(EOF)时,相应的错误标志或EOF标志会被设置。这些标志会影响后续的输入输出操作。通过清除这些标志,clearerr
函数将文件流恢复到一种"干净"的状态,使得后续操作可以不受之前错误或EOF状态的影响。 -
允许继续执行:在某些情况下,文件流错误可能是暂时的或者可以通过某些方式解决(例如,通过重新定位文件指针、关闭并重新打开文件或修改文件访问模式)。清除错误标志后,程序可以继续尝试执行之前失败的操作,或者执行其他依赖于文件流状态的操作。
-
错误处理:在清除错误标志之前,程序可能会根据错误状态执行特定的错误处理代码(如打印错误消息、释放资源、尝试恢复等)。清除错误标志后,这些错误处理代码可能不再适用,因为错误状态已经被重置。因此,程序需要适当地调整其错误处理逻辑,以考虑错误标志被清除后的情况。
-
文件结束(EOF)的重新评估 :如果文件流遇到了EOF,并且随后调用了
clearerr
来清除EOF标志,那么程序可能会错误地认为文件尚未结束,并尝试继续读取数据。这可能会导致未定义的行为,如读取到无效的数据或进入无限循环。因此,在清除EOF标志之前,程序应该仔细考虑其后果,并确保这是期望的行为。 -
对后续操作的潜在影响 :清除错误标志可能会影响到依赖于这些标志来检测文件状态变化的后续操作。例如,如果程序依赖于
feof
函数来检测文件是否结束,那么在清除EOF标志后,feof
将不再返回真值,直到文件流再次真正到达文件末尾。