← 返回文章列表

C 语言(八)|字符串与内存函数:实现、比较与边界安全

详细介绍C语言字符操作函数、字符串处理函数、内存操作函数的用法与模拟实现。

版权声明:本文为作者原创,版权归作者所有。如需转载或引用,请联系作者获得授权,并在使用时注明出处。

一、字符操作函数

字符操作函数是处理单个字符的工具,分为字符分类函数和字符转换函数两大类,所有函数都需要包含头文件<ctype.h>。

1.1 字符分类函数

这类函数的核心作用是:判断传入的字符是否符合指定类型,符合则返回非0整数,不符合则返回0。

函数名符合条件返回真(非0)
iscntrl任何控制字符
isspace空白字符(空格、换行\n、回车\r、制表符\t等)
isdigit十进制数字字符 '0'~'9'
isxdigit十六进制数字字符(0-9、a-f、A-F)
islower小写字母 a~z
isupper大写字母 A~Z
isalpha字母(a~z、A~Z)
isalnum字母或数字
ispunct标点符号(非字母、数字的可打印图形字符)
isgraph任何图形字符
isprint任何可打印字符(含图形字符和空白字符)

实例:判断字符是否为小写字母

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

int main() {
    char str[] = "Test String.\n";
    int i = 0;
    while (str[i]) {
        char c = str[i];
        if (islower(c)) {
            printf("%c是小写字母\n", c);
        }
        i++;
    }
    return 0;
}

1.2 字符转换函数

C语言标准库提供了两个专门的字符转换函数:

  • int tolower(int c):将大写字母转换为小写字母,非字母字符不做修改
  • int toupper(int c):将小写字母转换为大写字母,非字母字符不做修改

实例:字符串小写转大写

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

int main() {
    char str[] = "Test String. 123!";
    int i = 0;
    while (str[i]) {
        if (islower(str[i])) {
            str[i] = toupper(str[i]);
        }
        i++;
    }
    printf("转换结果:%s\n", str);
    return 0;
}

运行结果:转换结果:TEST STRING. 123!

二、字符串函数

所有字符串函数都需要包含头文件<string.h>。

2.1 strlen - 字符串长度统计

函数原型:size_t strlen(const char *str);

核心功能:统计str指向的字符串中,\0之前的字符个数(不包含\0本身)

关键坑点:

  • 字符串必须以\0结尾,否则strlen会继续向后遍历内存
  • 返回值类型是size_t(无符号整数),两个strlen结果相减永远不会得到负数

错误示例:

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

int main() {
    const char *str1 = "abcdef";
    const char *str2 = "bbb";

    if (strlen(str2) - strlen(str1) > 0) {
        printf("str2更长\n");
    } else {
        printf("str1更长\n");
    }
    return 0;
}

正确写法:if (strlen(str2) > strlen(str1))

strlen的三种模拟实现:

方式1:计数器法(最直观)

#include <stdio.h>
#include <assert.h>

int my_strlen(const char *str) {
    assert(str);
    int count = 0;
    while (*str) {
        count++;
        str++;
    }
    return count;
}

方式2:递归实现(不创建临时变量)

#include <stdio.h>
#include <assert.h>

int my_strlen(const char *str) {
    assert(str);
    if (*str == '\0')
        return 0;
    else
        return 1 + my_strlen(str + 1);
}

方式3:指针-指针法(最简洁)

#include <stdio.h>
#include <assert.h>

int my_strlen(const char *str) {
    assert(str);
    const char *start = str;
    while (*str) {
        str++;
    }
    return str - start;
}

2.2 strcpy - 字符串拷贝

函数原型:char strcpy(char dest, const char *src);

核心功能:将src指向的字符串拷贝到dest指向的空间

注意事项:

  • 源字符串必须以\0结束
  • 目标空间必须足够大
  • 目标空间必须可修改

模拟实现:

#include <stdio.h>
#include <assert.h>

char *my_strcpy(char *dest, const char *src) {
    assert(dest && src);
    char *ret = dest;
    while (*dest++ = *src++) {
        ;
    }
    return ret;
}

int main() {
    char dest[20];
    my_strcpy(dest, "Hello");
    printf("%s\n", dest);
    return 0;
}

2.3 strcat - 字符串追加

函数原型:char strcat(char dest, const char *src);

核心功能:将src指向的字符串追加到dest指向的字符串末尾

