文章目录
前言
学习本文前需要新了解的相关知识Linux系统中的错误码与缓冲区
一、IO相关知识
1.1 最先接触过的IO
下面的头文件就是在我们学习时最先接触过的一个IO。
#include <stdio.h>
stdio
:标准的输入输出,也就是我们本文要学习的标准IO。
printf/scanf
:标准IO的接口。
1.2 标准IO与文件IO的区别
标准IO:是库函数
文件IO:是系统调用
1.3 库函数与系统调用的区别
系统调用(posix):从用户空间进入内核空间的一次过程就是一次系统调用。
系统调用没有缓冲区效率比较低。系统调用的可移植性比较差(接口不统一)。
函数库(ANSI C):函数库=缓冲区+系统调用。
库函数效率比系统调用的高,并且可以移植性比较强。
二、FILE指针
FILE是一个结构体,fopen
函数返回的就是一个结构体指针,这个FILE指针记录了打开文件的一切信息,我们在操作文件的时候就通过这个FILE完成。
typedef struct _IO_FILE FILE;
struct _IO_FILE {
char* _IO_buf_base; //缓冲区的起始地址
char* _IO_buf_end; //缓冲区的结束地址
...
}
在一个正在运行的程序中默认已经产生了三个FILE指针
stdin
:标准输入,程序从终端读取数据的时候使用。
stdout
:标准输出,程序向终端输出数据的时候使用。
stderr
:标准出错。
三、标准IO常用的接口
标准IO常用的接口:
printf、scanf、fopen、fclose、fgetc、fputc、fgets、fputs、fread、fwrite…(由于printf和scanf我们已经很熟悉了本文不再讲解)。
3.1 fopen/fclose函数
#include <stdio.h>
FILE *fopen(const char *pathname, const char *mode);
功能:使用标准IO接口打开文件
参数:
@pathname:文件的路径和名字 例:"./hello.txt"
@mode: 文件打开的方式 例:"r","w"
方式有以下6种:
"r" 以只读的方式打开文件,将光标定位到开头
"r+" 以读写的方式打开文件,将光标定位到开头
"w" 以只写的方式打开文件,若文件不存在则创建文件,若文件
存在则清空文件,将光标定位到开头
"w+" 以读写的方式打开文件,若文件不存在则创建文件,若文件
存在则清空文件,将光标定位到开头
"a" 以追加的方式打开文件,若文件不存在则创建文件,若文件
存在则将光标定位到结尾
"a+" 以读和追加的方式打开文件,若文件不存在则创建文件,若
文件存在不会清空,读的时候光标定位在开头,写的时候光
标定位在结尾
返回值:成功返回FILE指针,失败返回NULL,置位错误码。
#include <stdio.h>
int fclose(FILE *stream);
功能:关闭文件
参数:
@stream:文件指针
返回值:成功返回0,失败返回EOF(#define EOF (-1)),置位错误码
3.1.1 代码示例
fopen代码示例:
#include <stdio.h>
int main(int argc, const char* argv[])
{
FILE* fp;
fp = fopen("./hello.txt","w");
if(fp == NULL){
printf("fopen file error\n");
return -1;
}
return 0;
}
fclose代码示例:
#include <stdio.h>
#define PRINT_ERR(msg) \
do \
{ \
printf("%s %s %d\n",__FILE__,__func__,__LINE__); \
perror(msg); \
return -1; \
} while (0)
int main(int argc, const char* argv[])
{
FILE* fp;
if ((fp = fopen("./hello.txt", "w")) == NULL)
PRINT_ERR("fopen hello.txt error");
if (fclose(fp))
PRINT_ERR("close file error");
//关闭标准输入
if (fclose(stdin))
PRINT_ERR("close stdin error");
char ch;
scanf("%c",&ch);
printf("11111111111111111\n");
if (fclose(stdout))//关闭标准输出
PRINT_ERR("close stdin error");
printf("222222222222222222\n"); //终端上不会显示2打印信息
return 0;
}
结果展示:
3.1.2 总结
- 如果文件没有关闭,在程序结束的时候,操作系统会自动回收
- 对于文件指针,只能关闭一次,不能关闭多次
- 如果在一个服务器,打开的文件不关闭,最后就不能够打开新的文件的,因为它属于资源,所以最好自己打开的文件就自己关闭。
- 如果关闭了标准输入
stdin
就不能够在输入了,比如不能够调用scanf函数。 - 如果关闭了标准输出
stdout
,不能够使用printf函数。
3.2 fgetc/fputc函数
int fgetc(FILE *stream);
功能:从文件中获取一个字符
参数:
@stream:文件指针
返回值:成功返回字符的ascii,失败返回EOF
int fputc(int c, FILE *stream);
功能:向文件中输入一个字符
参数:
@c:要输入的字符
@stream:文件指针
返回值:成功返回字符的ascii,失败返回EOF
3.2.1 代码示例
使用fgetc/fputc实现文件拷贝
#include <stdio.h>
#define PRINT_ERR(msg) \
do \
{ \
printf("%s %s %d\n",__FILE__,__func__,__LINE__); \
perror(msg); \
return -1; \
} while (0)
int main(int argc, const char* argv[])
{
//1.判断传参是否正确./a.out srcfile destfile
if(argc!=3)
{
printf("传参错误,请重新输入\n");
printf("usage:./a.out srcfile destfile\n");
return -1;
}
//2.打开源文件和目标文件
FILE *sfp,*dfp;
if((sfp=fopen(argv[1],"r"))==NULL)
{
PRINT_ERR("fopen srcfile error");
}
if((dfp=fopen(argv[2],"w"))==NULL)
{
PRINT_ERR("fopen destfile error");
}
//3.循环读取拷贝
char ch;
while((ch=fgetc(sfp))!=EOF)
{
fputc(ch,dfp);
}
//4.关闭文件
fclose(dfp);
fclose(sfp);
return 0;
}
结果展示:
注:拷贝文件之后使用diff命令比较两个文件,如果命令没有任何输出,说明两个文件是一样的
3.2.2 总结
当读取到文件的结尾的时候,打印的是EOF(-1)
3.3 fgets/fputs函数
char *fgets(char *s, int size, FILE *stream);
功能:从文件中读取字符串
/*从文件中最多读取小于size的字符,并将其储存到s所指向的缓冲区中。读取
在遇到EOF或者换行符('\n')之后停止,如果读取换行符则将其储存到缓冲区
中,并将一个终止符NULL('\0')存储在缓冲区的最后一个字符之后*/
参数:
@s:存储读取到的字符的首地址
@size:想读取的大小
@stream:文件指针
返回值:成功返回s,失败返回NULL
int fputs(const char *s, FILE *stream);
功能:向文件中写入字符串
/*向文件写入字符串,但是不包括字符串结尾的终止符'\0'*/
参数:
@s:字符串的首地址
@stream:文件指针
返回值:成功返回大于0的值,失败返回EOF
3.3.1 代码示例
使用fgets/fputs拷贝文件
#include <stdio.h>
#define PRINT_ERR(msg) \
do \
{ \
printf("%s %s %d\n",__FILE__,__func__,__LINE__); \
perror(msg); \
return -1; \
} while (0)
int main(int argc, const char* argv[])
{
//1.判断传参是否正确./a.out srcfile destfile
if(argc!=3)
{
fputs("input error,try again\n", stderr);
fputs("usage: ./a.out srcfile destfile\n", stderr);
return -1;
}
//2.打开源文件和目标文件
FILE *sfp,*dfp;
if((sfp=fopen(argv[1],"r"))==NULL)
{
PRINT_ERR("fopen srcfile error");
}
if((dfp=fopen(argv[2],"w"))==NULL)
{
PRINT_ERR("fopen destfile error");
}
//3.循环读取拷贝
char buf[128]={0};
while(fgets(buf,sizeof(buf),sfp)!=NULL)
{
fputs(buf,dfp);
}
//4.关闭文件
fclose(dfp);
fclose(sfp);
return 0;
}
结果展示:
3.3.2 总结
当用追加读的方式打开文件的时候,如果有fputs的操作,光标的位置会移动到结尾的位置,fgets
则会读取不到内容。
如果将错误信息写入到标准出错中,标准错误没有缓冲区会直接显示。
3.4 fread/fwrite函数
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
功能:从文件中读取数据到ptr中
参数:
@prt:用来存储读取到的数据的首地址
@size:每一项的大小
@nmemb:项目的个数
@stream:文件指针
返回值: 成功返回读到项目的个数,如果每一项的大小为1字节,则返回项目的
个数就是字节的个数
失败返回小于项目的个数或者是0
如果失败需要通过ferror或者feof判断是哪种错误
if(ferror(fp))
{
printf("读的时候发生了错误\n");
]
if(feof(fp))
{
printf("读取到文件的结尾了\n");
}
size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);
功能:将ptr中的数据写到文件中
参数:
@ptr:要向文件中写入数据的首地址
@size:每一项的大小
@nmemb:项目的个数
@stream:文件指针
返回值:成功返回向文件中写入项目的个数
失败返回小于项目的个数或者是0
3.4.1 代码示例
使用fread和fwrite完成文件的拷贝
#include <stdio.h>
#define PRINT_ERR(msg) \
do \
{ \
printf("%s %s %d\n",__FILE__,__func__,__LINE__); \
perror(msg); \
return -1; \
} while (0)
int main(int argc, const char* argv[])
{
//1.判断传参是否正确./a.out srcfile destfile
if(argc!=3)
{
fputs("input error,try again\n", stderr);
fputs("usage: ./a.out srcfile destfile\n", stderr);
return -1;
}
//2.打开源文件和目标文件
FILE *sfp,*dfp;
if((sfp=fopen(argv[1],"r"))==NULL)
{
PRINT_ERR("fopen srcfile error");
}
if((dfp=fopen(argv[2],"w"))==NULL)
{
PRINT_ERR("fopen destfile error");
}
//3.循环读取拷贝
char buf[128]={0};
int ret;
while((feof(sfp)||ferror(sfp))==0)
{
ret=fread(buf,1,sizeof(buf),sfp);
fwrite(buf,1,ret,dfp);
}
//4.关闭文件
fclose(dfp);
fclose(sfp);
return 0;
}
结果展示:
3.4.2 总结
使用fread/fwrite可以读写结构体,整数,和字符串。
(feof(sfp)||ferror(sfp)
等于零的时候才说明没有出错或者没有读到结尾。
3.5 sprintf/snprintf/fprintf函数
int sprintf(char *str, const char *format, ...);
功能:将字符串格式化到str中,(sprintf在越界输入的时候会发生错误)
参数:
@str:储存格式化后的字符串
@format,...:可变参数,和printf里的写法完全相同
返回值:成功返回格式化后的字符个数,失败或遇到错误则返回负值
int snprintf(char *str, size_t size, const char *format, ...);
功能:将字符串格式化到str中
参数:
@str:储存格式化后的字符串
@size:输入的字符的个数,最后一个成员包含'\0'
@format,...:可变参数,和printf里的写法完全相同
返回值:成功返回格式化后的字符个数,失败或遇到错误则返回负值
int fprintf(FILE *stream, const char *format, ...);
功能:将字符串格式化到对应的stream文件中
参数:
@stream:文件指针
@format,...:可变参数,和printf里的写法完全相同
返回值:成功返回格式化后的字符个数,失败或遇到错误则返回负值
3.5.1 代码示例
sprintf/snprintf/fprintf函数举例
#include <stdio.h>
#include <string.h>
#define PRINT_ERR(msg) \
do \
{ \
printf("%s %s %d\n",__FILE__,__func__,__LINE__); \
perror(msg); \
return -1; \
} while (0)
struct {
char name[20];
int age;
} a = {
"zhangsan",
22
};
int main(int argc, const char* argv[])
{
int num=123456;
char buf[128]={0};
//1.打开文件
FILE *fp;
if((fp=fopen("hello.txt","w"))==NULL)
{
PRINT_ERR("fopen error");
}
//2.将整数格式化成字符串放到buf中
sprintf(buf,"2.num=%d,%d\n",num,num+1);
fwrite(buf,1,strlen(buf),fp);
//3.将结构体的成员格式化为字符串放到buf中
memset(buf,0,sizeof(buf));
sprintf(buf,"3.name=%s,age=%d\n",a.name,a.age);
fwrite(buf,1,strlen(buf),fp);
//4.即使这里越界输入,snprintf也只会格式化size-1个字符
char buff[5]={0};
snprintf(buf,5,"4.12345fsdfasdfasdfsadfasdfasf");
printf("buf = %s\n",buf);
//5.向标准错误中写字符串
fprintf(stderr,"5.hello world\n");
//6.向标准输出中写字符串
fprintf(stdout,"6.1234567890\n");
//7.向文件中写
fprintf(fp,"7.我是夜猫徐%d\n",666);
//8.关闭文件
fclose(fp);
return 0;
}
3.5.2 结果展示
3.6 fseek/ftell/rewind光标操作函数
int fseek(FILE *stream, long offset, int whence);
功能:用来设置光标的位置
参数:
@stream:文件指针
@offset:将光标设置(偏移)到哪里
>0 向后偏移
<0 向前偏移
=0 不偏移
@whence:从哪开始偏移
SEEK_SET 开头
SEEK_CUR 当前位置
SEEK_END 结尾
返回值:成功返回0,失败返回-1,置位错误码
long ftell(FILE *stream);
功能:获取光标到开头的值(单位字符)
参数:
@stream:文件指针
返回值:成功返回光标当前位置到开头的字符个数,失败返回-1,置位错误码
void rewind(FILE *stream);
功能:将光标设置到文件的开头
参数:
@stream:文件指针
返回值:无
3.6.1 代码示例
fseek/ftell/rewind函数举例
#include <stdio.h>
#include <string.h>
#define PRINT_ERR(msg) \
do \
{ \
printf("%s %s %d\n",__FILE__,__func__,__LINE__); \
perror(msg); \
return -1; \
} while (0)
int main(int argc, const char* argv[])
{
//1.打开文件
FILE *fp;
if((fp=fopen("hello.txt","r+"))==NULL)
{
PRINT_ERR("fopen error");
}
//2.从开头向后偏移10位
fseek(fp,10,SEEK_SET);
//3.返回光标到开头的位置
printf("pos=%ld\n",ftell(fp));
printf("%c\n", fgetc(fp));
fputc('1',fp);
//4.将光标设置到开头位置
rewind(fp);
fputc('2',fp);
//.关闭文件
fclose(fp);
return 0;
}
结果展示:
3.6.2 总结
rewind(fp);
等价于fseek(fp,0,SEEK_SET);
fseek(fp,0,SEEK_END);
和ftell(fp);
这两句代码就可以统计文件的大小。
注:
如果文件是以a+/a
的方式打开,通过fseek
修改光标的位置无效。写的内容永远在结尾。但是可以修改读的光标的位置。