APUE学习笔记--标准I/O流(一)(fopen、perror、strerror、fgetc、fputc、gets、fgets、fputs、fread、fwrite)

  • 前言

    linux中,一切皆文件,许多高级技术都是基于I/O的。I/O必须掌握得非常扎实。

  • fopen

在这里插入图片描述

  • 返回结果
    • 若执行成功:返回FILE 指针,一个非负整数。
    • 若执行失败:返回NULL,并设置errno为对应的非负整数。
      • errno为全局变量,所以想获得正确的出错原因,得立马打印,否则可能会被其他进程修改。
      • errno在现在的系统中,很多时候是定义为一个宏。
  • 参数解析
    • path:文件所在路径。
    • mode:一个字符串用于表达该文件的打开方式。
      • r:在文件起始位置打开文件,只读。若文件不存在,则执行失败。
      • r+:在文件起始位置打开文件,可读可写。若文件不存在,则执行失败。
      • w:若文件存在,则清空,指针指向文件头;若不存在该文件,则创建该文件,指针指向文件头。只写。
      • w+:同上,只是可读可写。
      • a:若文件不存在,则创建文件。若存在,则指针指向文件末尾。只写。
      • a+:若文件不存在,则创建文件。若存在,那最初读的时候,指针指向文件头。最初写的时候,指针指向文件尾。
      • b:这使得标准I/O系统可以区分文本文件和二进制文件。而unix内核(包括linux)并不对这两种文件进行区分,所以在linux中,b这个选项加与不加并没有意义。但如果这个程序需要从linux移植到windows中去,那需要加上。
  • 注意事项
    • mode对应的字符串,系统会从前往后读,截取满足规定的头部,其余部分丢弃。如mode=“r+sffsfsff”,这不会报错,系统会认为mode=“r+”,其余部分丢弃。
    • 使用fopen得到的FILE指针所指向的内存块,存放在堆中。
    • fopen打开的流,记得要用fclose关闭。
    • 一般有互逆操作的创建函数,它的指针指向的内存块都存放在堆中的。
    • 当使用头文件中的函数时,man 该函数,该函数必须包含哪些头文件,也必须在调用它的函数中全部包含。否则会出错。test1.c的完整代码如下:
int main()
{
	int *p = malloc(sizeof(int));
	return 0;
}

编译运行时,出现如下错误:

在这里插入图片描述
按道理,malloc返回void * ,可以赋值给任何类型的指针,可这里还是报错。使用man 3 malloc,得到如下信息:
在这里插入图片描述
可知,malloc需要<stdlib.h>。我们在源代码中添加头文件,就会顺利编译通过。

  • perror

在这里插入图片描述

  • 简析:
    • 如上图所示,perror需要包含头文件<stdio.h>,且使用到了全局变量,errno、sys_nerr、sys_errlist[]。
    • perror函数的会在参数s字符串之后加上,与errno对应的具体出错原因。
  • strerror

    -
    • 简析:
      通过errnum参数获得errno值,然后返回具体的出错文本信息。

例子如下:

#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>

int 
main()
{
    FILE * fp;
	fp = fopen("tmp","r");//当前目录下没有tmp文件,则以r的方式打开,会失败。
	if(fp==NULL)
	{
		fprintf(stderr,"fopen() failed !errno=%d\n ",errno);
		perror("perror output:");
		fprintf(stderr,"strerror output:%s\n",strerror(errno));
		exit(1);
	}
	puts("OK!");
	return 0;
}

输出结果如下:
在这里插入图片描述

  • fopen打开文件上限问题

通过如下程序不断的打开文件流,直到出错,并将已打开的流的数量输出。

#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
int 
main()
{
	int count = 0;
    	FILE * fp;
	while(1)
	{
	
		fp = fopen("tmp","w");
		if(fp==NULL)
		{
		//	fprintf(stderr,"fopen() failed !errno=%d\n ",errno);
		//	perror("ffff");
			fprintf(stderr,"fopen failed!%s\n",strerror(errno));
			break;
		}
		count++;
	}
	printf("%d\n",count);
	return 0;
}

在这里插入图片描述
一个进程默认还打开了stdin、stdout、stderr三个流,所以3+1021=1024个流。即一个进程最多只能打开1024个流。
通过ulimit -a,可以得知系统设置了进程所能打开流的上限。
在这里插入图片描述
open files 对应的选项为 -n,就可以通过ulimit -n 修改上限值。

  • fgetc、fputc

    • int fgetc(FILE * stream )

      • 返回值:从指定的流中读取下一个字符,并把unsigned 的字符强制转换为int。若读取失败或者读到EOF,就返回EOF。
    • int fputc(int c, FILE *stream);

      • 将int类型的c强制转换为unsigned char,输出到指定的流中。
      • 返回值。输出失败,返回EOF。输出成功,则把c返回。
    • 利用fgetc、fputc实现自己的cp程序如下:

#include <stdlib.h>
#include <stdio.h>
#include <error.h>

int main(int argc,char * argv[])
{
	if(argc!=3)
	{
	//命令格式不正确的话,给出提示信息。
		fprintf(stderr,"Usage....%s <source file> <dest file>",argv[0]);
		exit(1);
	}
	
	FILE * fps,*fpd;
	int ch;   //这里不能用char,因为要保存EOF,很多系统的实现都为-1.
	fps = fopen(argv[1],"r");
	if(fps == NULL)
	{
		perror("source file open failed! ");	
		exit(1);
	}
	fpd = fopen(argv[2],"w");
	if(fpd == NULL)
	{
		fclose(fps); //fps已经打开,fpd没打开成功的话,这里必须关闭。
		perror("des file open failed ! ");
		exit(1);
	}

	while(1)	
	{
		ch = fgetc(fps);
		if(ch == EOF)
			break;
		fputc(ch,fpd);
	}
	fclose(fpd);
	fclose(fps);
	exit(0);
}

