深入浅出之文件操作

一、文件定义

所谓“文件”是记录在外部介质上的数据的集合。

二、缓冲文件系统(标准I/O)

目前C语言所使用的磁盘文件有两大类,一类称为缓冲文件系统,又称为标准文件系统或高层文件系统;另一类称为非缓冲文件系统,又称为底层文件系统。

缓冲文件系统的特点是对程序中的每一个文件都在内存中开辟一个缓冲区,从磁盘文件输入的数据先输入到缓冲区中,然后再通过缓冲区依次将数据送给接收变量。这样做的目的是减少对磁盘文件读写次数,因为每一次对磁盘的读写都要移动磁头并寻找磁道扇区,这个过程是要花一些时间,如果每一次用读写函数时都对应依次时间的磁盘访问,那么就会花费较多的读写时间。用缓冲区就可以依次读入一批数据,或输出一批数据,即不是执行一次输入或输出函数就实际访问磁盘一次,而是若干次读写函数语句对应一次实际的磁盘访问。缓冲文件系统自动设置所需的缓冲区,缓冲区的大小随机器而异。

非缓冲区文件系统不由系统自动设置缓冲区,而是由用户自己根据需要设置。

2.1 文件类型指针(FILE)

缓冲文件系统为每一个文件开辟了一个“文件信息区”,用来存放以上这些信息。

例如:FILE *fp;

注意:FILE不是结构体变量名,它是用typedef定义的新类型名。

2.2 文件的打开与关闭

1)文件打开,格式:fp = fopen(文件指针变量,文件使用方式)

模式描述
r打开一个已有的文本文件,允许读取文件。
w打开一个文本文件,允许写入文件。如果文件不存在,则会创建一个新文件。在这里,您的程序会从文件的开头写入内容。如果文件存在,则该会被截断为零长度,重新写入。
a打开一个文本文件,以追加模式写入文件。如果文件不存在,则会创建一个新文件。在这里,您的程序会在已有的文件内容中追加内容。
r+打开一个文本文件,允许读写文件。
w+打开一个文本文件,允许读写文件。如果文件已存在,则文件会被截断为零长度,如果文件不存在,则会创建一个新文件。
a+打开一个文本文件,允许读写文件。如果文件不存在,则会创建一个新文件。读取会从文件的开头开始,写入则只能是追加模式。

返回值:如果顺利打开,则将该文件信息区(结构体变量)起始地址赋给指针变量fp,也就是fp所指向该文件信息区的结构体;如果打开失败,则fp的值为NULL,此时输出信息“不能打开此文件”。

2)文件关闭,格式:fclose(文件指针变量)

它通知系统,将此指针指向文件关闭,释放相应的文件信息区(结构体变量)。如果不关闭文件,而直接使程序停止运行,这时就会丢失缓冲区中还未写入文件的信息。因此注意:文件使用完必须关闭。

2.3 文件的顺序读写

2.3.1  输出一个字符

格式:fputc(ch,fp),把字符变量ch的值输出到指针变量fp所指向的FILE结构体的文件(简称为指向该变文件)

#include <QCoreApplication>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    FILE *fp;
    const char str[]={"12345678"};
    if ((fp = fopen("file.txt","w")) == NULL){
        exit(0);
    }

    int i = 0;
    while ( str[i] != '\0'){
        fputc(str[i],fp);
        i++;
    }
    fclose(fp);
    return a.exec();
}

2.3.2 从磁盘文件中接收一个字符

格式:ch =fgetc(fp),功能为从指针变量fp所指向的文件中读入一个字符并赋给字符变量ch,fgetc函数的值就是该字符。如果执行fgetc函数时遇到文件结束符,则函数返回文件结束符EOF(即-1)。注意这个-1并不是函数读入的字符值,因为没有一个字符的ASCII码为-1,当操作系统判断出文件中最后一个字符已被读出时,它使函数的返回值为-1.

#include <QCoreApplication>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    FILE *fp;
    char ch;
    if ((fp = fopen("file.txt","r")) == NULL){
        qDebug("can not open file");
        exit(0);
    }

    while ((ch = fgetc(fp)) != EOF){
        qDebug("ch:%c",ch);
    }
    fclose(fp);
    return a.exec();
}
输出结果:
ch:1
ch:2
ch:3
ch:4
ch:5
ch:6
ch:7
ch:8

2.3.3 输入一个字符串

用fgets函数读入一个字符串

格式:

char *fgets(char *str, int n, FILE *stream)

功能为从fp指向的文件读取n-1个字符,并把它放到字符数组str中。如果在读入n-1个字符完成之前遇到换行符“\n”或文件结束符EOF,即结束读入。但将遇到的换行符“\n”,也作为一个字符送入str数组,在读入的字符串之后自动加一个“\0”,因此送到str数组的字符串(包括“\0”)最多可占用n个字符。

fgets函数返回值为str数组的首地址,如果读到文件尾或出错则返回NULL。

注意:fgets不能读二进制文件

#include <QCoreApplication>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    FILE *fp;
    char ch;
    char str[81];
    if ((fp = fopen("file.txt","r")) == NULL){
        qDebug("can not open file");
        exit(0);
    }

    while (fgets(str,81,fp) != NULL){
        qDebug("str:%s",str);
    }
    fclose(fp);
    return a.exec();
}
输出结果:
str:12345678

