映月读书网 > C语言解惑 > 22.1 文件的打开与关闭 >

22.1 文件的打开与关闭

在对文件操作之前,可以先判别内存是否有给定的文件,也可以直接删除指定的文件。在打开和关闭指定文件时,也需要判别是否正确打开和关闭了文件。

1.判别文件是否存在

【例22.1】程序是要判别文件是否存在,虽然存在文件TEST.TXT,但其运行结果正相反,没有回答“有”,也没说“没有”。请找出并改正错误。


#include <stdio.h>
#include <io.h>
int file_state
(char *filename
);
int main
(void
)
{
     char filename[16]
;
     printf
("
请输入要查找文件的名字:"
);
     scanf
("%s"
,filename
);
     printf
("
有%s
文件吗? %s\n"
,
     filename
, file_state
(filename
) 
? "
有" 
: "
没有"
);
     return 0
;
}
int file_state
(char *filename
)
{
     return 
(access
(filename
, 0
));
}
  

【解答】由主程序可知,问题出现在函数file_state的返回值不满足“?:”的判别条件。

file_state函数很简单,直接返回access函数的返回值。而access函数是返回0和-1两种情况,所以引起主函数输出结果错误。

函数access用来对指定内存文件或文件夹进行检查,被定义在头文件io.h中。


函数原型:int access
(const char * pathname
, int mode
);
  

功能:确定文件或文件夹的访问权限,即检查某个文件的存取方式(只读或只写方式等)。如果指定的存取方式有效,则函数返回0,否则函数返回-1。

用法:int access(const char*filenpath,int mode);

或者int_access(const char*path,int mode);

参数filenpath:文件或文件夹的路径,当前目录直接使用文件或文件夹名

备注:当该参数为文件的时候,access函数能使用mode参数所有的值,当该参数为文件夹的时候,access函数值能判断文件夹是否存在。在WIN NT中,所有的文件夹都有读和写的权限。

Mode:要判断的模式。在头文件unistd.h中的预定义如下:


#define R_OK 4   /* Test for read permission. */
#define W_OK 2   /* Test for write permission. */
#define X_OK 1   /* Test for execute permission. */
#define F_OK 0   /* Test for existence. */
  

具体含义如下:

R_OK:只判断是否有读权限。

W_OK:只判断是否有写权限。

X_OK:判断是否有执行权限。

F_OK:只判断是否存在。

本程序没有包含unistd.h(有些系统没有这个头文件),只能直接使用数值0。

因为access函数的返回值是0和-1,所以得到错误的结构。“?:”要求正确时为1,不正确为0,所以将file_state的返回值改为成功返回1,失败返回0。将返回语句改为


return 
(access
(filename
, 0
)==0
);
  

即可。当access返回0,则上式返回1;access返回-1,则上式返回0,这就符合要求了。


//
改正后的程序
#include <stdio.h>
#include <io.h>
int file_state
(char *filename
);
int main
(void
)
{
char filename[16]
;
      printf
("
请输入要查找文件的名字:"
);
      scanf
("%s"
,filename
);
      printf
("
有%s
文件吗? %s\n"
,
      filename
, file_state
(filename
) 
? "
有" 
: "
没有"
);
      return 0
;
}
int file_state
(char *filename
)
{
      return 
(access
(filename
, 0
)==0
);
}
  

目录中有文件TEST.TXT,运行结果如下:


请输入要查找文件的名字:
text.txt
有text.txt
文件吗? 
没有
请输入要查找文件的名字:
test.txt
有test.txt
文件吗? 
有
请输入要查找文件的名字:
TEST.TXT
有TEST.TXT
文件吗? 
没有
  

【例22.2】直接利用返回值进行判断的例子。


#include <stdio.h>
#include <io.h>
int main
(void
)
{
      if
( 
(access
( "test.txt"
, 0 
)) 
!= -1 
)          //
检查文件是否存在
      {
            printf
( "
存在 test.txt 
文件。\n" 
);
            if
( 
(access
( "test.txt"
, 2 
)) 
!= -1 
)     //
检查文件是否允许写操作
                 printf
( "
允许对 test.txt 
进行写操作。\n" 
);
      }
      return 0
;
}
  

