基于Linux的标准I/O编程

1、标准I/O概述

1.1什么是标准I/O

标准I/O指的是ANSI C中定义的用于I/O操作的一系列函数。

1.2标准I/O的优点

(1)增加了源代码的移植性
--------因为标准I/O的一系列函数在C库中定义,只要操作系统安装了C库,那么对于标准I/O的函数,更换操作系统并不影响源代码的运行。
(2)减少了系统调用次数,提高了系统效率。
-------在执行标准I/O函数的时候也会用到系统调用。在执行系统调用时,Linux必须从用户态切换到内核态,处理相应的请求,然后再返回到用户态。如果频繁的执行系统调用会增加系统的开销。为了避免这种情况,标准I/O使用时在用户空间创建了缓冲区,读写时先操作缓冲区,在合适的时机再通过系统调用访问实际的文件,从而减少了系统调用的次数。

1.3流的含义

标准I/O的核心对象就是流。那么什么是呢?
当用标准I/O打开一个文件时,就会创建一个FILE结构体描述该文件(或者理解为创建一个FILE结构体和实际打开的文件关联起来),我们把这个FILE结构体形象的称为流。
标准I/O函数都基于流进行各种操作。
标准I/O中流的缓冲类型有以下3中:
(1)全缓冲。在这种情况下,当填满标准I/O缓冲区后才进行实际的I/O操作。对于存放在磁盘上的普通文件,用标准I/O打开时默认是全缓冲的。当缓冲区已满或执行fflush(或flush文件I/O)操作时才会进行磁盘操作。
【 flush() 是把缓冲区的数据强行输出, 主要用在IO中,即清空缓冲区数据,一般在读写流(stream)的时候,数据是先被读到了内存中,再把数据写到文件中,当你数据读完的时候不代表你的数据已经写完了,因为还有一部分有可能会留在内存这个缓冲区中。这时候如果你调用了close()方法关闭了读写流,那么这部分数据就会丢失,所以应该在关闭读写流之前先flush()。】
(2)行缓冲。在这种请况下,当在输入和输出中遇到换行符时执行I/O操作。标准输入流和标准输出流就是使用行缓冲的典型例子。
(3)无缓冲。不对I/O操作进行缓冲,即在对流的读写时会立即操作世纪的文件。标准出错流是不带缓冲的,这就使得出错信息可以立刻显示在终端上,而不管输出的内容是否包含换行符。

2、标准I/O编程

2.1 流的打开

使用标准I/O打开文件的函数有fopen()、fdopen()、freopen()。它们可以以不同的模式打开文件,都返回一个指向FILE的指针,该指针指向对应的I/O流。
(1)fopen(),可以指定打开文件的目录和模式;
(2)fdopen(),可以指定打开的文件描述符和模式;
(3)freopen(),除可指定打开的文件和模式外,还可以指定特定的I/O流。
这里主要总结fopen()函数的语法要点:
FILE * fopen (const char * path , const char * mode);

  • 头文件:#include<stdio.h>
  • 函数参数:
    path:要打开文件的路径和文件名
    mode:文件的打开方式
  • 返回值:
    成功:返回FILE的指针
    失败:NULL
    mode的取值方式
r或rb以只读的方式打开文件,该文件必须存在
r+或r+b以读写的方式打开文件,该文件必须存在
w或wb以只写的方式打开文件,若文件存在,则擦写文件以前的内容,默认文件长度为0,若文件不存在,则创建文件
w+或w+b以读写的方式打开文件,若文件存在,则擦写文件以前的内容,默认文件长度为0,若文件不存在,则创建文件
a或ab以附加的方式打开的文件,若文件不存在,则创建文件 ,若文件存在,则在文件的末尾追加要写入的内容,文件原来的被保留
a+或a+b以附加并可读的方式打开文件,若文件不存在,则创建文件 ,若文件存在,则在文件的末尾追加要写入的内容,文件原来的被保留

注意:在每一个选项后加b字符用来告诉函数库打开的是二进制文件,而非纯文本文件,不过在Linux中该字符会被忽略。
用户程序运行时,系统会自动打开3个流,分别是stdin标准输入流、stdout标准输出流、stderr标准出错流。
stdin用来向标准输入设备(默认是键盘)读取输入内容,文件描述符为0;
stdout用来向标准输出设备(默认是当前终端)输出内容,文件描述符为1;
stderr用来向标准错误设备(默认是当前终端)输出错误信息,文件描述符为2。

2.2 流的关闭