//实现统计一个文件的字符数
#include <stdio.h>
#include <stdlib.h>


int main(int argc,char * argv[])
{
	if(argc != 2)
	{
		fprintf(stderr,"Usage---%s <source file>",argv[0]);
		exit(1);
	}

	int count = 0;	
	int ch;
	
	FILE * fps;
	fps = fopen(argv[1],"r");
	if(fps == NULL)
	{
		perror("source file open failed !");
		exit(1);
	}
	
	ch = fgetc(fps);
	while(ch !=EOF)
	{
		count++;
		ch = fgetc(fps);
	}
	printf("count = %d \n",count);
	exit(0);
}

  • gets

    gets不安全,存在溢出的可能,不建议使用gets,建议使用fgets。

  • fgets、fputs

    • char * fgets(char *s, int size, FILE *stream);
      • 功能介绍:从指定的流中读取不多于size-1个字节存放在s中。
      • 返回值:成功返回s。失败或者这一行没有读取到有效字符,返回NULL。(EOF不属于有效字符)
      • 读取终止条件: 读到’\n’ 或者 读取到size - 1个字符。
      • 注意事项:
        • 若size=5;要读取的字符为“abcdefjg”,则读取"abcd",再在s末尾添加一个’\0’。
        • 若size=5;要读取的字符为"abc",则读取"abc\n",再在末尾加上’\0’。
        • 若size =5;要读取的字符为,“abcd”,则读取“abcd‘’,再在末尾添加一个’\0’。还剩下一个\n没有读取,存储在缓冲区中,等待读取。
      • int fputs(const char *s, FILE *stream);
        • 功能简介:将s字符串写入指定的流中。不包括‘\0’。
        • 返回值:输出成功,返回非负值。失败返回EOF。
      • 使用fputs、fgets实现cp的代码如下:

#include <stdlib.h>
#include <stdio.h>
#include <error.h>

#define BUFFSIZE 1024

int main(int argc,char * argv[])
{
	if(argc!=3)
	{
		fprintf(stderr,"Usage....%s <source file> <dest file>",argv[0]);
		exit(1);
	}
	char buf[BUFFSIZE];	
	FILE * fps,*fpd;
	int ch;
	fps = fopen(argv[1],"r");
	if(fps == NULL)
	{
		perror("source file open failed! ");	
		exit(1);
	}
	fpd = fopen(argv[2],"w");
	if(fpd == NULL)
	{
		fclose(fps);
		perror("des file open failed ! ");
		exit(1);
	}

	while(fgets(buf,BUFFSIZE,fps) != NULL)	
	{
		fputs(buf,fpd);		
	}

	fclose(fpd);
	fclose(fps);


	exit(0);
}

  • fread、fwrite

    • size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
      • 功能介绍:fread从指定流中,读取nmemb个数据块,每个数据块大小为size,然后存放在ptr指向的内存中。
      • 返回值:返回已经完整的成功的读取的数据块个数。如size=2,那读取了20Byte的数据,返回10;读取了19Byte的数据,返回9。
    • size_t fwrite(const void *ptr, size_t size, size_t nmemb,
      FILE *stream);
      • 从ptr中读取nmemb个数据块,每个数据块size字节,然后输出到指定的流中。
      • 返回值:成功完整输出的数据块个数。
    • 注意事项:

      • fread、fwrite容易出错。
      • fread(buf,1,10,fp)-------A
        fread(buf,10,1,fp)-------B
        第一种情况:若数据量足够,则A、B都不出问题。
        第二种情况:加入数据量只有五字节,则A读取后,返回5;B读取后返回0。由于读取0到9个字节都是返回0,所以此时不知道到底读了几个字节。
      • 所以,使用尽可能使用size=1的fread函数,避免许多问题。
      • 使用fread、fwrite实现cp的代码如下:
#include <stdlib.h>
#include <stdio.h>
#include <error.h>

#define BUFFSIZE 1024

int main(int argc,char * argv[])
{
	if(argc!=3)
	{
		fprintf(stderr,"Usage....%s <source file> <dest file>",argv[0]);
		exit(1);
	}
	char buf[BUFFSIZE];	
	FILE * fps,*fpd;
	fps = fopen(argv[1],"r");
	if(fps == NULL)
	{
		perror("source file open failed! ");	
		exit(1);
	}
	fpd = fopen(argv[2],"w");
	if(fpd == NULL)
	{
		fclose(fps);
		perror("des file open failed ! ");
		exit(1);
	}
	int n;
/*此处非常需要注意!

若使用如下方式进行复制,就会出问题。极有可能使得复制得到的文件比源文件多内容。
while(fread(buf,1,BUFFSIZE,fps))	
	{
		fwrite(buf,1,BUFFSIZE,fpd);
	}
分析:这个文件的大小如果刚好是BUFFERSIZE的整数倍,那不会出问题。假如不是(大概率不是),那在最后一次执行fread函数(也就是那次fread读取到文件尾)时,buf中必然没有BUFFSIZE个字符,但使用fwrite写入时,仍然会写入BUFFSIZE个字符,这就导致复制得到的文件,内容变多。
*/
	while((n = fread(buf,1,BUFFSIZE,fps)) >0)	
	{
		fwrite(buf,1,n,fpd);
	}

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值