Linux标准IO概述

Linux标准I/O概述

1.0 标准I/O 的由来

标准IO是指ANSI C(ANSI C是美国国家标准协会(ANSI)对C语言发布的标准)中定义的用于I/O操作的一系列函数
只要操作系统中安装了C库,标准I/O函数就可以调用,换句话说,如果程序中使用的是标准I/O函数,那么源代码不需要修改就可以在其它操作系统下编译运行,具有更好的可移植性
在执行系统调用时,Linux必须从用户态切换到内核态,处理相应的请求,然后再返回到用户态。如果频繁执行必定增加系统开销,为避免这种情况,标准I/O使用时在用户空间创建缓冲区,读写实先操作缓冲区,再通过系统调用访问世纪的文件,而减少系统调用的次数。

1.2 流的含义

标准I/O的核心对象就是流。那流到底是什么呢?
当使用标准I/O打开一个文件时,就会创建一个FILE结构体,这个结构体用来描述改文件(可以理解为创建一个FILE结构体和实际打开的文件进行关联起来),我们把这个FILE结构体就称为流,标准I/O函数都基于流进行所有的操作。

1.3 标准I/O的缓冲机制

在前面我们提到了标准I./O是有缓冲机制的,缓冲区创建于用户空间,那么缓冲的具体类型有哪些呢?
其实,标准I/O中流的缓冲类型有3种:
1、全缓冲: 当填满标准I/O缓冲区后再进行实际的I/O操作。
对于存放在磁盘上的普通文件,标准I/O打开的默认就是全缓冲。当缓冲区已满或者执行FULSH操作室才会进行磁盘操作。
2、行缓冲:当在输入和输出中遇到换行符时执行I/O操作。
标准输入流和输出流就是使用行缓冲,想想printf();的格式,就是遇到\n后进行输出,行缓冲。
3、无缓冲: 不对I/O操作进行缓冲。
即对流的读写时会立刻操作实际的文件。标准出错流是不带缓冲的,理论上所有出错输出都不应该是缓冲的,因为出错就应该立刻输出。

1.4 标准I/O编程

本节只介绍最常用的函数。本就所讨论的I/O操作都是基于流的,它包含ANSI C的标准。
首先扔一个问题,想想 open() 与 fopen()的区别?

1.4.1 流的打开

使用标准I/O打开的函数有fopen(),fdopen(),freopen()。它们可以用不同的模式打开文件,每一个都返回一个指向FILE的指针。 此后,对文件的读写都通过这个FILE指针来进行。
fopen():可以指定打开文件的路径和模式;
fdopen():可以指定打开文件的描述符和模式;
freopen():可以指定打开的文件和模式,还可以指定特定的 I/O 流;
在这里插入图片描述
注意:给环境变量赋值需要用 export, 而不是直接path = "********"
在这里插入图片描述
有些人可能会疑惑 “b” 是干嘛的,其实在每个选项中加入 b 字符是用来告诉函数库打开的文件是二进制文件,不是纯文本文件,不过在linux系统中会忽略该符号。

注意: 当用户进程运行时,系统会自动打开 3 个流: 标准输入流stdin;标准输出流stdout;标准错误流 stderr
标准输入流stdin:用来从标准输入设备(默认键盘)中读取输入内容;
标准输出流stdout:用来从标准输出设备(默认当前终端)中输出内容;
标准错误流 stderr:用来向标准错误设备(默认当前终端)中输出错误信息;

1.4.2 流的关闭

关闭流的函数为 fclose();该函数将流的缓冲区的数据全部写入文件中,并释放相关资源。
在这里插入图片描述
注意:程序结束时,会自动关闭所有的打开的流

1.4.3 错误信息

标准 I/O 函数执行时如果出现错误,会把错误码保存在全局变量 errno 中,程序员可以打印错误信息,处理错误的相关函数:1. perror(); 2. strerror()

  1. perror():
    在这里插入图片描述
    其实,perror() 函数的使用还是比较简单的,在之前的错误信息输出中很多同学采用的是标记性输出,printf("************"); 这种方式,这样可以利用输出的信息来断点在某个区间,但无法获取准确的错误信息,采用perror() 可以得到详细的错误原因。
    来,咱不止说,拿个例子试试看。
#include <stdio.h>
 
int main(int argc, char *argv[])
{
	FILE *fp;   //指定流指针
	if ( NULL ==  ( fp = fopen( "a.txt","r")))   // NULL为系统宏,用流指针接收 fopen()的返回值;
	{
		perror(" fail to open");
		return -1;
	}
	fclose(fp);
	
	return 0;
}

接下来,我们看看实际的效果

在这里插入图片描述
错误信息分析:

 fail to open: No such file or directory

错误处理相关函数 :
2. strerror()
在这里插入图片描述
同样的,我们拿个例子试试手,看看怎么操作,什么结果。

#include <stdio.h>
#include <errno.h>
#include <string.h>
 
int main(int argc, char *argv[])
{
	FILE *fp;   //指定流指针
	if ( NULL ==  ( fp = fopen( "AA.txt", "r"))) 
	{
		printf(" fail to fopen: %s\n", strerror(errno));
		return -1;
	}
	
	fclose(fp);
	return 0;
}

在当前路径下,并没有“AA.txt” 文件。看看结果:
在这里插入图片描述
根据这两个错误信息的打印,能够知道这俩个都可以将具体的错误原因进行罗列输出。

