zoukankan      html  css  js  c++  java
  • 文件操作的详细使用

    补上之前的文件操作部分,下面是对文件操作的具体知识点列举与使用说明:

    文件操作

    文件是根据特定的目的而收集在一起的有关数据的集合。C++把每一个文件都看成是一个有序的字节流,每个文件都以文件结束标志结束,如果要操作某个文件,程序必须首先打开该文件。当一个文件被打开后,该文件就和一个流关联起来,这里的流实际上是一个字节序列。

    C++将文件分为文本文件和二进制文件。

    二进制文件一般含有特殊的格式或计算机代码,如图文件和可执行文件等(实际上二进制文件适用于任何文件,包括文本文件,音频文件,视频文件,图像文件以及可执行文件等)。文本文件则是可以用任何文字处理程序阅读和编辑的简单ASCII文件(人们能够看明白其含义的文件)。
    二进制文件和文本文件都是按照二进制存储的,只不过文本文件是把一个字节一个字节解读成字符,而二进制文件可以任意定义解读方式。因此二进制文件与文本文件的区别是在逻辑上而不是物理储存。
    记事本无论打开什么文件都按既定的字符编码工作(如ASCII码),所以当它打开二进制文件时,出现乱码也是很必然的一件事情了,解码和译码不对应嘛。例如文件流”00000000_00000000_00000000_00000001”可能在二进制文件中对应的是一个四字节的整数int 1,在记事本里解释就变成了“NULL_NULL_NULL_SOH”这四个控制符。
    在windows上,用记事本就可以打开文本文件了,但要打开二进制文件需要对应的二进制文件解码器,因此,文本文件是更为大家所熟知的文件形式,而二进制文件的优点在于它的输入输出都省去了转换的过程,而且更省空间。

    下面我们学习如何编写C++代码来实现对文本文件的输入和输出。

    文件操作主要有fopen,freopen重定向版与文件输入输出流三类:

    一、用fopen打开文件:
    函数原型:

    FILe*fopen(const char *filename,const char *mode);
    

    FILe是在stdio.h中定义的一个结构,用于存放和文件有关的信息。打开文件的模式主要有以下几种:
    r:以文本方式打开文件,只进行读操作。
    w:以文本方式打开文件,只进行写操作。
    a:以文本方式打开文件,只往其末尾添加内容。
    rb:以二进制方式打开文件,只进行读操作。
    wb:以二进制方式打开文件,只进行写操作。
    ab:以二进制方式打开文件,只往其末尾添加内容。
    r+:以文本方式打开文件,既读取数据,也要往文件中写入数据。
    r+b:以二进制形式打开文件,既读取数据,也要往文件中写入数据。
    fopen函数返回一个FILE *类型的指针,称为文件指针,该指针指向的FILE类型变量中存放着关于文件的一些信息,文件打开后,对文件的读写操作就不再使用文件名而都是通过fopen函数返回的指针进行。
    如果试图以只读方式打开一个不存在的文件或因其他原因(比如没有权限等)导致打开文件失败时,fopen函数会返回NULL指针。对文件进行读写操作前,判断fopen函数的返回值是否为NULL,是非常重要的习惯。

    用 fclose 关闭文件:
    fclose函数的原型是:

    int fclose(FILE *stream);
    

    stream即是先前用fopen打开文件时得到的文件指针。
    一定要注意,打开文件后,要确保程序执行的每一条路径上都会关闭该文件。一个程序
    能同时打开的文件数目是有限的,如果总是打开文件没有关闭,那么文件打开数目到达一定限度后,就再也不能打开新文件了。一个文件,可以被以只写的方式同时打开很多次,这种
    情况也会占用程序能同时打开的文件总数的资源。
    调用fclose函数时,如果参数stream的值是NULL, 那么很可能会出现程序异常终止的错误。

    用 fscanf,fprintf 读写文件:
    fscanf函数原型如下;

    int fscanf(FILE *stream, const char *format[, address, ...]); 
    

    fscanf和scanf函数很象,区别在于多了第一个参数----文件指针stream。scanf函数从
    键盘获取输入数据,而fscanf函数从与stream相关联的文件中读取数据。该函数适用于读取以文本方式打开的文件。如果文件的内容都读完了,那么fscanf函数返回值为 EOF (stdio.h 中定义的一个常量)。
    fprintf函数能用于向文件中写入数据,用法和printf、fscanf类似。
    其原型是:

    int fprintf(FILE *stream, const char *format[, argument, ...]);
    

    具体用法参考下面的程序。

    用 fgetc读文件,用fputc写文件:
    fgetc函数原型如下:

    int fgetc(FILE *stream); 
    

    它用于从文件中读取一个字节,返回值即是所读取的字节数。每个字节都被当作一个无
    符号的8位(二进制位)数,因此每个被读取字节的取值范围都是0-255。反复调用fgetc函数可以读取整个文件。如果已经读到文件末尾,无法再读,那么fgetc函数返回EOF(实际上就是-1)。
    fputc函数原型如下:

    int fputc(int c, FILE *stream); 
    

    它将一个字节写入文件。参数c即是要被写入的字节。虽然c是int类型的,但实际上只有其低8位才被写入文件。如果写入失败,该函数返回EOF。

    用fgets函数读文件, fputs函数写文件:
    fgets函数原型如下;

    char *fgets(char *s, int n, FILE *stream); 
    

    它一次从文件中读取一行,包括换行符,放入字符串s中,并且加上字符串结尾标志
    符’’。参数n代表缓冲区s中最多能容纳多少个字符(不算结尾标志符’’)。
    fgets函数的返回值是一个char *类型的指针,和s指向同一个地方。如果再没有数据可
    以读取,那么函数的返回值就是NULL。
    fputs函数原型如下:

    int fputs(const char *s, FILE *stream); 
    

    它往文件中写入字符串s。注意,写完s后它并不会再自动向文件中写换行符。

    用 fread读文件,用fwrite写文件:
    fread函数原型如下:

    unsigned fread(void *ptr, unsigned size, unsigned n, FILE *stream); 
    

    该函数从文件中读取n个大小为size字节的数据块, 总计n*size字节,存放到从地址
    ptr 开始的内存中。返回值是读取的字节数。如果一个字节也没有读取,返回值就是0。
    fwrite函数原型如下:

    unsigned fwrite(const void *ptr, unsigned size, unsigned n, FILE *stream); 
    

    该函数将内存中从地址 ptr 开始的n*size个字节的内容,写入到文件中去。
    这两个函数的返回值,表示成功读取或写入的“项目”数。每个“项目”的大小是size
    字节。 其实使用这两个函数时,总是将size置为1,n置为实际要读写的字节数,也是没有问题的。
    fread函数成功读取的字节数,有可能小于期望读取的字节数。比如反复调用fread读取
    整个文件,每次读取100个字节,而文件有1250字节,那么显然最后一次读取,只能读取50 字节。
    使用fread和fwrite函数读写文件,文件必须用二进制方式打开。
    有些文件由一个个“记录”组成,一个记录就对应于C/C++中的一个结构,这样的文件,就适合用fread和fwrite来读写。比如一个记录学生信息的文件students.dat,该文件里每个“纪录”对应于以下结构:

    struct Student{ 
    char szName[20]; 
    unsigned nId; 
    short nGender; //性别 
    short nBirthYear, nBirthMonth, nBirthDay; 
    float fGPA; 
    }; 
    

    下面的程序先读取前例提到的students.txt中的学生信息,然后将这些信息写入
    students.dat中。接下来再打开students.dat,将出生年份在1985年之后的学生记录提取出来,写到另一个文件 students2.dat中去。

    1. #include <stdio.h> 
    2. #include <string.h> 
    3. struct Student{ 
    4. char szName[20]; 
    5. unsigned nId; 
    6. short nGender; //性别 
    7. short nBirthYear, nBirthMonth, nBirthDay; 
    8. float fGPA; 
    9. }; 
    10. 
    11. int main() 
    12. { 
    13. FILE * fpSrc, * fpDest; 
    14. struct Student Stu; 
    15. fpSrc = fopen( "c:\tmp\students.txt", "rb"); 
    16. if( fpSrc == NULL ) { 
    17. printf( "Failed to open the file."); 
    18. return 0; 
    19. } 
    20. fpDest = fopen( "students .dat", "wb"); 
    21. if( fpDest == NULL) { 
    22. fclose( fpSrc); 
    23. printf( "Destination file open failure."); 
    24. return 0; 
    25. } 
    26. char szName[30], szGender[30]; 
    27. int nId, nBirthYear, nBirthMonth, nBirthDay; 
    28. float fGPA;
    29. while( fscanf( fpSrc, "%s%d%s%d%d%d%f", szName, & nId, 
    30. szGender, & nBirthYear, & nBirthMonth, & nBirthDay, & fGPA) != EOF) { 
    31. strcpy(Stu.szName, szName); 
    32. Stu.nId = nId; 
    33. if( szGender[0] == 'f' ) 
    34. Stu.nGender = 0; 
    35. else{
    36. Stu.nGender = 1; 
    37. Stu.nBirthYear = nBirthYear; 
    38. Stu.nBirthMonth = nBirthMonth; 
    39. Stu.nBirthDay = nBirthDay; 
    40. fwrite( & Stu, sizeof(Stu), 1, fpDest); 
    41. } 
    42. fclose(fpSrc); 
    43. fclose(fpDest); 
    44. fpSrc = fopen( "students.dat", "rb"); 
    45. if( fpSrc == NULL ) { 
    46. printf( "Source file open failure."); 
    47. return 0; 
    48. } 
    49. fpDest = fopen( "students2.dat", "wb"); 
    50. if( fpDest == NULL) { 
    51. fclose( fpSrc); 
    52. printf( "Destination file open failure."); 
    53. return 0; 
    54. } 
    55. while(fread( & Stu, sizeof(Stu), 1 , fpSrc)) { 
    56. if( Stu.nBirthYear >= 1985 ) 
    57. fwrite( & Stu, sizeof(Stu), 1, fpDest); 
    58. } 
    59. fclose( fpSrc); 
    60. fclose( fpDest); 
    61. return 0; 
    62. } 
    

    一般来说使用fscanf,fprintf的频率比较高,fgetc,fputc,fgets,fputs相对少一些,对于fread,fwrite这样的二进制读取写入相对更少,因为对于一个类来讲使用fscanf,fprintf搭配重载输入输出有时就基本可以代替fread,fwrite。

    用fseek 改变文件当前位置:
    文件是可以随机读写的,即读写文件并不一定要从头开始,而是直接可以从文件的任意
    位置开始读写。比如,可以直接读取文件的第200个字节,而不需将前面的199个字节都读一遍。同样,也可以直接往文件第1000个字节处写若干字节,覆盖此处原有内容。甚至可以先在文件的第200字节处读取100字节,然后跳到文件的第1000字节处读取20字节,然后再跳到文件的第20字节处写入30字节。这就叫“随机读写”。然而,前面提到的那些文件读写函数,都没有参数能够指明读写是从哪个位置开始,这又是怎么回事呢?
    答案是:所有的文件读写函数,都是从文件的“当前位置”开始读写的。文件的“当前位置”信息保存在文件指针指向的 FILE结构变量中。一个文件在以非 “添加”方式打开,尚未进行其他操作时,其“当前位置”就是文件的开头;以添加方式打开时,其“当前位置”在文件的末尾。此后调用读写函数读取或写入了n个字节,“当前位置”就往后移动n个字节。
    如果“当前位置”到达了文件的末尾,那么文件读取函数再进行读操作就会失败。
    注意,文件开头的“当前位置”值是0,而不是1。
    综上所述,要实现随机读写,前提是能够随意改变文件的“当前位置”。fseek函数就
    起到这个作用。其原型如下:

    int fseek(FILE *stream, long offset, int whence); 
    

    该函数将与stream关联的文件的“当前位置”设为距whence处offset字节的地方。whence可以有以下三种取值,这三种取值都是在stdio.h里定义的标识符:
    SEEK_SET: 代表文件开头
    SEEK_CUR: 代表执行本函数前文件的当前位置
    SEEK_END: 代表文件结尾处
    例如,假设 fp是文件指针,那么:
    fseek(fp, 200, SEEK_SET);
    就将文件的当前位置设为200,即距文件开头200个字节处;
    fseek(fp, 0, SEEK_SET);
    将文件的当前位置设为文件的开头。
    fseek(fp, -100, SEEK_END);
    将文件的当前位置设为距文件尾部100字节处。
    fseek(fp, 100, SEEK_CUR);
    将文件的当前位置往后(即往文件尾方向)移动100个字节。
    fseek(fp, -100, SEEK_CUR);
    将文件的当前位置往前(即往文件开头方向)移动100个字节。
    (offset过大,需要long long 型的offset时,fseek 改用 _fseeki64。)
    当然也可以这样表示:
    origin 数值 代表的具体位置
    SEEK_SET 0 文件开头
    SEEK_CUR 1 文件指针当前位置
    SEEK_END 2 文件尾
    例如: fseek(fp,10L,0); 把文件指针从文件开头移到第10字节处,fseek(fp,-15L,2); 把文件指针从文件尾向前移动15字节。由于offset参数要求是长整型数,故其数后带L。
    下面的程序,读取文件students.dat中的第4个记录到第10个记录(记录从0开始算),
    并将这部分内容写入到第20个记录开始的地方,覆盖原有的内容。

    1. #include <stdio.h> 
    2. #include <string.h> 
    3. #define NUM 10 
    4. #define NAME_LEN 20 
    5. struct Student{ 
    6. char szName[NAME_LEN]; 
    7. unsigned nId; 
    8. short nGender; //性别
    9. short nBirthYear, nBirthMonth, nBirthDay; 
    10. float fGPA; 
    11. }; 
    12.
    13. int main() 
    14. { 
    15. FILE * fpSrc; 
    16. Student aStu[NUM]; 
    17. fpSrc = fopen( "c:\tmp\students4.dat", "r+b"); 
    18. if( fpSrc == NULL ) { 
    19. printf( "Failed to open the file."); 
    20. return 0; 
    21. } 
    22. fseek( fpSrc, sizeof(Student)* 4 , SEEK_SET); 
    23. fread( aStu, sizeof(Student), 7, fpSrc); 
    24. fseek( fpSrc, sizeof(Student) * 20, SEEK_SET); 
    25. fwrite( aStu, sizeof(Student), 7, fpSrc); 
    26. fclose( fpSrc); 
    27. return 0; 
    28. }
    

    在这里用到了fread与fwrite,这里要比for循环搭配重载输入输出好用一些。

    当然对于文件操作还有很多函数可以使用,比如类似于fgetc,fputc,fgets,fputs的fgetw,fputw函数对整数的操作(其实用的时候特别特别少),其中fgetw,fputw与getw,putw表现形式有些差异,同fgetc,fputc与getc,putc差异类似:
    fgetc一定是函数, 而getc可能由宏来实现(但不一定)。 这就是两者的差别。一般来说,调用宏比调用函数耗费的时间少。一般来说宏产生较大的代码,但是避免了函数调用的堆栈操作,所以速度会比较快。
    用得较多的可能有ftell()函数:给出处在文件中的当前位置(从文件头算起的字节数)rewind()函数(把位置设在文件开头)

    谨记:
    文件未打开之前不能使用。
    如果以w模式打开一个已有的文件,那么该文件的内容将被清除。
    EOF是一个值为-1的整数,因此必须使用一个整数变量来测试EOF。
    当使用文件函数时,遗漏了文件指针是错误的。
    如果要从以写模式打开的文件中读取数据是错误的,反之亦然。
    程序终止之前关闭所有文件是一个良好的编程习惯。

    二、freopen重定向版
    命令格式:

       FILE * freopen ( const char * filename, const char * mode, FILE * stream );
    

    参数说明:
    filename: 要打开的文件名
    mode: 文件打开的模式,和fopen中的模式(r/w)相同
    stream: 文件指针,通常使用标准流文件(stdin/stdout/stderr)
    其中stdin是标准输入流,默认为键盘;stdout是标准输出流,默认为屏幕;
    stderr是标准错误流,一般把屏幕设为默认。通过调用freopen,就可以修改标准流文件的默认值,实现重定向。
    因为文件指针使用的是标准流文件,因此我们可以不定义文件指针。
    接下来就是使用freopen()函数的优点了,我们不再需要修改scanf,printf,cin和cout。而是维持代码的原样就可以了。因为freopen()函数重定向了标准流,使其指向前面指定的文件,省时省力。最后只要使用fclose关闭输入文件和输出文件即可。
    格式:fclose(stdin);fclose(stdout);
    重定向与System(“pause”);冲突:执行的时候出于某些原因,有字符从文件流中输入,导致程序终止。 解决办法:
    恢复句柄,可以重新打开标准控制台设备文件,这个设备文件的名字是与操作系统相关的。格式:freopen("CON", "r", stdin);
    问题:

    1. 如何判断文件是否打开了
      可以直接 if( freopen(“a.txt”,“r”,stdin)== NULL ) return false;
      或 if( freopen(“b.txt”,“w”,stdout)== NULL ) return false;
      表示没有打开
    2. 如何使流重新回到控制台上
      如果你不想输入或输出到文件了,就加上一句
      freopen(“CON”,“r”,stdin ); 对应输入
      freopen(“CON”,“w”,stdout); 对应输出
      注意的问题, 因为参数都是 c_字符串, 故不能把 c++ 里面的 string 类对象作为参数传进去,比如 string str= “a.txt”;你不能这样写 freopen( str, “r”, stdin );可以先把 string 类对像化成 c_字符串, 就用 c_str() 函数,上面的可以这样写 freopen( str.c_str(), “r”, stdin );
      另外while (fin>>temp)和(scanf("%d",&temp)==1)主要是用于判断数据是否已经读完,以便及时终止循环。还可以用成员函数eof来判断是否达到数据流的末尾。
      重定向用起来很方便,但并不是所有算法竞赛都允许读写文件。甚至有的竞赛允许访问文件,但不允许使用freopen这样的重定向方式读写文件,可以使用fopen版,对scanf和printf语句适用。

    三、文件输入输出流
    在C++中,文件输入流(ifstream)和文件输出流(ofstream)的类,它们的默认输入输出设备都是磁盘文件。C++可以在创建对象时,设定输入或输出到哪个文件。由于这些类的定义是在fstream中进行的,因此,在使用这此类进行输入输出操作时,必须要在程序的首部利用#include指令包进fstream头文件。
    例如:若想用fin作为输入对象,fout作为输出对象,则可以使用如下定义:
    ifstream fin(“输入文件名.扩展名”);
    ofstream fout(“输出文件名.扩展名”);
    例:

    #include<fstream>                                  //使用文件输入输出流,对cin、cout语句适用
    using namespace std;
    int main()
    {
       ifstream fin("in.txt");                             //定义输入文件名
       ofstream fout("out.txt");                       //定义输出文件名
       int temp,sum=0;
       while (fin>>temp)  sum=sum+temp;   //从输入文件中读入数据
       fout<<sum<<endl;
       fin.close();fout.close();                        //关闭文件,可省略
       return 0;
    }
    
    命令行参数:

    如果我们编写了一个在屏幕上输出文本文件内容的程序,编译生成的可执行文件是 listfile.exe,那么,很可能我们希望该程序的用法是,在 Windows 的控制台窗口(也叫DOS 命令窗口)中输入: listfile 文件名 然后敲回车,就能启动 listfile 程序,并将“文件名”所指定的文件的内容输出。比如敲“listfile file1.txt”,就能将 file1.txt 这个文件的内容输出。 要做到这一点,显然,listfile 程序必须知道用户输入的那个文件名。我们将用户在DOS 窗口输入可执行文件名的方式启动程序时,跟在可执行文件名后面的那些字符串,称为“命令行参数”。比如上例中的“file1.txt”,就是一个命令行参数。命令行参数可以有多个,以空格分隔。比如“listfile file1.txt file2.txt”。
    在程序中如何知道用户输入的命令行参数呢? 要做到这一点,main 函数的写法须和以
    往的不同,要增加两个参数:

    int main(int argc, char * argv[]) 
    { 
     …… 
    } 
    

    参数 argc 就代表启动程序时,命令行参数的个数。C/C++语言规定,可执行程序程序本身的文件名,也算一个命令行参数,因此,argc 的值至少是 1。argv 是一个数组,其中的每个元素都是一个 char* 类型的指针,该指针指向一个字符串,这个字符串里就存放着命令行参数。例如,argv[0]指向的字符串就是第一个命令行参数,即可执行程序的文件名,
    argv[1]指向第二个命令行参数,argv[2]指向第三个命令行参数……。
    例:

    1. #include <stdio.h> 
    2. int main(int argc, char * argv[]) 
    3. { 
    4. for(int i = 0;i < argc; i ++ ) 
    5. printf( "%s
    ", argv[i]); 
    6. return 0; 
    7. } 
    

    将上面的程序编译成 2.18.exe,然后在控制台窗口敲:

    2.18 para1 para2 s.txt 5 4 
    

    输出结果就是:

    2.18 
    para1 
    para2 s.txt 
    5 
    4 
    
  • 相关阅读:
    内网Windows Server时间自动同步
    处理sqlserver数据
    virtualenv使用
    vue过渡动画效果
    vue视图
    vue组件
    Vue实例
    vue介绍
    Bootstrap布局
    Bootstrap组件
  • 原文地址:https://www.cnblogs.com/study-hard-forever/p/12129991.html
Copyright © 2011-2022 走看看