首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >字符串比较函数strcmp()和strncmp()详解

字符串比较函数strcmp()和strncmp()详解

作者头像
byte轻骑兵
发布2026-01-20 17:09:12
发布2026-01-20 17:09:12
930
举报

在 C 语言开发中,字符串比较是高频操作 —— 从用户名密码验证、配置项匹配,到数据排序与查找,都离不开字符串比较函数。C 标准库<string.h>提供的strcmp()与strncmp()是最常用的两个工具,但开发者常因混淆两者的适用场景、忽略边界条件导致 bug(如缓冲区越界、比较结果异常)。

一、函数简介

字符串比较的本质是按 ASCII 码值逐字符对比,而非比较字符串长度。C 语言中字符串以'\0'作为结束标志,这一特性直接决定了strcmp()与strncmp()的设计逻辑差异:

函数名

核心功能

关键特性

strcmp()

比较两个字符串完整内容,直到'\0'

依赖'\0'结束符,可能越界

strncmp()

比较两个字符串前 n 个字符,或到'\0'

不强制依赖'\0',可控制比较范围

简单来说:strcmp()是 “全自动” 比较(直到结束符),strncmp()是 “半自动” 比较(指定最大长度)。两者的返回值逻辑一致 —— 均通过字符 ASCII 差值判断大小,但适用场景和安全性差异显著。

二、函数原型与参数解析

要正确使用函数,首先需理解其原型定义(来自<string.h>头文件),尤其是参数的约束与含义。

2.1 strcmp () 原型

代码语言:javascript
复制
int strcmp(const char *str1, const char *str2);

参数说明

  • str1/str2:待比较的两个字符串指针(const修饰表示函数不会修改输入字符串,避免误操作);
  • 要求:两个字符串必须以'\0'结尾(否则会触发越界)。

返回值:int 类型,代表比较结果:

  • 返回0:str1与str2内容完全相同;
  • 返回正数:str1中第一个不同字符的 ASCII 值 > str2对应字符;
  • 返回负数:str1中第一个不同字符的 ASCII 值 < str2对应字符。

2.2 strncmp () 原型

代码语言:javascript
复制
int strncmp(const char *str1, const char *str2, size_t n);

参数说明

  • 前两个参数与strcmp()一致;
  • n:最大比较字符数(size_t是无符号整数类型,取值≥0);
  • 优势:无需强制要求字符串以'\0'结尾(若n小于字符串长度,会提前终止)。

返回值:逻辑与strcmp()完全一致,仅比较范围受n限制:

  • 若前n个字符完全相同,无论后续内容如何,均返回0;
  • 若未比较到n个字符时已出现不同,返回第一个不同字符的 ASCII 差值。

三、函数实现逻辑

理解函数的实现逻辑,能帮你规避 90% 的使用错误。以下伪代码基于 C 标准(ISO/IEC 9899)的规范,还原了两个函数的核心执行流程(非编译器真实实现,但逻辑一致)。

3.1 strcmp () 伪代码实现

代码语言:javascript
复制
// 功能:比较str1和str2,直到'\0'或不同字符
function strcmp(const char* str1, const char* str2) -> int:
    // 1. 校验指针合法性(真实库函数可能不校验,直接触发段错误)
    if str1 == NULL or str2 == NULL:
        触发未定义行为(如程序崩溃)
    
    // 2. 逐字符比较(直到'\0'或不同)
    while *str1 != '\0' and *str2 != '\0':
        if *str1 != *str2:
            // 返回当前字符ASCII差值(str1 - str2)
            return (unsigned char)*str1 - (unsigned char)*str2
        // 指针向后移动
        str1 = str1 + 1
        str2 = str2 + 1
    
    // 3. 处理一方已到'\0'的情况(如"abc" vs "abcd")
    return (unsigned char)*str1 - (unsigned char)*str2

  • 用unsigned char转换字符:避免 ASCII 码中负数(如扩展 ASCII 的 0x80-0xFF)导致的比较错误;
  • 终止条件:必须同时满足 “字符相同” 且 “未到 '\0'”,否则退出循环;
  • 最终差值:若一方先到 '\0'(如"a" vs "aa"),则*str1为 '\0'(ASCII 0),*str2为 'a'(ASCII 97),返回 - 97。

3.2 strncmp () 伪代码实现