char *gets(char *str) 从标准输入 stdin 读取一行,并把它存储在 str 所指向的字符串中。当读取到换行符时,或者到达文件末尾时,它会停止,具体视情况而定。

如果成功,该函数返回 str。如果发生错误或者到达文件末尾时还未读取任何字符,则返回 NULL。

fgets会将换行符一并存入到指定的buf中,gets会丢掉换行符。

2.3.4 输出一个字符串

用fputs函数输出一个字符串

格式:

int fputs(const char *str, FILE *stream)把字符串写入到指定的流 stream 中,但不包括空字符。

把字符数组str中的字符串(或字符指针指向的字符串,或字符串常量)输出到fp所指向的文件。但文件结束符“\0”不输出。 

#include <QCoreApplication>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    FILE *fp;
    const char str[]={"12345678"};
    if ((fp = fopen("file.txt","w")) == NULL){
        exit(0);
    }

    fputs(str,fp);

    fclose(fp);
    return a.exec();
}

int puts(const char *str) 把一个字符串写入到标准输出 stdout,直到空字符,但不包括空字符。换行符会被追加到输出中。

如果成功,该函数返回一个非负值,如果发生错误则返回 EOF。

fputs会将结尾的NULL丢掉,puts会自动加上换行符。

2.3.5 格式化的输出fprintf

格式化输出:int fprintf(FILE *stream, const char *format, ...)

fp 为文件指针,format 为格式控制字符串,… 表示参数列表。fprintf() 返回成功写入的字符的个数,失败则返回负数。fscanf() 返回参数列表中被成功赋值的参数个数。 

specifier(说明符)输出
c字符
d 或 i有符号十进制整数
e使用 e 字符的科学科学记数法(尾数和指数)
E使用 E 字符的科学科学记数法(尾数和指数)
f十进制浮点数
g自动选择 %e 或 %f 中合适的表示法
G自动选择 %E 或 %f 中合适的表示法
o有符号八进制
s字符的字符串
u无符号十进制整数
x无符号十六进制整数
X无符号十六进制整数(大写字母)
p指针地址
n无输出
%字符

返回值:如果成功,则返回写入的字符总数,否则返回一个负数。

#include <QCoreApplication>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    FILE *fp;
    if ((fp = fopen("file.txt","w")) == NULL){
        exit(0);
    }

    int len = fprintf(fp,"%d%d%d",123,456,78);
    qDebug("%d",len);

    fclose(fp);
    return a.exec();
}
输出结果是:
8

 2.3.6 格式化输入fscanf

int fscanf(FILE *stream, const char *format, ...)
类型合格的输入参数的类型
c单个字符:读取下一个字符。如果指定了一个不为 1 的宽度 width,函数会读取 width 个字符,并通过参数传递,把它们存储在数组中连续位置。在末尾不会追加空字符。char *
d十进制整数:数字前面的 + 或 - 号是可选的。int *
e,E,f,g,G浮点数:包含了一个小数点、一个可选的前置符号 + 或 -、一个可选的后置字符 e 或 E,以及一个十进制数字。两个有效的实例 -732.103 和 7.12e4float *
o八进制整数。int *
s字符串。这将读取连续字符,直到遇到一个空格字符(空格字符可以是空白、换行和制表符)。char *
u无符号的十进制整数。unsigned int *
x,X十六进制整数。int *

返回值:如果成功,该函数返回成功匹配和赋值的个数。如果到达文件末尾或发生读错误,则返回 EOF。 

#include <QCoreApplication>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    FILE *fp;
    char str[81];
    char str1[81];
    if ((fp = fopen("file.txt","r")) == NULL){
        exit(0);
    }

    int len = fscanf(fp,"%s",str);
    qDebug("%s,%d",str,len);
    int len1 = fscanf(fp,"%s",str1);
    qDebug("%s,%d",str1,len1);

    fclose(fp);
    return a.exec();
}
结果输出:
12345678,1
?98?,-1

如果将 fp 设置为 stdin,那么 fscanf() 函数将会从键盘读取数据,与 scanf 的作用相同;设置为 stdout,那么 fprintf() 函数将会向显示器输出内容,与 printf 的作用相同。例如:

#include<stdio.h>
int main(){
    int a, b, sum;
    fprintf(stdout, "Input two numbers: ");
    fscanf(stdin, "%d %d", &a, &b);
    sum = a + b;
    fprintf(stdout, "sum=%d\n", sum);
    return 0;
}

  2.3.7 按数据块的方式写入fwrite

size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)把 ptr 所指向的数组中的数据写入到给定流 stream 中

参数

  • ptr -- 这是指向要被写入的元素数组的指针。
  • size -- 这是要被写入的每个元素的大小,以字节为单位。
  • nmemb -- 这是元素的个数,每个元素的大小为 size 字节。
  • stream -- 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输出流。

返回值

如果成功,该函数返回nmemb。如果该数字与 nmemb 参数不同,则会显示一个错误。

#include <QCoreApplication>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    FILE *fp;
    char str[] = {"12345678"};
    if ((fp = fopen("file.txt","w")) == NULL){
        exit(0);
    }

    size_t len = fwrite(str,sizeof(str),2,fp);
    qDebug("%d",len);

    fclose(fp);
    return a.exec();
}
输出结果是2

