C++实现strcmp字符串比较的深入探讨

发布时间:2026/11/2 6:41:42
C++实现strcmp字符串比较的深入探讨

前言


strcmp声明在<cstring>里(C 头文件<string.h>),签名是int strcmp(const char* lhs, const char* rhs)。它按字典序比较两个以空字符结尾的字节串,返回负值、零、正值分别表示lhs小于、等于、大于rhs。


关于它最常见的误解是「返回值一定是 -1、0、1」。标准只规定返回值的符号,不规定具体数值,也不要求返回值等于两个字符之差。很多实现返回的就是字节差,但也有实现返回归一化后的 -1/1,甚至返回其它负数。把strcmp(a, b) == -1当作判断「a 小于 b」的写法不可移植,正确写法是strcmp(a, b) < 0。


第二个误解是「strcmp和memcmp差不多」。两者都在比较字节,但strcmp遇到空字符就停止,memcmp按给定长度比较、不关心内容;用memcmp比较字符串时如果长度取错,会把结尾之后的字节也纳入比较。


本文从比较语义、手写实现、与相关函数的关系、以及在 C++ 中该不该用四个角度展开。代码按 C++17 编写,未在本机编译执行。


一、比较语义:按unsigned char逐字节


标准规定strcmp比较的是两个字符串的对应字符,字符按unsigned char解释。这一点非常关键:如果平台上的char是有符号的(这是实现定义行为,Windows 与多数 x86 平台上有符号,部分 ARM 平台默认无符号),直接比较char会把 0x80 以上的字节当成负数,排序结果与按字节值排序相反。


// 适用于 C++17
int my_strcmp(const char* lhs, const char* rhs) {
const unsigned char* a = reinterpret_cast<const unsigned char*>(lhs);
const unsigned char* b = reinterpret_cast<const unsigned char*>(rhs);
while (*a != '\0' && *a == *b) {
++a;
++b;
}
return static_cast<int>(*a) - static_cast<int>(*b);
}

几点说明:



  • 循环条件是「当前字符非空且两字符相等」,退出时要么遇到空字符,要么遇到不同的字符。

  • 转成unsigned char再比较,保证 0x80~0xFF 按 128~255 排序,与标准语义一致。直接把char转int在char有符号时会得到负值,顺序就反了。

  • 返回*a - *b是常见做法,但不要依赖调用方拿它和具体数值比较;标准只保证符号。

  • 两个指针都不能为空,传空指针是未定义行为(undefined behavior, UB),不是「返回 0」或抛异常。


strcmp在第一个不同字节处就能确定结果,不需要比较到末尾;这也是它比「先算长度再比较」更高效的原因——后者要先遍历两遍求长度。


二、与strncmp、memcmp、std::string::compare的区别




函数终止条件长度参数适用场景



strcmp遇到空字符或不同字节无常规 C 字符串比较

strncmp最多 n 字节,或空字符/不同字节有只比较前缀,防止读越界

memcmp恰好 n 字节有二进制数据、定长字段

std::string::compare字符串长度无(有重载带区间)C++ 字符串,不依赖空终止

std::string_view::compare视图长度无不拷贝的字符串视图



strncmp(a, b, n)在「前 n 个字节都相同」时返回 0,即使后面还有差异;它适合比较协议头、固定前缀。注意strncmp仍然会在遇到空字符时提前停止,所以它并不是「安全的memcmp」——如果数据中间可能含 0,应该用memcmp。


memcmp不认空字符,按字节比较,返回值的符号同样只表示大小关系。用memcmp比较两个 C 字符串时,长度至少要取max(strlen(a), strlen(b)) + 1,否则可能漏掉结尾差异;但这样又要先求长度,通常不如直接用strcmp。


std::string::compare的返回值也是「负/零/正」,参数可以是另一个std::string、const char*、string_view,还可以带位置与长度。它不需要空终止,也不会读越界,是 C++ 代码里的首选。


三、C++ 中该不该继续用strcmp


绝大多数情况下不该。std::string的operator==、operator<直接表达意图,长度已知,不会因为缺少空终止而读越界:


// 适用于 C++17
#include <string>
#include <string_view>
#include <iostream>

bool same_name(const std::string& a, const std::string& b) {
return a == b; // 明确、无越界风险
}

bool has_prefix(std::string_view s, std::string_view prefix) {
return s.size() >= prefix.size() && s.compare(0, prefix.size(), prefix) == 0;
}

int main() {
std::string a = "apple";
std::string b = "apply";
std::cout << (a < b) << '\n'; // 1,按字典序
std::cout << has_prefix(a, "app") << '\n'; // 1
}

std::string_view需要 C++17。它不拥有字符,只持有指针与长度,因此不能从临时std::string构造后长期保存——临时对象销毁后视图悬垂。has_prefix里先比较长度再调用compare,是因为compare(pos, count, sv)在pos > size()时会抛std::out_of_range。


必须用strcmp的场合主要是与 C 接口交互:第三方库返回const char*、系统 API 要求 C 字符串、解析命令行参数。这时也建议尽快转成std::string或string_view,把 C 字符串的生命周期控制在最小范围。


四、排序与大小写:strcmp不处理语言规则


strcmp是字节比较,不是「人类语言排序」。它区分大小写,所有大写字母排在小写字母之前(因为 ASCII 中'A'是 65,'a'是 97);它也不理解重音符号、连字、不同语言的字母顺序。


需要忽略大小写时,不要用strcasecmp(POSIX,非标准)作为跨平台方案,可以自己写比较函数或先转小写再比:


// 适用于 C++17
#include <cctype>
#include <string>
#include <algorithm>

bool less_ignore_case(const std::string& a, const std::string& b) {
return std::lexicographical_compare(
a.begin(), a.end(), b.begin(), b.end(),
[](unsigned char x, unsigned char y) {
return std::tolower(x) < std::tolower(y);
});
}

std::tolower的参数必须是unsigned char可表示的值或EOF;传负的char是 UB,所以 lambda 参数写成unsigned char。另外,tolower的行为依赖当前的 C 区域设置(locale),对非 ASCII 字节不要期待「正确的大小写折叠」。


如果要做符合语言习惯的排序(例如中文按拼音、德文把ß当ss),应使用std::collate或平台提供的国际化库,而不是strcmp。


常见坑点



  • ❌if (strcmp(a, b) == -1)判断小于。


✅ 用strcmp(a, b) < 0,标准只保证符号不保证数值。



  • ❌ 手写比较时直接return *a - *b;,char有符号时高位字节顺序颠倒。


✅ 转成unsigned char再比较。



  • ❌ 对可能为空的指针调用strcmp。


✅ 调用前判空,或改用std::string/string_view接口。



  • ❌ 用strcmp比较未以空字符结尾的缓冲区。


✅ 这类数据用memcmp或std::string_view(带长度)比较。



  • ❌ 用memcmp(a, b, sizeof(a))比较字符串,把数组尾部未初始化字节也比了进去。


✅ 用strcmp,或memcmp配合准确的长度(含或不含结尾按语义决定)。



  • ❌ 把std::string直接传给strcmp。


✅ 用s.c_str()(注意指针在原字符串被修改后失效),或直接用std::string的比较运算符。



  • ❌ 认为strcmp会按 locale 排序。


✅ 它是字节比较;语言相关排序用std::collate或专门的库。



  • ❌ 在循环里反复对同一对字符串调用strcmp做排序比较,每次都从头扫描。


✅ 排序时用std::string的operator<,或先转成可比较的键;比较次数由排序算法决定。


总结




问题答案



返回值含义负/零/正,仅符号有保证

比较单位unsigned char,逐字节

终止条件遇到空字符或不同字节

空指针未定义行为

与memcmp区别是否识别空字符、是否需要长度

C++ 首选std::string/std::string_view的比较

大小写与 localestrcmp都不处理



strcmp是一个语义精确、边界严格的 C 函数:它要求空终止、要求非空指针、只承诺返回值的符号。理解这三点,就不会写出「等于 -1 才算小」「memcmp也能比字符串」这类错误。在 C++ 里,能用std::string就用std::string,把strcmp留给真正需要与 C 接口打交道的边界处。