ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

C++单引号与双引号的区别:字符字面量vs字符串字面量底层原理与避坑指南

C++单引号与双引号的区别:字符字面量vs字符串字面量底层原理与避坑指南 写这篇东西的起因是我在带几个刚入门C的同事时发现一个特别有意思的现象很多人会把单引号和双引号搞混要么编译报错半天找不到原因要么代码能跑但运行结果完全不对。哪怕是一些写了几年代码的人被问到“单引号和双引号到底有什么区别”时也常常只答得出一句“一个是字符一个是字符串”再往深处问就卡壳了。这其实是个特别基础的C问题但基础不牢后面真的会连环踩坑。今天我就把这个“老生常谈”彻底掰开揉碎讲一遍从类型、内存、编译规则到实际工程里的各种坑一次性说透。先说结论在C里单引号包住的是字符字面量character literal类型是char本质是一个整数占1字节双引号包住的是字符串字面量string literal类型是const char[N]本质是一个以\0结尾的只读字符数组占N字节N等于字符个数加1。这个区别看起来简单但牵扯出来的编译规则、内存布局、函数重载、String初始化、跨语言调用等问题每一环都能让人栽跟头。这篇文章适合所有正在学C或者写C的人尤其是刚从Python、Java转过来的朋友——因为Python里单双引号几乎等价这个“惯性思维”到了C里就是灾难。1. 单引号与双引号的核心差异类型与内存布局1.1 类型系统里的“天壤之别”在C的类型系统里a和a完全是两种东西。你可以把a理解为“一个值为97的整数常量”只不过这个整数被特化成了char类型。它在内存里就是1个字节存的二进制就是01100001ASCII码97。而a是一个由2个char组成的数组第一个元素是a第二个元素是字符串结束符\0。整个数组在内存里占2个字节而且它存放在程序的只读数据段.rodata / .rdata不是普通的栈上变量。这个区别直接决定了你能拿它们干什么。比如char c1 a; // 正确1字节整数拷贝 char c2 a; // 编译错误不能用 const char[2] 初始化 char第二行报错的原因很直观你试图把一个“包含2个字符的数组”塞进一个“只能装1个字符的变量”。编译器会提示invalid conversion from const char* to char——注意它说的是const char*因为数组名在表达式里会退化成指针。这是理解这一整套问题的钥匙双引号产生的字符串绝大多数场景下会被当作const char*指针来用。再看这个经典对比char arr1[] abc; // arr1 是 char[4]内容a,b,c,\0 char arr2[] {a,b,c}; // arr2 是 char[3]内容a,b,c没有\0很多人第一次看到arr2没有\0时都惊了。没错花括号初始化字符数组不会自动补结束符只有双引号字符串字面量才会在末尾自动加上\0。这带来的后果是std::cout arr2在输出时会因为找不到\0而一路读取到内存里相邻的字节直到偶然碰到一个0为止。这就是典型的“缓冲区越界读取”可能输出乱码严重时还会引发内存访问违规。我见过不止一次有人用这种arr2去调用strlen得到的结果是随机的——因为strlen也会一直数到\0才停下。所以你看单双引号不只是“多一个括弧”的问题它们生成的类型、存储方式、是否带结束符全都不同。这些差异会在后续的运算、传参、初始化中一层层放大。1.2 char 是“整数”而非“字符”另一个容易忽略的点是C里的char本质上是一种整数类型它是C整型家族integral types的一员只是取值范围小。你可以对它做加减乘除、比较大小、位运算这些操作在char上是合法的。char ch A; ch ch 1; // 结果是 BASCII码从65变成66 std::cout ch; // 输出 BA这个字面量在编译期就被转换成了整数65所以你写A 1实际上就是65 1。这个特性在处理大小写转换、凯撒密码、字符计数等问题时非常有用。但同时也意味着你写std::cout a输出的是字符a而你写std::cout (int)a输出的是97——同一个字面量因为语境不同表现完全不同。理解了“char是整数”这个本质你就能明白为什么单引号里只能放一个字符因为char类型的容量只有1字节放不下两个字符的信息。如果你非要写ab编译器在标准模式下会直接报错因为这是一个“多字符字面量”multi-character literal属于非标准扩展不同编译器处理方式完全不同。GCC可能只警告然后取最后一个字符或某种拼接结果MSVC则可能直接错误。这种歧义是工程上的定时炸弹千万别碰。同样的道理中文这种编码后超过1字节的字符自然也不能直接放单引号里操作后面我会单独讲。2. 混淆使用会引发的编译错误与运行期问题2.1 常见的编译报错场景先列几个我实际工作中高频出现的编译错误全是因为单双引号用错地方// 场景1双引号初始化 char char c x; // error: invalid conversion from const char* to char // 场景2单引号初始化 const char* const char* p x; // error: invalid conversion from char to const char* // 场景3比较 char 和字符串 char c A; if (c A) { } // error: comparison between pointer and integer // 场景4单引号里放多个字符 int x ab; // 警告或错误取决于编译器和标准场景3特别值得展开讲。c是char类型层面就是一个整数A是const char[2]在表达式里退化成const char*指针。你拿一个整数和一个指针做比较编译器直接拒绝。但如果是if (c A)两边都是char/int比较的就是数值97和65完全合理。这个对比清晰地展示了单引号参与的是“值语义”比较内容本身双引号参与的是“指针语义”比较的是内存地址很多人不理解为什么不能c A其实就是没分清值语义和指针语义。想比较字符串内容应该用strcmp(c, A)或者C风格的std::string。还有一种常见错误是把双引号字符串直接赋给char数组的每个元素char str[10]; str[0] h; // error: invalid conversion from const char* to char str[1] i; // 正确有时候你会觉得编译器提示“不近人情”但本质上这都是在阻止你做“把8字节指针塞进1字节变量”这种荒唐事。2.2 能编译但运行结果诡异的典型case编译错误还算“死得明白”更头疼的是那些编译通过了、但运行结果莫名其妙的情况。一个非常经典的case是把std::string和字符字面量搞混std::string s1 a; // 这行居然能编译过你没看错这行代码在某些C标准下是能编译的。原因很绕std::string有一个构造函数basic_string(size_t count, char ch)而a作为char会被隐式转换成size_t也就是97于是这行代码变成了“创建一个包含97个字符 a 的字符串”。运行起来就是97个a完全不是你想要的。这种坑最阴险因为它不报错只在运行期给你一个离谱的结果。另一个经典场景是std::cout输出指针和字符的区别const char* str hello; std::cout str; // 输出 hello因为 const char* 被特化为输出以\0结尾的字符串 std::cout x; // 输出 x因为 char 被当作字符输出 std::cout (void*)str; // 输出地址但如果你写std::cout (int)x输出的是120。这是因为流运算符对const char*有特殊重载它认为你这个指针指向的是一个C风格字符串会一直读到\0为止。反过来如果你把char*指针指向一个没有正确以\0结尾的字符数组流会一直读下去直到崩溃或遇到随机字节。这也是为什么我前面强调用花括号初始化字符数组时一定要记得手动加\0。再分享一个更隐蔽的\0和0的区别。\0是ASCII码为0的空字符作为字符串的结束标记0则是包含字符0ASCII码48和\0两个元素的字符串。在写文件、拼协议、处理二进制数据时把这两个搞混会导致数据错位。char buf[64]; strcpy(buf, hello); // 正确的追加方式直接赋值 \0 buf[5] \0; // 错误的方式 buf[5] 0; // 编译错误 buf[5] 48; // 这会让字符串变成 hello0结尾\0在哪取决于原有数据这些“能编译但结果诡异”的case比编译错误危害更大因为它们会潜伏在你的代码里直到某天生产环境出问题才暴露。排查这类问题第一反应永远应该是检查每个字符、字符串字面量的类型。3. 实际工程项目里的高频应用场景3.1 字符串数组初始化一字之差结局不同看两个非常接近的代码// 写法A数组大小由双引号决定自动带 \0 char msg1[] OK; // 写法B数组大小由花括号决定没有自动 \0 char msg2[] {O, K};msg1占据3字节内容为O,K,\0msg2占据2字节内容为O,K。如果你用strlen(msg2)结果是未定义的它可能返回2也可能返回5、10取决于栈上相邻内存恰好什么时候出现0。用std::cout msg2同理输出可能是OK后面跟一串垃圾字符。这在做嵌入式开发、网络协议封包时尤其致命。我做过一段时间的网络通信模块每次构造协议缓冲区都会确认凡是需要以字符串形式传给strlen/strcpy/sprintf的必须保证以\0结尾。而很多时候协议帧是二进制结构里面天然包含0x00字节这时候你根本不能用C字符串函数去处理得用memcpy 长度。这就引出另一个关键认知C风格字符串不是二进制安全的数据结构因为它用\0作为终止标记。正确初始化字符串数组的几种姿势char s1[] hello; // 最推荐让编译器帮你算好大小并加\0 char s2[] {h,e,l,l,o,\0}; // 手动写\0容易漏 char s3[6] hello; // 显式指定大小注意要留1字节给\0 char s4[6] {h,e,l,l,o,\0};如果你用char s3[5] hello编译器会报错因为hello需要6字节含\0放不进5字节的数组。这个报错其实是保护你避免缓冲区溢出。但如果你用char s3[5] {h,e,l,l,o}编译器不报错因为5个元素都能放进去——只是没有\0。所以记住花括号初始化不会默认加\0这是程序员自己的责任。3.2 std::string 的初始化规则现代C工程里字符串操作基本都交给std::string但单双引号的坑依然存在而且表现更隐蔽。std::string s1 abc; // 正确调用 string(const char*) std::string s2 a; // 危险调用 string(size_t, char) 构造97个a std::string s3 ; // 正确空字符串等价于 std::string() std::string s4 \0; // 危险转换后构造 size_t0 个字符结果是空串还不一定std::string s2 a这段代码在一些老标准下能编译新标准可能报错这就是“未定义行为”的温床。更推荐的做法是std::string s5(1, a); // 明确构造1个字符a结果就是 a std::string s6{a}; // C11起用花括号初始化列表明确放入1个字符 std::string s7 a; // 正常也不会有问题核心原则是你要明确告诉编译器你的意图别让它“聪明”地做隐式转换。另外还有一个很常见的面试题变种std::string str hello; char c str[0]; // 正确c h const char* p str.c_str(); // 正确拿到的是以\0结尾的只读C字符串 str[0] H; // 正确修改字符串第一个字符 // str[0] H; // 错误不能用 const char* 给 char 赋值从std::string取字符返回的是char只能接受char类型的赋值。你要是用双引号编译器会直接崩溃给你看。这类问题在代码评审里非常常见尤其是刚从其他语言转过来的人写str[i] x几乎是本能反应。3.3 C风格函数调用字符与指针的博弈C工程里总会遇到C风格库比如strchr、strstr、atoi、sprintf这些函数。它们对参数类型极其敏感const char* str hello world; // 在字符串中查找字符 o char* pos strchr(str, o); // 正确第二个参数是 intchar提升为int // 常见错误strchr(str, o) // 编译错误期望int拿到const char* char target w; // 判断 target 是否为空格 bool isSpace (target ); // 正确 // bool isSpace (target ); // 错误再比如sprintf系列char buf[64]; int x 42; // 正确将格式化后的字符串写入buf sprintf(buf, value: %d, x); // 注意%s 需要 const char*%c 需要 int/char sprintf(buf, %s, hello); // 正确 sprintf(buf, %c, h); // 正确 sprintf(buf, %s, h); // 未定义行为h被当作指针使用大概率崩溃第三行sprintf(buf, %s, h)是很多人会犯的低级错误%s告诉函数“我有一个const char*请把它当作字符串输出”但实际传进去的是一个整数104。函数会把这个整数当作一个内存地址来访问几乎必然触发段错误。这种错误在运行时才暴露而且极其难排查——你看到的只是一个神秘的崩溃地址。3.4 结合热词的实战场景VSCode调试与跨语言调用看过我VSCode配置C/C环境那篇文章的朋友应该记得调试器里观察变量时单双引号的类型差异会直接影响你看到的内容。char变量显示为单个字符加上ASCII码而const char*显示为字符串内容。如果你在监视窗口里把一个char变量当成字符串看调试器会把它解释成一个地址——因为char提升为int后调试器尝试把它当作指针来读内存结果就是“无法读取内存”之类的报错。再说一个热词相关的场景C#调用C时出现 access violation (c0000005)。这个崩溃码经常出现在P/Invoke封送字符串参数的时候。当你把C函数声明为接收const char*参数但C#侧传入了一个char或者错误编码的字符串运行时就会尝试把字符值当作地址去访问直接触发目的地的内存访问违规。前阵子还有个朋友问过我一个类似的案例他C的函数签名是void PrintMessage(const char* msg);C#侧写成[DllImport(cpp.dll)] public static extern void PrintMessage(char msg); PrintMessage(A); // 触发 access violation这里char在C#里是2字节Unicode字符在C里是1字节整数封送完全错位。即使传入的是合法的字符值C侧也会把它当成指针解引用。解法是C#侧使用string并加上[MarshalAs(UnmanagedType.LPStr)]或使用byte[]。这类跨语言崩溃根因往往就是对“字符字面量是值、字符串字面量是地址”这一点的理解不够深。还有一个热词“c字符串转数组”其实就来源于字符串字面量的数组本质。hello本身就是一个只读数组想转成可修改的数组只需要char arr[] hello; // 拷贝到栈上可修改 // 或 std::vectorchar vec(hello, hello 6); // 包含结束符一共6个元素这里的hello 6不是数学加法而是指针算术指向数组最后一个元素之后的位置。你把字符串字面量当作数组名/指针来用它就真的表现得像一个数组。这个视角对理解C字符串处理极有帮助。4. 常见问题与排查技巧实录4.1 一张速查表应对90%的单双引号困惑我在团队内部给新人的培训文档里整理过一张对照表这里分享出来对比维度单引号a双引号a官方名称字符字面量字符串字面量类型charconst char[2]N1个元素内存大小1字节N1字节N是字符个数是否含\0否是默认存储位置可能直接是立即数只读数据段.rodata表达式中的行为值整数指针指向数组首元素的地址能否赋给char能不能能否赋给const char*不能能退化后能否直接比较c a比较数值p a比较地址C风格函数适用%c、strchr等字符参数%s、strcpy等字符串参数这张表基本覆盖了所有日常场景。遇到编译报错或运行期诡异问题先对照这张表确认你的字面量类型用对了没有能省下大量排查时间。4.2 排查实录一个真实的“字符串乱码”案例分享一下我之前排查过的一个真实问题。有个模块在日志里输出了一段16字节的十六进制数据但客户反馈说数据里混入了乱码。我拉下来一看十六进制数据序列是0x48 0x65 0x6C 0x6C 0x6F 0x00 0x78 0xFF ...前5个字节是“Hello”的ASCII码第6个字节是0x00这没错。但再往后原本应该是协议数据的字节却出现了0xFF这种“脏数据”。我定位到代码发现问题出在构造缓冲区的部分char deviceId[16]; for (int i 0; i 5; i) { deviceId[i] source[i]; // 前5个字节拷贝正常 } deviceId[5] \0; // 这里用双引号写\0deviceId[5] \0这一行的表面意图是“把第6个字节设置为字符串结束符”但\0的类型是const char[2]一个\0字符加一个\0结束符赋值给char直接编译报错。这个同事当时为了“快速修复”改成了deviceId[5] (char)0; // 能跑但某个编译器把0转成了 NULL 宏其实这还是绕了弯路。正确写法应该是deviceId[5] \0;单引号、一个字符、ASCII 0非常明确。这个案例最有价值的教训是当你发现自己要“强转”才能让代码编译通过时大概率是字面量类型用错了而不是编译器太严格。这时候停下来反问一句“我到底想要单引号还是双引号”往往几秒钟就能解决。4.3 我踩过的坑多字符字面量与中文处理最后聊两个进阶坑都是我亲身踩过的。多字符字面量AB在GCC下AB会被当作一个int类型的扩展具体数值由实现定义一般是(int)A 8 | B之类的拼接。这意味着AB会被编译成一个“超大整数”你拿去和char比较时行为完全取决于编译器实现。跨平台代码里出现这种东西轻则警告重则不同平台行为不一致。我记得有一次在Linux和Windows上跑同一份代码结果一个平台进了if (AB something)分支另一个平台没进查了半天才发现是多字符字面量在“捣乱”。从那以后我的代码规范里明确禁止写超过1个字符的单引号字面量。中文及其他非ASCII字符C标准规定char是1字节中文UTF-8编码通常占3字节UTF-16/32占更多。直接写中在大多数编译器里会报“character literal too long”之类的错误。处理中文字符串正确姿势是const char* chinese 中文; // OK这是一个UTF-8字符串占7字节含\0 wchar_t wide L中; // 宽字符通常占4字节Linux或2字节Windows const wchar_t* wstr L中文; char8_t c8 u8中; // C20保证UTF-8编码的单个码元但注意中是3个码元这里会报错真正需要处理单个中文字符时应该用u8中结合字符串处理或者用宽字符类型。比如std::wstring ws L中文; wchar_t firstChar ws[0]; // 如果是UTF-16中可能占一个wchar_t中文基本在BMP内这里的关键是在源码文件里写中文字面量必须保证编译器按你预期的编码解析源文件。MSVC默认可能是本地代码页GBKGCC/Clang默认UTF-8这就可能导致同一份代码在不同工具链下的行为不同。处理这类问题我建议在项目里统一约定源码文件一律UTF-8 without BOM并且C20后尽量使用u8前缀或std::u8string来明确编码。说到编码这正好能引出另一个热词“vscode配置c/c环境”的关联点在VSCode里任务tasks.json和调试配置launch.json中经常要用到路径字符串。JSON格式本身要求字符串用双引号有些人在VSCode任务里写路径时习惯性地用了单引号结果整个任务无法解析。这也是单双引号在不同语境下的又一种“分家”——JSON、Python、Shell里单双引号的语义各不相同而C只是其中一种规则。搞混这些跨语言/跨配置文件的引号规则会在工程配置上浪费大量时间。5. 从字符字面量延伸出去的C冷知识5.1 前缀修饰符L、u8、u、U 到底怎么影响单双引号C里字符串和字符字面量的前缀修饰符是个我见过很多人搞混的知识点。其实规则很整齐字符字面量a、La、u8a、ua、Ua 字符串字面量a、La、u8a、ua、Ua其中L前缀对应宽字符类型wchar_tu8对应char8_tC20起或charC17及之前按UTF-8编码处理u对应char16_tU对应char32_t。加了前缀之后单双引号的基本语义不变单引号依然是单个字符双引号依然是数组。但类型变了内存大小和编码方式也随之改变。举个例子char c a; // 1字节ASCII wchar_t wc L中; // Windows下2字节UTF-16Linux下4字节UTF-32 char16_t c16 u中; // 2字节UTF-16 char32_t c32 U中; // 4字节UTF-32这里特别容易踩坑的是u8前缀。u8a是单个UTF-8码元一个字节没问题但u8中在C20之前是合法的代表一个UTF-8编码的字符对象——可一个中文字符需要3个UTF-8码元放不进单引号里。C20把u8字符字面量类型改成了char8_t并且要求只能包含一个基本字符或一个编码为单个码元的额外字符所以u8中直接变成编译错误。正确做法是u8中它是UTF-8字符串。我遇到过有人用u8A去初始化std::string结果得到的是一个包含一个码元的字符串。这也是一字之差结果大不同。掌握前缀规则后建议把所有字符/字符串字面量都加上明确的类型前缀尤其是在做跨平台、跨语言交互时这能避免大量编码相关bug。5.2 原始字符串字面量 R(...) 双引号的“逃生通道”另一个与双引号强相关的话题是C11引入的原始字符串字面量raw string literal。普通字符串字面量里遇到双引号和反斜杠需要转义写Windows文件路径、正则表达式时特别难受。原始字符串的语法是const char* path R(C:\Program Files\MyApp); const char* regex R(\d\.\d);这段代码里反斜杠和双引号都“字面化”了不再需要转义。注意两点第一R(...)里的定界符本身也是括号加双引号第二如果你想在字符串里包含)这个子串可以用扩展定界符Rdelimiter(...)delimiter。举个小栗子const char* str Rfoo(内容里可以有) 双引号)foo;这样)就不会提前结束字符串因为编译器识别的是)foo才算结束。原始字符串处理正则表达式和HTML片段非常香我写代码时凡是遇到多级转义都会本能地改成R(...)。这个特性的存在某种程度上也是C对“双引号怎么用”这个问题给出的另一个回答当引号本身成为问题时C给了你绕开的工具。5.3 函数重载与引号选择不一样的“签名陷阱”C函数重载靠参数类型区分而单双引号直接决定了调用哪个重载。看一个例子void f(char c) { /* 处理单个字符 */ } void f(const char* s) { /* 处理字符串 */ } f(a); // 调用 f(char) f(a); // 调用 f(const char*)这个例子简单但把函数放在泛型代码或模板里就容易出麻烦template typename T void process(T t) { // 如果T是chart参与算术运算如果T是const char*t参与指针运算 } process(a); // 模板实例化为 processchar process(a); // 模板实例化为 processconst char*一旦你写process(a)但本意是想处理一个字符串模板推导出的类型就会不同可能会调用到完全不同的函数重载或特化版本。这在你用STL容器、算法、格式化库时尤为明显。有个很经典的例子是std::map或std::unordered_map的查找std::mapstd::string, int scores; scores[alice] 90; auto it1 scores.find(alice); // 正确接受 const char*隐式构造 std::string auto it2 scores.find(a); // 编译错误find 需要 const std::string 或 std::string第二个find(a)会尝试构造std::string{a}是多字符构造还是隐式转换多数情况下会直接编译失败或行为诡异。这些都是“引号影响类型类型影响重载”的连锁反应。我建议在写这种代码时养成“字面量类型先行”的习惯先想清楚这个位置需要什么类型的参数再决定用单引号还是双引号。反过来如果评估代码时看到某个函数传参用的是a而函数签名其实是const char*那一定要停下来确认意图。6. 工程规范与习惯养成如何彻底避免引号踩坑聊了这么多技术细节最后想分享一些关于“日常习惯”的建议。因为这类基础问题光靠“知道”不够还得靠“习惯”防御。我在团队里定过几条规则实践下来效果很好1. 用类型别名明确字符/字符串意图。如果在函数签名里看到char c和const char* str语义已经很清楚。但如果变量名模糊比如auto x a;后续就很容易混乱。我给新代码的建议是需要字符语义就明写类型char需要字符串语义就写std::string或const char*尽量不要用auto去偷懒接收字面量类型。2. 对编译警告零容忍。很多编译器对多字符字面量、隐式转换、指针整数比较都会给警告比如GCC的-Wmultichar、-Wpointer-to-int-castMSVC的 C4305/C4311 等。把警告级别开到/W4MSVC或-Wall -Wextra -WpedanticGCC/Clang能让大部分引号问题在编译期就暴露出来。我个人的习惯是把警告当作错误对待-Werror这样能在CI阶段就拦截问题而不是等到代码合入后某天线上崩溃。3. 写测试覆盖字符与字符串边界。我之前在一个解析器模块里写了一个测试用例专门验证单个字符解析输入x应该得到char单个字符构成的字符串输入x应该得到长度为1的std::string空字符串应该得到空串而\0是单个空字符这些边界在逻辑上很容易认为是“一样的”但实际上完全不同。有了测试保护后续哪怕有人不小心改错了引号测试也会立即报警。4. 写代码时自言自语这个字的类型是什么这听起来很傻但真的能救你。每当你写下一个字面量花0.5秒想一下“我想要的是单个值还是序列”就能避免大部分问题。如果你想要单个值字符、整数、布尔用单引号如果你想要文本字符串、字节序列用双引号。这个思维习惯养成了比记住任何表格都管用。还有一个很多人忽视的细节在文件路径、配置串、日志消息里即使内容只有一个字符只要它有“文本”语义就应该用双引号而不是单引号。例如// 错误倾向用单引号构造一个看起来像字符串的东西 const char* newline \n; // 编译错误 // 正确\n 是字符... 只是不需要但如果你要的是换行文本 const char* nlText \n;这里的边界在于从数据存储角度看\n是一个字节0x0A从文本角度看\n是两个字节0x0A和0x00。它们在二进制协议、文本解析、控制台处理里语义完全不同。你要时刻根据自己的使用场景来选择而不是“图省事”混着写。我个人在实际操作中的体会是C里单引号和双引号的区别本质上不是“语法细节”而是“值 vs 序列”、“单个 vs 多个”、“类型安全 vs 指针退化”这三种对立关系的缩影。把这个问题彻底搞懂了你就能顺带理解char为什么是整数、数组为什么退化成指针、std::string为什么要包装C字符串、以及为什么跨语言调用时要格外注意字符串封送。很多C新手觉得基础问题“简单”其实基础问题才是最能拉开代码质量的杠杆。最后再分享一个小技巧如果你的编译错误信息里同时出现了char和const char*第一反应先把代码里的引号种类通读一遍大概率能找到根因。如果你在处理二进制协议需要往字节流里写入一个字符就写buf[i] \0;不要写buf[i] 或者buf[i] 0如果你需要构造一个显示用的文本就用std::string text 0;。简而言之想表达一个“值”用单引号想表达一段“文本”用双引号。这个习惯足够应对95%的日常开发剩下的5%回来翻这篇博文就够了。
返回列表