2.3.8 按数据块的方式读出fread

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)从给定流 stream 读取数据到 ptr 所指向的数组中。

参数

  • ptr -- 这是指向带有最小尺寸 size*nmemb 字节的内存块的指针。
  • size -- 这是要读取的每个元素的大小,以字节为单位。
  • nmemb -- 这是元素的个数,每个元素的大小为 size 字节。
  • stream -- 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输入流。

返回值

成功返回nmemb。如果总数与 nmemb 参数不同,则可能发生了一个错误或者到达了文件末尾。

#include <QCoreApplication>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    FILE *fp;
    char str[9];
    if ((fp = fopen("file.txt","r")) == NULL){
        exit(0);
    }

    size_t len = fread(str,sizeof(str),2,fp);
    qDebug("%s,%d",str,len);

    fclose(fp);
    return a.exec();
}
输出结果:12345678,2

2.4 文件的定位与随机读写

2.4.1 文件的定位fseek

int fseek(FILE *stream, long int offset, int whence) 设置流 stream 的文件位置为给定的偏移 offset,参数 offset 意味着从给定的 whence 位置查找的字节数。

参数

  • stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了流。
  • offset -- 这是相对 whence 的偏移量,以字节为单位。
  • whence -- 这是表示开始添加偏移 offset 的位置。它一般指定为下列常量之一:
常量描述
SEEK_SET(0)文件的开头
SEEK_CUR(1)文件指针的当前位置
SEEK_END(2)文件的末尾

“offset(位移量)”指以“起始点”向前移动的字节数。如果它是负数,表示向后移动,所谓向前就是指从文件开头项文件末尾移动的方向。位移量应为long型。

fseek(fp,10L,SEEK_SET) 将位置移动到离文件开始处10个字节

fseek(fp,-10L,SEEK_CUR)指位置指针从当前位置向后移动10个字节 

返回值

如果成功,则该函数返回零,否则返回非零值。

#include <QCoreApplication>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    FILE *fp;
    char str[9];
    if ((fp = fopen("file.txt","r")) == NULL){
        exit(0);
    }

    fseek(fp,3L,SEEK_SET);
    size_t len = fread(str,sizeof(str),2,fp);
    qDebug("%s,%d",str,len);
    qDebug("%d",ftell(fp));
    rewind(fp);
    len = fread(str,sizeof(str),1,fp);
    qDebug("%s,%d",str,len);
    qDebug("%d",ftell(fp));

    fclose(fp);
    return a.exec();
}
输出结果:
45678,1
18
12345678,1
9

2.4.1 文件的定位ftell

long int ftell(FILE *stream)返回给定流 stream 的当前文件位置。

参数

  • stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了流。

返回值

该函数返回位置标识符的当前值。如果发生错误,则返回 -1L,全局变量 errno 被设置为一个正值。

#include <stdio.h>

int main ()
{
   FILE *fp;
   int len;

   fp = fopen("file.txt", "r");
   if( fp == NULL ) 
   {
      perror ("打开文件错误");
      return(-1);
   }
   fseek(fp, 0, SEEK_END);

   len = ftell(fp);
   fclose(fp);

   printf("file.txt 的总大小 = %d 字节\n", len);
   
   return(0);
}

2.4.2 文件定位rewind 

void rewind(FILE *stream)设置文件位置为给定流 stream 的文件的开头。

#include <stdio.h>

int main()
{
   char str[] = "This is runoob.com";
   FILE *fp;
   int ch;

   /* 首先让我们在文件中写入一些内容 */
   fp = fopen( "file.txt" , "w" );
   fwrite(str , 1 , sizeof(str) , fp );
   fclose(fp);

   fp = fopen( "file.txt" , "r" );
   while(1)
   {
      ch = fgetc(fp);
      if( feof(fp) )
      {
          break ;
      }
      printf("%c", ch);
   }
   rewind(fp);
   printf("\n");
   while(1)
   {
      ch = fgetc(fp);
      if( feof(fp) )
      {
          break ;
      }
      printf("%c", ch);
     
   }
   fclose(fp);

   return(0);
}

2.5 文件操作的出错检测

1) int ferror(FILE *stream)

参数

  • stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了流。

返回值

如果设置了与流关联的错误标识符,该函数返回一个非零值(出错),否则返回一个零值(未出错)。

#include <stdio.h>

int main()
{
   FILE *fp;
   char c;

   fp = fopen("file.txt", "w");

   c = fgetc(fp);
   if( ferror(fp) )
   {
      printf("读取文件:file.txt 时发生错误\n");
   }
   clearerr(fp);
   if( ferror(fp) )
   {
      printf("读取文件:file.txt 时发生错误\n");
   }
   fclose(fp);

   return(0);
}

2) void clearerr(FILE *stream)清除给定流 stream 的文件结束和错误标识符。

参数

  • stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了流。

返回值

这不会失败,且不会设置外部变量 errno,但是如果它检测到它的参数不是一个有效的流,则返回 -1,并设置 errno 为 EBADF。

3) int feof(FILE *stream) 测试给定流 stream 的文件结束标识符。

