C 语言实例 – 连接字符串(保姆级教程)

更新时间:

💡一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...点击查看项目介绍 ;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;

截止目前, 星球 内专栏累计输出 82w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 2900+ 小伙伴加入学习 ,欢迎点击围观

字符串的基础概念与存储方式

在 C 语言中,字符串的本质是字符数组,以空字符 '\0'(即 ASCII 码为 0 的字符)作为结尾标识。例如,字符串 "Hello" 在内存中实际存储为 'H', 'e', 'l', 'l', 'o', '\0' 六个连续的字符单元。

字符串的存储方式有两种:

  1. 字符数组:例如 char str[] = "Hello";,数组长度会自动包含末尾的空字符。
  2. 字符指针:例如 char *ptr = "World";,指针指向字符串常量的起始地址。

比喻说明
可以把字符串想象成一条项链,每个字符是一个珠子,而空字符 '\0' 就是项链的末端扣环。当程序处理字符串时,就像沿着项链寻找扣环来确定字符串的结束位置。


使用标准库函数连接字符串

C 标准库提供了 string.h 头文件中的函数,最常用的是 strcatstrcpy,它们可以高效地连接字符串。

strcat 函数:逐字节拼接

strcat 的函数原型为 char *strcat(char *dest, const char *src),其作用是将 src 字符串追加到 dest 字符串的末尾,并返回 dest 的地址。

示例代码

#include <stdio.h>  
#include <string.h>  

int main() {  
    char dest[50] = "Hello";  
    char src[] = " World!";  

    strcat(dest, src);  
    printf("连接后的字符串: %s\n", dest);  
    return 0;  
}  

输出

连接后的字符串: Hello World!

注意事项

  • dest 必须有足够空间容纳 src 内容,否则会发生缓冲区溢出。
  • dest 必须是可修改的数组或动态分配的内存,不能是字符串常量。

strcpy 函数:复制与拼接的结合

虽然 strcpy 主要用于复制字符串,但也可以通过两步操作实现拼接:

  1. 先复制 dest 到临时变量。
  2. 再将 src 追加到 dest

示例代码

#include <stdio.h>  
#include <string.h>  

int main() {  
    char dest[50] = "Hello";  
    char src[] = " World!";  

    strcpy(dest + strlen(dest), src); // 从 dest 的当前末尾开始复制  
    printf("连接后的字符串: %s\n", dest);  
    return 0;  
}  

此方法与 strcat 的效果相同,但需手动计算 dest 的当前长度。


手动实现字符串连接

如果希望理解底层逻辑,可以尝试手动编写字符串连接函数。

步骤分解

  1. 遍历 dest 数组,找到其末尾的 '\0'
  2. src 的字符逐个复制到 dest 的末尾,直到遇到 src'\0'
  3. dest 的末尾添加新的 '\0'

手动实现代码

#include <stdio.h>  

void manual_strcat(char *dest, const char *src) {  
    // 找到 dest 的末尾位置  
    while (*dest != '\0') {  
        dest++;  
    }  

    // 复制 src 的内容到 dest  
    while (*src != '\0') {  
        *dest = *src;  
        dest++;  
        src++;  
    }  

    // 添加结束符  
    *dest = '\0';  
}  

int main() {  
    char dest[50] = "Hello";  
    char src[] = " World!";  

    manual_strcat(dest, src);  
    printf("连接后的字符串: %s\n", dest);  
    return 0;  
}  

此代码与 strcat 的底层逻辑完全一致,但需要开发者自行处理指针移动和边界条件。


内存管理与安全性

1. 缓冲区溢出风险

dest 的空间不足,strcatstrcpy 会覆盖内存中的其他数据,导致程序崩溃或安全漏洞。例如:

char small_dest[10] = "Hello"; // 最大容量为 10 字节(包括空字符)  
strcat(small_dest, " World!"); // "Hello" 需要 6 字节," World!" 需要 8 字节,总长度 13,超出空间  

解决方法

  • 使用 snprintfstrncat 控制最大复制长度。
  • 在声明数组时预留足够空间,例如 char dest[100];

2. 动态内存分配

当无法预估字符串长度时,可以使用 mallocrealloc 动态调整内存:

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  

