strlen;strcpy ;strcat

一.strlen

strlen 是 C 语言标准库 <string.h> 中的核心函数,用于计算字符串的有效长度 (不包含字符串结束符 \0)。

一、基本语法

cs 复制代码
size_t strlen(const char *str);
参数说明:
  • str:指向待计算长度的字符串的指针(字符串需以 \0 结尾,否则会导致未定义行为)。
返回值:
  • size_t:无符号整数类型(本质是 unsigned int),返回字符串中有效字符的个数 (不包含 \0)。

二、核心特性

  1. 只统计 \0 之前的字符 :字符串的结束标志是 \0strlen 从起始地址开始遍历,遇到 \0 立即停止,返回遍历过的字符数。
  2. 不修改原字符串 :参数用 const 修饰,保证字符串只读。
  3. 无符号返回值size_t 是无符号类型,需注意与有符号数运算的坑(如 strlen(a) - strlen(b) 不会为负数)。

三、使用示例

cs 复制代码
#include <stdio.h>
#include <string.h>

int main() {
    // 示例1:正常字符串(含 \0)
    char str1[] = "hello";  // 实际存储:h e l l o \0
    printf("str1长度:%zu\n", strlen(str1));  // 输出 5(不含 \0)
    printf("str1占用字节:%zu\n", sizeof(str1));  // 输出 6(包含 \0)

    // 示例2:手动指定 \0 位置
    char str2[] = "abc\0def";
    printf("str2长度:%zu\n", strlen(str2));  // 输出 3(遇到 \0 停止)

    // 示例3:空字符串
    char str3[] = "";
    printf("str3长度:%zu\n", strlen(str3));  // 输出 0

    // 反例:无 \0 的字符数组(未定义行为,可能返回随机值)
    char str4[3] = {'a', 'b', 'c'};
    // printf("str4长度:%zu\n", strlen(str4));  // 危险!会越界遍历

    return 0;
}
输出结果:
cs 复制代码
str1长度:5
str1占用字节:6
str2长度:3
str3长度:0

四、常见坑点

  1. sizeof 的区别

    特性 strlen sizeof
    作用 计算字符串有效长度 计算变量 / 类型的字节大小
    是否包含 \0 不包含 包含(字符串数组)
    类型 函数(运行时计算) 运算符(编译时计算)
  2. 无符号返回值的陷阱

    cs 复制代码
    char a[] = "123";
    char b[] = "12345";
    if (strlen(a) - strlen(b) > 0) {
        printf("a更长\n");  // 实际执行!因为无符号数相减结果仍为无符号(负数会转成大正数)
    }

    解决:先转为有符号数再运算:

    cs 复制代码
    if ((int)strlen(a) - (int)strlen(b) > 0) { ... }
  3. 空指针传入strlen(NULL) 会直接崩溃(访问非法内存),使用前需检查指针非空。

五、手动实现 strlen(模拟底层逻辑)

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

// 自定义strlen:遍历字符串直到 \0
size_t my_strlen(const char *str) {
    if (str == NULL) return 0;  // 空指针保护
    size_t len = 0;
    while (str[len] != '\0') {   // 遍历到 \0 停止
        len++;
    }
    return len;
}

int main() {
    char str[] = "hello world";
    printf("自定义strlen:%zu\n", my_strlen(str));  // 输出 11
    return 0;
}

总结

  • strlen 核心是统计 \0 前的字符数 ,必须保证字符串以 \0 结尾;
  • 区分 strlen(运行时、有效长度)和 sizeof(编译时、内存大小);
  • 注意 size_t 无符号类型的运算陷阱,空指针需提前判断。

strlen 模拟

cs 复制代码
#include <stdio.h>
#include <assert.h>
size_t my_strlen(const char* p) {
	assert(p);
	size_t i = 0;
	while ((*p++) != '\0') {
		i++;

	}
	return i;
}
int main() {
	char a[] = "abcde";
	size_t s = my_strlen(a);
	printf("%zu\n", s);
	return 0;
}

使用size_t是为了匹配 "长度 / 大小" 的语义、保证范围足够、提升代码兼容性,具体原因如下:

1. 语义匹配:长度是 "非负数值"

字符串的长度、内存块的大小等属性天然是 "非负的" ,而size_t是 C 标准定义的无符号整数类型 (本质是unsigned int/unsigned long,随平台字长变化),完全契合 "长度不能为负" 的语义;若用int(有符号类型),则可能出现逻辑矛盾(比如长度被错误表示为负数)。

2. 范围足够:适配平台最大内存

size_t的取值范围与平台的最大可寻址内存对齐:

  • 32 位系统中,size_t通常是unsigned int(范围:0 ~ 2^32-1,约 4GB);
  • 64 位系统中,size_t通常是unsigned long(范围:0 ~ 2^64-1,远超当前硬件内存上限)。

若用int(32 位下范围仅-2^31 ~ 2^31-1),当字符串 / 内存块的大小超过2^31-1时,会发生溢出 (结果变成负数),而size_t可覆盖平台支持的所有合法长度。

3. 类型一致性:与标准库接口统一

C 标准库中,所有表示 "大小 / 长度 / 索引" 的接口都使用size_t

  • sizeof的返回值是size_t
  • 内存分配函数malloc(size_t size)的参数是size_t
  • 数组索引、拷贝函数(如memcpy)的长度参数也用size_t

使用size_t能避免 "有符号 / 无符号类型混编" 的陷阱(比如strlen(a) - strlen(b)若用int会出现负数,用size_t则符合无符号运算规则)。

4. 可移植性:跨平台自动适配

size_t是 C 标准(<stddef.h>)定义的类型,不同编译器 / 平台会自动选择对应的无符号类型(无需手动修改);若用unsigned int等具体类型,在 64 位平台下会因字长不匹配导致兼容性问题。

综上,size_t是 "表示长度 / 大小" 的标准、安全、通用的类型 ,因此strlen等统计长度的函数会使用它。


二.strcpy函数

strcpy(string copy)是 C 标准库中用于字符串拷贝 的函数,定义在 <string.h> 头文件中,核心功能是将源字符串(包括终止符 '\0')完整复制到目标字符数组中。

一、函数原型

cs 复制代码
char *strcpy(char *dest, const char *src);
参数说明
参数 说明
dest 目标字符数组(接收拷贝的字符串),必须有足够空间且可修改(不能是只读内存)
src 源字符串(被拷贝的字符串),const 修饰表示不修改源字符串内容
返回值

返回 dest 的起始地址(方便链式调用,比如 strlen(strcpy(dest, src)))。

二、核心特性

  1. 自动拷贝终止符 '\0' :会把 src 中从第一个字符到 '\0' 的所有内容(包括 '\0')复制到 dest
  2. 无长度检查 :这是 strcpy 最关键的安全隐患 ------ 如果 dest 空间小于 src 长度,会导致缓冲区溢出,触发未定义行为(程序崩溃、数据篡改等)。
  3. 内存不能重叠 :若 destsrc 的内存区域重叠,结果未定义(需用 memmove 替代)。

三、正确使用示例

cs 复制代码
#include <stdio.h>
#include <string.h>

int main() {
    // 1. 目标数组足够大(需容纳源字符串 + '\0')
    char dest[20];  // 长度20,远大于源字符串长度
    const char *src = "Hello, strcpy!";

    // 2. 执行拷贝
    strcpy(dest, src);

    // 3. 输出结果
    printf("拷贝后的字符串:%s\n", dest);  // 输出:Hello, strcpy!
    printf("字符串长度:%zu\n", strlen(dest));  // 输出:12(不含'\0')

    return 0;
}

四、常见错误示例

错误 1:目标缓冲区过小(缓冲区溢出)
cs 复制代码
#include <stdio.h>
#include <string.h>

int main() {
    char dest[5];  // 仅能容纳4个有效字符 + '\0'
    const char *src = "Hello, strcpy!";  // 长度12 + '\0' = 13
    strcpy(dest, src);  // 溢出!未定义行为(程序可能崩溃)
    printf("%s\n", dest);
    return 0;
}
错误 2:目标是只读内存(字符串常量)
cs 复制代码
#include <stdio.h>
#include <string.h>

