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'
六个连续的字符单元。
字符串的存储方式有两种:
- 字符数组:例如
char str[] = "Hello";
,数组长度会自动包含末尾的空字符。 - 字符指针:例如
char *ptr = "World";
,指针指向字符串常量的起始地址。
比喻说明:
可以把字符串想象成一条项链,每个字符是一个珠子,而空字符 '\0'
就是项链的末端扣环。当程序处理字符串时,就像沿着项链寻找扣环来确定字符串的结束位置。
使用标准库函数连接字符串
C 标准库提供了 string.h
头文件中的函数,最常用的是 strcat
和 strcpy
,它们可以高效地连接字符串。
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
主要用于复制字符串,但也可以通过两步操作实现拼接:
- 先复制
dest
到临时变量。 - 再将
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
的当前长度。
手动实现字符串连接
如果希望理解底层逻辑,可以尝试手动编写字符串连接函数。
步骤分解:
- 遍历
dest
数组,找到其末尾的'\0'
。 - 将
src
的字符逐个复制到dest
的末尾,直到遇到src
的'\0'
。 - 在
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
的空间不足,strcat
或 strcpy
会覆盖内存中的其他数据,导致程序崩溃或安全漏洞。例如:
char small_dest[10] = "Hello"; // 最大容量为 10 字节(包括空字符)
strcat(small_dest, " World!"); // "Hello" 需要 6 字节," World!" 需要 8 字节,总长度 13,超出空间
解决方法:
- 使用
snprintf
或strncat
控制最大复制长度。 - 在声明数组时预留足够空间,例如
char dest[100];
。
2. 动态内存分配
当无法预估字符串长度时,可以使用 malloc
和 realloc
动态调整内存:
#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 语言的字符串连接操作看似简单,但涉及内存管理、安全性和效率等深层问题。通过掌握 strcat
、strcpy
等标准函数,结合手动实现和动态内存分配技巧,开发者可以灵活应对不同场景的需求。关键点在于:
- 预留足够的内存空间,避免缓冲区溢出。
- 优先使用安全函数(如
strncat
),或通过realloc
动态调整内存。 - 理解指针与数组的关系,避免操作不可修改的字符串常量。
希望本文通过实例和代码示例,帮助读者逐步掌握 C 语言实例 – 连接字符串的核心技术,并在实际项目中避免常见陷阱。