IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> Linux C语言中的IO--标准IO -> 正文阅读

[系统运维]Linux C语言中的IO--标准IO

1. IO概述

IO 指的是输入输出函数。
站在计算机的角度,输入是读操作,输出是写操作。

例如:我们从终端输入信息给计算机,计算机是从终端读取信息,而我们让计算机输出信息时,计算机是要向终端写信息。

IO分为文件IO和标准IO

  • 文件IO属于系统调用
  • 标准IO属于库函数

系统调用和库函数有什么区别?

系统调用:用户通过应用层函数操作Linux内核,Linux内核再控制硬件。

Linux内核中封装了大量的系统调用,用户通过应用层的系统调用函数调用内核中的系统调用。
因为不同的Linux内核中的系统调用是不同的,所以采用系统调用的应用程序移植性不高,而对底层硬件的操作一般都是用的系统调用

库函数:库函数本质还是系统调用,但比系统调用多了一个缓冲区,缓冲区用来减少系统调用次数,只有在缓冲区被刷新的时候,才会执行系统调用。

库函数是为了统一不同操作系统对文件操作的函数,只要支持C语言,库函数一般就可以使用,所以库函数的移植性更高。

库函数调用系统调用
移植性可移植性好依赖内核,不保证移植性
调用属性调用函数库的代码调用系统内核的服务
调用类型普通函数的调用操作系统的入口点
运行空间用户空间执行内核空间执行
运行时间所属用户时间系统时间
开销属于过程调用,开销较小在用户空间和内核空间上下文环境切换,开销较大
数量库函数数量多Unix大约有90个系统调用,较少
典型调用函数printf scanf mallocfork open write

对硬件进行操作时,会先操作Linux内核
操作Linux内核的三种方式:shell、系统调用、库函数

2. 标准IO

2.1 缓冲区 / 缓存区

分类解释
行缓冲终端操作对应的缓冲区为行缓冲
全缓冲文件操作对应的缓冲区为全缓冲
无缓冲终端输出错误信息对应的缓冲区为无缓冲

缓冲区需要被刷新,如果不刷新缓冲区,数据就停留在缓冲区,无法执行系统调用。

向无缓冲写入数据会直接打印在终端

行缓冲区刷新方法

  1. 输出\n
  2. 缓冲区切换 (输出缓冲转换成输入缓冲)
  3. 使用fflush函数,刷新缓冲区
  4. 缓冲区满时,行缓冲大小为1024 Bytes
  5. 程序正常结束

全缓冲区刷新方法

  1. 使用fflush函数
  2. 缓冲区满
  3. 程序正常结束

2.2 文件指针