int main() {
    // char* dest 指向只读的字符串常量区,不可修改
    char *dest = "固定字符串";  
    const char *src = "Hello";
    strcpy(dest, src);  // 运行时错误(段错误/Segmentation fault)
    return 0;
}

五、安全替代方案

由于 strcpy 无长度检查,实际开发中优先使用更安全的替代函数:

1. strncpy(C 标准库,可控长度)
cs 复制代码
char *strncpy(char *dest, const char *src, size_t n);
  • 功能:最多拷贝 n 个字符到 dest
  • 注意:若 src 长度 ≥ n,不会自动添加 '\0',需手动补全;若 src 长度 < n,剩余空间用 '\0' 填充。

示例

cs 复制代码
#include <stdio.h>
#include <string.h>

int main() {
    char dest[20];
    const char *src = "Hello, strncpy!";
    size_t dest_size = sizeof(dest);

    // 拷贝 dest_size-1 个字符(留1位给'\0')
    strncpy(dest, src, dest_size - 1);
    dest[dest_size - 1] = '\0';  // 手动补终止符(关键!)

    printf("拷贝结果:%s\n", dest);  // 输出:Hello, strncpy!
    return 0;
}
2. strlcpy(非标准 C,更安全)
  • 定义:常见于 Linux/BSD 系统,需包含 <string.h>,自动补 '\0'
  • 原型:size_t strlcpy(char *dest, const char *src, size_t n);
  • 返回值:源字符串 src 的长度(方便判断是否截断)。

示例

cs 复制代码
#include <stdio.h>
#include <string.h>

int main() {
    char dest[10];
    const char *src = "Hello, strlcpy!";
    size_t src_len = strlcpy(dest, src, sizeof(dest));

    printf("拷贝结果:%s\n", dest);        // 输出:Hello, st(被截断)
    printf("源字符串长度:%zu\n", src_len); // 输出:13(提示截断)
    return 0;
}

六、总结

函数 优点 缺点 适用场景
strcpy 简单、效率高 无长度检查,易缓冲区溢出 确定源字符串长度 ≤ 目标缓冲区大小
strncpy 可控拷贝长度 需手动补 '\0',可能填充多余 '\0' 需限制拷贝长度的场景
strlcpy 自动补 '\0'、更安全 非标准 C,跨平台性差 Linux/BSD 系统,追求安全优先

提示:C++ 中建议直接使用 std::string 的赋值(std::string dest = src;),无需手动管理内存,完全规避缓冲区溢出问题。

七.函数复刻

cs 复制代码
#include <stdio.h>
#include <assert.h>  // 用于 assert 断言(调试阶段检查)

// 健壮版 my_strcpy:增加 NULL 检查
char *my_strcpy_safe(char *dest, const char *src) {
    // 1. 空指针检查(调试阶段断言,Release 可替换为返回 NULL)
    assert(dest != NULL && src != NULL);  // 断言失败会终止程序并提示
    // 生产环境可选写法:if (dest == NULL || src == NULL) return NULL;

    // 2. 保存起始地址
    char *dest_start = dest;

    // 3. 逐字节拷贝(同基础版)
    while (*dest++ = *src++) {}

    return dest_start;
}

// 测试:传入 NULL 会触发断言
int main() {
    char dest[20];
    const char *src = NULL;
    my_strcpy_safe(dest, src);  // 运行时断言失败:Assertion failed: (dest != NULL && src != NULL)
    return 0;
}

三.strcat函数

strcat(string concatenate,字符串拼接)是 C 标准库 <string.h> 中专门用于将源字符串拼接到目标字符串末尾 的函数,其核心逻辑是覆盖目标字符串原有末尾的 '\0',并在拼接完成后重新添加终止符 '\0'

二、核心语法

