Who are you?

C/C++文件读写

​ 最近编程发现利用C/C++读写二进制文件出了一点问题,嗯,不仅笨且记性也不好,所以就用这篇文档来整理记录一下关于C和C++文件读写的相关内容,以备以后用到可以直接查阅而不用到处瞎找。本文主要分成两个部分,前一小部分对C的文件读写做下简要的回顾,后一大半内容主要都是C++文件I/O。

C文件的I/O

文本文件I/O

打开文件

我们使用fopen()函数来创建一个新文件或者打开一个已有的文件,这个调用会初始化类型位FILE的一个对象,类型 FILE 包含了所有用来控制流的必要的信息。函数原型为:

1
FILE *fopen( const char * filename, const char * mode );

其中,filename 是字符串,用来命名文件,访问模式 mode 的值可以是下列值中的一个:

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

关闭文件

我们使用fclose函数来关闭文件,其函数原型为:

1
2
int fclose( FILE *fp );
//函数实际作用:清空缓冲区中的数据,关闭文件,并释放用于该文件的所有内存。

Tips

  • 如果成功关闭文件,fclose( ) 函数返回零;
  • 如果关闭文件时发生错误,函数返回 EOF。EOF 是一个定义在头文件 stdio.h 中的常量。

写入文件

  1. fputc()

    原型:

    1
    int fputc( int c, FILE *fp );

    作用:把参数 c 的字符值写入到 fp 所指向的输出流中;

    返回:

    • 如果写入成功,它会返回写入的字符
    • 如果发生错误,则会返回 EOF
  2. fputs()

    原型:

    1
    int fputs( const char *s, FILE *fp );

    作用: 把字符串 s 写入到 fp 所指向的输出流中

    返回:

    • 如果写入成功,它会返回一个非负值,
    • 如果发生错误,则会返回 EOF
  3. fprintf()

    原型:

    1
    2
    int fprintf( FILE *fp, const char *format, [ argument ]...)
    // format: 格式字符串,用于格式化写入文件

    作用:把一个字符串写入到文件中,直至遇到’\0’

    返回:同上

    示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    /* 在 /tmp 目录中创建一个新的文件 test.txt,并使用两个不同的函数写入两行。*/
    #include <stdio.h>
    main()
    {
    FILE *fp;
    fp = fopen("/tmp/test.txt", "w+");
    fprintf(fp, "This is testing for fprintf...\n");
    fputs("This is testing for fputs...\n", fp);
    fclose(fp);
    }

读取文件

  1. fputc()

    原型:

    1
    int fgetc( FILE * fp );

    作用:从 fp 所指向的输入文件中读取一个字符。

    返回:

    • 如果成功,则返回值是读取的字符
    • 如果发生错误则返回 EOF
  2. fgets()

    原型:
    
    1
    char *fgets( char *buf, int n, FILE *fp );
    作用:从 fp 所指向的输入流中读取 n - 1 个字符。它会把读取的字符串复制到缓冲区 **buf**,并在最后追加一个 **null** 字符来终止字符串。
    
    返回:如果这个函数在读取最后一个字符之前就遇到一个**换行符 '\n' 或文件的末尾 EOF**,则只会返回读取到的字符,包括换行符。
    
  1. fscanf()

    原型:

    1
    int fscanf(FILE*stream,constchar*format,[argument...]);

    作用:从文件中读取字符串,但是在遇到第一个空格字符时,它会停止读取。

    返回:同fgets()

    示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    /* 首先,fscanf() 方法只读取了 This,因为它在后边遇到了一个空格。
    * 其次,调用 fgets() 读取剩余的部分,直到行尾。
    * 最后,调用 fgets() 完整地读取第二行。 */
    include <stdio.h>
    int main()
    {
    FILE *fp;
    char buff[255];
    fp = fopen("/tmp/test.txt", "r");
    fscanf(fp, "%s", buff);
    printf("1 : %s\n", buff );
    fgets(buff, 255, (FILE*)fp);
    printf("2: %s\n", buff );
    fgets(buff, 255, (FILE*)fp);
    printf("3: %s\n", buff );
    fclose(fp);
    }

    运行结果

    1
    2
    3
    4
    1 : This
    2: is testing for fprintf...
    3: This is testing for fputs...

二进制 I/O

文件打开

函数同文本文件打开,

1
FILE *fopen( const char * filename, const char * mode );