代码语言:javascript
复制
// 功能:比较str1和str2的前n个字符,或到'\0'
function strncmp(const char* str1, const char* str2, size_t n) -> int:
    // 1. 校验指针合法性
    if str1 == NULL or str2 == NULL:
        触发未定义行为
    
    // 2. 逐字符比较(直到n=0、字符不同或'\0')
    while n > 0 and *str1 != '\0' and *str2 != '\0':
        if *str1 != *str2:
            return (unsigned char)*str1 - (unsigned char)*str2
        // 指针移动 + 计数递减
        str1 = str1 + 1
        str2 = str2 + 1
        n = n - 1
    
    // 3. 处理三种终止情况:n=0、str1到'\0'、str2到'\0'
    if n == 0:
        // 已比较完n个字符,视为相同
        return 0
    else:
        // 一方到'\0',返回差值(同strcmp)
        return (unsigned char)*str1 - (unsigned char)*str2

  • n的优先级最高:即使字符未到'\0',只要n减至 0,立即返回 0;
  • 无'\0'安全:若字符串无结束符(如数组存储的固定长度数据),只要n设置为数组长度,就不会越界。

四、典型使用场景:何时用 strcmp (),何时用 strncmp ()

选择函数的核心依据是是否需要完整比较是否确定字符串有 '\0' ,以下是两类函数的典型应用场景。

4.1 strcmp () 的适用场景

当满足 “字符串以'\0'结尾” 且 “需比较完整内容” 时,优先用strcmp(),代码更简洁。

场景 1:用户名 / 密码验证

用户输入的字符串(如密码)通常以'\0'结尾,需完整匹配才能通过验证:

代码语言:javascript
复制
#include <stdio.h>
#include <string.h>

#define CORRECT_USER "admin"
#define CORRECT_PWD "Secure@2025"

// 验证用户名密码
int auth_user(const char* input_user, const char* input_pwd) {
    // 用户名和密码必须都完整匹配
    if (strcmp(input_user, CORRECT_USER) == 0 && 
        strcmp(input_pwd, CORRECT_PWD) == 0) {
        return 1; // 验证通过
    }
    return 0; // 验证失败
}

int main() {
    char input_user[32], input_pwd[32];
    printf("请输入用户名:");
    scanf("%s", input_user);
    printf("请输入密码:");
    scanf("%s", input_pwd);

    if (auth_user(input_user, input_pwd)) {
        printf("登录成功!\n");
    } else {
        printf("用户名或密码错误!\n");
    }
    return 0;
}

运行结果

  • 输入admin和Secure@2025 → 登录成功;
  • 输入admin和Secure@2024 → 密码错误。

场景 2:判断文件后缀名

文件路径字符串以'\0'结尾,需完整匹配后缀名(如.txt):

代码语言:javascript
复制
// 判断文件是否为txt格式
int is_txt_file(const char* file_path) {
    // 找到最后一个'.'的位置
    const char* dot = strrchr(file_path, '.');
    if (dot == NULL) {
        return 0; // 无后缀名
    }
    // 完整比较后缀名是否为".txt"
    return strcmp(dot, ".txt") == 0;
}

4.2 strncmp () 的适用场景

当需要 “部分比较” 或 “字符串无'\0'” 时,必须用strncmp(),避免越界风险。

场景 1:判断字符串前缀

如判断 URL 是否为 HTTP/HTTPS 协议(只需比较前 7 个字符"http://"或前 8 个"https://"):

代码语言:javascript
复制
#include <stdio.h>
#include <string.h>

// 判断URL是否为HTTP/HTTPS协议
int is_http_proto(const char* url) {
    if (url == NULL) return 0;
    // 比较前7个字符(http://)或前8个(https://)
    return strncmp(url, "http://", 7) == 0 || 
           strncmp(url, "https://", 8) == 0;
}

int main() {
    char urls[][64] = {
        "http://blog.example.com",
        "https://github.com",
        "ftp://file.example.net",
        "https://127.0.0.1:8080"
    };

    for (int i = 0; i < 4; i++) {
        if (is_http_proto(urls[i])) {
            printf("[%s] 是HTTP/HTTPS协议\n", urls[i]);
        } else {
            printf("[%s] 非HTTP/HTTPS协议\n", urls[i]);
        }
    }
    return 0;
}

运行结果:

场景 2:固定长度字段比较

硬件通信、数据库存储中,常以固定长度数组存储字符串(无'\0'),需指定长度比较:

代码语言:javascript
复制
#include <stdio.h>
#include <string.h>

// 设备信息结构体(设备编号固定8字节,无'\0')
typedef struct {
    char dev_id[8]; // 如"DEV00001"(占7字节,剩余1字节补空格)
    int status;     // 设备状态:0-离线,1-在线
} Device;

// 查找指定设备(比较固定8字节的dev_id)
Device* find_device(Device dev_list[], int count, const char* target_id) {
    for (int i = 0; i < count; i++) {
        // 比较前8个字符(dev_id的固定长度)
        if (strncmp(dev_list[i].dev_id, target_id, 8) == 0) {
            return &dev_list[i];
        }
    }
    return NULL; // 未找到
}