关闭流的函数为fclose(),该函数将流的缓冲区内的数据全部写入文件中,并释放相关资源。【程序结束时会自动关闭所有打开的流】
fclose函数的语法要点:
int fclose(FILE * stream);

  • 头文件:#include<stdio.h>
  • 函数参数:
    stream:已将打开的流指针
  • 返回值:
    成功:0
    失败:EOF(end of file -1)

2.3 出错处理

标准I/O函数执行时如果出现错误,会把错误码保存在全局变量errno中。
标准I/O中提供了perror()函数来处理错误,该函数直接从标准错误流上获取错误信息。
函数perror()的语法要点:
void perror(const char s);

  • 头文件:#include<stdio.h>

  • 函数参数:
    s:在标准错误流上获得的错误信息

  • 返回值:无

在这里还有一种错误处理的函数strerror(),该函数主要是在全局变量errno上获取错误信息。
函数strerror()的语法要点:
char * strerror(int errno);

  • 头文件: #include<string.g> , #include<errno.h>
  • 函数参数 errno:错误码
  • 返回值:错误码对应的错误信息
    示例:
if(fp=fopen("hello.c","r") == NULL)
{
	printf("fail to fopen: %s\n",strerror(errno));
	return -1;
}

2.4流的读写

2.4.1按字符(字节)输入/输出

字符输入/输出函数一次仅读写一个字符。
字符输入函数语法要点:
int getc ( FILE * stream);
int fgetc (FILE stream);
int getchar (void);

  • 头文件:#include<stdio.h>
  • 函数参数:stream 要输入的文件流
  • 返回值:
    成功:读取的字符
    失败:EOF;
    getchar()从stdin中读取一个字符/字节。
    字符输出函数语法要点:
    int putc ( int c,FILE * stream);
    int fputc (int c,FILE stream);
    int putchar (int c);
  • 头文件:#include<stdio.h>
  • 函数参数:c 要输出的字符 stream 要输入的文件流
  • 返回值:
    成功:输出的字符c
    失败:EOF
    putchar是向终端输出字符,输出的字符可以是一个字符变量或常量,也可以是一个转义字符。【其格式为putchar©,其中c可以是被单引号(英文状态下)引起来的一个字符,可以是介于0~127之间的一个十进制整型数(包含0和127),也可以是事先用char定义好的一个字符型变量。】
    (这里存在一个小疑惑,明明是字符输出函数,为什么参数是int型????)

2.4.2 按行输入/输出

行输入/输出函数一次输入/输出一行。
行输入函数语法要点:
char * gets(char s);
char * fgets(char s ,int size , FILE stream);

  • 头文件:#include<stdio.h>
  • 函数参数:
    s:存放输入字符串的缓冲区的首地址
    size:输入字符串的长度
    stream:对应的流
  • 函数返回值:
    成功:输入的字符串缓冲区首地址 s
    失败或达到文件末尾:NULL;

gets函数容易造成缓冲区溢出,不推荐使用。
fgets从指定的流中读取一个字符串,当遇到’\n’时,会读取’\n’或读取size-1个字符后返回。** fgets不能保证每一次都读取一行 **

行输出函数语法要点:
int puts(const char s);
int fputs(const char s,FILE stream);

  • 头文件:#include<stdio.h>
    s:存放输出字符串的缓冲区的首地址
    stream:对应的流
  • 函数返回值:
    成功:输出的字符串缓冲区首地址 s
    失败:NULL;

用fgets()算出文件有多少行的思想

while(fgets(buf,128,fp)!=NULL)
{
	if(buf[sizeof(buf)-1] == '\n')
		line++;
}

2.4.3 以指定大小为单位读写文件

主要用到两个函数fread()和fwrite();
fread()函数的语法要点:
size_t fread(void * ptr ,size_t size, size_t count ,FILE * stream);

  • 头文件:#include<stdio.h>
  • 函数参数
    ptr:存放读入记录的缓冲区
    size:读取的每个记录的大小,一般填1
    nmemb:读取的记录数
    stream:要读取的文件流
  • 函数返回值:
    成功:返回实际读取到的count数目
    失败:EOF
    重点:
    (1) 返回成功读取的对象个数,若出现错误或到达文件末尾,则可能小于count。若size或count为零,则fread返回零且不进行其他动作。
    fread不区分文件尾和错误,因此调用者必须用feof和ferror才能判断发生了什么。
    (2)fread的返回值不是字节数,只有在size为1的时候,返回的count数目才等于字节数,因为size为1的时候数据块的大小刚好是一个字节。