运行结果如下:


存在 test.txt 
文件。
允许对 test.txt 
进行写操作。
  

2.删除文件

【例22.3】演示使用remove函数删除文件的例子。


#include <stdio.h>
int main
(void
)
{
      char filename[16]
;
      printf
("
请输入要查找文件的名字:"
);
      scanf
("%s"
,filename
);
      if 
(remove
(filename
) == 0
)
            printf
("
删除了%s
文件。\n"
,filename
);
      else
            printf
("
没有删除%s
文件。\n"
,filename
);
      return 0
;
}
  

运行示范如下:


请输入要查找文件的名字:
tt.txt
没有删除tt.txt
文件。
请输入要查找文件的名字:
try.txt
删除了try.txt
文件。
  

remove函数用来删除一个文件,在Visual C++6.0中可以用stdio.h也可以用io.h,前者更普遍些。如果删除成功,remove返回0,否则返回EOF(-1)。


函数原型: int remove
( const char *filename
);
  

【例22.4】找出下面程序中的错误。


#include <stdio.h>
#include <io.h>
int main
(void
)
{
     char filename[16]
; //int remove1=0
;
     printf
("
请输入要查找文件的名字:"
);
     scanf
("%s"
,filename
);
     if
(access
(filename
, 0 
))
     {
            int remove = 1
;
     }
     if 
(remove
)
     {
            printf
("
请删除%s
文件。\n"
,filename
);
     }
     return 0
;
}
  

【解答】第1次扫描给出警告,第2次生成可执行文件。但程序总是输出删除文件。首先修改if(access(filename,0))语句,但运行结果不变。remove是C语言函数名,不能重名使用,改为remove1。修改后又出现新问题,remove1在第2个if语句仍然是没定义的,所以将定义放在外面并且初始化为0值(否则仍然错误,因为remove不为1时,则成为没初始化的值)。


//
改正后的程序
#include <stdio.h>
#include <io.h>
int main
(void
)
{
     char filename[16]
;
     int remove1=0
;
     printf
("
请输入要查找文件的名字:"
);
     scanf
("%s"
,filename
);
     if
(access
(filename
, 0 
) 
!= -1
)
     {  remove1 = 1
;  }
     if 
(remove1
)
     {  printf
("
请删除%s
文件。\n"
,filename
);  }
     return 0
;
}
  

运行示范如下:


请输入要查找文件的名字:
me
请输入要查找文件的名字:
try.txt
请删除try.txt
文件。
  

3.打开文件

C语言中的文件是一个逻辑概念,可以用来表示从磁盘文件到终端等所有东西。C语言中的文件并不是由记录(record)组成的,而是字符的序列,即由一个一个字符(字节)的数据顺序组成,所以文件的存取是以字符(字节)为单位的。根据文件中数据的组织形式,文件可分为ASCII文件和二进制文件。ASCII文件又称文本(text)文件,它的每一个字节放一个ASCII代码,代表一个字符。二进制文件则把内存中的数据按其在内存中的存储形式原样输出到磁盘上存放。如果有一个整数1 000,在内存中占2个字节,如果按ASCII码形式输出,则占4个字节;而按二进制形式输出,在磁盘上只占2个字节。用ASCII码形式输出与字符一一对应,一个字节代表一个字符,因而便于对字符进行逐个处理,也便于输出字符。但这种形式一般占存储空间较多,而且要花费转换时间(二进制形式与ASCII码间的转换)。用二进制形式输出数据,可以节省外存空间和转换时间,但一个字节不对应一个字符,不能直接输出字符形式。

在一个程序开始执行时,3个预定的文字流:stdin,stdout和stderr就被打开。它们与和系统相连接的标准I/O设备有关。在一些C编译器中还打开stdprn(标准打印机)和stdaux(标准辅助设备)。对大多数计算机系统来说,stdaux是控制台。包括DOS在内的大多数操作系统都允许I/O重定向,使向某文件上读写的东西重定向到其他设备,但不应该试图直接去打开或关闭该文件。