参数

  • stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了流。

返回值

当设置了与流关联的文件结束标识符时,该函数返回一个非零值(结尾),否则返回零(未结尾)。

#include <QCoreApplication>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    FILE *fp;
    char ch;
    if ((fp = fopen("file.txt","r")) == NULL){
        qDebug("can not open file");
        exit(0);
    }

    while (1){
        ch = fgetc(fp);
        qDebug("ch:%c",ch);
        if (feof(fp)){
            break;
        }
    }
    fclose(fp);
    return a.exec();
}
输出结果是:
ch:1
ch:2
ch:3
ch:4
ch:5
ch:6
ch:7
ch:8
ch:?

2.6 feof 函数 

feof 函数的函数原型为:

int feof (FILE * fp);

所在头文件:<stdio.h>

函数功能:检查 fp 所关联文件流中的结束标志是否被置位,如果该文件的结束标志已被置位,返回非 0 值;否则,返回 0。

需要注意的是:
1) 在文本文件和二进制文件中,均可使用该函数判断是否到达文件结尾。

2) 文件流中的结束标志,是最近一次调用输入等相关函数(如 fgetc、fgets、fread 及 fseek 等)时设置的。只有最近一次操作输入的是非有效数据时,文件结束标志才被置位;否则,均不置位。

【例 3】从键盘输入若干名学生的姓名、学号、语数外三门课成绩并计算平均成绩,将这些学生信息以二进制方式保存到当前目录文件 Stia_Info.dat 中。采用 fwrite 函数写入数据。存储空间要求采用数组形式。采用静态数组形式,仅为了复习数组作为函数参数的情况,且便于理解,实际编程中不建议采用这种方案。

#include<stdio.h>
#include<stdlib.h>
typedef struct {
    char name[10];
    char no[15];
    int sc[3];
    float aver;
}STU;
void Input_Info(STU a[],int n) ; //输入函数原型声明
void Write_Info (STU a[],int n) ; //文件写入函数原型声明
#define N 10 //最多可存储的学生数,可调整
int main (void)
{
    int n;
    STU a[N]; //学生数组,最多容纳N人
    printf ("输入学生人数:");
    scanf("%d",&n);
    Input_Info (a,n) ; //输入学生信息
    Write_Info (a,n); //写人文件
    return 0;
}

void Input_Info (STU a[], int n)
{
    int i;
    for(i=0;i<n;i++)
    {
        printf ("%dth stu (姓名、学号、语数外):",i+1);
        scanf("%s%s%d%d%d",a[i].name,a[i].no,&a[i].sc[0],&a[i].sc[1],&a[i].sc[2]);
        a[i].aver= (a[i].sc[0]+a[i].sc[1]+a[i].sc[2])/3.0;
    }
}
void Write_Info (STU a[], int n)
{
    FILE *fp=fopen ("Stu_Info.dat","wb") ; //"wb":二进制文件写操作
    if(NULL==fp)
    {
        printf ("Failed to open the file !\n");
        exit (0);
    }
    fwrite (a, sizeof (STU) ,n, fp) ; //把a数组中n个学生信息写入文件
    fclose (fp);
}

由于采用二进制形式存储,故打开生成的二进制文件 Stu_Info.dat 可能是“乱码”。通过判断文件的生成以及文件中部分显示正常的数据,可判断代码是否运行正确。

2.7 保存结构体到文件

#include <stdio.h>
#include <errno.h>
#include <string.h>
typedef struct _Account{
  char usrname[128];
  char passwd[128];
  int age;
  int level;
}Account;
void write_account2file(const char *path, Account *account)
{
  FILE *fp = NULL;
  fp = fopen(path, "w+");
  if (NULL == fp)
  {
    printf("open %s fail, errno: %d", path, errno);
    return;
  }
  fwrite(account, sizeof(Account), 1, fp);
  fclose(fp);
  return;
}

void read_accountFromFile(const char *path, Account *account)
{
  FILE *fp = fopen(path, "r");
  if (NULL == fp)
  {
    printf("open %s fail, errno: %d", path, errno);
    return;
  }
  fread(account, sizeof(Account), 1, fp);
  fclose(fp);
}


void main(void)
{
  #if 0
  Account account;
  strncpy(account.usrname, "fellow", strlen("fellow"));
  account.usrname[strlen("fellow")] = '\0';
  strncpy(account.passwd, "1111", strlen("1111"));
  account.passwd[strlen("1111")] = '\0';
  account.age = 18;
  account.level = 0;
  write_account2file("/mnt/hgfs/share/test/account.txt", &account);
  #endif
  Account read;
  read_accountFromFile("/mnt/hgfs/share/test/account.txt", &read);
  printf("read:%s,%s,%d,%d\n", read.usrname, read.passwd, read.age,read.level);

}

三、非缓冲文件系统(系统I/O)

非缓冲文件系统没有文件结构体,不设文件系统类型文件,不能读写单个字符、字符串和格式化数据、一般用于二进制文件,它只有一种文件读写方法,即成块(包含多个字节)读入二进制的数据。

从使用角度来看,缓冲文件系统功能强大,使用方便,但效率较低。而非缓冲文件系统最大的优点是执行效率高。

3.1 打开文件