1.4.4 流的写入

  1. 按照字符(字节)输入
    字符输入 / 输出函数一次仅读写一个字符。要点总结一下,如下:
    字符输入函数原型:
    1. int getc ( FILE * stream);
    2. int fgetc ( FILE * stream);
    3. int getchar ( void );
    getc()fgetc()从指定的流中读取一个字符(节),getchar()stdin 中读取一个字符(节)。
  2. 按照字符(字节)输出
    字符输入函数原型:
    1. int putc ( FILE * stream);
    2. int fputc ( FILE * stream);
    3. int putchar ( void );
    putc()fputc()从指定的流中读取一个字符(节),putchar()stdout 中输出一个字符(节)。
    同样,我们还是简单的实验一下,结合fputc() 和 fgetc()尝试一下。
#include <stdio.h>
#include <errno.h>
#include <string.h>
 
int main(int argc, char *argv[])
{
	int c;
	while( 1 )    //循环
	{
		c = fgetc (stdin);    // 从键盘读取一个字符
		if (( '0' < c) && ( '9' >= c) )   // 字符判断是否为数字
		{
			fputc( c,stdout);      // 满足标准输出
		}
		else if( '\n' == c)			// 当遇到‘\n’时跳出
		{
			break;
		}
	}
	return 0;
}

运行后输入1321rrerf,最终结果:1321

  1. 按照行输入
    行输入/输出函数一次操作一行。
    行输入函数总结一波如下:
    1. char * gets( char * s);
    2. char * fgets( char *s, int size, FILE* stream);
    注意:gets()函数不安全,容易造成缓冲区溢出 ,不建议使用。(原因:由于gets()不检查字符串string的大小,必须遇到换行符或文件结尾才会结束输入,因此容易造成缓存溢出的安全性问题,导致程序崩溃,可以使用fgets()代替。)
    fgets()从指定的流中读取一个字符,当遇到换行符 \n 时,会读取 \n 或者读取sizee -1个字符后返回。
    fgets()不能保证每次都能读取到一行。

  2. 行输出函数
    1. int puts( const char * s);
    2. int fputs( const char * s, FILE * stream);
    惯例哈,思考:怎么获取一个文本文件的行数。文后提供代码

  3. 指定大小为单位读写文件
    在文件流被打开以后,可对文件流按照指定大小为单位进行读写操作。这里简单介绍一下,毕竟其他都差不多,只是函数参数和返回值的问题
    函数:1. fread() ;2. fwrite();
    fread():
    在这里插入图片描述
    fwrite();
    在这里插入图片描述

1.4.5 流的定位

每个打开的流内部都有一个当前读写位置。留在打开时,当前读写位置为 0,表示文件的开始位置,每读写一次后,当前读写位置自动增加实际读写的大小,在读写流之间可先对流进行定位,即移动到当前指定的位置再进行操作。
函数1. fseek(), 2. ftell();

  • fseek()
    在这里插入图片描述
  • ftell():
    在这里插入图片描述
    惯例哈,思考:怎么获取一个文件的大小。文后提供代码
    好了,今天我们就将Linux标准I/O编程进行了简单的介绍,其中很多东西还需要朋友们自己去消化,比如偏移量是什么,基准值是什么,缓冲区在用户空间什么位置,怎么确定缓冲区到底多大等等,篇幅问题就不过多介绍。

1.6 总结

接下来,我们进行两个问题的解答:
1、 怎么计算一个文本文件的行数,比如算一下当前路径下 1.txt 有多少行。
首先拿到题目,看目标,计算文本文件的行数,那么,回忆一下用什么行数可以来实现呢?
——行输入函数
可是行输入函数有 gets()fgets(),到底用什么呢?
——fgets()
那为什么要选择fgets()而不用gets()呢?

前面已经都介绍过了,自己回忆回忆,就不再重复介绍了。这道题我们用fgets()来实现一下。

#include <stdio.h>
#include <errno.h>
#include <string.h>
 
int main(int argc, char *argv[])
{
	int line = 0;    // 保存具体的行数
	char buf[128];   // 用数组来装输入的字符串,128字节大小
	FILE *fp;        // 指定流
	
	if( argc < 2)	// 参数个数,其实就是要求文件和 a.out一起执行
	{
		printf( "Usage: %s<file>\n", argv[0]);
		return -1;
	}
	
	if ( NULL == ( fp = fopen( argv[1], "r")))   // 判断是否正常打开文件
	{
		perror(	"fail to open");
		return -1;
	}
	
	while( NULL != fgets( buf, 128, fp))   // 循环读取,buf是数组名,也是首地址
	{
		if ( '\n' == buf[ strlen(buf) -1])    // 换行符即代表着新的一行了嘛,这就是行数增加的依据
		{
			line++;
		}
	}
	
	printf(" The lines of %s is %d \n",argv[1],line);
	return 0;
}

来来来,运行一下试试。在当前路径下创建一个 1. txt,然后联合a.out 运营一下看看实际效果
在这里插入图片描述
2、怎么计算一个文件的大小。
思路理一理:
想想,文件的大小,这是什么玩意?
文件大小是什么意思?
大小,在计算机中指的是什么?
单位是什么?
这个单位是最小的单位吗?
如果不是,那和最小的单位换算关系是什么?
最小的单位又是什么?
最小的单位怎么计算呢?
来来来,继续哈,想清楚了就继续搞起来。

#include <stdio.h>
#include <errno.h>
#include <string.h>
 
int main(int argc, char *argv[])
{
	FILE *fp;
	
	if ( argc <2)
	{
		printf( "Usage: %s<file>\n ", argv[0]);
		return -1;
	}
	
	if ( NULL == (fp = fopen( argv[1], "r")))
	{
		perror(" fail to open");
		return -1;
	}
	fseek(fp, 0, SEEK_END);
	printf( " The size of %s is %d k \n", argv[1],ftell(fp)) ;
	
	return 0;
} 

fseek()函数在前面流的定位中已经讲过相关参数,请理解偏移量,基准值。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章