fread()函数的语法要点:
size_t fwrite(void * ptr ,size_t size, size_t count ,FILE * stream);

  • 头文件:#include<stdio.h>
  • 函数参数
    ptr:存放写入记录的缓冲区
    size:读取的每个记录的大小,一般填1
    nmemb:写入的记录数
    stream:要写入的文件流
  • 函数返回值:
    成功:返回实际写入到的count数目
    失败:EOF

2.5流的定位

每个打开的流内部都有一个当前读写位置。流在打开时,当前读写位置为0,表示文件的开始位置。每读写一次后,当前读写位置自动增加实际读写的大小。在读写流之前可先对流进行定位,即移动到指定的位置后再操作。
定位流的函数是fseek(),语法要点如下:
int fseek(FILE * stream ,long offest , int whence);

  • 头文件:#include<stdio.h>
  • 函数参数;
    stream:要定位的文件流
    offest:相对于基准值的偏移量
    whence:基准值
    【 SEEK_SET代表文件起始位置、SEEK_END代表文件结束位置、SEEK_CUR代表文件当前位置 】
  • 函数返回值:
    成功:0
    失败:EOF

获取文件文件当前位置的函数: ftell();
语法要点:
long ftell(FILE stream);

  • 头文件:#include<stdio.h>
  • 参数:stream要定位的文件流
  • 返回值:
    成功:返回当前的读写位置
    失败:EOF

fseek()函数和ftell函数联合起来可以计算文件大小,思想如下:

fseek(fp,0,SEEK_END);
printf("%ld\n",ftell(fp));

2.6格式化输入/输出

格式化输入/输出函数可以指定输入/输出的具体格式。

格式化输入函数的语法要点:
int scanf(const char * format,…);
int fscanf(FILE * stream ,const char * format, …);
int sscanf(char buf ,const char format,…);

  • 函数传入值:
    format:输入格式;
    stream:作为输入的流
    buf:作为输入的缓冲区

  • 返回值:
    成功:输入字符数
    失败:EOF

格式化输出函数的语法要点:
int printf(const char * format,…);
int fprintf(FILE * stream ,const char * format, …);
int sprintf(char buf ,const char format,…);

  • 函数传入值:
    format:输出格式;
    stream:作为输出的流
    buf:作为输出的缓冲区

  • 返回值:
    成功:输出字符数
    失败:EOF

3、示例代码,加深标准I/O的理解

3.1文件复制示例代码

#include<stdio.h>
#include<string.h>
#include<errno.h>
#define N 2
int main(int argc, const char *argv[])
{
	FILE *fpd,*fps;
	char buf[N];
	size_t n;
	if(argc<3)
	{
		printf("Format:%s <file_1> <file_2 >\n",argv[0]);
		return -1;
	}
	if( (fpd=fopen(argv[1],"r")) == NULL )
	{
		fprintf(stderr,"fail to fopen %s: %s\n",argv[1],strerror(errno));
		return -1;
	}
	if((fps=fopen(argv[2],"w")) ==NULL )
	{
		printf("fail to fopen %s: %s\n",argv[2],strerror(errno));
		fclose(fpd);
		return -1; 
	}
    fseek(fpd,0,SEEK_SET);
//	while((n=fread(buf,1,N,fpd))>0)
//	{
//		fwrite(buf,1,N,fps);
//	}
	do
	{
		n=fread(buf,1,N,fpd);
		fwrite(buf,1,n,fps);
		if(feof(fpd))
		{
			break;
		}
	}while(1);

	fclose(fpd);
	fclose(fps);
	return 0;
}

这里注销部分是个坑,我的理解是它无法判断是否达到了文件末尾,用后面的方法是正确的。

3.1循环记录系统时间示例代码

#include<stdio.h>
#include<time.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
	time_t t;
	FILE *fp;
	if(argc<2)
	{
		printf("Format: %s <file_time>\n",argv[0]);
		return -1;
	}
	if((fp=fopen(argv[1],"w")) == NULL)
	{
		perror("fail to fopen");
		return -1;
	}
	while(1)
	{
		time(&t);
		fprintf(fp,"%s\n",ctime(&t));
		sleep(1);
		fflush(fp);
	}
	/*do{
	time(&t);
	fprintf(fp,"%s\n",ctime(&t));
	//sleep(1);
	printf("ok\n");
	}while(1);*/
	fclose(fp);
	return 0;
}

理解点:在磁盘上的普通文件,用标准I/O打开默认是全缓冲,所以在死循环里需要有刷新流的操作,不然死循环的数据一直在缓冲区中,进不了文本文件。这里很重要,不能忽略。

总结

在标准I/O的一系列函数里,用着用着就不陌生了,本人以为这里重点就是两个示例代码的两个坑,不要忽略!!!!。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值