注意事项:

  • 源字符串必须以\0结束
  • 目标空间必须足够大,能容纳源字符串内容
  • 目标字符串的\0会被覆盖,追加后自动添加新的\0

模拟实现:

#include <stdio.h>
#include <assert.h>

char *my_strcat(char *dest, const char *src) {
    assert(dest && src);
    char *ret = dest;
    while (*dest) {
        dest++;
    }
    while (*dest++ = *src++) {
        ;
    }
    return ret;
}

int main() {
    char str[50] = "Hello";
    my_strcat(str, " World!");
    printf("%s\n", str);
    return 0;
}

2.4 strcmp - 字符串比较

函数原型:int strcmp(const char str1, const char str2);

比较规则:比较两个字符串中对应位置上字符的ASCII码值

返回值:

  • 第一个字符串 > 第二个字符串:返回正数
  • 第一个字符串 < 第二个字符串:返回负数
  • 两个字符串相等:返回0

模拟实现:

#include <stdio.h>
#include <assert.h>

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

int main() {
    printf("%d\n", my_strcmp("abc", "abd"));
    printf("%d\n", my_strcmp("abc", "abc"));
    return 0;
}

2.5 strncpy - 指定长度拷贝

函数原型:char strncpy(char dest, const char *src, size_t n);

核心功能:将src指向的字符串的前n个字符拷贝到dest

注意事项:如果src的长度小于n,则拷贝完src后会在后面补\0直到拷贝了n个字符

2.6 strncat - 指定长度追加

函数原型:char strncat(char dest, const char *src, size_t n);

核心功能:将src指向的字符串的前n个字符追加到dest末尾

2.7 strncmp - 指定长度比较

函数原型:int strncmp(const char str1, const char str2, size_t n);

核心功能:比较两个字符串的前n个字符

模拟实现:

#include <stdio.h>
#include <assert.h>

int my_strncmp(const char *str1, const char *str2, size_t num) {
    assert(str1 && str2);
    while (num > 0) {
        if (*str1 != *str2) {
            return (unsigned char)*str1 - (unsigned char)*str2;
        }
        if (*str1 == '\0') {
            return 0;
        }
        str1++;
        str2++;
        num--;
    }
    return 0;
}

2.8 strstr - 字符串查找

函数原型:char strstr(const char str1, const char *str2);

核心功能:在str1主串中查找str2子串第一次出现的位置

返回值:找到返回子串起始地址,找不到返回NULL

模拟实现(暴力匹配算法):

#include <stdio.h>
#include <assert.h>

char *my_strstr(const char *haystack, const char *needle) {
    assert(haystack && needle);
    const char *cp = haystack;
    
    if (*needle == '\0') {
        return (char *)haystack;
    }
    
    while (*cp) {
        const char *s1 = cp;
        const char *s2 = needle;
        
        while (*s1 != 0 && *s2 != 0 && *s1 == *s2) {
            s1++;
            s2++;
        }
        
        if (*s2 == '\0') {
            return (char *)cp;
        }
        cp++;
    }
    return NULL;
}

2.9 strtok - 字符串分割

函数原型:char strtok(char str, const char *delim);

核心功能:根据delim指定的分隔符集合,把str字符串拆分成多个子串

关键特性:

  • 首次调用传入待分割字符串,后续调用第一个参数传NULL
  • 会直接修改原字符串,将分隔符替换为\0
  • 多个连续分隔符视为一个

用法示例:

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

int main() {
    char arr[] = "wyh@luminous.com";
    char arr2[20] = {0};
    const char *delim = "@.";
    
    strcpy(arr2, arr);
    char *res = NULL;
    
    for (res = strtok(arr2, delim); res != NULL; res = strtok(NULL, delim)) {
        printf("%s\n", res);
    }
    return 0;
}

2.10 错误信息处理函数

2.10.1 strerror - 错误码转字符串

函数原型:char *strerror(int errnum);

核心功能:将错误码转换为对应的错误信息字符串

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

int main() {
    FILE *pf = fopen("data.txt", "r");
    if (pf == NULL) {
        printf("打开失败:%s(错误码:%d)\n", strerror(errno), errno);
        return 1;
    }
    return 0;
}

2.10.2 perror - 直接打印错误信息

函数原型:void perror(const char *s);

核心功能:自动打印当前errno对应的错误信息

#include <stdio.h>
#include <errno.h>

