专治白癜风的专科医院 https://disease.39.net/bjzkbdfyy/231013/j1hvyqj.html前两篇文章介绍了fopen函数以及文件访问模式”w”、”r”、”a”、“+”、“t”、“b”的各种用法。本篇介绍fopen函数的安全版本fopen_s函数,以及数据流重定向函数freopen和它的安全版本freopen_s以,以及标准流被重定向后如何恢复等知识点。
fopen_s函数
这个函数是fopen函数的安全版本,它提供了比fopen函数更好的错误检查机制,以防止缓冲区溢出等问题。
函数原型
errno_tfopen_s(
FILE**pFile,
constchar*filename,
constchar*mode
);
参数解释:
pFile
类型:FILE**(指向FILE指针的指针)
含义和用法:这是一个指向FILE指针的指针,用于存储成功打开文件后返回的文件指针。如果函数成功打开文件,它会在pFile所指向的位置存储一个有效的FILE指针。如果函数失败,FILE*对象将是NULL。
举例:
FILE*fp;
fopen_s(fp,”foo.txt”,”r”);
//假设foo.txt不存在,
//fp的值就是NULL,
//因为r模式要求文件必须存在。
filename
类型:constchar*
含义和用法:这是要打开文件的名称(包括路径)。它应该是一个以空字符(\0)结尾的字符串。
mode
类型:constchar*
含义和用法:这是文件的打开模式。它决定了文件应如何被打开和访问。例如,r表示以只读方式打开文件,w表示以写入方式打开文件(如果文件已存在,则其内容将被覆盖),a表示以追加模式打开文件等。还可以组合使用其他字符以提供额外的选项,例如b用于二进制模式。详细内容可以参看之前的文章:《C语言输入输出流()文件打开函数和文件访问模式》
返回值
类型:errno_t
作用:如果函数成功打开文件,则返回零。如果函数失败,则返回非零错误代码。比如用r模式打开一个不存在的文件,就会返回非零错误码为2,也可以用perror函数输出错误码代表的具体含义。
举例:
FILE*fp;
errno_terr;
err=fopen_s(fp,”foo.txt”,”r”);
//假设foo.txt不存在
if(err!=0){
printf(“errorcode:%d.\n”,err);
//此时printf输出错误码2
perror(“errormessage:”);
//此时perror输出错误码的含义:Nosuchfileordirectory
下面是一个完整的例子:
#includestdio.h
#includeerrno.h
intmain(){
FILE*fp;
errno_terr;
err=fopen_s(fp,"foo.txt","r");
if(err==0)
printf("文件被正常打开。\n");
else{
printf("fpisNULL.\n");
printf("错误码:%d.\n",err);
perror("错误码含义:");
returnerr;
}
fclose(fp);
return0;
}
程序运行效果如下图:
图1完整的错误码数字和含义请参见这篇文章:《C语言错误码:perror函数和errno_t数据类型》freopen函数
freopen函数的作用非常强大,拥有比fopen更灵活的用途。它的主要作用归纳如下:
将标准流重定向到文件流
将文件对象重定向到另一个文件
将文件流重新打开改变访问模式
如果不明白也没关系,下面我们来详细的介绍下函数的用法。
函数原型如下:
FILE*freopen(
constchar*filename,
constchar*mode,
FILE*stream);
参数:
filename
将要重新定向到的文件名称。这个参数是一个指向字符的指针,通常是一个字符串常量。
mode
文件的访问模式。
stream
要被重新改变指向的标准流或者文件流。这通常是一个指向FILE类型的指针,代表一个已经打开的文件流,比如stdin(标准输入流)、stdout(标准输出流)或stderr(标准错误流)。
返回值
FILE*
freopen函数的返回值是一个指向新文件流的指针。如果文件顺利打开,它将返回这个指针;如果文件打开失败,它将返回NULL,并将错误代码存储在全局变量errno中,用perror函数可以输出。错误代码列表参见我的专栏文章。
下面是一个使用freopen函数的简单例子。这个例子中将标准输入流stdin重定向到一个名为foo.txt的文件,并从该文件中读取数据:
#includestdio.h
intmain(){
inta[5];
inti;
//将标准输入流stdin重定向到文件foo.txt
freopen("foo.txt","r",stdin);
//scanf函数从stdin标准输入流中接受输入
//stdin本来应该指向键盘
//但是被重定向到foo.txt
//所以scanf函数就从foo.txt中读取数据
for(i=0;i5;i++)
scanf("%d",a);
//输出读取到的数据
for(i=0;i5;i++){
printf("%d",a);
}
}
在这个例子中,我们首先使用freopen函数将stdin重定向到foo.txt文件。然后,我们使用scanf函数从重定向后的stdin读取数据,这些数据实际上来自foo.txt文件。最后,我们使用printf函数输出读取到的数据。
需要注意的是,如果foo.txt文件不存在或无法访问,freopen函数会失败,并返回NULL。在实际编程中,你应该检查freopen函数的返回值,以确保文件已成功打开。同时,你也应该检查scanf、printf等函数的返回值,以确保输入或输出操作已成功完成。
freopen函数的应用场景
(一)改变当前文件的打开模式
使用freopen可以将一个已经打开的文件,关闭后重新打开,并改成新的访问模式
#includestdio.h
intmain(){
FILE*file=fopen("foo.txt","r");
if(file==NULL){
perror("Errormsg:");
return1;
}
file=freopen("foo.txt","w",file);
if(file==NULL){
perror("Errormsg:");
return1;
}
fprintf(file,"输入数据到foo.txt文件。\n");
fclose(file);
return0;
}
(二)将标准流重定向到文件
可以将标准流stdin、stdout或stderr重定向到到文件。
#includestdio.h
intmain(){
freopen("foo.txt","w",stdout);
printf("这段话将输出到foo.txt文件。\n");
freopen("bar.txt","r",stdin);
intnum;
scanf("%d",num);
printf("%d\n",num);
//bar.txt中的数据输出到foo.txt中。
fcloseall();
return0;
}
(三)替换文件指针指向的文件
将已存在的文件指针,通过freopen函数使其指向一个新的文件。
#includestdio.h
intmain(){
FILE*fp=fopen("file1.txt","r");
if(fp==NULL){
perror("Errormsg:");
return1;
}
//假设我们想要让fp指向另一个文件
fp=freopen("file2.txt","r",fp);
//现在fp指向file2.txt
charch;
while((ch=fgetc(fp))!=EOF){
putchar(ch);
}
fclose(fp);
return0;
}
(四)临时更改文件以进行错误日志记录
你可以使用freopen来临时改变标准错误流,以便将错误信息保存到日志文件中。
#includestdio.h
intmain(){
FILE*errFile=freopen("error.log","a",stderr);
fprintf(stderr,"错误信息保存到error.log文件中\n");
return0;
}
(五)在读取文件后追加内容
你可以使用freopen在读取文件内容后,不关闭文件,而是直接以追加模式重新打开文件,以便添加新内容。
#includestdio.h
intmain(){
FILE*file=fopen("data.txt","r");
//假设我们读取了文件内容...
//现在我们想要在文件末尾追加内容
file=freopen("data.txt","a",file);
fprintf(file,"新数据追加到data.txt尾部。\n");
fclose(file);
return0;
}
被重定向的标准流如何重新恢复?
标准流被重定向到文件,虽然很方便,特别是将错误信息保存到日志文件,或者从文件中输入内容到另一个文件。但是有一个问题,就是如何将标准流恢复到本来的功能,比如如何让stdin恢复到指向键盘,stdout和stderr恢复到显示器屏幕。
我们现在介绍下在windows系统如何恢复标准流。需要用到这几个函数:_fileno()、_dup()和_dup2()函数。
函数原型:
int_fileno(FILE*fp);
int_dup(intfd);
int_dup2(intfd1,intfd2);
1)_fileno函数用来获取标准流的文件说明符然后传给_dup或_dup2函数,用法:
intfd_stdout=_fileno(stdout);
2)_dup函数中的参数就是用_fileno函数获取的某个标准流的文件说明符,可以为这个标准流创建一个副本,这样后期需要将标准流恢复到原本功能时,就可以用创建的副本来恢复。
intbak=_dup(fd_stdout);
stdin的文件说明符是0,stdout的文件说明符就是1,stderr的文件说明符就是2。
)_dup2函数的作用就是用fd1所对应的标准流强制覆盖fd2所指向的文件对象。其用法类似于freopen函数。但是freopen函数无法恢复被重定向的标准流,但是_dup2函数可以。例如:
_dup2(bak,_fileno(stdout));
就可以用之前_dup函数保存的stdout副本强制覆盖被重定向的stdout,然后就恢复了stdout,重新指向了屏幕。
下面是完整的例子:
#includestdio.h
#includeio.h
#includestdlib.h
intmain(){
//获取标准输出流stdout的“文件说明符”
intfd=_fileno(stdout);
//保存stdout的副本
intbak=_dup(fd);
FILE*fp;
fp=fopen("foo.txt","w+");
fprintf(fp,"我会在foo.txt里。\n");
fflush(fp);
//stdout被重定向到foo.txt
fp=freopen("foo.txt","a+",stdout);
printf("\n我本应输出到屏幕结果却输出到foo.txt里。\n");
fflush(stdout);
//强制恢复stdout原本指向
_dup2(bak,_fileno(stdout));
printf("我又被输出到屏幕。\n");
fcloseall();
}
程序运行后效果如下图:
freopen_s函数
和之前介绍的fopen_s函数类似,所有安全版本函数都将原本函数的返回值作为输出参数,而将返回值用来作为当函数执行失败时返回错误信息。
输出参数和输入参数的概念,如果不了解,可以查看我真的专栏文章:《c语言函数不简单,参数也分输入输出?值和指针的传递只是表面!》。
函数原型如下:
errno_tfreopen_s(
FILE**pfp,
constchar*fileName,
constchar*mode,
FILE*redirectStream
);
参数解释如下:
pfp
FILE指针的指针,用来存储函数返回时指向重新打开的文件流对象的指针。
fileName
要重新打开的文件的名称。
mode
重新打开时的访问模式。
oldStream
将被重新指向filename。
返回值
如果成功,则为零;否则为错误代码。如果发生错误,则文关闭原始文件,新文件不被打开,stream的值是NULL。错误代码的详细信息可以用perror函数输出。
freopen_s函数通常用于将stdin、stdout和stderr重定向到另一个文件。
例子如下:
#includestdio.h
#includeerrno.h
intmain(){
FILE*fp;
errno_terr;
err=fopen_s(fp,“foo.txt”,”w+”);
if(err!=0){
perror(“errmsg:”);
return-1;
}
err=freopen_s(fp,”foo.txt”,”r+”,fp);
if(err!=0){
perror(“errmsg:”);
return-1;
}
//...
fclose(fp);
}
这是一个非常简单的例子,先用fopen_s函数打开foo.txt,然后再次重新打开foo.txt,目的就是为了切换文件访问模式,两个函数用的安全版本。因为使用起来比较简单,就不举太多例子浪费篇幅了。
总结
本章最重要的就是freopen函数的应用场景要熟练使用,同时也适用于freopen_s函数(是相同的)。同时,如何恢复标准流的重定向也是非常重要的,很多C程序员都不知道如何实现。本文介绍的是windows系统的方法,linux系统也是类似,都需要通过底层的函数,比如unistd.h头文件(linux)的dup函数来保存文件说明符,然后强制恢复。目前我写的基本都是面向初级C程序员的内容,后期在中级文章里会以为linux环境的操作为主。
段誉,年月26日,写于合肥。