open(文件名,使用方式)

使用方式

使用方式意义
宏名数字
O_RDONLY1打开一个文件,只能读
O_RDONLY2打开一个文件,只能写
O_RDWR3打开一个文件,能读能写

3.2 读文件

用read函数实现系统级别I/O

格式:read(文件号,缓冲地址,读入最大字节数)

例如read(fd,buff,512)

表示依次从fd所代表的文件中读入512个字节到起始地址为buff的缓冲区中。如果文件可供本次读入字节数不足,则将这些字节全部读入。read函数返回值为实际读入的字节数。如果返回0,表示文件结束,无读入。如果读操作失败,则函数返回值为-1,。

3.3 写文件

格式:write(文件号,缓冲区地址,一次输出的字节数)

如:write(fd,buff,100)

表示将内存中起始地址为buff的缓冲区中的100字节输出到fd所代表的文件中去,可以看出,write函数可以输出整个缓冲区的内容,也可以输出部分缓冲区内容,如果成功,返回值为实际写入的字节数,如果失败,则返回值为-1 。

3.4 关闭文件

格式:close(文件名)

例如:close(fd)的作用是关闭fd所代表的文件

3.5 缓冲区设置

非缓冲系统不自设置缓冲区,而由设计者自己设置缓冲区,也就是说缓冲区是程序的一部分,它的位置在用户缓冲区中。在读入数据时,由磁盘文件将数据读到缓冲区中,然后由程序可以直接引用缓冲区中的数据,在输出时,将缓冲区中的数据送到磁盘文件。实际上,常用一个数组来作为缓冲区。

设置缓冲区的方法,实际上就是讲数据输入到该数组中,由程序对数组内容进行处理。

四、C++文件流操作

在C++程序中使用的保存数据的文件按存储格式分为两种类型,一种是文本文件,一种是二进制文件。文本文件又称为ASCII码文件或字符文件,二进制文件又称为字节文件。在文本文件中,每个字节的内容为字符的ASCII码,在二进制文件中,文件内容是数据内存的表示形式,是从内存中直接复制过来的。

在C++文件流类中,

ifstream为输入文件流类,用于实现文件的输入。

ofstream为输出文件流类,用于实现文件的输出。

fstream为输入输出流类,用于实现输入输出。

4.1 打开操作

1) 用文件流的成员函数open()打开文件

ifstream、ofstream、fstream各有一个成员函数open()成员函数。

void open (const char * filename, openmode mode);

filename 是一个字符串,代表要打开的文件名。

mode 是以下标志符的一个组合:

ios::in

为输入(读)而打开文件

ios::out

为输出(写)而打开文件

ios::ate

初始位置:文件尾

ios::app

所有输出附加在文件末尾

ios::trunc

如果文件已存在则先删除该文件

ios::binary

二进制方式

ofstream, ifstream 和 fstream所有这些类的成员函数open 都包含了一个默认打开文件的方式,这三个类的默认方式各不相同:

参数的默认方式

ofstream

ios::out | ios::trunc

ifstream

ios::in

fstream

ios::in | ios::out

 2)用文件流类的构造函数打开文件

ifstream infile(“file.txt”) //! 利用构造函数定义时直接打开一个输入文件
ofstream outfile("file.txt")  //! 利用构造函数定义时直接打开一个输出文件
fstream  infile("file.txt",ios::in) //! 利用构造函数定义时直接打开一个输入文件
fstream  outfile("file.txt",ios::out) //! 利用构造函数时直接打开一个输出文件

3)判断文件打开是否成功

#include <QCoreApplication>
#include <fstream>
#include <iomanip>
#include <iostream>
using namespace  std;

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    char data[100];

    fstream infile;
    infile.open("file.txt",ios::in);
    qDebug("%d",infile);//! infile 返回1 表示成功,否则表示不成功
    qDebug("%d",infile.is_open()); //! 返回
    infile>>data;
    qDebug("%s",data);
    infile.close();
    return a.exec();
}

is_open()函数的原型为: bool is_open();

作用:判定文件是否打开成功 ;

返回值: 打开成功返回true;否则,返回false;

使用位置:一般在放在创建输入输出流后,如果文件打开错误,直接退出程序。

 4.2 关闭文件

格式:void close()

 4.3 文件的读写 

文件读写有两种方式:

1) 直接采用流插入运算“<<”和提取运算符“>>”,这些运算符将完成文件的字符转换工作

2) 使用流成员函数,输出流成员函数有put和write,输入流成员函数有get,getline和read

#include <QCoreApplication>
#include <fstream>
#include <iomanip>
#include <iostream>
using namespace  std;

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    //! 写操作
    char str[] = {"12345678"};
    char data[100];
    ofstream outfile;
    outfile.open("file.txt");
    if (!outfile.is_open()){
        qDebug("can not open file");
        exit(0);
    }
    //! 方式1
    for (int i= 0;i < 3; i++){
        outfile<<str;
        outfile<<"\n";
    }

    outfile.close();

    //! 读操作
    ifstream infile;
    infile.open("file.txt");
    if (!infile.is_open()){
        qDebug("can not open file");
        exit(0);
    }
    //! 方式1
    while(infile.good()){
        infile>>data;
        qDebug("%s",data);
    }

    infile.close();
    return a.exec();
}
结果输出:
12345678
12345678
12345678