每一个与文件相结合的流,都有一个类型名为FILE的文件控制信息的结构,这个结构定义在头部文件stdio.h中。

关于文件的处理,首先要注意的是实际的外部文件名称如何与实际读写数据的语句取得联系。对文件读写之前应该打开该文件,在使用结束之后应关闭该文件。

文件指针是贯穿缓冲型I/O系统的主线。一个文件指针是一个指向文件有关信息的指针,这些信息定义了文件的许多东西,它包括文件名、状态和当前位置。从概念上讲,文件指针标志一个指定的磁盘文件,用来告诉系统的每个缓冲型函数应该到什么地方去完成操作。文件指针实际是一个指向目标结构的指针变量,该目标结构与FILE结构类型相同,在stdio.h中定义。

根据以上特点可知,使用文件必须注意正确打开文件和及时关闭文件,必须判断写入的数据大小及解决相应的读写措施和编程中如何正确使用涉及文件的有关判断条件(表达式)及相应判断语句。

ANSI C规定了标准输入输出函数库,用fopen函数来实现打开文件。

fopen函数的调用方式通常为:


FILE  *fp
;
  

其中,fp=fopen(文件名,使用文件方式)。例如:


fp = fopen 
( "A1"
,"r" 
)
  

表示要打开名字为A1的文件,使用文件方式为“读入”。fopen函数返回指向A1文件的指针并赋给文件指针变量fp,这样fp就和A1相联系了,或者说fp指向A1文件。

文件指针变量fp是用文件控制信息结构FILE来定义的。由此可以知道,通过调用fopen函数打开一个文件时,通知编译系统以下3条信息。

(1)需要打开的文件名,也就是准备访问的文件的名字。

(2)让哪一个指针变量指向被打开的文件。

(3)使用文件的方式(读还是写等)。

典型的文件使用方式如表22-1所示。

表22-1 使用文件方式表

文件的使用方法说明如下。

(1)用"r"方式打开的文件只能用于向计算机内存输入,而不能用于向该文件输入数据。而且该文件应该已经存在,不能打开一个并不存在的用于"r"方式的文件(用于向计算机内存输入的文件),否则出错。

(2)用"w"方式打开的文件只能用于向该文件写数据,而不能用来向计算机内存输入。如果原来不存在该文件,则在打开时新建立一个以指定名字命名的文件。如果原来已存在一个以该文件名命名的文件,则在打开时将该文件删去,然后重新建立一个新文件。

(3)如果希望向文件末尾添加新的数据(不希望删除原有数据),则应该用"a"方式打开。但此时该文件必须已存在,否则将得到出错信息。打开时,位置指针移到文件末尾。

(4)用"r+"、"w+"、"a+"方式打开的文件可以用来输入和输出数据。用"r+"方式时该文件应该已经存在,以便能向计算机内存输入数据。用"w+"方式时则新建立一个文件,先向此文件写数据,然后可以读此文件中的数据。用"a+"方式打开的文件,原来的文件不被删去,位置指针移到文件末尾,可以添加也可以读。

(5)如果不能实现打开的任务,fopen函数将会返回一个出错信息。出错的原因可能是:用"r"方式打开一个并不存在的文件;磁盘出故障;磁盘已满无法建立新文件等。结果fopen函数将带回一个空指针值NULL(NULL在stdio.h文件中已被定义为0)。常用方法为


if
( 
( fp = fopen 
( "file1"
,"r" 
) 
) == NULL 
)
{
       printf 
("cannot open this file.\n"
);
       exit
(1
);
}
  

在打开一个文件后先检查打开是否出错,如果有错就在终端上输出“cannot open this file”。exit函数的作用是关闭所有文件,终止正调用的过程。待程序员检查出错误并改正后再运行。

(6)用以上方式可以打开文本文件或二进制文件。ANSI C规定用同一种缓冲文件系统来处理文本文件和二进制文件,但目前使用的有些C编译系统可能不完全提供所有这些功能(如有的只能用"r"、"w"、"a"方式),有的C版本不用"r+"、"w+"、"a+"而用"rw"、"wr"、"ar"等,请注意所用系统的规定。

