C语言字符串函数

C语言字符串函数全面详解(优化版)

我将基于您的讲义进行优化,为每个函数增加示例代码和大致实现原理,使内容更加全面和实用。

1. 字符串基础概念

字符串表示

在C语言中,字符串是以\0(空字符)结尾的字符数组。

// 三种常见的字符串表示方式
char str1[] = "Hello";           // 自动添加\0
char str2[6] = {'H','e','l','l','o','\0'}; // 手动添加\0
char *str3 = "Hello";            // 字符串字面量(常量区,不可修改)

// 验证字符串以\0结尾
char test[] = "hello";
printf("长度: %zu, 实际占用: %zu\n", strlen(test), sizeof(test)); 
// 输出: 长度: 5, 实际占用: 6 (包含\0)

实现原理:C语言通过检测空字符\0来确定字符串的结束位置,所有字符串函数都依赖这个约定。

2. 字符串长度函数

strlen

#include <string.h>
size_t strlen(const char *str);

功能:计算字符串长度(不包括\0

示例

char str[] = "Hello";
printf("长度: %zu\n", strlen(str)); // 输出: 5

// 错误用法:缺少\0的情况
char bad[] = {'a', 'b', 'c'}; // 没有\0结束符
printf("%zu\n", strlen(bad)); // 未定义行为,可能输出随机值

实现原理

// strlen的模拟实现
size_t my_strlen(const char *str) {
    size_t count = 0;
    while (*str != '\0') { // 遍历直到遇到\0
        count++;
        str++;
    }
    return count;
}

// 另一种实现:指针减法
size_t my_strlen2(const char *str) {
    const char *start = str;
    while (*str != '\0') str++;
    return str - start;
}

核心思想是遍历字符串直到遇到\0,统计字符数量。

3. 字符串复制函数

strcpy

char *strcpy(char *dest, const char *src);

功能:将src复制到dest(包括\0

示例

char dest[10];
strcpy(dest, "Hello"); // 安全
printf("复制结果: %s\n", dest); // 输出: Hello

// 危险用法:缓冲区溢出
// strcpy(dest, "This is too long"); // 可能导致程序崩溃

实现原理

// strcpy的模拟实现
char *my_strcpy(char *dest, const char *src) {
    char *ret = dest; // 保存目标字符串起始位置
    while ((*dest++ = *src++)) { // 逐个字符复制,包括\0
        ; // 空循环体
    }
    return ret; // 返回目标字符串指针
}

strncpy

char *strncpy(char *dest, const char *src, size_t n);

功能:安全复制,最多复制n个字符

示例

char dest[10];
strncpy(dest, "Hello World", 9);
dest[9] = '\0'; // 必须手动添加结束符
printf("安全复制: %s\n", dest); // 输出: Hello Wor

// 处理短于n的字符串
char dest2[10] = "*********";
strncpy(dest2, "Hi", 5);
printf("结果: %s\n", dest2); // 输出: Hi\0\0\0*** (需要手动添加\0)

实现原理

char *my_strncpy(char *dest, const char *src, size_t n) {
    size_t i;
    for (i = 0; i < n && src[i] != '\0'; i++) {
        dest[i] = src[i]; // 复制字符
    }
    for (; i < n; i++) {
        dest[i] = '\0'; // 剩余空间填\0
    }
    return dest;
}

4. 字符串连接函数

strcat

char *strcat(char *dest, const char *src);

功能:将src连接到dest末尾

示例

char path[50] = "/home/";
strcat(path, "user"); 
printf("路径: %s\n", path); // 输出: /home/user

char greeting[20] = "Hello";
strcat(greeting, " World");
printf("问候: %s\n", greeting); // 输出: Hello World

实现原理

char *my_strcat(char *dest, const char *src) {
    char *ret = dest;
    // 找到dest的结尾
    while (*dest != '\0') {
        dest++;
    }
    // 追加src内容
    while ((*dest++ = *src++)) {
        ; // 空循环体
    }
    return ret;
}

strncat

char *strncat(char *dest, const char *src, size_t n);

功能:安全连接,最多连接n个字符

示例

char buf[20] = "Hello";
strncat(buf, " World!!!", 6); // 只连接" World"
printf("结果: %s\n", buf); // 输出: Hello World

// 自动添加\0是strncat的优点
char small[10] = "Hi";
strncat(small, " there everyone", 7); // 只连接" there"
printf("安全连接: %s\n", small); // 输出: Hi there

5. 字符串比较函数

strcmp

int strcmp(const char *str1, const char *str2);

返回值

  • <0:str1 < str2
  • =0:str1 = str2
  • >0:str1 > str2

示例

printf("比较结果: %d\n", strcmp("apple", "banana")); // 负数
printf("比较结果: %d\n", strcmp("apple", "apple"));  // 0
printf("比较结果: %d\n", strcmp("banana", "apple")); // 正数

// 实际应用
if (strcmp(username, "admin") == 0) {
    printf("管理员登录\n");
}

实现原理

int my_strcmp(const char *str1, const char *str2) {
    while (*str1 && (*str1 == *str2)) {
        str1++;
        str2++;
    }
    return *(unsigned char *)str1 - *(unsigned char *)str2;
}

strncmp

int strncmp(const char *str1, const char *str2, size_t n);

功能:比较前n个字符

示例

// 比较前3个字符
if (strncmp("apple", "application", 3) == 0) {
    printf("前3字符相同\n"); // 会执行
}

// 文件扩展名检查
char filename[] = "document.txt";
if (strncmp(filename + strlen(filename) - 4, ".txt", 4) == 0) {
    printf("这是文本文件\n");
}

6. 字符串查找函数

strchr

char *strchr(const char *str, int c);

功能:查找字符c第一次出现的位置

示例

char *p = strchr("hello", 'l');
if (p != NULL) {
    printf("找到字符'l',位置: %ld\n", p - "hello"); // 输出: 2
}

// 提取文件扩展名
char filename[] = "photo.jpg";
char *dot = strchr(filename, '.');
if (dot != NULL) {
    printf("扩展名: %s\n", dot + 1); // 输出: jpg
}

strstr

char *strstr(const char *haystack, const char *needle);

功能:查找子串

示例

char *p = strstr("hello world", "world");
if (p != NULL) {
    printf("找到子串: %s\n", p); // 输出: world
}

// 检查电子邮件格式
char email[] = "user@example.com";
if (strstr(email, "@") != NULL) {
    printf("有效的电子邮件格式\n");
}

实现原理

char *my_strstr(const char *haystack, const char *needle) {
    if (*needle == '\0') return (char *)haystack;
    
    for (const char *p = haystack; *p != '\0'; p++) {
        const char *h = p, *n = needle;
        while (*h && *n && *h == *n) {
            h++;
            n++;
        }
        if (*n == '\0') return (char *)p; // 找到子串
    }
    return NULL; // 未找到
}

7. 字符串分割函数

strtok

char *strtok(char *str, const char *delim);

功能:根据分隔符分割字符串

示例

char data[] = "name=john;age=25;city=beijing";
char *token = strtok(data, ";=");
while (token != NULL) {
    printf("字段: %s\n", token);
    token = strtok(NULL, ";=");
}
// 输出:
// 字段: name  
// 字段: john
// 字段: age
// 字段: 25
// 字段: city
// 字段: beijing

// CSV文件解析
char csv[] = "Alice,25,Engineer,Boston";
char *field = strtok(csv, ",");
while (field != NULL) {
    printf("值: %s\n", field);
    field = strtok(NULL, ",");
}

注意事项

  • 会修改原字符串,将分隔符替换为\0
  • 非线程安全,使用strtok_r用于多线程

8. 内存操作函数(常用于字符串)

memcpy

void *memcpy(void *dest, const void *src, size_t n);

功能:内存复制(不处理重叠)

示例

char src[] = "hello";
char dest[10];
memcpy(dest, src, 6); // 复制5字符+\0
printf("复制结果: %s\n", dest); // 输出: hello

// 复制结构体数组
struct Person { char name[20]; int age; };
struct Person people1[5], people2[5];
// ...初始化people1...
memcpy(people2, people1, sizeof(people1)); // 高效复制

memset

void *memset(void *ptr, int value, size_t n);

功能:内存设置

示例

char buffer[100];
memset(buffer, 0, 100); // 清零初始化
memset(buffer, 'A', 10); // 前10字节设为'A'
buffer[10] = '\0';
printf("结果: %s\n", buffer); // 输出: AAAAAAAAAA

// 初始化结构体
struct Data { int id; char name[50]; };
struct Data item;
memset(&item, 0, sizeof(item)); // 全部初始化为0

9. 字符串转换函数

字符分类函数(ctype.h)

#include <ctype.h>

// 检查字符类型
char c = 'A';
if (isalpha(c)) printf("%c 是字母\n", c);
if (isdigit(c)) printf("%c 是数字\n", c);
if (isalnum(c)) printf("%c 是字母或数字\n", c);

// 输入验证
char input[100];
fgets(input, sizeof(input), stdin);
if (isdigit(input[0])) {
    printf("第一个字符是数字\n");
}

大小写转换

// 单个字符转换
printf("小写: %c\n", tolower('A')); // 输出: a
printf("大写: %c\n", toupper('a')); // 输出: A

// 整个字符串转换
char text[] = "Hello World";
for (int i = 0; text[i] != '\0'; i++) {
    text[i] = tolower(text[i]);
}
printf("全小写: %s\n", text); // 输出: hello world

10. 数值转换函数

字符串转数值

#include <stdlib.h>

// 基本转换
int num1 = atoi("123");         // 字符串转整数
double num2 = atof("3.14");     // 字符串转浮点数
long num3 = atol("1000000");   // 字符串转长整数

// 安全版本(可检测错误)
char *endptr;
long value = strtol("123abc", &endptr, 10);
if (*endptr == '\0') {
    printf("转换成功: %ld\n", value);
} else {
    printf("转换失败,无效字符: %s\n", endptr);
}

数值转字符串

#include <stdio.h>

// 使用sprintf
char buffer[20];
int num = 255;
sprintf(buffer, "%d", num);
printf("字符串: %s\n", buffer); // 输出: 255

// 安全版本snprintf
char safe_buf[10];
int written = snprintf(safe_buf, sizeof(safe_buf), "数值: %d", 12345);
if (written >= sizeof(safe_buf)) {
    printf("缓冲区不足,需要%d字节\n", written + 1);
}
printf("安全格式化: %s\n", safe_buf);

11. 安全字符串处理技巧

自定义安全函数

// 安全的字符串复制
char* safe_strcpy(char *dest, const char *src, size_t dest_size) {
    if (dest_size == 0) return dest;
    
    strncpy(dest, src, dest_size - 1);
    dest[dest_size - 1] = '\0'; // 确保字符串终止
    return dest;
}

// 安全的字符串连接
char* safe_strcat(char *dest, const char *src, size_t dest_size) {
    size_t dest_len = strlen(dest);
    if (dest_len >= dest_size) return dest; // 没有空间了
    
    size_t avail = dest_size - dest_len - 1; // 保留空间给\0
    strncat(dest, src, avail);
    return dest;
}

// 使用示例
char path[20] = "/home/";
safe_strcat(path, "verylongusername", sizeof(path));
printf("安全路径: %s\n", path); // 不会溢出

12. 综合应用示例

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

void comprehensive_string_demo() {
    printf("=== C语言字符串函数综合演示 ===\n");
    
    // 1. 基本操作
    char text[100] = "Hello World";
    printf("1. 原始: %s\n", text);
    printf("   长度: %zu\n", strlen(text));
    
    // 2. 查找和替换
    char *found = strstr(text, "World");
    if (found) {
        strncpy(found, "C Language", 11);
    }
    printf("2. 替换后: %s\n", text);
    
    // 3. 分割字符串
    char data[] = "apple,banana,cherry";
    printf("3. 分割结果:\n");
    char *token = strtok(data, ",");
    while (token) {
        printf("   - %s\n", token);
        token = strtok(NULL, ",");
    }
    
    // 4. 数值转换
    char num_str[] = "123.45";
    double num = atof(num_str);
    printf("4. 数值转换: %s -> %.2f\n", num_str, num);
    
    // 5. 大小写转换
    char mixed[] = "HeLLo WoRLd";
    for (int i = 0; mixed[i]; i++) {
        mixed[i] = tolower(mixed[i]);
    }
    printf("5. 小写转换: %s\n", mixed);
    
    // 6. 内存操作应用
    char source[] = "Memory operations";
    char destination[50];
    memcpy(destination, source, strlen(source) + 1); // +1包含\0
    printf("6. 内存复制: %s\n", destination);
}

int main() {
    comprehensive_string_demo();
    return 0;
}

13. 常见错误和最佳实践

要避免的错误

// 错误1: 缓冲区溢出
char small[5];
// strcpy(small, "This is too long"); // 危险!会导致缓冲区溢出

// 正确做法:使用安全函数
strncpy(small, "Hello", sizeof(small));
small[sizeof(small) - 1] = '\0';

// 错误2: 使用未初始化的指针
char *ptr;
// strcpy(ptr, "hello"); // 未定义行为,可能崩溃

// 正确做法:分配内存
ptr = malloc(20 * sizeof(char));
if (ptr != NULL) {
    strcpy(ptr, "hello");
    free(ptr); // 记得释放
}

// 错误3: 修改字符串字面量
char *p = "constant";
// p[0] = 'C'; // 尝试修改只读内存,会崩溃

// 正确做法:使用字符数组
char safe[] = "modifiable";
safe[0] = 'M'; // 可以修改

最佳实践总结

  1. 总是检查缓冲区大小,使用sizeof操作符获取数组大小
  2. 优先使用安全函数(带n的版本,如strncpystrncat
  3. 手动添加字符串结束符,特别是在使用strncpy
  4. 验证函数返回值,特别是动态内存分配和文件操作
  5. 使用const保护不应修改的字符串
  6. 避免使用不安全的函数,如sprintf,改用snprintf

通过理解每个函数的实现原理和正确用法,您可以编写出更加健壮、安全的C语言程序。本指南涵盖了C语言中最重要的字符串处理函数,掌握这些函数对于系统级编程至关重要。

posted @ 2025-12-04 11:45  FxorG  阅读(314)  评论(0)    收藏  举报