zl程序教程

您现在的位置是:首页 >  其他

当前栏目

C语言进阶——字符串&&内存函数(上)

2023-03-14 22:57:08 时间


目录


🌇前言


🌇正文


🌆字符串函数


🌉长度不可控的字符串函数


🌃strlen 长度统计


🌃strcpy 拷贝


🌃strcmp 比较


🌃strcat 追加


🌉长度可控的字符串操作函数


🌃strncpy 可控拷贝


🌃strncmp 可控比较


🌃strncat 可控追加


🌉特殊字符串函数


🌃strstr 寻找


🌃strtok 分割


🌃strerror 报错


🌆字符分类函数


🌃isdigit 十进制判断


🌃isxdigit 十六进制判断


🌃isupper 大写判断


🌃islower 小写判断


🌃toupper 转为大写


🌃tolower 转为小写


🌆内存函数


🌃memcpy 拷贝


🌃memmove 移动


🌃memcmp 比较


🌃memset 设置


🌇总结


🌇前言


eaa5a6b751c249ed81a615794b9bbe34.png

  这是牛客网上的一道简单题:判断输入字符是否为字母,一般的解决方法是通过ASCII码判断,不过这样做的话判断表达式较长,此时我们可以利用C语言中的库函数isalpha(判断是否为字母) 来完成这个题目,不仅代码量少,而且通俗易懂。要实现这种效果,就需要学习C语言中的各种库函数,而本文会列出大多数字符串函数和内存函数的使用及其实现,如果你想学习C语言库函数或对字符串、内存有好奇之心,不妨仔细来看看吧!🎉🎉🎉

此文章分为三部分:字符串函数、 字符分类函数、内存函数。

3b6315e98ac045e9823070764a4a5247.png

通过库函数简化后的代码

🌇正文

 首先我们从字符串函数开始介绍,顾名思义,字符串函数就是为字符串设计的函数,我们比较熟悉的有字符串长度统计函数 strlen、字符串比较函数 strcmp,除此之外还有很多实用的字符串函数,比如字符串追加、字符串分割、字符串寻找等等,话不多说,让我们直接进入主题:


3cab8a95305e49df8b406ed1507ef7ad.gif

🌆字符串函数

🌉长度不可控的字符串函数

下面介绍的是对目标字符串操作长度不可控的函数,使用场景相对有限。


🌃strlen 长度统计

 strlen 是用来统计字符串长度的一个库函数,因为字符串有个重要特征:以''作为结束标志。strlen 正是根据这一特点,从首地址开始,逐个比对统计,得出此字符串的长度。当然如果没有结束标记,strlen 就会一直往后找,得出一个随机值。

022459603aec44b7a31198dcd3b78afb.png

这是 strlen 的标准格式


28fcfa91b10842f982cbb9b194a3c70f.png

strlen 计算字符串长度

 

使用注意事项:

  • strlen 统计的是 之前出现的字符个数
  • 使用时,必须包含结束标志
  • 返回值是 size_t (unsigned int 无符号整型)
  • 112f5412beaa46e0b0173e0bb41b84b8.png模拟实现 strlen


 这个函数的工作原理我们已经清楚了,可以试着模仿库函数的方式,写出一个属于自己的 strlen。


 既然是模仿库函数,那么在返回类型、参数类型等方面要和库函数一致,在统计长度前,我们可以使用一个临时指针记录起始位置,当我们的源指针指向结束标志时,循环结束,将两个指针相减,就能得到元素个数(关于指针 - 指针得到元素个数) ,也就是字符串长度。下面来看看具体代码实现吧:

//strlen 计算字符串长度
size_t myStrlen(const char* p)
{
    assert(p);//断言,防止空指针
    char* tmp = p;//记录起始位置
    while (*p)
    {
        p++;//在循环内指向+1操作,避免位置出错
    }
    return (size_t)(p - tmp);//指针 - 指针得到元素个数
}
int main()
{
    char* pa = "Hello World!";
    //size_t len = strlen(pa);
    //printf("库函数实现结果:
%zu
", len);
    size_t len = myStrlen(pa);//此时调用我们写的函数
    printf("模拟函数实现结果:
%zu
", len);
    return 0;
}

  同样的,我们使用之前的示例来验证此函数的可行性 ,可以看到结果与库函数一致。

98841249671e448d86d077c55b4d1a60.png

🌃strcpy 拷贝


 字符串拷贝需要两个字符串,字符数组 dest(目标) 与字符串 src(源),strcpy 中只需要这两个参数,即把源字符串内容拷贝到目标字符数组中(源字符串中的结束标志也会拷贝),其中要确保字符数组  dest 可改变,数组空间也要足够大,不然装不下源字符串就尴尬了。


21027d40f7d846fc8fecf26635e26584.png

strcpy 标准格式

f487a968bee04855812ccbc23d2aaad0.png

使用注意事项:

  • 源字符串中必须包含
  • 源字符串中的 会拷贝到目标字符数组中
  • 目标空间必须足够大,能够装下源字符串
  • 目标空间必须是可修改的 
  • 0969bb1a09764f2faa1041eeae9b6588.png
  • 模拟实现 strcpy


 同样的,我们可以对这个函数进行模拟实现,拷贝的本质就是赋值,当源字符串中的首元素拷贝到目标字符数组中后仍然位于首位置,也就是说两个字符串元素拷贝位置是同步的,既然源字符串中的结束标志也要拷贝过去,那么我们就可以将其和赋值写进一个循环判断条件中(这样会构成一段非常奇妙的代码),这样一来我们整个程序的可读性就很不错了。

//strcpy 字符串拷贝
char* myStrcpy(char* dest, const char* src)
{
    assert(dest && src);//断言
    char* tmp = dest;//记录起始位置
    //当*src 为结束标志并赋给 *dest时,整体为假,
    //循环终止,目标数组也拿到了结束标志
    while (*dest++ = *src++)
    {
        ;//空语句
    }
    return tmp;//返回起始地址
}
int main()
{
    char arr1[20] = "xxxxxxxxx";
    char arr2[] = "Hello!";
    //printf("库函数实现结果:
%s
", strcpy(arr1, arr2));
    printf("模拟函数实现结果:
%s
", myStrcpy(arr1, arr2));
    return 0;
}

 当然,我们的模拟函数也能实现需求 

805be971ff25498a85a75e1e6866f8e5.png

🌃strcmp 比较

  字符串比较函数是从首字符开始比较,通过字符对应的ASCII码值对比,就能知道大小,如果str1>str2,返回1,如果str1<str2,返回-1,如果两个字符串相等,则返回0。因为比较并不需要改变值,所以使用常量字符串也能比较。

52a64b764d8047ed810a2bc24fd4ede9.png

strcmp 标准格式

72c04ba512c64196a2b2834fb3e07ecc.png

strcmp 的返回值

6345e21d3c4d44d9941c4297eec55f2f.png

使用注意事项:

  • 字符串大小比较时,与长度无关
  • 从首字符开始,逐字符比较
  • 通过字符对应的ASCII码值做对比
  • 86780521a3b94766b53534f7c69c47f7.png
  • 模拟实现 strcmp 

  我们可以通过指针的移动来模拟实现这个函数,即指向 str1 的指针 dest、指向 str2 的指针 src,对两个指针解引用后的值进行比较,如果相同就同时向后偏移,直到找到不同的值或移动到结束标志处停止,最后再分情况确定返回值就行了。

//strcmp 字符串比较
int myStrcmp(const char* dest, const char* src)
{
    assert(dest && src);//断言
    //当找到不同数或移动到处时,循环停止
    while (*dest == *src && (*dest || *src))
    {
        dest++;//没有找到不同的元素
        src++;//需要向后偏移
    }
    //分情况判断,确定返回值
    if (*dest - *src > 0)
        return 1;
    else if (*dest - *src < 0)
        return -1;
    else
        return 0;
}
int main()
{
    char* str1 = "BATZ";
    char* str2 = "BAT";
    printf("库函数实现结果:
%d
", strcmp(str1, str2));
    //printf("模拟实现函数结果:
%d
", myStrcmp(str1, str2));
    return 0;
}

  使用模拟函数通过测试用例:

87ad5e006002462997c025c16d723e99.png

🌃strcat 追加

 追加,就是在目标字符数组的末尾(处)添加源字符串的值,比如目标字符串数组中为abcd,源字符串为1234,经过追加后,字符数组就变为了abcd1234。值得一提的是,strcat 无法自己给自己追加,因为在追加过程中,目标字符数组结束标志会被覆盖掉,导致源字符串(其实就是目标字符,因为是自己给自己追加)中的结束标志也消失了,追加过程会无法停止。

1cef557cf3d14943bc8068c5a9e3d3cd.png

strcat 标准格式

d0497235f9bb435fb99d520f8efd1766.png

使用注意事项:

  • 源字符串和目标字符数组中都必须有
  • 目标空间必须足够大
  • 目标空间必须可修改,所以是字符数组 
  • 93ab5d6485e64bcdb88a9bc48f43e54b.png
  • 模拟实现 strcat


 既然是在目标字符数组的末尾处追加字符,就需要把指向首地址处的指针 dest 移向尾地址,当然在移动前要保存此地址,将此时的尾地址看作首地址2,将源字符串中的元素从此处开始拷贝至目标字符数组中,这样就完成了追加的操作,最后再返回之前记录的首地址就行了。

//strcat 字符串追加
char* myStrcat(char* dest, const char* src)
{
    assert(dest && src);//断言
    char* tmp = dest;//记录目标字符数组首地址
    while (*dest)
    {
        dest++;//将指针dest移动至尾元素处
    }
    //类似 strcpy 拷贝操作
    while (*src)
    {
        //判断条件用 *src就行了
        *dest++ = *src++;//确保源字符串中的每个元素都能追加上
    }
    return tmp;//返回首地址
}
int main()
{
    char arr1[20] = "ABCD";
    char arr2[] = "1234";
    //printf("库函数实现结果:
%s
", strcat(arr1,arr2));
    printf("模拟函数实现结果:
%s
", myStrcat(arr1, arr2));
    return 0;
}

b7fbbe4aa34544d9822c0933c87fe401.png

🌃strncmp 可控比较

  同样的,strncmp 也能控制比较长度,当然控制长度不能超过源字符串的长度,不然是无意义的

1ada5821d3cc46e58e3f32213d5176a6.png

strncmp 标准格式

c8557473e10a45d7aabf114b5304196e.png

返回值与 strcmp 完全一致

722b515ad11f4a83a56a02b8b4854d04.png

使用注意事项:

  • 与 strcmp 基本一致
  • 控制比较字节数不能为负数 
  • 8ee0a476338340ec99353c63973b470a.png
  • 模拟实现 strncmp

  这个模拟实现也比较简单,大体思路与 strcmp 的模拟一样,只是循环判断条件变为了 k 和 *dest (当对比到结束标志处,循环也会停止)。

//strncmp n个字符串比较
int myStrncmp(const char* dest, const char* src, size_t k)
{
    assert(dest && src);//断言
    //为何使用前置--?
    //因为这样能有效避免多判断一次的情况
    while (--k && *dest == *src)
    {
        dest++;//确保每位都能对比到
        src++;
    }
    //分情况返回
    if (*dest - *src > 0)
        return 1;
    else if (*dest - *src < 0)
        return -1;
    else
        return 0;
}
int main()
{
    char* str1 = "BATZ";
    char* str2 = "BAT";
    //printf("库函数实现结果:
%d
", strncmp(str1, str2, 3));
    printf("模拟函数实现结果:
%d
", myStrncmp(str1, str2, 3));
    return 0;
}

7c47796174134aa29f7448133c9ca000.png

🌃strncat 可控追加

 可控追加,旨在控制源字符串中字符追加数,比如目标字符数组为abcd,源字符串为1234,我们传递字节数为2,当追加结束后,目标字符数组变为abcd12,同所有可控家族成员一样,strncat 也会自动添加结束标志 。因此利用 strncat 自己给自己追加,能够很好的完成任务,避免结束标志吞噬问题。

1446abcbce694a1b803fa5a903c288da.png

strncat 标准格式

739533284aff4282932bf75f6c177552.png

使用注意事项:

  • 目标字符数组中必须有
  • 目标空间必须足够大
  • 目标空间必须可修改
  • 源字符串中可以不包含
  • e4addafa106a41c58831c1cf6893169d.png
  • 模拟实现 strncat

  代码大体与模拟 strcat 的一致,只不过有两个地方需要注意:1.循环判断条件 2.最后 的添加

//strncat n个字符串追加
char* myStrncat(char* dest, const char* src, size_t k)
{
    assert(dest && src);//断言
    char* tmp = dest;//记录起始位置
    //使指针移向目标字符数组的末尾处
    while (*dest)
    {
        dest++;
    }
    //判断条件为k,即传入的控制字节数
    while (k--)
    {
        *dest++ = *src++;//确保每个元素都能追加
    }
    *dest = '';
    return tmp;//返回目标字符数组的起始地址
}
int main()
{
    char arr1[20] = "xxxxxxx";
    char arr2[] = "Hello World!";
    //printf("库函数实现结果:
%s
", strncat(arr1, arr2, 5));
    printf("模拟函数实现结果:
%s
", myStrncat(arr1, arr2, 5));
    return 0;
}

5f0c8d56df694bd59330487755e14327.png

🌉特殊字符串函数

🌃strstr 寻找

  字符串寻找函数,作用是在目标字符串中寻找是否出现过源字符串,如果出现则返回第一次其在目标字符串中第一次出现的地址,如果没有出现,则返回一个空指针。

0a4b07591872488cb2d6e5adbabadc27.png

ststr 标准格式

c45b4b63d7f64447ba4340edc450b590.png

使用注意事项:

  • 只要传入的字符串地址就行了
  • 这个函数没有什么需要特别注意的事项

611686e6cb3440f8a4d3c38dd5484c74.png