(7)在用文本文件向计算机内存输入时,将回车换行符转换为一个换行符,在输出时把换行符转换成回车和换行两个字符。在用二进制文件时,不进行这种转换,在内存中的数据形式与输出到外部文件中的数据形式完全一致,一一对应。

(8)在程序开始运行时,系统自动打开3个标准文件:标准输入、标准输出、标准出错输出。通常3个文件都与终端相联系。因此以前所用到的从终端输入或输出,都不需要打开终端文件。系统自动定义了三个文件指针stdin、stdout和stderr,分别指向终端输入、终端输出和标准出错输出(也从终端输出)。如果程序中指定要从stdin所指的文件输入数据,就是指从终端键盘输入数据。

由此可见,打开文件主要是按规定格式书写即可。因为文件名有时是通过程序获得的,所以常常出现的问题不是打开语句,而是文件名。下面就举例说明文件名容易出现的问题。

【例22.5】判断给定的文件名是否可以使用。


#include <stdio.h>
#include <string.h>
int filename
( const char name[ ]
)
{
      static const char *name_list={
               "test.txt"
,
               "try.txt"
,
               "student.txt"
,
               "list.txt"
,
               NULL
      }
;
      int i
,n=0
;
      for
(i=0
;name_list[i]
!=0
;i++
){
               if
(strcmp
(name
,name_list[i]
)==0
)
                     return 1
;
      }
      return 0
;
}
int main
(void
)
{
      printf
("
文件test.txt=%d\n"
,filename
("test.txt"
));
      printf
("
文件student.txt=%d\n"
,filename
("student.txt"
));
      printf
("
文件st.txt=%d\n"
,filename
("st.txt"
));
      printf
("
文件list.txt=%d\n"
,filename
("list.txt"
));
      return 0
;
}
  

【解答】程序if语句的表达式有问题。strcmp函数是返回0和非0(其实是-1)。程序中的两个字符串不相等时返回非0,也就是表达式的值为1,也执行“return1;”语句。把这条语句改为


if
(strcmp
(name
,name_list[i]
)==0
)
  

即可。运行结果如下:


文件test.txt=1
文件student.txt=1
文件st.txt=0
文件list.txt=1
  

【例22.6】下面的函数用来获取临时文件名,找出并改正错误。


#include <stdio.h>
char *tmp_name
( void 
)
{
     char name[32]
;
     const char DIR="/var/temp/temp"
;
     static int sequence=0
;      //
序列号
     ++sequence
;                  //
序列号顺增
     sprintf
( name
, "%s.%d"
, DIR
, sequence
);
     return 
(name
);
}
int main
(void
)
{
     char *a_name=tmp_name
();
     printf
("Name
:%s\n"
,a_name
);
     a_name=tmp_name
();
     printf
("Name
:%s\n"
,a_name
);
     return 0
;
}
  

【解答】函数tmp_name中定义一个局部字串变量name,而且把一个指针返回给这个局部变量name。函数tmp_name结束时,该函数内的全有非静态局部变量都会被重新分配存储空间,当然也包括name。这样一来,返回的指针就指向了一个随机的内存区域。随之而来的下一个函数调用可能会改写这块内存区域,这就使a_name变成一个危险的内存区域。将“char name[32];”改为“static char name[32];”即可。修改后的运行结果将为:


Name
:/var/temp/temp.1
Name
:/var/temp/temp.2
  

一定要注意函数返回值的方法是否正确,同时注意调用方法是否正确。

【例22.7】找出并改正程序中的错误。


#include <stdio.h>
char *tmp_name
( void 
)
{
      static char name[32]
;
      const char DIR="/var/temp/temp"
;
      static int sequence=0
;       //
序列号
      ++sequence
;                  //
序列号顺增
      sprintf
( name
, "%s.%d"
, DIR
, sequence
);
      return 
(name
);
}
int main
(void
)
{
      char *a1_name=tmp_name
();
      char *a2_name=tmp_name
();
      printf
("Name
:%s\n"
,a1_name
);
      printf
("Name
:%s\n"
,a2_name
);
      return 0
;
}
  