文件指针又称为流指针,简称流。(stream pointer
可以理解为向文件读取或者写入信息时,那些字符像流水一样有序地流出或者流入。
文件指针标识一个文件,用库函数对文件操作时,需要用到文件指针。

文件指针是结构体类型指针FILE *typedef struct _IO_FILE FILE;
_IO_FILE结构体中,保存了打开的文件的各种信息

程序运行时,操作系统会自动为当前程序分配三个文件指针

FILE *作用
stdin标准输入流指针,用于从终端读取数据
stdout标准输出流指针,用于对终端写数据
stderr标准错误输出流指针,用于向终端输出错误信息

2.3 标准IO常用函数

fopen()

fopen不仅打开一个文件,还会创建缓冲区——如果是读写模式,打开两个;只读或者只写,则只打开一个,再创建一个包含文件和缓冲区数据的结构体。
以文本模式打开,返回的就是文本流
以二进制模式打开,返回的就是二进制流

#include <stdio.h>
FILE *fopen(const char *pathname, const char *mode);

功能:打开或者创建一个文件
参数:
pathname:
	要打开或者创建的文件名,可以跟路径,如果不跟路径,默认就是当前路径

mode:操作权限
	 r 以只读的方式打开,如果文件不存在则报错,文件指针定位到文件的起始位置
	 r+ 以读写的方式打开,如果文件不存在则报错,文件指针定位到文件的起始位置
	 以 "r" 或者 "r+" 打开,文件不存在,返回错误码为 2 。
	 w 以只写的方式打开,如果文件存在则清空,文件不存在则创建,文件指针定位到文件的起始位置
	 w+ 以读写的方式打开,如果文件存在则清空,文件不存在则创建,文件指针定位到文件的起始位置
	 a 以只写的方式打开,如果文件存在则追加,文件不存在则创建,文件指针定位到文件的起末尾位置
	 a+ 以读写的方式打开,如果文件存在则追加,文件不存在则创建,文件指针定位到文件的起末尾位置
	 在 r / w / a 后面加上 b 就代表以二进制方式打开文件
	 w 后面加上 x   (c11)如果文件已存在或以独占模式打开文件,则打开文件失败
 
返回值:
	成功:文件指针
 	失败:NULL

使用a+操作时,无论读到文件哪个位置,执行写操作时,都会在文件结尾处追加。

fclose()

#include <stdio.h>
int fclose(FILE *stream);

功能:
	关闭一个文件指针,如果关闭了文件指针,就无法再通过这个文件指针对文件进行操作了

参数:
	stream:文件指针,fopen的返回值

返回值:
	成功:0
	失败:EOF

EOFend of file文件结束描述符,一般值为-1

perror()

#include <stdio.h>
void perror(const char *s);

功能:输出一个函数调用失败之后的错误信息,输出会自动换行

参数:
	s:提示语句
	
返回值:无

errno

#include <errno.h>
int errno;
 
errno是一个全局变量,用于输出函数调用失败的错误码
可以调用strerror函数通过errno值打印错误信息

char * strerror(int errnum);
printf("%s\n", strerror(errno));

fgetc()

#include <stdio.h>
int fgetc(FILE *stream);

功能:从文件中读取一个字符

参数:
	stream:文件指针

返回值:
	成功:读取的字节(char 类型本质仍是int类型)
	失败:EOF
	文件内容读取完毕  EOF
	

这里函数执行失败和读到文件结束的返回值相同,是一个比较棘手的问题,因为如果直接返回了EOF,我们并不知道该文件是空的,还是函数执行失败。
所以,请看函数feof/ferror

文件中每一行的结尾都有一个结束标志换行符,fgetc可以读取到整个换行符,可以根据这个来判断文件中有多少行。

fputc()

#include <stdio.h>
int fputc(int c, FILE *stream);

功能:向指定的文件中写入一个字符

参数:
	c:要写入的字符
	stream:文件指针
	
返回值:
	成功:写入的字符
	失败:EOF

注意

如果先往文件中写数据,然后读数据,读的位置是最后写完数据的下一个位置,因为写和读的文件指针定位的位置是同一个

feof() / ferror() 判断返回值EOF类型

如果标准输入函数返回EOF,通常表明函数已经到达文件末尾,但读取错误时,也会返回EOFfeof()feorror()就是用于区分这两种情况。

int feof(FILE *fp);

功能:判断上一次EOF是否为文件末尾

参数:要检测的文件指针

返回值:
	当EOF是
		文件末尾:非0
		错误:     0
int ferror(FILE *fp);

功能:判断上一次EOF是否为读取错误

参数:要检测的文件指针

返回值:
	当EOF是
		错误:       非0
		文件末尾:     0

ungetc() 将字符放回输入流

#include <stdio.h>
int ungetc(int c, FILE * fp);

功能:
	将指定字符放回输入流,且是已有缓存的最前端,下次调用函数读取时,将会先读取到该字符

参数:
	c  要放回流的字符
	fp 目标流

返回值:
	成功  c
	失败  EOF

Linux c语言,只有在程序阻塞等待输入,并在终端输入字符时,并按下回车,才会把输入字符放入输入流。

fgets()

#include <stdio.h>
char *fgets(char *s, int size, FILE *stream);

功能:从文件中读取一个字符串

参数:
	s:保存读取的内容
	size:要读取的字节数
	stream:文件指针

返回值:
	成功:返回读取的内容
	失败:NULL
	如果读到文件末尾,也返回NULL

注意:如果能够读取到的字节数小于第二个参数size,会将回车符\n也当做是字符保存在第一个参数s里面。

如果输入的数据的字节数大于第二个参数size,则只会保存size - 1个字节,最后一个位置补\0

注意需要保证第一个参数字节数足够大,至少是size + 1,否则会内存越界,可能会影响程序正常执行,好点话会报段错误。

fputs()

#include <stdio.h>
int fputs(const char *s, FILE *stream);

功能:向文件写入一个字符串

参数:
	s:要写入的字符串
	stream:文件指针

返回值:
	成功:1
	失败:EOF

注意:这里输出字符串,结束标志是\0

fprintf() / sprintf() / dprintf / snprintf

先解析fprintf,第一个ffile,中间print指打印,最后一个fformat,连起来就是将字符串格式化地打印到文件中。类似于printf

那么sprintfs即为string字符串,意为字符串格式化打印到字符串中

#include <stdio.h>	

int printf(const char *format, ...);

int fprintf(FILE *stream, const char *format, ...);

int dprintf(int fd, const char *format, ...);

int sprintf(char *str, const char *format, ...);

int snprintf(char *str, size_t size, const char *format, ...);

第5 - 9行的函数fprintfdprintfsprintf,第一个参数都是要打印的目标区,都是将后面的格式化字符串打印到该目标区,目标区可以是文件指针,文件描述符或者字符串,不过函数不同而已。

snprintf多了一个第二个参数size,这里指定了要向字符串中打印多少个字节。

这5个函数返回值都是成功打印的字节数。

使用dprintf时,或者read write时,可能文件描述符0(STDIN_FILENO)、1(STDOUT_FILENO)、2(STDERR_FILENO)都指向同一个文件,所以我可以从标准输出中读取终端的数据,也能通过标准输入向终端写数据。
而通过流指针进行类似操作时,库函数应该已经进行了优化,是无法达到类似现象的
(此处,本人不才,无法过多解释,望有大佬指教)

fread() 与 fwrite()

为保证数据在存储前后保持一致,标准I/O的fread()fwrite()函数用二进制形式处理数据。
意味着我们不光能向文件中写字符,还可以写入intdouble等类型的数据。

如果以程序所用的表示方法把数据存储在文件中,则称以二进制形式存储数据
——————————————————
例如,double类型的值存在一个double大小的单元中

#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nmemb, FILE * stream);

功能:
	从文件中读取任意类型的内容
	
参数:
	ptr:保存数据变量的地址,如数组、结构体等
	size:每一个块(对象)的长度
	nememb:块数(对象数)
	stream:文件指针
	
返回值:
	成功:实际读取的块数(对象数)
	失败:0
	读取到文件末尾也返回0
#include <stdio.h>
size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE * stream);

