【维生素C语言】第十一章 - 字符串函数与内存函数

386 阅读12分钟

「这是我参与2022首次更文挑战的第14天,活动详情查看:2022首次更文挑战」。

原标题:string.h和stype.h常用函数讲解,隔壁老王看了直呼eazy!【C语言】

前言:

📚 在C语言中对字符和字符串的处理是很常见的,但是C语言本身是并没有字符串类型的,字符串通常放在 常量字符串 中或 字符数组 中。字符串常量 适用于那些对它不做修改的字符串函数。

一、求字符串长度

0x00 strlen 函数

📜 头文件: string.h

🔍 链接:strlen - C++ Reference

📚 说明:字符串以 \0 作为结束标志,strlen 返回的是在字符串中 \0 前面出现的字符个数

📌 注意事项:

① 参数指向的字符串必须以 \0 结束

② 函数的返回值为 size_t ,无符号(unsigned)

💬 代码演示:

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

int main()
{
    int len = strlen("abcdef");
    printf("%d\n", len);

    return 0;
}

🚩 6

二、长度不受限制的字符串函数

0x00 strcpy 函数

📜 头文件: string.h

🔍 链接:strcpy - C++ Reference

📚 说明:字符串拷贝,将含有 \0 的字符串复制到另一个地址空间,返回值的类型为 char*

📌 注意事项:

① 源字符串 src 必须以 \0 结束

② 会将源字符串 src 中的 \0 一同拷贝到目标空间 dest

③ 目标空间必须足够大,以确保能够存放源字符串 dest (下面讲 strncmp 的时候演示)

④ 目标空间必须可变,即目标空间 dest 不可以被 const 声明

💬 代码演示:

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

int main()
{
    char arr1[] = "abcdefghi";
    char arr2[] = "123";
    printf("拷贝前:%s\n", arr1);

    strcpy(arr1, arr2); // 字符串拷贝(目标空间,源字符串)

    printf("拷贝后:%s\n", arr1);

    return 0;
}

🚩 运行结果如下:

0x02 strcat 函数

📜 头文件: string.h

🔍 链接: www.cplusplus.com/reference/c…

📚 说明:将 src 所指向的字符串复制到 dest 所指向的字符串后面(删除 *dest 原来末尾的 \0 )

📌 注意事项:

① 源字符串 src 必须以 \0 结束

② 会将源字符串 src 中的 \0 一同拷贝到目标空间 dest ,并删除 *dest 原来末尾的 \0

③ 目标空间必须足够大,以确保能够存放源字符串 dest

④ 目标空间必须可变,即目标空间 dest 不可以被 const 声明

💬 代码演示:

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

int main()
{
    char arr1[30] = "hello";
    char arr2[] = "world";
    
    strcat(arr1, arr2);
    printf("%s\n", arr1);

    return 0;
}

🚩 hello world

0x03 strcmp 函数

📜 头文件: string.h

🔍 链接: www.cplusplus.com/reference/c…

📚 说明:用于比较两个字符串并根据比较结果返回整数, 两个字符串自左向右逐个字符相比,按照 ASCII值 大小相比较,从第一对字符开始比,如果相等则比下一对,直到出现不同的字符或遇 \0 才停止。对比规则如下:

💬 代码演示:

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

int main()
{   
    char *p1 = "abcdef";
    char *p2 = "aqwer";
    int ret = strcmp(p1, p2); // p1和p2比
    // a==a, 对比下一对,b<q,所以p2大
    printf("%d\n", ret);

    return 0;
}

🚩 -1( 返回负数,所以 p1< p2 )

🔑 解析:

📌 注意事项:根据编译器的不同,返回的结果也不同

VS2013 中,大于返回 1,等于返回 0,小于返回 -1。但在 Linux-gcc 中,大于返回正数,等于返回0,小于返回负数。因此,我们需要注意判断部分的写法:

// 不推荐 ❌
    if(strcmp(p1, p2) == 1) {
        printf("p1 > p2");
    } else if(strcmp(p1, p2 == 0)) {
        printf("p1 == p2");
    } else if(strcmp(p1, p2) == -1) {
        printf("p1 < p2");
    }
    
// 推荐 ✅
    if(strcmp(p1, p2) > 0) {
        printf("p1 > p2");
    } else if(strcmp(p1, p2 == 0)) {
        printf("p1 == p2");
    } else if(strcmp(p1, p2) < -1) {
        printf("p1 < p2");
    }

三、长度受限制的函数字符串

0x00 strncpy 函数

📜 头文件: string.h

🔍 链接: www.cplusplus.com/reference/c…

📚 说明:从源字符串中拷贝 n 个字符到目标空间

📌 注意事项:

① 如果源字符串的长度小于 n,则拷贝完源字符串之后,在目标的后面追加 0,填充至 n 个

② dest 和 src 不应该重叠(重叠时可以用更安全的 memmove 替代)

③ 目标空间必须足够大,以确保能够存放源字符串 dest

④ 目标空间必须可变,即目标空间 dest 不可以被 const 声明

💬 代码演示:

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

int main()
{
    char arr1[5] = "abc";
    char arr2[] = "hello world";
    strncpy(arr1, arr2, 4); // 从arr2中拷贝4个到arr1
    printf("%s\n", arr1);

    return 0;
}

🚩 hell

❌ 目标空间不够大会导致报错:

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

int main()
{
    char arr1[5] = "abc"; // 大小为5的数组
    char arr2[] = "hello world";
    strncpy(arr1, arr2, 6); // 要求拷贝6个字节
    printf("%s\n", arr1);

    return 0;
}

🚩 运行结果如下:

0x01 strncat 函数

📜 头文件: string.h

🔍 链接: strncat - C++ Reference

📚 说明:追加 n 个字符到目标空间

📌 注意事项:如果源字符串的长度小于 n,则只复制 \0 之前的内容。

💬 代码演示:

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

int main()
{
    char arr1[30] = "hello";
    char arr2[] = "world";
    strncat(arr1, arr2, 3); // 从arr2中取3个追加到arr1中
    printf("%s\n", arr1);

    return 0;
}

🚩 hellowor

0x02 strncmp 函数

🔍 链接: strncmp - C++ Reference

📚 说明:比较到出现另个字符不一样或者一个字符串结束或者 n 个字符全部比较完。

( 除了增了了个 n,其他和 strcmp 一样 )

💬 代码演示:

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

int main()
{
    const char* p1 = "abczdef";
    const char* p2 = "abcqwer";
    // int ret = strcmp(p1, p2);
    int ret = strncmp(p1, p2, 1);
    int ret2 = strncmp(p1, p2, 4);
    printf("%d %d\n", ret, ret1);

    return 0;
}

🚩 0 1

四、字符串查找

0x00 strstr 函数

📜 头文件: string.h

🔍 链接: strstr - C++ Reference

📚 说明:返回字符串中首次出现子串的地址。若 str2 是 str1 的子串,则返回 str2 在 str1 中首次出现的地址。如果 str2 不是 str1 的子串,则返回 NULL 。

💬 代码演示:是子串,返回首次出现的地址

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

int main()
{
    char* p1 = "abcdef";
    char* p2 = "def";
    char* ret = strstr(p1, p2); // 判断p2是否是p1的子串
    printf("%s\n", ret);

    return 0;
}

🚩 def ( p2 是 p1 的子串,所以返回 def )

💬 代码演示:不是子串,返回 NULL

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

int main()
{
    char* p1 = "abcdef";
    char* p2 = "zzz";
    char* ret = strstr(p1, p2);
    printf("%s\n", ret);

    return 0;
}

🚩 (null)

💬 我们用 if 判断来添加描述,更好地呈现:

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

int main()
{
    char* p1 = "abcdef";
    char* p2 = "def";
    char* ret = strstr(p1, p2);
    if ( ret == NULL ) {
        printf("子串不存在\n");
    } else {
        printf("%s\n", ret);
    }
    return 0;
}