【解答】函数tmp_name是上例改正后的程序,没有错误,所以只能是主函数使用方法不当造成两个输出


Name a1
:/var/temp/temp.2
Name a2
:/var/temp/temp.2
  

都一样的错误结果。错误就是因为虽然有两个指针,但都是指向相同的文件名变量name。第1次是使用a1_name调用tmp_name,得到“Name:/var/temp/temp.1”。但第2次调用时,a2_name也是调用tmp_name。虽然得到“Name:/var/temp/temp.2”,但a1_name也指向name,因此第二次调用覆盖了第1次调用结果所占用的内存,即使用a2_name的name覆盖了原来a1_name的name,使两者输出相同。

一种解决的办法是为它们准备各自的字符数组以存储自己的名字,例如:


char *tmp
, a1_name[32]
,a2_name[32]
;
tmp=tmp_name
();
strcpy
(a1_name
,tmp
);
tmp=tmp_name
();
strcpy
(a2_name
,tmp
);
  

这要包含头文件string.h。另一种是各自解决自己的内存分配。


//
为各自分配自己的内存
#include <stdio.h>
#include <stdlib.h>
char *tmp_name
( void 
)
{
        char *name
;
        const char DIR="/var/temp/temp"
;
        static int sequence=0
;       //
序列号
        ++sequence
;                  //
序列号顺增
        name=
(char*
)malloc
(32
);
        sprintf
( name
, "%s.%d"
, DIR
, sequence
);
        return 
(name
);
}
int main
(void
)
{
      char *a1_name
,*a2_name
;
      a1_name=tmp_name
();
      a2_name=tmp_name
();
      printf
("Name a1
:%s\n"
,a1_name
);
      printf
("Name a2
:%s\n"
,a2_name
);
      return 0
;
}
  

4.关闭文件

为防止误用文件,在使用完文件之后,应马上关闭它。关闭就是使文件指针变量不指向该文件,也就是文件指针变量与文件“脱钩”,此后不能再通过该指针对其关联的文件进行读写操作,除非再次打开,使该指针变量重新指向该文件。用fclose函数关闭文件时,fclose函数调用的一般形式为


fclose
(文件指针);
  

例如:


fclose 
( fp 
);
  

用fopen函数打开文件时所返回的指针赋给了fp,因此调用fclose函数时再通过fp把该文件关闭。应该养成在程序终止之前关闭所有文件的习惯,如果不关闭文件将会丢失数据。

在向文件写数据时,是先将数据输出到缓冲区,待缓冲区充满后才正式输出给文件。如果当数据未充满缓冲区而程序已经结束运行时,就会将缓冲区中的数据丢失。用fclose函数关闭文件,可以避免这个问题,这就要先把缓冲区中的数据输出到磁盘文件,然后才释放文件指针变量。

fclose函数也带回一个值:当顺利地执行了关闭操作,则返回值为0;如果返回值为非零值,则表示关闭时有错误。可以用ferror函数来测试。

注意文件关闭时的正确判别条件是


if 
(fclose 
(fp
)== EOF
)
  

而常犯的误判是使用


if 
(fclose 
(fp
)== NULL
)
  

这是使用出错标志判断,因为C语言规定如果这个文件被成功关闭,fclose返回0,否则返回EOF(-1)。

【例22.8】判断关闭文件实例。


#include <stdio.h>
#include <string.h>
int main
(void
)
{
      FILE *fp=NULL
;
      const char*buf="0123456789"
;
      fp=fopen
("TRY.FIL"
,"w"
);       //
创建一个包含10
个字节的文件
      fwrite
(buf
,strlen
(buf
),1
,fp
);  //
将buf
内容写入到文件中
      if 
(fclose 
(fp
)== EOF
)
       printf
("
文件非正常关闭!\n"
);
      else
       printf
("
文件正常关闭!\n"
);
      return 0
;
}