# 20 字符串函数 ## 20.1 完整概念讲解 ### 字符串基础 C语言中字符串是**以`'\0'`结尾的字符数组**。字符串字面量(如`"hello"`)自动在末尾添加`'\0'`。 **字符串长度 vs 数组大小:** - `strlen()`:返回字符数(不含`'\0'`) - `sizeof`:返回数组总字节数(含`'\0'`) ### 标准字符串函数 | 函数 | 功能 | 安全版本 | | ---------- | ------------------ | --------- | | `strlen` | 计算字符串长度 | - | | `strcpy` | 复制字符串 | `strncpy` | | `strcat` | 连接字符串 | `strncat` | | `strcmp` | 比较字符串 | `strncmp` | | `strchr` | 查找字符首次出现 | - | | `strstr` | 查找子串首次出现 | - | | `strtok` | 分割字符串 | - | | `snprintf` | 安全格式化到字符串 | - | ### 字符串转换函数 | 函数 | 功能 | 说明 | | -------- | -------------- | ---------- | | `atoi` | 字符串转整数 | 无错误检测 | | `atof` | 字符串转浮点数 | 无错误检测 | | `strtol` | 字符串转长整数 | 可检测错误 | ## 20.2 核心API/语法 ```c #include // 字符串长度 size_t strlen(const char *s); // 字符串复制 char *strcpy(char *dest, const char *src); char *strncpy(char *dest, const char *src, size_t n); // 字符串连接 char *strcat(char *dest, const char *src); char *strncat(char *dest, const char *src, size_t n); // 字符串比较 int strcmp(const char *s1, const char *s2); int strncmp(const char *s1, const char *s2, size_t n); // 查找 char *strchr(const char *s, int c); char *strstr(const char *haystack, const char *needle); char *strtok(char *str, const char *delim); // 安全格式化 int snprintf(char *str, size_t size, const char *format, ...); // 字符串转换 int atoi(const char *str); double atof(const char *str); long strtol(const char *str, char **endptr, int base); ``` ## 20.3 代码示例(完整可编译,附gcc命令) ```c #include #include #include void demonstrate_basic(void) { printf("=== 基础字符串函数 ===\n"); char src[] = "Hello, World!"; char dest[50]; // strlen printf("strlen(\"%s\") = %zu\n", src, strlen(src)); printf("sizeof(src) = %zu\n", sizeof(src)); // strcpy strcpy(dest, src); printf("strcpy: dest = \"%s\"\n", dest); // strncpy(注意:不会自动添加'\0') char buf[10]; strncpy(buf, src, sizeof(buf) - 1); buf[sizeof(buf) - 1] = '\0'; printf("strncpy: buf = \"%s\"\n", buf); // strcat char str1[50] = "Hello"; char str2[] = " World"; strcat(str1, str2); printf("strcat: str1 = \"%s\"\n", str1); // strcmp printf("strcmp(\"abc\", \"abc\") = %d\n", strcmp("abc", "abc")); printf("strcmp(\"abc\", \"abd\") = %d\n", strcmp("abc", "abd")); printf("strcmp(\"abd\", \"abc\") = %d\n", strcmp("abd", "abc")); } void demonstrate_search(void) { printf("\n=== 查找函数 ===\n"); char str[] = "Hello, World!"; // strchr char *p = strchr(str, 'W'); if (p) { printf("strchr('W'): found at index %ld, rest = \"%s\"\n", p - str, p); } // strstr p = strstr(str, "World"); if (p) { printf("strstr(\"World\"): found at index %ld\n", p - str); } // strtok char data[] = "one,two,three,four"; char *token = strtok(data, ","); printf("strtok tokens: "); while (token != NULL) { printf("[%s] ", token); token = strtok(NULL, ","); } printf("\n"); } void demonstrate_snprintf(void) { printf("\n=== snprintf安全格式化 ===\n"); char buffer[50]; int result; result = snprintf(buffer, sizeof(buffer), "Number: %d, Float: %.2f", 42, 3.14); printf("snprintf: \"%s\" (would write %d chars)\n", buffer, result); // 截断测试 char small_buf[10]; result = snprintf(small_buf, sizeof(small_buf), "This is a long string"); printf("Truncated: \"%s\" (would write %d chars)\n", small_buf, result); } void demonstrate_conversion(void) { printf("\n=== 字符串转换 ===\n"); // atoi printf("atoi(\"123\") = %d\n", atoi("123")); printf("atoi(\"abc\") = %d\n", atoi("abc")); // strtol(更安全,可检测错误) char *endptr; long val; val = strtol("123abc", &endptr, 10); printf("strtol(\"123abc\"): val = %ld, endptr = \"%s\"\n", val, endptr); val = strtol("abc", &endptr, 10); printf("strtol(\"abc\"): val = %ld, endptr = \"%s\"\n", val, endptr); // 十六进制转换 val = strtol("0xFF", NULL, 16); printf("strtol(\"0xFF\", 16) = %ld\n", val); } int main() { demonstrate_basic(); demonstrate_search(); demonstrate_snprintf(); demonstrate_conversion(); return 0; } ``` **编译命令:** ```bash gcc -Wall -Wextra -o string_functions string_functions.c ./string_functions ``` ## 20.4 注意事项与易错点 1. **strncpy不添加'\0'**:如果src长度>=n,dest不会以'\0'结尾,需手动添加 2. **缓冲区溢出**:strcpy/strcat不检查目标空间,使用strncpy/strncat并指定大小 3. **strtok修改原字符串**:strtok会将分隔符替换为'\0' 4. **atoi无错误检测**:返回0表示失败或真的为0,无法区分 5. **字符串比较不能用==**:`==`比较指针地址,应用`strcmp` ## 20.5 面试要点(3-5个Q&A) **Q1: strcpy和strncpy有什么区别?** A: strcpy不检查目标空间,可能溢出。strncpy最多复制n个字符,但不保证添加'\0'。安全用法:`strncpy(dest, src, n-1); dest[n-1] = '\0';` **Q2: 为什么说atoi不安全?** A: atoi无法区分"0"和非法输入,没有错误检测。应使用strtol,通过endptr检测转换是否成功。 **Q3: strtok的线程安全性问题?** A: strtok使用静态内部状态保存位置,多线程调用不安全。可使用strtok_r(POSIX)或strtok_s(C11)替代。 **Q4: 如何安全地拼接多个字符串?** A: 使用snprintf计算所需空间,动态分配,或使用足够大的缓冲区。避免使用strcat多次追加导致溢出。 **Q5: sizeof和strlen对字符串的区别?** A: sizeof返回数组总大小(含'\0'),strlen返回字符数(不含'\0')。对指针使用sizeof得到的是指针大小,不是字符串长度。