4.3.1 状态标志符的验证(Verification of state flags)

验证流的状态的成员函数(所有都返回bool型返回值):

  • bad()

如果在读写过程中出错,返回 true 。例如:当我们要对一个不是打开为写状态的文件进行写入时,或者我们要写入的设备没有剩余空间的时候。

  • fail()

除了与bad() 同样的情况下会返回 true 以外,加上格式错误时也返回true ,例如当想要读入一个整数,而获得了一个字母的时候。

  • eof()

如果读文件到达文件末尾,返回true。

  • good()

这是最通用的:如果调用以上任何一个函数返回true 的话,此函数返回 false 。

要想重置以上成员函数所检查的状态标志,你可以使用成员函数clear(),没有参数。

4.3.2 istream::getline

读取一行到字符数组。

istream& getline (char* s, streamsize n );
//默认delim是换行字符'\n',遇到后丢弃,第二次读取从delim后开始读。

istream& getline (char* s, streamsize n, char delim );
<string> 字符串头文件也定义了从流中读取一行的函数 getline() 
因为它不是流的成员函数,所以不能通过点访问。

std::getline (string)

(1) 用户定义截止字符
istream& getline (istream&  is, string& str, char delim);
istream& getline (istream&& is, string& str, char delim); //c++11 标准

(2) 截止字符默认'\n'
istream& getline (istream&  is, string& str);
istream& getline (istream&& is, string& str); // c++11 标准

用法: 
从流对象is中读取一行存到字符串str 直到遇到截止字符,如果遇到截止字符,则把它从流中取出来,然后丢弃(它不被存储,下一个操作的起点在它之后)函数调用前str 中的内容将被覆盖。 

#include <QCoreApplication>
#include <fstream>
#include <iomanip>
#include <iostream>
using namespace  std;

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    //! 写操作
    char str[] = {"12345678"};
    char data[100];
    ofstream outfile;
    outfile.open("file.txt");
    if (!outfile.is_open()){
        qDebug("can not open file");
        exit(0);
    }
    //! 方式1
    for (int i= 0;i < 3; i++){
        outfile<<str;
        outfile<<"\n";
    }
    
    outfile.close();

    //! 读操作
    ifstream infile;
    infile.open("file.txt");
    if (!infile.is_open()){
        qDebug("can not open file");
        exit(0);
    }
   
    while(!infile.eof()){
        infile.getline(data,100);
        qDebug("%s",data);
    }
   
    infile.close();
    return a.exec();
}

 4.4 文件的随机读写

获得和设置流指针(get and put stream pointers)

所有输入/输出流对象(i/o streams objects)都有至少一个流指针:

  • ifstream, 类似istream, 有一个被称为get pointer的指针,指向下一个将被读取的元素。
  • ofstream, 类似 ostream, 有一个指针 put pointer ,指向写入下一个元素的位置。
  • fstream, 类似 iostream, 同时继承了get 和 put

我们可以通过使用以下成员函数来读出或配置这些指向流中读写位置的流指针:

  • tellg()  tellp()

这两个成员函数不用传入参数,返回pos_type 类型的值(根据ANSI-C++ 标准) ,就是一个整数,代表当前get 流指针的位置 (用tellg) 或 put 流指针的位置(用tellp).

  • seekg() seekp()

这对函数分别用来改变流指针get 和put的位置。两个函数都被重载为两种不同的原型:

seekg ( pos_type position ); seekp ( pos_type position );

使用这个原型,流指针被改变为指向从文件开始计算的一个绝对位置。要求传入的参数类型与函数 tellg 和tellp 的返回值类型相同。

seekg ( off_type offset, seekdir direction ); seekp ( off_type offset, seekdir direction );

使用这个原型可以指定由参数direction决定的一个具体的指针开始计算的一个位移(offset)。它可以是:

ios::beg

从流开始位置计算的位移

ios::cur

从流指针当前位置开始计算的位移

ios::end

从流末尾处开始计算的位移

流指针 get 和 put 的值对文本文件(text file)和二进制文件(binary file)的计算方法都是不同的,因为文本模式的文件中某些特殊字符可能被修改。由于这个原因,建议对以文本文件模式打开的文件总是使用seekg 和 seekp的第一种原型,而且不要对tellg 或 tellp 的返回值进行修改。对二进制文件,你可以任意使用这些函数,应该不会有任何意外的行为产生。

以下例子使用这些函数来获得一个二进制文件的大小:

     // obtaining file size
      #include <iostream.h>
      #include <fstream.h>
      
      const char * filename =   "example.txt";
      
      int main () {
          long l,m;
          ifstream file (filename,   ios::in|ios::binary);
          l = file.tellg();
          file.seekg (0, ios::end);
          m = file.tellg();
          file.close();
          cout << "size of   " << filename;
          cout << " is "   << (m-l) << " bytes.\n";
          return 0;
      }

size of example.txt is 40   bytes.

4.5 二进制文件(Binary files)


在二进制文件中,使用<< 和>>,以及函数(如getline)来操作符输入和输出数据,没有什么实际意义,虽然它们是符合语法的。