但是mode有点区别,

1
"rb", "wb", "ab", "rb+", "r+b", "wb+", "w+b", "ab+", "a+b"

文件关闭

同文本文件

写入文件

函数原型:

1
2
3
4
5
6
7
8
9
/* buffer:是一个指针,对fwrite来说,是要获取数据的地址;
* size_of_elements:要写入内容的单字节数;
* number_of_elements:要进行写入size字节的数据项的个数;
* a_file:目标文件指针;
* 返回实际写入的数据项个数。
× size_t:一般表示是unsigned int类型,它是一个与机器相关
× 的unsigned类型,其大小足以保证存储内存中对象的大小。*/
size_t fwrite(const void *buffer, size_t size_of_elements,
size_t number_of_elements, FILE *a_file);

Tips:写入到文件的哪里? 这个与文件的打开模式有关,

  • 如果是w+,则是从file pointer指向的地址开始写,替换掉之后的内容,文件的长度可以不变,a_file的位置移动number_of_elements个数;
  • 如果是a+,则从文件的末尾开始添加,文件长度加大。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
struct mystruct
{
int i;
char cha;
};
int main(void)
{
FILE *stream;
struct mystruct s;
if ((stream = fopen("TEST.$$$", "wb")) == NULL) /* open file TEST.$$$ */
{
fprintf(stderr, "Cannot open output file.\n");
return 1;
}
s.i = 0;
s.cha = 'A';
fwrite(&s, sizeof(s), 1, stream); /* 写的struct文件*/
fclose(stream); /*关闭文件*/
return 0;
}

读取文件

原型:

1
2
3
4
5
6
/* 从a_file指向的文件中读取number个数据,每个数据大小为size,
* 将其写入到buffer中
* 若调用成功,则返回哦实际读取到的个数,如果不成功或是读到文件
* 末尾则返回零 */
size_t fread(void *buffer, size_t size_of_elements,
size_t number_of_elements, FILE *a_file);

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include<stdio.h>
#include<string.h>
int main(void)
{
FILE*stream;
char msg[]="this is a test";
char buf[20];
if((stream=fopen("DUMMY.FIL","w+"))==NULL)
{
fprintf(stderr,"Can not open output file.\n");
return 0;
}
/*write some data to the file*/
fwrite(msg,1,strlen(msg)+1,stream);
/*sizeof(char)=1 seek to the beginning of the file*/
fseek(stream,0,SEEK_SET);
/*read the data and display it*/
fread(buf,1,strlen(msg)+1,stream);
printf("%s\n",buf);
fclose(stream);
return 0;
}

C++文件的I/O

C++ 文件I/O同处理标准输入输出的方式非常相似,同时C++在头文件fstream中定义了多个新类,包括①用于文件输入的ifstream类和②用于文件输出的ofstream类,此外还定义了一个③用于同步文件I/O的fstream类,这些类都是从iostream中派生而来的,因此可以将iostream的方法用到文件操作中。常用的两类文件操作:

  • 写人文件:创建一个ofstream对象,并运用ostream方法,如<<或write()方法
  • 读取文件:创建一个ifstream对象,并运用istream方法,如>>或get()方法

C++ 简单I/O示例

写入文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* 首先必须包含头文件
* 对大多数实现来说,包含该头文件也就意味着包含了iostream,
* 因此不必显式包含iostream */
#include<fatream>
/* Step1: 声明一个ofstream对象 */
ofstream fout;
/* Step2: 对象关联特定文件 */
fout.fopen("jar.txt");
if(!fout.is_open())
exit(1);
/* 以上两步骤可以合并为如下的一句命令 */
ofstream fout("jat.txt");
/* Step 3: 使用cout的方式使用使用该对象 */
fout<<"Hello world !";
/* Step4: 关闭文件 */
fout.close();

Tips:

  1. ofstream对象可以使用ostream的所有方法,包括各种插入运算付定义,格式化方法以及控制符等;
  2. fstream对象先先从程序中逐字节收集输出,待缓存区满后,再将缓存区中所有内容一同输出目标文件;
  3. 以上述方式打开的文件,如果没用这样的文件,将会报错,如果有这样的文件,将会清空内容。