0x01 strtok 函数

📜 头文件: string.h

🔍 链接: strtok - C++ Reference

📚 说明:

📌 注意事项:strtok 会破坏原字符串,分割后原字符串保留第一个分割符前的字符

💬 代码演示:分割ip

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

int main()
{
    //192.168.0.1
    //192 168 0 1 - strtok

    char ip[] = "192.168.0.1";
    // const char* sep = ".";
    // char* ret = strtok(ip, sep);
    char* ret = strtok(ip, ".");
    printf("%s\n", ret);

    ret = strtok(NULL, ".");
    printf("%s\n", ret);

    ret = strtok(NULL, ".");
    printf("%s\n", ret);

    ret = strtok(NULL, ".");
    printf("%s\n", ret);

    return 0;
}

🚩 运行结果如下:

💬 代码演示:分割邮箱

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

int main()
{
    //1300300100@qq.com
    //1300300100 qq com
    char arr[] = "1300300100@qq.com";
    printf("原字符串: %s\n", arr);
    
    const char* sep = "@."; // 创建sep
    char arr1[30];
    char* ret = NULL;
    strcpy(arr1, arr); // 将数据拷贝一份,保留arr数组的内容

    // 分行打印切割内容
    for (ret = strtok(arr, sep); ret != NULL; ret = strtok(NULL, sep)) {
        printf("%s\n", ret);
    }

    printf("保留的原内容:%s\n", arr1); // 保存的arr数组的内容
    printf("分割后原字符串被破坏: %s\n", arr); // 分割后原字符串保留第一个分割符前的字符

    return 0;
}

🚩 运行结果如下:

0x02 strerror 函数

📜 头文件: string.h

🔍 链接: strerror - C++ Reference

📚 说明:返回错误码,返回错误码所对应的错误信息

💬 代码演示:

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

int main()
{
     // 错误码       错误信息
     // 0 -          No error
     // 1 -          Operation not permitted
     // 2 -          No such file or directory
     // ...

    //errno 是一个全局的错误码变量
    //当c语言的库函数在执行过程中,发生了错误,
    //就会把对应的错误码赋值到errno中
    char* str = strerror(errno);
    printf("%s\n", str);

    return 0;
}

🚩 No error

📚 关于 errno:查看 errno 的详细介绍 [百度百科]

errno 是记录系统的最后一次错误代码。代码是一个 int 型的值,在 errno.h 中定义

💬 文件操作的时候可以使用(后面会讲文件操作)

FILE* pf = fopen("test.txt", "r");
if ( pf == NULL ) {
    printf("%s\n", strerror(errno));
} else {
    printf("*****open file success*****\n")
}

五、字符操作

0x00 字符分类

📜 头文件: ctype.h

💬 代码演示:islower

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

int main()
{
    char ch1 = 'a';
    int ret = islower(ch1); // 判断ch1是否为小写
    printf("%d\n", ret);

    char ch2 = 'B';
    int res = islower(ch2); // 判断ch2是否为小写
    printf("%d\n", res);

    return 0;
}

🚩 运行结果如下:

0x01 字符转换

📜 需引入头文件 ctype.h

💬 代码演示:tolower

int main()
{
    char ch = tolower('Q'); // 大写转小写
    putchar(ch);

    return 0;
}

🚩 q

💬 代码演示:toupper

int main()
{
    char ch = toupper('q'); // 小写转大写
    putchar(ch);
    
    return 0;
}

🚩 Q

💬 代码演示:字符串内容全部大写转小写( 利用 while 循环 )

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

int main()
{
    char arr[] = "I Am A Student";
    int i = 0;
    while(arr[i]) {
        if ( isupper(arr[i]) ) {
            arr[i] = tolower(arr[i]);
        } 
        i++;
    }
    printf("%s\n", arr);

    return 0;
}

🚩 i am a student

💬 模拟实现 Python 中的 swapcase 函数 ( 字符串大小写互换 )

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