void safe_strcat(char **dest, const char *src) {  
    size_t dest_len = strlen(*dest);  
    size_t src_len = strlen(src);  

    // 重新分配内存,容量为原长度 + 新内容长度 + 1(空字符)  
    *dest = realloc(*dest, dest_len + src_len + 1);  

    strcat(*dest, src);  
}  

int main() {  
    char *dynamic_str = malloc(6 * sizeof(char)); // 初始容量仅容纳 "Hello"  
    strcpy(dynamic_str, "Hello");  

    safe_strcat(&dynamic_str, " World!");  
    printf("动态连接结果: %s\n", dynamic_str);  

    free(dynamic_str);  
    return 0;  
}  

此方法通过 realloc 自动扩展内存,避免手动计算空间。


高级技巧与扩展

1. 拼接多个字符串

通过循环或递归可以连接任意数量的字符串。例如:

#include <stdio.h>  
#include <string.h>  

void concatenate(char *result, const char *strings[], int count) {  
    strcpy(result, strings[0]); // 初始化结果  
    for (int i = 1; i < count; i++) {  
        strcat(result, strings[i]);  
    }  
}  

int main() {  
    const char *parts[] = {"C语言", "实例", " – ", "连接字符串"};  
    char final_str[100];  

    concatenate(final_str, parts, 4);  
    printf("最终结果: %s\n", final_str); // 输出 "C语言实例 – 连接字符串"  
    return 0;  
}  

2. 使用 sprintf 函数

sprintf 可以格式化字符串并直接拼接:

#include <stdio.h>  

int main() {  
    char buffer[50];  
    int num = 42;  

    sprintf(buffer, "数值是: %d,类型是: %s", num, "整数");  
    printf("拼接结果: %s\n", buffer); // 输出 "数值是: 42,类型是: 整数"  
    return 0;  
}  

此方法适合需要格式化输出的场景,但需注意缓冲区大小。


常见错误与解决方案

1. 空指针或未初始化的数组

dest 未初始化或指向 NULL,直接调用 strcat 会导致崩溃。

错误示例

char *dest; // 未分配内存  
strcat(dest, "Hello"); // 程序崩溃  

修复方法

char dest[50] = ""; // 初始化为空字符串  
strcat(dest, "Hello");  

2. 混淆字符串常量与可修改数组

尝试修改字符串常量会导致未定义行为:

char *ptr = "Hello"; // ptr 指向常量字符串  
strcat(ptr, " World!"); // 错误!  

应改为:

char arr[] = "Hello"; // 使用字符数组  
strcat(arr, " World!"); // 正确  

实际案例分析

案例 1:构建文件路径

假设需要根据用户输入拼接文件路径:

#include <stdio.h>  
#include <string.h>  

#define MAX_PATH 256  

void build_path(char *result, const char *dir, const char *filename) {  
    strcpy(result, dir);  
    // 添加路径分隔符(假设为 /)  
    strcat(result, "/");  
    strcat(result, filename);  
}  

int main() {  
    char path[MAX_PATH];  
    const char *directory = "/home/user";  
    const char *file = "document.txt";  

    build_path(path, directory, file);  
    printf("完整路径: %s\n", path); // 输出 "/home/user/document.txt"  
    return 0;  
}  

案例 2:日志记录拼接

在程序中记录操作日志时:

#include <stdio.h>  
#include <time.h>  

void log_message(char *buffer, const char *message) {  
    time_t now = time(NULL);  
    strftime(buffer, 20, "%Y-%m-%d %H:%M:%S", localtime(&now));  
    strcat(buffer, ": ");  
    strcat(buffer, message);  
}  

int main() {  
    char log_entry[100];  
    log_message(log_entry, "用户登录");  
    printf("日志条目: %s\n", log_entry); // 输出类似 "2023-10-05 14:30:45: 用户登录"  
    return 0;  
}  

总结

C 语言的字符串连接操作看似简单,但涉及内存管理、安全性和效率等深层问题。通过掌握 strcatstrcpy 等标准函数,结合手动实现和动态内存分配技巧,开发者可以灵活应对不同场景的需求。关键点在于:

  • 预留足够的内存空间,避免缓冲区溢出。
  • 优先使用安全函数(如 strncat),或通过 realloc 动态调整内存。
  • 理解指针与数组的关系,避免操作不可修改的字符串常量。

希望本文通过实例和代码示例,帮助读者逐步掌握 C 语言实例 – 连接字符串的核心技术,并在实际项目中避免常见陷阱。

最新发布