int main() {
    FILE *pf = fopen("data.txt", "r");
    if (pf == NULL) {
        perror("打开文件失败");
    }
    return 0;
}

三、内存操作函数

3.1 memcpy - 内存拷贝

函数原型:void memcpy(void dest, const void *src, size_t n);

核心功能:从src的位置开始向后拷贝n个字节的数据到dest

与strcpy的区别:memcpy可以拷贝任意类型的数据,不限于字符串

模拟实现:

#include <stdio.h>
#include <assert.h>

void *my_memcpy(void *dest, const void *src, size_t n) {
    assert(dest && src);
    void *ret = dest;
    while (n--) {
        *(char *)dest = *(char *)src;
        dest = (char *)dest + 1;
        src = (char *)src + 1;
    }
    return ret;
}

int main() {
    int arr1[] = {1, 2, 3, 4, 5};
    int arr2[5];
    my_memcpy(arr2, arr1, sizeof(arr1));
    for (int i = 0; i < 5; i++) {
        printf("%d ", arr2[i]);
    }
    return 0;
}

3.2 memmove - 内存移动

函数原型:void memmove(void dest, const void *src, size_t n);

核心功能:与memcpy相同,但可以安全处理源内存和目标内存重叠的情况

内存重叠处理:

  • 当dest < src:从前向后拷贝
  • 当dest > src且dest < src + n:从后向前拷贝

模拟实现:

#include <stdio.h>
#include <assert.h>

void *my_memmove(void *destination, const void *source, size_t num) {
    assert(destination && source);
    char *dest = (char *)destination;
    const char *src = (const char *)source;
    
    if (dest > src && dest < src + num) {
        // 内存重叠,从后向前拷贝
        for (size_t i = num; i > 0; i--) {
            dest[i - 1] = src[i - 1];
        }
    } else {
        // 无重叠,从前向后拷贝
        for (size_t i = 0; i < num; i++) {
            dest[i] = src[i];
        }
    }
    return destination;
}

// 测试代码
int main() {
    int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    my_memmove(arr + 2, arr, 20);
    
    for (int i = 0; i < 10; i++) {
        printf("%d ", arr[i]);
    }
    return 0;
}

3.3 memset - 内存设置

函数原型:void memset(void dest, int c, size_t n);

核心功能:将dest指向的内存的前n个字节设置为值c

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

int main() {
    char str[] = "Hello World";
    memset(str, 'x', 5);
    printf("%s\n", str);
    return 0;
}

运行结果:xxxxx World

3.4 memcmp - 内存比较

函数原型:int memcmp(const void ptr1, const void ptr2, size_t n);

核心功能:比较ptr1和ptr2指向的内存的前n个字节

返回值:与strcmp类似

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

int main() {
    int arr1[] = {1, 2, 3, 4, 5};
    int arr2[] = {1, 2, 3, 4, 6};
    printf("%d\n", memcmp(arr1, arr2, 4 * sizeof(int)));
    return 0;
}

四、常见面试题

题目1:不使用strlen计算字符串长度

#include <stdio.h>

int my_strlen(const char *str) {
    const char *end = str;
    while (*end) {
        end++;
    }
    return end - str;
}

int main() {
    char str[] = "hello";
    printf("%d\n", my_strlen(str));
    return 0;
}

题目2:模拟实现strstr

#include <stdio.h>

char *my_strstr(const char *haystack, const char *needle) {
    if (*needle == '\0') return (char *)haystack;

    while (*haystack) {
        const char *h = haystack;
        const char *n = needle;

        while (*h && *n && (*h == *n)) {
            h++;
            n++;
        }

        if (*n == '\0') {
            return (char *)haystack;
        }

        haystack++;
    }

    return NULL;
}

int main() {
    char str[] = "Hello World";
    char *result = my_strstr(str, "World");
    printf("%s\n", result ? result : "未找到");
    return 0;
}

五、总结

本篇我们学习了C语言字符串与内存函数的核心知识:

  1. 字符分类函数:islower、isupper、isdigit等判断字符类型
  2. 字符转换函数:tolower、toupper进行大小写转换
  3. 字符串函数:strlen、strcpy、strcat、strcmp及其变体
  4. 内存函数:memcpy、memmove、memset、memcmp操作任意内存

熟练掌握这些函数的底层原理和模拟实现,能帮助我们更好地理解C语言的内存管理机制,在笔试面试中游刃有余!