功能:
	向文件写入任意类型的内容

参数:
	ptr:要写入的数据
	size:每一个块(对象)的长度
	nememb:块数(对象数)
	stream:文件指针

返回值:
	成功:写入的块数(对象数),从第一个参数的首地址数据开始一直写到指定的字节数
	失败:0

这里注意fread读取数据时,如果能够读取的数据大于指定的size*nememb,那么读取size*nememb大小的数据(也要小心越界问题),否则,能读多少读多少。

fwrite写数据时,如果给指针ptr下的数据比size*nememb大,那么只写size*nememb大小数据,否则,会一直向后读内存向文件中写数据,直到写入数据够size*nememb

fseek() / rewind() / ftell()

#include <stdio.h>
int fseek(FILE *stream, long offset, int whence);
功能:
	定位文件指针的位置
参数:
	stream 文件指针
	offset 要偏移的位置,可正可负
	whence 相对位置
		SEEK_SET 文件起始位置,为0
		SEEK_CUR 文件当前位置
		SEEK_END 文件末尾位置,最后一个字符的下一个位置
返回值:
	成功:0
	失败:‐1
void rewind(FILE *stream);

功能:
	将文件指针定位到起始位置

参数:
	stream:文件指针

返回值:无

rewind(fp) <==> fseek(fp, 0, SEEK_SET);

long ftell(FILE *stream);
功能:
	获取当前文件指针定位的位置

参数:
	stream:文件指针

返回值:
	成功:当前文件指针定位的位置
	失败:‐1

fgetpos() / fsetpos()

fseek()ftell()潜在问题是他们把文件大小现在在long类型范围下(大概20亿字节)
ANSI C新增两个函数,处理较大文件的定位。他们定义了一个新类型fpos_t(file position type,文件定位类型),它不是一个基本类型,不能定义为数组类型,其他没有限制。

不过,ANSI C定义了如何使用fpos_t类型

#include <stdio.h>
int fgetpos(FILE * stream, fpos_t * pos);

功能:
	获取文件中的当前位置距离文件开头的字节数

参数:
	stream 要获取位置的文件指针
	pos    文件的位置信息放在该指针指向的地址

返回值:
	成功 0
	失败 非0
#include <stdio.h>
int fsetpos(FILE * stream, const fpos_t * pos);

功能:
	设置文件指针指向偏移值后指定的位置。

参数:
	stream 文件指针
	pos    文件指针位置信息

返回值:
	成功 0
	失败 非0

获取行缓冲、全缓冲大小

#include <stdio.h>

int main(int argc, char const *argv[])
{
	 //stdout
	printf("hello world\n");
	printf("行缓冲大小:%ld字节\n", stdout->_IO_buf_end ‐ stdout>_IO_buf_base);

	FILE *fp = fopen("file.txt", "w");
	fputc('w', fp);
	printf("全缓冲大小:%ld字节\n", fp->_IO_buf_end ‐ fp‐>_IO_buf_base);

return 0;
}

获取缓冲区大小前,一定要先使用缓冲区,否则打印出来的值为0

setvbuf() 设置缓冲区的大小

int setvbuf(FILE * fp, char * buf, int mode, size_t size);

功能:
	创建一个供标准IO函数替换使用的缓冲区。
	使用要求,需要在打开文件后且未对流进行其他操作前,掉用该函数。

参数:
	fp  待处理的文件流
	buf 指向待使用的存储区,如果buf的值不为NULL,则必须创建缓冲区。
		如果传NULL,函数会为自己创建一个缓冲区(我也不知道这个函数要缓冲区干嘛😂),文件缓冲没有变化。
	mode 缓冲区的模式
		_IOFBF 全缓冲(full buf) 0 
		_IOLBF 行缓冲(line buf) 1
		_IONBF 无缓冲(no buf)   2   
	size 一般指传入数组大小(字节),文件的缓冲区也与此有关

返回值:
	成功 0
	失败 非0

当设置为全缓冲或者行缓冲时,读写数据会先放在指定的数组中,满足刷新条件后再读写文件,所以此处最好不要把size设置的比数组大,容易出大问题。

当设置无缓冲时,缓冲区大小为1,与传入数组和size大小无关。

还有注意,这里如果在没有刷新缓冲区前,把数组中的值修改了,则读写的数据也会改变😂

程序存储一个数据对象,建立的缓冲区会依照该数据对象来建立,缓冲区的大小是该数据对象到的整数倍

系统创建的缓冲区一般都为512或者512的倍数

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2021-10-04 13:11:34  更:2021-10-04 13:13:30 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/15 18:41:29-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码