1. 头文件(必须包含)
cs 复制代码
#include <string.h>
2. 函数原型
cs 复制代码
char *strcat(char *dest, const char *src);
3. 参数解析
参数 类型 / 权限 核心要求
dest 可写 目标字符串(字符数组 / 可修改的字符指针),必须预留足够空间容纳拼接结果
src 只读 源字符串(const 修饰),以 '\0' 结尾,内容不会被修改
4. 返回值

返回 dest 的首地址(支持链式调用,如 strcat(strcat(dest, "a"), "b"))。

三、基础使用示例(正确写法)

cs 复制代码
#include <stdio.h>
#include <string.h>

int main() {
    // 1. 目标字符串:预留足够空间("Hello" + " World!" + '\0' = 12 字节,这里给20字节)
    char dest[20] = "Hello"; 
    // 2. 源字符串:以'\0'结尾(字符串字面量默认带'\0')
    const char src[] = " World!";

    // 3. 拼接:src 拼到 dest 末尾
    strcat(dest, src);

    // 输出结果:Hello World!
    printf("拼接后:%s\n", dest);
    // 输出 dest 长度:11("Hello World!" 共11个可见字符,含'\0'则12)
    printf("拼接后长度:%zu\n", strlen(dest));

    return 0;
}

四、strcat 最容易踩的 3 个坑(必看)

坑 1:目标缓冲区空间不足(最常见)

strcat 不会检查 dest 的剩余空间,若空间不够会直接导致「缓冲区溢出」,破坏内存数据,程序可能崩溃 / 出现诡异行为。

❌ 错误示例(空间不足):

cs 复制代码
// dest 仅6字节("Hello" + '\0'),拼接 " World!" 需额外7字节,总需13字节
char dest[6] = "Hello"; 
const char src[] = " World!";
strcat(dest, src); // 溢出!内存被破坏,程序大概率崩溃
坑 2:字符串未以 '\0' 结尾

strcat 依赖 '\0' 识别字符串末尾,若 dest/src'\0',会一直遍历内存直到找到 '\0',引发未定义行为。

❌ 错误示例(无 '\0'):

cs 复制代码
// 字符数组手动初始化,未加'\0',strcat 无法识别末尾
char dest[20] = {'H','e','l','l','o'}; 
const char src[] = " World!";
strcat(dest, src); // 未定义行为:可能拼接乱码,或程序崩溃
坑 3:源 / 目标字符串内存重叠

destsrc 指向同一块内存区域,strcat 行为不可预测(覆盖自身数据)。

❌ 错误示例(内存重叠):

cs 复制代码
char str[20] = "abcdef";
// src 是 str+2(指向 "cdef"),与 dest(str)重叠
strcat(str, str+2); // 未定义行为:可能输出 "abcdefcdef",也可能崩溃

五、安全替代:strncat(推荐生产环境使用)

为解决 strcat 无长度检查的问题,C 提供了 strncat(指定最大拼接长度),核心是「限制拼接字符数,避免溢出」。

1. strncat 原型
cs 复制代码
char *strncat(char *dest, const char *src, size_t n);
  • n:最多从 src 拼接 n 个字符到 dest
  • 自动在拼接后添加 '\0',无需手动加。
2. 安全用法示例(计算剩余空间)
cs 复制代码
#include <stdio.h>
#include <string.h>

int main() {
    char dest[20] = "Hello";
    const char src[] = " World! 123456789";
    
    // 关键:计算 dest 剩余可用空间 = 总长度 - 已用长度 - 1(留'\0'位置)
    size_t dest_total = sizeof(dest);       // dest 总空间:20
    size_t dest_used = strlen(dest);        // dest 已用长度:5("Hello")
    size_t max_copy = dest_total - dest_used - 1; // 最大可拼接长度:14

    // 仅拼接 max_copy 个字符,避免溢出
    strncat(dest, src, max_copy);

    // 输出:Hello World! 123456789(刚好填满,无溢出)
    printf("安全拼接结果:%s\n", dest);
    return 0;
}

六、strcat 进阶用法:链式调用

利用返回值是 dest 首地址的特性,可实现「连续拼接」:

cs 复制代码
#include <stdio.h>
#include <string.h>

