摘要
C++打开文件时有个模式选项,该选项可以指定文件的访问方式,b字符表示二进制方式访问文件,不加b的表示文本方式访问文件。本文主要总结二者的区别。
FILE *fopen(const char *filename, const char *mode)
涉及术语
CR ,Carriage Return,中文名回车,对应十六进制编码0x0D
,ASCII编码为\r
。理解为控制台输出时光标移动到最左侧。
LF:Linefeed,中文名换行,对应十六进制编码0x0A
,ASCII编码\n
。理解为控制台输出时光标往下挪了一行。
CRLF组合起来就是光标移动到行首+光标下移一行。
区别
1、在处理换行上的区别
1.在windows系统中,文本模式下,文件以"\r\n"代表换行。若以文本模式打开文件,并用fputs等函数写入换行符"\n"时,函数会自动在"\n"前面加上"\r"。即实际写入文件的是"\r\n" 。xp操作系统的记事本程序必须包含"\r\n"显示时才能换行,高级版本只要"\n"即可换行。测试程序如下,管理员权限运行,使用notepad++打开即可看到编码。
// testFopen.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <stdio.h>
#include <stdarg.h>
#include <memory.h>
int _tmain(int argc, _TCHAR* argv[])
{
FILE *fp;
if ((fp = fopen("c:\\123.log", "a")) != NULL)//"a"文本文件追加的方式写入
{
fprintf(fp, "111111\n");
fclose(fp);
}
if ((fp = fopen("c:\\123.log", "ab")) != NULL)//"a"文本文件追加的方式写入
{
fprintf(fp, "111111\n");
fclose(fp);
}
return 0;
}
2.在类Unix/Linux系统中文本模式下,文件以"\n"代表换行。所以Linux系统中在文本模式和二进制模式下并无区别。
2、在读取数据时对结束(EOF)的判断
1.二进制方式,程序指定大小就读指定大小,除了读到文件尾部,则不存在提前结束的情况。
2.文件文本方式读取二进制数据, 可能在文件结束之前将某段数据判定为文件末尾EOF
。
我遇到过一次的具体Bug场景是文件里存的结构体数组,循环读数据到结构体变量,每次读指定字节长度的数据,最终结构是乱码。错误原因就是使用了文本文件方式读写。
bug清楚的记得,尝试了很多方案,没有复现。2023-03-08
终于复现了 2023-03-12
直接上代码,注意+++
标记处。windows有这个问题,是因为ctrl+z组合键被识别为程序的结束,对应的ASCII字符编码为26
// testFopen.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <stdio.h>
#include <stdarg.h>
#include <memory.h>
#include <iostream>
typedef struct _Data
{
char arr[20];
int nvar;
} Data;
int _tmain(int argc, _TCHAR* argv[])
{
using namespace std;
FILE *fp;
if ((fp = fopen("c:\\123.log", "wb+")) != NULL)//"a"文本文件追加的方式写入 "w" 重新写
{
cout << "fwrite ---------------------------------------- " << endl;
// for写入100个结构体变量的数据,每个数据占用sizeofData个字节
cout << sizeof Data << endl;
for (int i = 0; i < 100;i++)
{
Data data = { 0 };
memcpy_s(data.arr, sizeof(data.arr), "Yang^z Naifeng",sizeof "Yang Naifeng");
memset(&data.arr[1], 26, 2);
data.nvar = 123450;
fwrite(&data, sizeof Data, 1, fp);
}
//Data data2;
//memset(data2.arr, '8', 10);
//data2.arr[0] = 0; //插入0
//data2.arr[1] = 0;
//data2.arr[2] = 0x0D;
//data2.arr[3] = 0x0A;
//data2.nvar = 678910;
//fwrite(&data2, sizeof Data, 1, fp);
//fclose(fp);
fclose(fp);
}
/************************************************************************/
/* +++ rb+ 如果换成 r+就出错了!!! 因为文本文件格式 Ctrl+z 会被识别为文件的结束。 */
/************************************************************************/
if ((fp = fopen("c:\\123.log", "rb+")) != NULL)//"a"文本文件追加的方式写入
{
cout << "fread ---------------------------------------- " << endl;
fseek(fp, 0, SEEK_END);
long ret = ftell(fp);
fseek(fp, 0, SEEK_SET);
for (int i = 0; i < 100; i++)
{
Data data;
memset(data.arr, '0', 10);
data.nvar = 0;
auto ret = fread(&data, sizeof Data, 1, fp);
//fgets((char*)&data, sizeof Data, fp);
printf( "data.var : %d", data.nvar );
printf( "data1.arr: " );
for (int i = 0; i < sizeof data.arr; i++)
{
printf("%c", data.arr[i]);
}
printf("\n");
}
fclose(fp);
}
char ch;
cin >> ch;
return 0;
}
建议
对于一切非"\n"结束的简单字符串存储方式,采用二进制读写。自己把size确定准确。文本文件方式读复杂数据不可靠。
fread读指定字节的数据遇到整数值26结束,fgets是读字符串,字符串的结束符会提前结束。