【嵌入式 C 语言实战】手动实现字符串四大核心函数(strcpy/strcat/strlen/strcmp)

【嵌入式 C 语言实战】手动实现字符串四大核心函数(strcpy/strcat/strlen/strcmp)

大家好,我是学嵌入式的小杨同学。在嵌入式开发中,字符串处理是高频核心需求,而 C 语言标准库中的strcpystrcatstrlenstrcmp是字符串处理的四大基石。今天我们不依赖标准库,手动实现这四个函数,深入理解其底层逻辑,同时修正代码中的小问题,掌握嵌入式场景下字符串处理的核心技巧。

一、代码整体概述

这份代码的核心目标是模拟 C 语言标准库的字符串四大核心函数 ,不依赖<string.h>头文件,完全通过指针操作实现字符串的复制、拼接、长度统计和比较功能,是理解 C 语言字符串本质(以'\0'结尾的字符数组)的绝佳案例。

先明确四个函数的核心功能:

  1. Strcpy:字符串复制,将源字符串复制到目标字符数组中;
  2. Strlen:字符串长度统计,返回字符串有效字符个数(不包含'\0');
  3. Strcat:字符串拼接,将源字符串追加到目标字符串末尾;
  4. Strmpy(对应标准库strcmp):字符串比较,按 ASCII 码值比较两个字符串的大小。

二、先修正代码中的关键问题(保证正常运行)

在解析代码之前,先修正两处影响运行的小问题,避免编译警告和运行错误:

1. 函数名拼写修正:Strmpy → 统一为Strcmp(贴合标准库,提升可读性)

原代码中函数声明和实现为Strmpy,属于拼写笔误,统一修正为Strcmp(与标准库strcmp对应),避免混淆。

2. 修正main函数中只读字符串指针赋值问题

c

运行

arduino 复制代码
// 原代码问题:d、e是指向只读字符串常量的指针,不能直接用于修改(虽此处仅用于比较,无修改,但会有编译警告)
// 修正为:使用字符数组存储,符合嵌入式字符串处理规范
char d[]="HELLO3";
char e[]="HELLO4";

问题说明char *d="HELLO3"中,"HELLO3"是存储在只读存储区的字符串常量,指针d仅指向其首地址,无法修改该字符串;而字符数组char d[]="HELLO3"是将字符串常量复制到栈上的字符数组中,可读取可修改(此处用于比较,虽无需修改,但更符合规范,避免编译警告)。

3. 修正函数声明与实现的一致性

原代码中Strmpy声明参数为char *d,char *s,修正后Strcmp保持参数一致性,同时建议添加const修饰只读参数(源字符串不被修改),提升代码健壮性。

三、逐函数详细解析(核心实现与底层逻辑)