int main() {
    char dest[30] = "I love ";
    // 链式拼接:先拼 "C ",再拼 "programming!"
    strcat(strcat(dest, "C "), "programming!");
    printf("链式拼接:%s\n", dest); // 输出:I love C programming!
    return 0;
}

七、总结:strcat 使用原则

  1. 能用 strncat 就不用 strcat :生产环境优先选 strncat,避免缓冲区溢出;
  2. 必须保证 dest 空间足够 :若用 strcat,需提前确认 dest 总长度 ≥ 原长度 + 源字符串长度 + 1('\0');
  3. 字符串必须以 '\0' 结尾 :字符数组手动初始化时,务必加 '\0'
  4. 禁止内存重叠destsrc 不能指向同一块内存的重叠区域。

八.函数复刻

cs 复制代码
#include <stdio.h>
#include <stddef.h>  // 用于 NULL 定义

// 复刻 strcat 函数
char *my_strcat(char *dest, const char *src) {
    // 1. 边界检查:dest 或 src 为 NULL 直接返回 NULL
    if (dest == NULL || src == NULL) {
        return NULL;
    }

    // 2. 保存 dest 起始地址(最终要返回)
    char *dest_start = dest;

    // 3. 找到 dest 的末尾('\0' 位置)
    while (*dest != '\0') {
        dest++;
    }

    // 4. 逐字符复制 src 到 dest 末尾(包含 '\0')
    while (*src != '\0') {
        *dest = *src;
        dest++;
        src++;
    }
    // 确保最终字符串以 '\0' 结尾(上面循环结束后 src 是 '\0',这里显式赋值更稳妥)
    *dest = '\0';

    // 5. 返回 dest 的起始地址
    return dest_start;
}

// 测试代码
int main() {
    // 测试用例1:正常拼接
    char dest1[20] = "Hello, ";  // 预留足够空间
    const char *src1 = "World!";
    my_strcat(dest1, src1);
    printf("测试用例1:%s\n", dest1);  // 预期输出:Hello, World!

    // 测试用例2:dest 为空字符串
    char dest2[20] = "";
    const char *src2 = "Test Empty Dest";
    my_strcat(dest2, src2);
    printf("测试用例2:%s\n", dest2);  // 预期输出:Test Empty Dest

    // 测试用例3:src 为空字符串
    char dest3[20] = "Original";
    const char *src3 = "";
    my_strcat(dest3, src3);
    printf("测试用例3:%s\n", dest3);  // 预期输出:Original

    // 测试用例4:NULL 指针(鲁棒性测试)
    char *dest4 = NULL;
    const char *src4 = "NULL Test";
    char *result = my_strcat(dest4, src4);
    if (result == NULL) {
        printf("测试用例4:检测到 NULL 指针,返回 NULL\n");  // 预期输出
    }

    return 0;
}
相关推荐
_OP_CHEN2 小时前
【算法基础篇】(三十五)图论基础之最小生成树:从原理到实战,彻底吃透 Prim 与 Kruskal 算法
算法·蓝桥杯·图论·最小生成树·kruskal算法·prim算法·acm/icpc
LYFlied2 小时前
【算法解题模板】-【回溯】----“试错式”问题解决利器
前端·数据结构·算法·leetcode·面试·职场和发展
拾忆,想起2 小时前
设计模式:软件开发的可复用武功秘籍
开发语言·python·算法·微服务·设计模式·性能优化·服务发现
lxh01132 小时前
最长有效括号
数据结构·算法
橙子牛奶糖2 小时前
Science | 本周最新文献速递
算法·gwas·生物信息学·单细胞测序
皮卡蛋炒饭.2 小时前
背包问题Ⅱ与二分问题
算法
Code Slacker2 小时前
LeetCode Hot100 —— 普通数组(面试纯背版)(五)
数据结构·c++·算法·leetcode·面试
sin_hielo2 小时前
leetcode 3573(买卖股票问题,状态机dp)
数据结构·算法·leetcode
flashlight_hi2 小时前
LeetCode 分类刷题:110. 平衡二叉树
javascript·算法·leetcode