C: 字符串函数
字符串函数就像工具箱里的扳手和螺丝刀——每种工具解决一种问题:复制、拼接、比较、查找。掌握它们,处理文本才能得心应手。
1. string.h概览
<string.h>是C标准库中字符串操作的核心头文件,提供复制、拼接、比较、查找等函数。使用前必须包含:
#include <string.h>
2. strcpy与strncpy
(1) strcpy
strcpy将源字符串复制到目标数组,包括\0:
char dest[20];
strcpy(dest, "Hello World");
printf("%s\n", dest);
原型:char *strcpy(char *dest, const char *src);
strcpy不检查目标缓冲区大小。如果源字符串比目标数组大,直接越界写入。使用前必须确保目标数组足够大。
(2) strncpy
strncpy限定了最大复制字节数,更安全:
char dest[6];
strncpy(dest, "Hello World", sizeof(dest) - 1);
dest[sizeof(dest) - 1] = '\0';
printf("%s\n", dest);
原型:char *strncpy(char *dest, const char *src, size_t n);
strncpy最多复制n个字节。如果源字符串长度不足n,剩余位置填\0;如果源字符串长度达到或超过n,不会自动添加\0。因此需要手动添加。
strncpy预留\0的位置,并在复制后手动补\0。
3. strcat与strncat
(1) strcat
strcat将源字符串拼接到目标字符串末尾:
char buf[50] = "Hello";
strcat(buf, " World");
printf("%s\n", buf);
原型:char *strcat(char *dest, const char *src);
目标字符串原有的\0被覆盖,拼接完成后在末尾添加新的\0。
strcat不检查缓冲区大小。
(2) strncat
strncat限定了最大追加字符数,且会自动添加\0:
char buf[10] = "Hi";
strncat(buf, " World!", sizeof(buf) - strlen(buf) - 1);
printf("%s\n", buf);
原型:char *strncat(char *dest, const char *src, size_t n);
最多追加n个字符,然后自动补\0。比strncpy更安全,因为保证\0结尾。
4. strcmp与strncmp
(1) strcmp
strcmp逐字符比较两个字符串的字典序:
int result = strcmp("abc", "abd");
返回值含义:
< 0:s1小于s2= 0:s1等于s2> 0:s1大于s2
if (strcmp(s1, s2) == 0) {
printf("相等\n");
}
strcmp,不能用==。==比较的是两个指针的地址,不是字符串内容。
(2) strncmp
strncmp只比较前n个字符:
if (strncmp(str, "GET ", 4) == 0) {
printf("GET请求\n");
}
适合比较前缀或限定比较范围。
▶ 示例
字符串排序:对一组字符串按字典序排列:
#include <stdio.h>
#include <string.h>
void sort_strings(char arr[][32], int n) {
int i, j;
char temp[32];
for (i = 0; i < n - 1; i++) {
for (j = 0; j < n - 1 - i; j++) {
if (strcmp(arr[j], arr[j + 1]) > 0) {
strcpy(temp, arr[j]);
strcpy(arr[j], arr[j + 1]);
strcpy(arr[j + 1], temp);
}
}
}
}
int main(void) {
char names[5][32] = {
"david",
"alice",
"charlie",
"bob",
"eve"
};
int i;
sort_strings(names, 5);
for (i = 0; i < 5; i++) {
printf("%s\n", names[i]);
}
return 0;
}
alice
bob
charlie
david
eve
5. strlen
strlen返回字符串的有效长度(不含\0):
char s[] = "Hello";
printf("%zu\n", strlen(s));
printf("%zu\n", sizeof(s));
输出5和6。strlen数到\0为止,sizeof包含\0。
strlen遍历字符串找\0,时间复杂度O(n)。如果多次使用同一字符串的长度,建议缓存结果:
size_t len = strlen(s);
for (size_t i = 0; i < len; i++) {
}
而非每次循环条件都调用strlen。
6. strchr与strrchr
strchr在字符串中查找字符第一次出现的位置:
const char *p = strchr("Hello World", 'o');
if (p != NULL) {
printf("找到: %s\n", p);
}
输出"o World"。返回指向该字符的指针,未找到返回NULL。
strrchr查找字符最后一次出现的位置:
const char *p = strrchr("Hello World", 'o');
if (p != NULL) {
printf("最后出现: %s\n", p);
}
输出"orld"。
7. strstr
strstr在字符串中查找子串第一次出现的位置:
const char *p = strstr("Hello World", "World");
if (p != NULL) {
printf("子串位置: %s\n", p);
}
输出"World"。未找到返回NULL。
查找所有出现位置:
const char *text = "abababab";
const char *p = text;
while ((p = strstr(p, "ab")) != NULL) {
printf("位置 %ld\n", (long)(p - text));
p++;
}
每次找到后指针前进一步,继续搜索后续位置。
8. 自定义字符串函数实现
理解标准函数的实现原理,有助于深入掌握字符串操作的本质。
(1) 自定义strlen
size_t my_strlen(const char *s) {
size_t len = 0;
while (s[len] != '\0') {
len++;
}
return len;
}
(2) 自定义strcpy
char *my_strcpy(char *dest, const char *src) {
char *d = dest;
while ((*d++ = *src++) != '\0') {
}
return dest;
}
这个写法紧凑:*d++ = *src++先复制字符,再各自前进一步,直到复制了\0为止。
(3) 自定义strcmp
int my_strcmp(const char *s1, const char *s2) {
while (*s1 == *s2) {
if (*s1 == '\0') return 0;
s1++;
s2++;
}
return (unsigned char)*s1 - (unsigned char)*s2;
}
逐字符比较,相等就继续,遇到不等或\0就停止。返回差值表示大小关系。用unsigned char转换确保负字符值不会影响比较结果。
▶ 示例
自定义strcat和测试:
#include <stdio.h>
char *my_strcat(char *dest, const char *src) {
char *d = dest;
while (*d != '\0') {
d++;
}
while ((*d++ = *src++) != '\0') {
}
return dest;
}
int main(void) {
char buf[50] = "Hello";
my_strcat(buf, ", ");
my_strcat(buf, "World!");
printf("%s\n", buf);
return 0;
}
Hello, World!
先找到dest末尾的\0,然后从该位置开始复制src的内容。
❓ 常见问题
strncpy为什么不能自动补\0?strncpy设计初衷是填充固定长度缓冲区(如文件名字段),不足n时用\0填充,达到n时不加。作为安全strcpy使用时需手动补零。strcmp返回的具体数值有意义吗?==0、<0、>0判断。strcat多次拼接效率低怎么办?strcat都要从dest开头扫描到末尾再拼接,多次拼接时总效率O(n^2)。可以手动记录当前末尾位置,直接从末尾开始拼接。📖 小节
strcpy/strncpy复制字符串,strncpy更安全但需手动补\0strcat/strncat拼接字符串,strncat自动补\0strcmp/strncmp比较字符串,不能用==比较内容strchr/strrchr查找字符,strstr查找子串- 自定义实现帮助理解底层原理,实际项目用标准库
📝 作业
- 编写函数
void str_tolower(char *s),将字符串中所有大写字母转为小写。 - 编写函数
int str_count_char(const char *s, char ch),统计字符ch在字符串s中出现的次数。 - 不使用任何string.h函数,编写
my_strstr实现子串查找功能。