int main() {
    Device dev_list[] = {
        {"DEV00001", 1},
        {"DEV00002", 0},
        {"DEV00003", 1}
    };
    char target_id[] = "DEV00002"; // 注意:数组长度需≥8(或手动指定n=8)
    
    Device* dev = find_device(dev_list, 3, target_id);
    if (dev != NULL) {
        printf("找到设备:%s,状态:%s\n", 
               dev->dev_id, dev->status ? "在线" : "离线");
    } else {
        printf("未找到设备:%s\n", target_id);
    }
    return 0;
}

运行结果:

五、关键注意事项

strcmp()与strncmp()的很多 bug 源于忽略细节,以下是必须牢记的 5 个注意事项。

5.1 strcmp () 必须依赖 '\0',否则越界

strcmp()会一直遍历内存直到'\0',若字符串无结束符,会触发缓冲区越界访问(未定义行为):

代码语言:javascript
复制
#include <stdio.h>
#include <string.h>

int main() {
    // 错误:数组无'\0',strlen无法计算长度,strcmp会越界
    char str1[3] = {'a', 'b', 'c'}; 
    char str2[3] = {'a', 'b', 'd'};
    
    // 风险:strcmp会继续读取str1[3]、str2[3]及之后的内存
    int result = strcmp(str1, str2);
    printf("比较结果:%d\n", result); // 结果不可控,可能崩溃
    return 0;
}

解决方法

  • 定义数组时预留'\0'位置:char str1[4] = {'a','b','c','\0'};
  • 用strncmp()指定长度:strncmp(str1, str2, 3)。

5.2 strncmp () 的 n 是无符号整数,不可传负数

n的类型是size_t(无符号),若传入负数,会被强制转换为极大的正数(如 - 1→4294967295),导致越界:

代码语言:javascript
复制
#include <stdio.h>
#include <string.h>

int main() {
    char str1[] = "abc";
    char str2[] = "abd";
    int n = -1; // 错误:负数转换为size_t最大值
    
    // 风险:比较4294967295个字符,必然越界
    int result = strncmp(str1, str2, n);
    printf("结果:%d\n", result);
    return 0;
}

解决方法

  • 确保n是非负数:用n = (n < 0) ? 0 : n做防御;
  • 用sizeof()获取固定长度:如strncmp(str1, str2, sizeof(str1)-1)(减 1 是预留'\0'位置)。

5.3 返回值不是只有 - 1、0、1

C 标准仅规定返回值的符号(正 / 负 / 零),未规定具体数值。不同编译器实现可能返回 “第一个不同字符的 ASCII 差值”:

代码语言:javascript
复制
#include <stdio.h>
#include <string.h>

int main() {
    // 'a' ASCII=97,'c'=99 → 差值为-2
    printf("strcmp(\"a\", \"c\") = %d\n", strcmp("a", "c")); 
    // 'x'=120,'m'=109 → 差值为11
    printf("strcmp(\"x\", \"m\") = %d\n", strcmp("x", "m")); 
    return 0;
}

GCC 编译器运行结果

strcmp("a", "c") = -2 strcmp("x", "m") = 11

错误写法:if (strcmp(a, b) == -1) → 某些编译器可能返回 - 2,导致判断失效;

正确写法:if (strcmp(a, b) < 0)。

5.4 不可比较多字节字符串(如中文)

strcmp()与strncmp()均按单字节 ASCII 码比较,而中文(如 GBK、UTF-8)是多字节编码,比较结果会出错:

代码语言:javascript
复制
#include <stdio.h>
#include <string.h>

int main() {
    // UTF-8中,"中"占3字节,"国"占3字节
    char str1[] = "中";
    char str2[] = "国";
    
    // 错误:比较的是第一个字节的ASCII值,而非汉字本身
    int result = strcmp(str1, str2);
    printf("strcmp(\"中\", \"国\") = %d\n", result); 
    return 0;
}

运行结果:strcmp("中", "国") = -32(无实际意义)

解决方法:使用宽字符函数wcscmp()(比较wchar_t类型字符串)或专门的多字节比较库(如iconv)。

5.5 避免传入 NULL 指针

两个函数均未处理NULL参数,传入NULL会触发段错误(程序崩溃):

代码语言:javascript
复制
#include <string.h>

int main() {
    // 错误:str2为NULL,触发段错误
    strcmp("abc", NULL); 
    return 0;
}

解决方法:加指针合法性校验:

代码语言:javascript
复制
int safe_strcmp(const char* a, const char* b) {
    if (a == NULL && b == NULL) return 0;   // 两者都为NULL,视为相等
    if (a == NULL) return -1;               // a为NULL,视为小于b
    if (b == NULL) return 1;                // b为NULL,视为小于a
    return strcmp(a, b);
}

六、函数差异对比:一张表看懂核心区别

为方便快速查阅,以下表格总结了strcmp()与strncmp()的关键差异:

对比维度

strcmp()

strncmp()

比较范围

从首字符到'\0'(完整字符串)

从首字符开始,最多 n 个字符

参数数量

2 个(str1, str2)

3 个(str1, str2, n)

终止条件

遇到不同字符 或 双方都到'\0'

遇到不同字符 或 双方都到'\0' 或 已比较 n 个字符

依赖'\0'

必须依赖(否则越界)

不依赖(n 合理时)

安全性

低(无'\0'时越界)

高(可控制比较长度)

适用场景

完整比较、字符串有'\0'

部分比较、无'\0'字符串、防越界

典型风险

缓冲区越界

n 为负数导致越界

七、完整示例代码:综合场景实践

以下示例结合文件后缀判断(strcmp())与 URL 前缀判断(strncmp()),展示两个函数的协同使用:

代码语言:javascript
复制
#include <stdio.h>
#include <string.h>
#include <stdbool.h>

// 1. 用strcmp()判断文件是否为图片(.jpg/.png)
bool is_image_file(const char* file_path) {
    if (file_path == NULL) return false;
    const char* dot = strrchr(file_path, '.');
    if (dot == NULL) return false;
    return strcmp(dot, ".jpg") == 0 || strcmp(dot, ".png") == 0;
}

// 2. 用strncmp()判断URL是否为HTTPS(需比较前8个字符)
bool is_https_url(const char* url) {
    if (url == NULL) return false;
    // 确保URL长度≥8,避免n超过字符串长度(可选防御)
    if (strlen(url) < 8) return false;
    return strncmp(url, "https://", 8) == 0;
}

// 3. 综合判断:是否为HTTPS链接的图片文件
bool is_https_image(const char* https_url) {
    return is_https_url(https_url) && is_image_file(https_url);
}

int main() {
    char test_urls[][128] = {
        "https://blog.example.com/photo1.jpg",
        "http://example.com/photo2.png",
        "https://img.example.net/logo.svg",
        "ftp://file.example.com/doc.pdf",
        "https://example.com/avatar.jpg"
    };

    printf("=== HTTPS图片链接检测结果 ===\n");
    for (int i = 0; i < 5; i++) {
        const char* url = test_urls[i];
        if (is_https_image(url)) {
            printf("[√] %s → 是HTTPS图片链接\n", url);
        } else {
            printf("[×] %s → 不是HTTPS图片链接\n", url);
        }
    }

    return 0;
}

运行结果


strcmp()与strncmp()是 C 语言字符串操作的基础工具,两者的核心差异在于 “是否控制比较范围”。实际开发中,需根据 “是否有'\0'” 和 “是否需完整比较” 选择函数 —— 不确定场景下,strncmp()的安全性更高(只要n设置合理)。掌握本文的实现逻辑、注意事项与场景选择原则,能帮你避免 90% 以上的字符串比较 bug,写出更健壮的 C 语言代码。


博主简介 byte轻骑兵,现就职于国内知名科技企业,专注于嵌入式系统研发,深耕 Android、Linux、RTOS、通信协议、AIoT、物联网及 C/C++ 等领域。乐于技术分享与交流,欢迎关注互动! 📌 主页与联系方式

  • CSDN:https://blog.csdn.net/weixin_37800531
  • 知乎:https://www.zhihu.com/people/38-72-36-20-51
  • 微信公众号:嵌入式硬核研究所
  • 邮箱:byteqqb@163.com(技术咨询或合作请备注需求)

⚠️ 版权声明 本文为原创内容,未经授权禁止转载。商业合作或内容授权请联系邮箱并备注来意。


本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-09-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、函数简介
  • 二、函数原型与参数解析
    • 2.1 strcmp () 原型
    • 2.2 strncmp () 原型
  • 三、函数实现逻辑
    • 3.1 strcmp () 伪代码实现
    • 3.2 strncmp () 伪代码实现
  • 四、典型使用场景:何时用 strcmp (),何时用 strncmp ()
    • 4.1 strcmp () 的适用场景
    • 4.2 strncmp () 的适用场景
  • 五、关键注意事项
  • 六、函数差异对比:一张表看懂核心区别
  • 七、完整示例代码:综合场景实践
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档