void swapcase(char arr[]) 
{
    assert(arr != NULL); // 断言防止传空
    int i = 0;
    while (arr[i] != '\0') {
        if (islower(arr[i])) { //是小写吗?
            arr[i] = toupper(arr[i]); //如果是,让它变成大写
        } else {  //不是小写
            arr[i] = tolower(arr[i]); //把它变成小写
        }
        i++;
    }
}

int main()
{
    char arr[] = "AaBbCcDdEeFf";
    swapcase(arr);
    printf("%s\n", arr);

    return 0;
}

🚩 aAbBcCdDeEfF

六、字符操作函数

0x00 memcpy 函数

📜 头文件: string.h

🔍 链接: memcpy - C++ Reference

📚 说明:从源内存地址 src 的起始位置开始拷贝 n 个字节到目标内存地址 dest 中

📌 注意事项:

① memcpy 没有刹车,这个函数遇到 \0 并不会停下来

② 如果 src 和 dest 有任何的重叠,复制的结果都是未定义的

💬 代码演示:

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

int main()
{
    int arr1[] = {1, 2, 3, 4, 5};
    int arr2[5] = {0};
    memcpy(arr2, arr1, sizeof(arr1));

    // 打印 arr2 的内容
    int i = 0;
    for(i=0; i<5; i++) {
        printf("%d ", arr2[i]);
    }

    return 0;
}

🚩 1 2 3 4 5

💬 代码演示:拷贝结构体

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

struct S
{
    char name[20];
    int age;
};

int main()
{
    struct S arr3[] = { {"张三", 20}, {"李四", 30} };
    struct S arr4[3] = { 0 };

    memcpy(arr4, arr3, sizeof(arr3));

    return 0;
}

🔑 调试一下看看是否拷贝成功:

0x02 memmove 函数

📜 头文件: string.h

🔍 链接: memcpy - C++ Reference

📚 说明:用于拷贝字节,如果目标区域和源区域有重叠时,memmove 能够保证源串在被覆盖之前将重叠区域的字节拷贝到目标区域中,但复制后源内容会被更改。

📌 注意事项:

① 和 memcpy 的差别就是 memmove 函数处理的源内存块和目标内存块时可以重叠的

② 如果原空间和目标空间出现重叠,应使用 memmove 函数处理

C语言标准要求:

memcpy 用来处理不重叠的内存拷贝,而 memmove 用来处理重叠内存的拷贝。

💬 代码演示:

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

int main()
{
    int arr[] = {1,2,3,4,5,6,7,8,9,10};
    int i = 0;

    memmove(arr+2, arr, 20);

    for(i=0; i<10; i++) {
        printf("%d ", arr[i]);
    }

    return 0;
}

🚩 运行结果如下:

0x03 memcmp 函数

📜 头文件: string.h

🔍 链接: memcmp - C++ Reference

📚 说明:比较 ptr1 和 ptr2 指针开始的 n 个字节,按字节比较

📌 注意事项:memcmp 不同于 strcmp,memcmp 遇到 \0 不会停止比较

💬 代码演示:

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

int main()
{
    float arr1[] = {1.0, 2.0, 3.0, 4.0};
    float arr2[] = {1.0, 3.0};
    int ret = memcmp(arr1, arr2, 8); // arr1是否比arr2大,比较8个字节
    printf("%d\n", ret);
    
    return 0;
}

🚩 -1 ( 说明 arr1 小于 arr2 )

0x04 memset 函数

📜 头文件: string.h

🔍 链接: www.cplusplus.com/reference/c…

📚 说明:将某一块内存中的内容全部设置为指定的值,通常为新申请内存做初始化工作。

📌 注意事项:memset 是以字节为单位设置内存的

💬 代码演示:把整型数组将前 20 个字节全部设置为 1

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

int main()
{
    // 40
    int arr[10] = {0};
    memset(arr, 1, 20); // 将前20个字节全部设置为1

    return 0;
}