文 件流包括两个为顺序读写数据特殊设计的成员函数:write 和 read。第一个函数 (write) 是ostream 的一个成员函数,都是被ofstream所继承。而read 是istream 的一个成员函数,被ifstream 所继承。类 fstream 的对象同时拥有这两个函数。它们的原型是:
write ( char * buffer, streamsize size );
read ( char * buffer, streamsize size );
这里 buffer 是一块内存的地址,用来存储或读出数据。参数size 是一个整数值,表示要从缓存(buffer)中读出或写入的字符数。

#include <iostream>
#include <fstream>
using namespace std;
int main ()
{
    const char * filename = "example.txt";
    char * buffer;
    long size;
    ifstream file(filename, ios::in|ios::binary|ios::ate);
    size = file.tellg();
    file.seekg(0, ios::beg);
    buffer = new char [size];
    file.read(buffer, size);
    file.close();
    cout <<"the complete file is in a buffer";
    delete[] buffer;
    return 0;
}

4.6 缓存和同步(Buffers and Synchronization)


当我们对文件流进行操作的时候,它们与一个streambuf 类型的缓存(buffer)联系在一起。这个缓存(buffer)实际是一块内存空间,作为流(stream)和物理文件的媒介。例如,对于一个输出流, 每次成员函数put (写一个单个字符)被调用,这个字符不是直接被写入该输出流所对应的物理文件中的,而是首先被插入到该流的缓存(buffer)中。

当缓存被排放出来(flush)时,它里面的所有数据或者被写入物理媒质中(如果是一个输出流的话),或者简单的被抹掉(如果是一个输入流的话)。这个过程称为同步(synchronization),它会在以下任一情况下发生:

当文件被关闭时: 在文件被关闭之前,所有还没有被完全写出或读取的缓存都将被同步。
当缓存buffer 满时:缓存Buffers 有一定的空间限制。当缓存满时,它会被自动同步。
控制符明确指明:当遇到流中某些特定的控制符时,同步会发生。这些控制符包括:flush 和endl。
明确调用函数sync(): 调用成员函数sync() (无参数)可以引发立即同步。这个函数返回一个int 值,等于-1 表示流没有联系的缓存或操作失败。
在C++中,有一个stream这个类,所有的I/O都以这个“流”类为基础的,包括我们要认识的文件I/O,stream这个类有两个重要的运算符:

1、插入器(<<)
向流输出数据。比如说系统有一个默认的标准输出流(cout),一般情况下就是指的显示器,所以,cout<<"Write Stdout"<<'n';就表示把字符串"Write Stdout"和换行字符('n')输出到标准输出流。

2、析取器(>>)
从流中输入数据。比如说系统有一个默认的标准输入流(cin),一般情况下就是指的键盘,所以,cin>>x;就表示从标准输入流中读取一个指定类型(即变量x的类型)的数据。

五、Qt之文件操作 QFile

5.1 QFile

QFile类是一个操作文件的输入/输出设备
QFile是用来读写二进制文件和文本文件的输入/输出设备。QFile可以自己单独被使用,但是如果和QDataStream或QTextStream一起使用将更加方便。
文件名通常可以通过构造函数来传递,但也可以使用setName()来设置。目录分隔符在任何操作系统下都使用“/",“/"不被支持。你可以通过exists()来检查一个文件是否存在并且可以通过remove()来移去一个文件。更多操作系统相关的高级文件系统操作QT提供了QFileInfo和QDir类.
文件可以用open()来打开、用close()来关闭、用flush()来刷新。数据通常可以使用QDataStream或者QTextStream进行读写,但你也可以使用read(),readLine(),readAll(),write()读写。QFile也支持getChar(),putChar(),和ungetChar()
size()可以返回文件的大小。你可以通过使用pos()函数得到当前文件位置或者使用seek()移到一个新的文件位置。如果你到了文件的末尾,atEnd()返回真

5.2. 文件打开方式

bool QFile::open(OpenMode mode);

/*

*OpenMode mode 打开方式,是一个枚举类型

*QIODevice::NotOpen 不打开

*QIODevice::ReadOnly 只读方式

*QIODevice::WriteOnly 读写方式

*QIODevice::ReadWrite 读写方式

*QIODevice::Append   追加方式

*QIODevice::Truncate 阶段方式

*QIODevice::Text     转换不同平台的换行,读的时候把所有换行转成'\n',写的时候再把'\n'转换对应平台的换行

*QIODevice::Unbuffered 不使用缓冲区

*/

QIODevice类是输入/输出设备的基类。

QIODevice为设备提供了公共实现和抽象接口用于读写块数据。
QIODevice是一个抽象类,不能被实例化。

QFile file("d:/123.txt");
file.open(QIODevice::ReadOnly);

5.3 QFile类关闭文件

void QFileDevice::close(); //刷新缓冲区,并关闭文件

 

5.4 读文件

5.4.1 read()函数

qint64 QIODevice::read ( char * data, qint64 maxSize )//

从设备读取最多maxSize字节为数据,并返回读取的字节数。 如果发生错误,例如尝试从以WriteOnly模式打开的设备读取时,此函数返回-1。当没有更多数据可供读取时,返回0。在关闭的套接字上读取或在进程死亡后读取也会返回-1