读取文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* 首先必须包含头文件
* 对大多数实现来说,包含该头文件也就意味着包含了iostream,
* 因此不必显式包含iostream */
#include<fatream>
/* Step1: 声明一个ifstream对象 */
ifstream fin;
/* Step2: 对象关联特定文件 */
fin.fopen("jellyar.txt");
if(!fin.is_open())
exit(1);
/* 以上两步骤可以合并为如下的一句命令 */
ifstream fin("jellyjat.txt");
/* Step 3: 使用cin的方式使用使用该对象 */
char ch;
fin>>ch; /* 一次读取一个char */
char buf[80];
fin.getline(buf,80); /* 一次读取一行char */
/* Step4: 关闭文件 */
fin.close();

文本文件

  1. 基本代码结构同示例,不过在文件打开中,其模式有:
    | 模式 | 描述 |
    | :———: | ————– |
    | ios::in | 为输入(读)而打开文件 |
    | ios::out | 为输出(写)而打开文件 |
    | ios::ate | 初始位置:文件尾 |
    | ios::app | 所有输出附加在文件末尾 |
    | ios::trunc | 如果文件已存在则先删除该文件 |
    | ios::binary | 以二进制方式对文件进行操作 |

    Tips:这些打开模式可以组合使用,中间以或操作符”|”间隔,如我们可以

    1
    2
    3
    4
    5
    6
    /* 以二进制方式打开文件,并在末尾末尾写入数据 */
    ofstream file;
    file.open ("example.bin", ios::out | ios::app | ios::binary);
    /* OR */
    ofstream file ("example.bin", ios::out | ios::app | ios::binary);
  2. 文件打开检查

    调用成员函数is_open()来检查一个文件是否已经被顺利的打开,它返回一个布尔(bool)值,为真(true)代表文件已经被顺利打开,假( false )则相反

二进制文件

注意事项:

  1. 文件的打开关闭操作同示例,但是打开模式中必须要有ios::binary
  2. 在二进制文件中,使用<< 和>>,以及函数(如getline)来操作符输入和输出数据,没有什么实际意义,虽然它们是符合语法的。
  3. 在二进制文件操作中,其写入和读取数据的函数为write和read成员函数
  4. 【转】对于C++文件操作,你以什么模式打开文件根本不重要,因为你既改变不了文件本身的内容,也改变不了C/C++中系统函数的工作方式,所以在编程的时候,你只要关心这个文件里的数据内容本身是二进制格式还是文本格式就好了!如果内容是文本格式的,你就调用文本格式那一套函数,比如puts,gets,fscanf,fprintf,<<,>>等,如果内容是二进制格式的,你就调用二进制格式那一套函数,比如fread,fwrite,ifstream.read(),ofstream.write()等。只要保持文件内容与处理函数相对应相一致就可以了,别管它用什么模式打开文件!!假如你用<<向一个二进制文件中输入一个整数,那么其实里面保存的是文本格式的数据,那么你就照样可以以二进制模式打开它,然后用>>来读取这个整数。相反,如果你的二进制文件里面是一个以二进制形式保存的整数,那你肯定不能用>>来读取里面的整数了!!

写入文件

原型:

1
2
3
4
5
file.write( char * buffer, streamsize size );
/* Notice
* buffer的类型必须是(char *),不是此类型的需要强制类型转换,
* 它是内存中需要写入到文件的数据的首地址;
* size大小的确定一般用count*sizeof(type)获取

示例:

1
2
3
4
5
6
7
8
9
10
std::ofstream fout("a.dat", std::iOS::binary);
int nNum = 20;
std::string str("Hello, world");
/* 写入int类型数据 */
fout.write((char*)&nNum, sizeof(int));
/* 写入字符串 */
fout.write(str.c_str(), sizeof(char) * (str.size()));
fout.close();

读取文件

原型:

1
2
3
4
5
file.read ( char * buffer, streamsize size );
/* Notice
* buffer的类型必须是(char *),不是此类型的需要强制类型转换,
* 它是从文件中读取的数据进行存放的空间的首地址;
* size同上; */

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
std::ifstream fin("a.dat", std::ios::binary);
int nNum;
char szBuf[256] = {0};
/* 读取数据存放到nNum中 */
fin.read((char*)&nNum, sizeof(int));
/* 读取数据存放到szbuf中 */
fin.read(szBuf, sizeof(char) * 256);
std::cout << "int = " << nNum << std::endl;
std::cout << "str = " << szBuf << std::endl;
fin.close();

参考资料:

  1. C I/O
  2. C++ I/O