1. 字符串复制:Strcpy(对应标准库strcpy

核心功能

将源字符串(const char *s)的内容完整复制到目标字符数组(char *d)中,包括末尾的'\0',返回目标字符数组的首地址。

代码实现(修正后)

c

运行

arduino 复制代码
char *Strcpy(char *d,const char *s)
{
    // 保存目标数组首地址(后续d指针会移动,需返回原始首地址)
    char *p=d;
    
    // 遍历源字符串,直到遇到'\0'(*s为'\0'时,循环条件为假,终止循环)
    while(*s)
    {
        // 逐字符复制:将源字符串当前字符赋值给目标数组
        *d=*s;
        // 指针同时后移,遍历下一个字符
        d++;
        s++;
    }
    
    // 关键:在目标字符串末尾添加'\0',标识字符串结束
    *d='\0';
    
    // 返回目标数组原始首地址,支持链式调用
    return p;
}
关键细节解析
  1. const char *s:使用const修饰源字符串指针,表明源字符串仅用于读取,不被修改,是 C 语言良好编码习惯,防止函数内部误修改源字符串;
  2. 保存首地址char *p=d:由于后续d指针会不断后移指向目标数组的后续位置,若不保存原始首地址,无法返回目标数组的起始位置,影响链式调用(如b=Strcpy(a,b));
  3. 循环条件while(*s):等价于while(*s != '\0'),利用 C 语言 "0 为假,非 0 为真" 的特性,简洁高效;
  4. 手动添加*d='\0':这是字符串复制的核心要点,源字符串的'\0'不会被带入循环(循环终止于*s='\0'),必须手动在目标数组末尾添加'\0',否则目标数组无法被识别为合法字符串。
嵌入式注意点
  • 目标字符数组d必须有足够的容量,能够容纳源字符串的所有字符(包括'\0'),否则会导致数组越界,覆盖其他内存区域,引发系统崩溃;
  • 避免目标指针dNULL,嵌入式开发中需添加判空逻辑(如if(d==NULL || s==NULL) return NULL;),提升代码健壮性。

2. 字符串长度统计:Strlen(对应标准库strlen

核心功能

统计字符串的有效字符个数(不包含末尾的'\0'),返回整数长度。

代码实现

c

运行

arduino 复制代码
int Strlen(char *d)
{
    // 初始化长度计数器为0
    int len=0;
    
    // 遍历字符串,直到遇到'\0'
    while(*d)
    {
        // 每遍历一个有效字符,计数器+1
        len++;
        // 指针后移,遍历下一个字符
        d++;
    }
    
    // 返回有效字符长度
    return len;
}
关键细节解析
  1. 计数规则:仅统计'\0'之前的有效字符,例如"HELLO1"的长度为 6,不包含末尾隐藏的'\0'
  2. 指针遍历:无需知道字符数组的总容量,仅通过指针遍历到'\0'即可终止,符合 C 语言字符串的特性;
  3. 时间复杂度:O (n)(n 为字符串有效长度),仅需遍历一次字符串,效率高效。
嵌入式注意点
  • 若传入的字符串未以'\0'结尾,会导致while循环无限执行,引发栈溢出或程序崩溃,嵌入式开发中必须确保字符串以'\0'结尾;
  • 避免传入NULL指针,可添加判空逻辑(如if(d==NULL) return 0;)。

3. 字符串拼接:Strcat(对应标准库strcat

核心功能

将源字符串(const char *s)追加到目标字符串(char *d)的末尾,覆盖目标字符串原来的'\0',并在拼接后的新字符串末尾添加'\0',返回目标字符串的首地址。

代码实现

c

运行

arduino 复制代码
char *Strcat(char *d,const char *s)
{
    // 保存目标数组首地址
    char *p=d;
    
    // 第一步:遍历目标字符串,找到其末尾的'\0'
    while(*d)
    {
        d++;
    }
    
    // 第二步:与Strcpy逻辑一致,将源字符串逐字符复制到目标字符串末尾
    while(*s)
    {
        *d=*s;
        d++;
        s++;
    }
    
    // 关键:在拼接后的新字符串末尾添加'\0'
    *d='\0';
    
    // 返回目标数组原始首地址
    return p;
}
关键细节解析
  1. 两步遍历逻辑:第一步找到目标字符串的末尾('\0'位置),第二步从该位置开始复制源字符串,实现 "追加" 效果,而非覆盖;
  2. Strcpy的区别:Strcpy从目标数组的起始位置开始复制,Strcat从目标字符串的'\0'位置开始复制;
  3. 手动添加*d='\0':确保拼接后的字符串是合法的 C 语言字符串,否则后续遍历(如Strlenprintf)会出现异常。
嵌入式注意点
  • 目标字符数组d的剩余容量必须足够容纳源字符串的所有字符(包括'\0'),例如目标数组a[1024]已存储"HELLO1"(长度 6),则剩余容量需大于源字符串"HELLO2"的长度(6),否则会导致数组越界;
  • 不支持自拼接(如Strcat(a,a)),会导致源字符串被覆盖,引发逻辑错误。

4. 字符串比较:Strcmp(对应标准库strcmp,原代码Strmpy

核心功能

按 ASCII 码值逐字符比较两个字符串的大小,返回比较结果:

  1. 返回值 > 0:第一个字符串大于第二个字符串;
  2. 返回值 = 0:两个字符串完全相等;
  3. 返回值 < 0:第一个字符串小于第二个字符串。
代码实现(修正后)

c

运行

arduino 复制代码
int Strcmp(char *d,char *s)
{
    // 第一步:逐字符比较,直到其中一个字符串遇到'\0',或出现不相等字符
    while(*d&&*s)
    {
        // 若当前字符不相等,直接返回两个字符的ASCII码差值
        if(*d!=*s)
        {
            return *d-*s;
        }
        // 字符相等,指针同时后移,继续比较下一个字符
        d++;
        s++;
    }

    // 第二步:若其中一个字符串已遍历完毕,返回剩余字符的ASCII码差值
    // (若两个字符串完全相等,此处* d和*s均为'\0',差值为0)
    return *d-*s;
}
关键细节解析
  1. 比较规则:按字符的 ASCII 码值比较,例如'A'(65)<'a'(97),'3'(51)<'H'(72);
  2. 循环条件while(*d&&*s):仅当两个字符串当前字符都不为'\0'时,才进行比较,任意一个字符串遍历完毕则终止循环;
  3. 返回值逻辑:直接返回字符的 ASCII 码差值,符合标准库strcmp的返回值规范,无需额外判断正负;
  4. 完全相等的情况:当两个字符串长度相同且所有字符都相等时,循环结束后*d*s均为'\0'(ASCII 码值为 0),返回值为 0。
嵌入式注意点
  • 支持长度不同的字符串比较,例如"HELLO3""HELLO345",会先比较到"HELLO3"'\0',返回0 - '4'(即 - 52);
  • 无需修改字符串内容,建议添加const修饰参数(int Strcmp(const char *d, const char *s)),提升代码安全性。

四、完善后的完整代码(可直接编译运行)

c

运行

arduino 复制代码
#include<stdio.h>

// 函数声明(修正命名,保持一致性)
char *Strcpy(char *d,const char *s);
char *Strcat(char *d,const char *s);
int Strcmp(const char *d,const char *s); // 添加const修饰,提升健壮性
int Strlen(char *d);

int main()
{
    char a[1024] = {0}; // 目标字符数组,初始化全0,确保有足够容量
    const char *b="HELLO1";
    
    // 字符串复制
    Strcpy(a,b);
    printf("字符串复制结果:%s\n",a);
    
    // 字符串长度统计
    int len=Strlen(a);
    printf("字符串长度:%d\n",len);
    
    // 字符串拼接
    const char *c="HELLO2";
    Strcat(a,c);
    printf("字符串拼接结果:%s\n",a);
    
    // 字符串比较
    char d[]="HELLO3"; // 修正为字符数组,避免只读警告
    char e[]="HELLO4";
    int s=Strcmp(d,e);
    printf("字符串比较结果:%d\n",s);
    
    return 0;
}

// 字符串比较实现(修正命名,添加const修饰)
int Strcmp(const char *d,const char *s)
{
    while(*d&&*s)
    {
        if(*d!=*s)
        {
            return *d-*s;
        }
        d++;
        s++;
    }
    return *d-*s;
}

// 字符串复制实现
char *Strcpy(char *d,const char *s)
{
    // 判空逻辑(嵌入式开发推荐添加,避免NULL指针)
    if(d==NULL || s==NULL)
    {
        printf("指针为空,复制失败\n");
        return NULL;
    }
    char *p=d;
    while(*s)
    {
        *d=*s;
        d++;
        s++;
    }
    *d='\0';
    return p;
}

// 字符串拼接实现
char *Strcat(char *d,const char *s)
{
    // 判空逻辑(嵌入式开发推荐添加,避免NULL指针)
    if(d==NULL || s==NULL)
    {
        printf("指针为空,拼接失败\n");
        return NULL;
    }
    char *p=d;
    while(*d)
    {
        d++;
    }
    while(*s)
    {
        *d=*s;
        d++;
        s++;
    }
    *d='\0';
    return p;
}

// 字符串长度统计实现
int Strlen(char *d)
{
    // 判空逻辑(嵌入式开发推荐添加,避免NULL指针)
    if(d==NULL)
    {
        printf("指针为空,长度统计失败\n");
        return 0;
    }
    int len=0;
    while(*d)
    {
        len++;
        d++;
    }
    return len;
}

五、编译运行与预期结果

1. 编译命令(Linux/macOS)

将代码保存为string_functions.c,在终端执行以下命令:

bash

运行

bash 复制代码
gcc string_functions.c -o string_functions
./string_functions

2. 预期运行结果

plaintext

复制代码
字符串复制结果:HELLO1
字符串长度:6
字符串拼接结果:HELLO1HELLO2
字符串比较结果:-1

结果解析

  • HELLO3HELLO4比较,最后一个字符'3'(51)比'4'(52)小 1,返回 - 1,符合预期。

六、嵌入式开发优化建议

  1. 添加严格的指针判空逻辑 :嵌入式设备中内存资源有限,指针容易出现NULL,必须在函数入口处判断ds是否为NULL,避免野指针导致系统崩溃;
  2. 限制字符串长度,避免数组越界 :可在函数中添加长度参数(如Strcpy(char *d, const char *s, int max_len)),限制复制 / 拼接的字符个数,防止目标数组溢出;
  3. 使用静态内存替代动态分配 :嵌入式开发中尽量避免malloc,优先使用固定大小的字符数组或静态内存池,减少内存碎片化问题;
  4. 避免栈溢出,优先使用全局 / 静态字符数组:栈空间通常较小(几 KB 到几十 KB),局部字符数组过大容易导致栈溢出,可将大字符数组定义为全局或静态(存储在全局存储区);
  5. 兼容多编码格式:若处理中文等多字节字符,需注意编码格式(如 UTF-8),避免按单字节遍历导致逻辑错误。

七、总结

  1. 手动实现四大字符串函数的核心是指针操作 +'\0'结束标记,所有逻辑都围绕 "逐字符遍历" 和 "合法字符串收尾" 展开;
  2. StrcpyStrcat的核心区别是遍历起始位置不同,且都必须手动添加'\0'
  3. Strlen仅统计'\0'前的有效字符,Strcmp按 ASCII 码值逐字符比较,返回差值;
  4. 嵌入式开发中使用字符串函数,需重点关注指针判空、数组容量、栈溢出三大问题,兼顾功能实现和系统稳定性。

掌握这四个函数的底层实现,不仅能加深对 C 语言字符串的理解,更能为嵌入式开发中的字符串解析、配置项处理等场景打下坚实基础。我是学嵌入式的小杨同学,关注我,解锁更多嵌入式 C 语言实战技巧!

相关推荐
小辉笔记2 小时前
Transformer讲解
人工智能·深度学习·transformer
Gofarlic_OMS2 小时前
MATLAB许可证闲置自动检测与智能提醒
java·大数据·运维·开发语言·人工智能·算法·matlab
工业甲酰苯胺2 小时前
推荐算法闲谈:如何在不同业务场景下理解和拆解核心指标
算法·机器学习·推荐算法
平生不喜凡桃李2 小时前
LeetCode: 基本计算器详解
算法·leetcode·计算器·逆波兰表达式
噜~噜~噜~2 小时前
损失曲线(loss surface)的个人理解
人工智能·深度学习·持续学习·任务边界感知·损失曲线
haing20192 小时前
卡尔曼滤波(Kalman Filter)原理
线性代数·算法·机器学习
Dev7z2 小时前
基于深度学习的泳池溺水行为检测算法设计
人工智能·深度学习·算法
利刃大大2 小时前
【RabbitMQ】重试机制 && TTL && 死信队列
分布式·后端·消息队列·rabbitmq·队列
Pluchon2 小时前
硅基计划4.0 算法 优先级队列
数据结构·算法·排序算法