关于write和read函数里面的传参类型说明(sizeof和strlen的区分)
ostream& write( const char* s, streamsize n ); //参数 1:const char* 常量字符指针(数据源起始地址) //参数 2:streamsize 要写入的总字节数量(有符号整数类型) istream& read( char* s, streamsize n ); //参数 1:char* 字符指针(存放读取结果的缓冲区) //参数 2:streamsize 最多读取的总字节数量为什么传变量 / 结构体要强制转换 (char*)
函数只接收 char*,任意其他类型(int*, 结构体指针)都必须强转成 char*,把数据当成原始字节流处理。
因为标准设计初衷就是用于二进制原始字节读写。read/write的工作逻辑永远不变: 只管搬运指定数量的内存字节,不解析内容、不转换字符,函数本身就是用来读写原始字节的 API。
接下来详细讲解怎么区分第二个参数的总字节量,关于sizeo和strlen的使用区分
看之前先把这两条铁律记住
read/write= 原始字节搬运函数。只要用这两个函数做数据存档,打开文件务必ios::binary;strlen只服务内存中带\0的 C 字符串,int、结构体、double 永远沾不上 strlen。
示例 1:二进制成对读写(sizeof 配对,标准存档用法)
适用场景:保存 int、结构体、完整字符串,之后程序读回来复原
#include <iostream> #include <fstream> using namespace std; int main() { // 写入 ofstream out("data.bin", ios::binary); int num = 12345; char str[] = "hello"; out.write((char*)&num, sizeof(num)); // sizeof 写入整数 out.write(str, sizeof(str)); // sizeof写入字符串,连带 '\0' out.close(); // 读取,必须和写入字节数保持一致 ifstream in("data.bin", ios::binary); int read_num; char buf[20]; in.read((char*)&read_num, sizeof(read_num)); in.read(buf, sizeof(str)); // 和write的sizeof配对 in.close(); cout << read_num << endl; cout << buf << endl; return 0; }对于int类型和结构体类型,strlen绝对不能用:strlen 只识别以\0结尾的 char 字符串,给 int 指针或者结构体直接传进去会发生内存越界、未定义行为。
致命后果:写入的是 int 内存原始字节,记事本打开是一堆乱码符号,这根本不是文本存储!
只要你调用 write 往文件写 int,本质就是二进制导出,和 “文本存储” 无关。
示例 2:文本输出(write 使用 strlen,只存可见字符)
记事本打开能直接看到文字,不保存 \0
#include <iostream> #include <fstream> #include <cstring> using namespace std; int main() { ofstream out("test.txt"); char str[] = "hello world"; // strlen:只写入有效字符,丢弃末尾'\0' out.write(str, strlen(str)); out.close(); // =====重点:读取不能用strlen!===== ifstream in("test.txt"); char buf[100] = {0}; // 方案简单处理:一次性读完(不知道长度一般循环读取) in.read(buf, sizeof(buf)-1); buf[strlen(buf)] = '\0'; // 手动补上结束符 cout << buf << endl; return 0; }这段代码是特殊折中写法:使用write/read处理人可读文本,不属于规范二进制存档写法,存在缺陷,只适合理解原理,工程不推荐。
in.read (buf, sizeof (buf)-1) ,给手动添加字符串结束符 \0 预留1 个字节的位置。
因为write写入时用的strlen,导致文件里没有写入\0这个字符
而read 只会搬运文件字节,不会自动写入 \0。
如果读满 100 个字节,整个 buf 全部被文件数据占满,没有空余位置存放 \0。
后续直接当作字符串 cout 打印,缺少终止符,内存越界,出现乱码。
这只是文本字符串场景的习惯写法
用途:把读取到的字节当成 C 风格字符串使用
注意:二进制读取结构体、int 数组绝对不要写 -1!
二进制数据不需要\0,-1 会少读一个字节,数据直接损坏。
所以一般二进制读写字符串都用sizeof就行了,这样也不需要在读的时候.read (buf, sizeof (buf)-1)
错误示范(千万不要写)
char buf[100]; ifstream in("test.txt"); // 严重错误!read执行前buf是乱码,strlen直接越界 in.read(buf, strlen(buf));示例 3:工程常用方案(字符串先存长度,完美解决文本 / 二进制读取难题)
规避 strlen /sizeof 各种坑
#include <iostream> #include <fstream> #include <cstring> using namespace std; int main() { ofstream out("str.bin", ios::binary); char str[] = "good morning"; int len = strlen(str); out.write((char*)&len, sizeof(len)); // 先写入字符串长度 out.write(str, len); out.close(); // 读取 ifstream in("str.bin", ios::binary); int read_len; in.read((char*)&read_len, sizeof(read_len)); // 先读出长度 char buf[100] = {0}; in.read(buf, read_len); // 使用读到的长度作为参数 buf[read_len] = '\0'; cout << buf << endl; return 0; }最后总结
1、为什么二进制存储场景写的时候优先 sizeof?
二进制读写目标:把内存里的数据原样存入文件,之后读取能完整复原内存数据
sizeof 获取变量 / 数组在内存占用全部字节。 如果是字符串数组,会连同内存末尾的\0一并写入文件。后续读取拿到全部字节,内存结构和写入前完全一致,可以直接当成字符串正常使用。
strlen 只统计可见字符,丢弃\0。 一旦不保存\0,文件内没有字符串边界标记;等你读取到内存后,程序不知道字符串在哪里终止,访问字符串会发生内存越界。所以适用于读写int,结构体类型的文件
所以二进制持久化,统一使用 sizeof,保证内存镜像完整保存。
为什么 read 永远不能拿 strlen 当作第二个参数?
先记住底层事实:
strlen 是操作【内存】的函数;它扫描一块内存,遇到内存中的\0才停止计数。
read 是读取【硬盘文件】的函数;调用 read 之前,数据还不在内存缓冲区。
逻辑链条:
调用 read(buf, strlen(buf)) 时,buf 缓冲区此时还没有从文件读到任何数据,内存里是随机垃圾值;
strlen 直接扫描充满乱码的空缓冲区,大概率找不到\0,疯狂越界访问内存,直接触发未定义行为(崩溃);
文件本身只是一堆连续字节。就算文件里原本是文字,硬盘文件中不存在自动的\0标记。strlen 根本没有办法提前预知文件里有多少字符。
简单一句话:
你还没把数据读进内存,strlen 没有数据可以统计长度,它根本无法预判文件内容。
最容易踩的坑就是,不要在文本文件写入字符串的时候参数使用sizeof
弊端①:文本编辑器打开会出现乱码
\0是 ASCII 空字符。
记事本、VS Code 打开 txt,遇到0x00字节,会显示方框 / 异常乱码。
文本文件设计初衷就是给人阅读,乱码等于破坏用途。
弊端②:文本模式下换行转换的隐藏风险(重点!)
如果你不加 ios::binary,以默认文本模式打开文件:
Windows 系统会自动转换换行 \n <=> \r\n。
一旦发生字节增减,你写入的总字节数 和 读出的总字节数不再相等!
举个最坏情况:
你写入 sizeof 一共 6 字节(hello\0);
中途换行被系统多插入一个\r;
最终读取 6 字节时,最后读到的不再是\0,字符串直接报废。
只有带上ios::binary,才不会篡改字节;
但开了ios::binary,本质已经脱离标准「文本文件」的规范。
清晰划分两套规范写法(彻底分清,避免混淆)
方案 A:标准文本文件(目标:人可以用记事本正常打开阅读)
写入:write(str, strlen(str)) 【不存\0】
读取:read(buf, sizeof(buf)-1),读完手动添加\0
方案 B:二进制数据存档 (目标:只给程序读写,人不用编辑器打开)
打开文件带上 ios::binary
写入:write(str, sizeof(str)) 【存入\0】
读取:read(buf, sizeof(str)),无需-1
有\0都是对于字符串而言,而int类型不推荐使用
对于int或者其他类型(double)
文本文件写读直接用 流插入运算符(写)<< 流提取运算符(读)>>
而对于结构体只能写入二进制文件中就是能是结构体只能配合 read /write 做二进制存储,不存在文本存储方案! 想要记事本看懂结构体数据,不能直接 write,必须手动把每个成员转成文本(用 << 逐个输出成员)。
(这篇文章整体看下来其实是写的非常的乱想,不利于理解,只能作为各位读者在方便的时候看一看记忆的,详细的解释大家还是要具体自己去理解,能力有限非常抱歉)
