文章目录
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的一系列函数里,用着用着就不陌生了,本人以为这里重点就是两个示例代码的两个坑,不要忽略!!!!。