QByteArray QIODevice::read ( qint64 maxSize )  //从设备读取最多maxSize字节,并返回读取为QByteArray的数据。
此功能无法报告错误; 返回一个空的QByteArray()可能意味着当前没有数据可用于读取,或者发生了错误。
    QFile file("file.txt");
    if (file.open(QFile::ReadOnly))
    {
     //QByteArray arr = file.read(1024);
         //qDebug() << arr;
         char buf[1024];
         qint64 lineLength = file.read(buf, sizeof(buf));
         if (lineLength != -1)
         {
             QString str(buf);//将字符数组转换为QString
             qDebug() << str;
         }
     }

打印结果:

"hello qfile!
hello qfile!
hello"

5.4.2 readAll()函数

QByteArray QIODevice::readAll ()

从设备读取所有可用数据,并将其作为QByteArray返回。
此功能无法报告错误; 返回一个空的QByteArray()可能意味着当前没有数据可用于读取,或者发生了错误。

    QFile file("file.txt");
    if (file.open(QFile::ReadOnly))
    {
         QByteArray arr = file.readAll();
         qDebug() << arr;

     }

打印结果:

"hello qfile!
hello qfile!
hello"

5.4.3 readLine()函数

qint64 QIODevice::readLine ( char * data, qint64 maxSize )

此函数从设备读取一行ASCII字符,最大为(maxSize - 1)个字节,将字符存储在数据中,并返回读取的字节数。 如果无法读取行但没有出现错误,则此函数返回0.如果发生错误,则此函数返回可读取的长度,如果未读取任何内容,则返回-1。
终止'\ 0'字节始终附加到数据,因此maxSize必须大于1。
读取数据,直到满足以下任一条件:
读取第一个'\ n'字符。
maxSize - 读取1个字节。
检测到设备数据的结束。

此函数调用readLineData(),readLineData()是使用对getChar()的重复调用实现的。 您可以通过在自己的子类中重新实现readLineData()来提供更高效的实现。

    QFile file("file.txt");
    if (file.open(QFile::ReadOnly))
    {
        char buf[1024];
        qint64 lineLength = file.readLine(buf, sizeof(buf));
        if (lineLength != -1)
        {
            QString str(buf);
            qDebug() << str;
        }
    }

打印结果:

"hello qfile!
"

可以看出"\n"也被附加到了数据里。

5.4.4 getChar()函数

bool QIODevice::getChar ( char * c )

从设备中读取一个字符并将其存储在c中。 如果c为0,则丢弃该字符。 成功时返回true; 否则返回false。

5.5 QFile类文件写操作

QIODevice::write函数

qint64 QIODevice::write(const QByteArray &byteArray); //将byteArray写入文件,写完内部位置指针后移
QFile file("d:/123.txt");
file.open(QIODevice::ReadWrite | QIODevice::Text); //打开模式可以使用‘|'组合
QByteArray byte("hellworld");
file.write(byte);
file.write(byte);
file.close();

5.6. QDataStream

流控文件输入输出可以使用QDataStream。QDataStream 重载了运算符了"<<"写数据,>>读数据

#include <QDataStream>
#include <QDebug>
#include <QFile>
int main(int argc, char**argv)
{
    QFile file("file.txt");
    file.open(QIODevice::ReadWrite);
    QDataStream stream(&file);
    int a = 10;
    QString str = "helloworld";
    stream << a << str;
    file.close();
    return 0;
}
#include <QDataStream>
#include <QDebug>
#include <QFile>
int main(int argc, char**argv)
{
    QFile file("file.txt");
    file.open(QIODevice::ReadWrite);
​
    QDataStream stream(&file);
    int a;
    QString str;
    stream >> a >> str;
    qDebug() << "a:" << a << "str:" << str << endl;
    file.close();
    return 0;
}
QFile file("file.txt");    
    if(!file.open(QIODevice::ReadWrite | QIODevice::Text)) {    
        qDebug()<<"Can't open the file!"<<endl;    
    }    
    QTextStream stream(&file);    
    QString line_in;    
//    while( !stream.atEnd()){    
//        line_in = stream.readLine();    
//        qDebug() << line_in;    
//    }    
//    stream.seek(stream.pos());    
    stream.seek(file.size());//将当前读取文件指针移动到文件末尾    
    int count = 0;    
    while(count < 10){    
        stream << QObject::trUtf8("新建行:") <<++count<<"/n";    
    }    
    stream.seek(0);//将当前读取文件指针移动到文件开始    
    while( !stream.atEnd()){    
        line_in = stream.readLine();    
        qDebug() << line_in;    
    }    

一行一行直接读取文件

QFile file("file.txt");    
if(!file.open(QIODevice::ReadOnly | QIODevice::Text)) {    
    qDebug()<<"Can't open the file!"<<endl;    
}    
while(!file.atEnd()) {    
    QByteArray line = file.readLine();    
    QString str(line);    
    qDebug()<< str;    
}    

使用QTextStream读取文件 

QFile file("file.txt");  
 if (!file.open(QIODevice::ReadOnly | QIODevice::Text))  
     return;  
  
 QTextStream in(&file);  
 QString line = in.readLine();  
 while (!line.isNull()) {  
     process_line(line);  
     line = in.readLine();  
 }  

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值