说明

这是一个完善了但又不完善的笔记,或许以后会更新

可以参考但请务必超越

源文件


Tools

VS2019
VScode
Typora

字符串

本章重点

重点介绍处理字符和字符串的函数的使用和注意事项

1. 求字符串长度

1.1 strlen

size_t strlen ( const char * str );

size_t 右键或F12转到定义

    int sz = strlen("abcdef");//接受返回值用int时,有些编译器其实是会报错的
    //因为strlen他的类型是
    //size_t - unsigned int
    printf("%d\n", sz);//没问题
    printf("%d\n", strlen("abcdef"));//直接打印也是不太合适的

应该这样写

    size_t sz = strlen("abcdef");
    printf("%u\n", sz);//使用%u

数组也没问题

    char arr[] = "abcdef";
    size_t sz1 = strlen("abcdef");
    size_t sz2 = strlen(arr);
    printf("%u\n", sz1);
    printf("%u\n", sz2);

还要注意她是无符号的

    if (strlen("abc") - strlen("abcdef") > 0)
    {
        printf("hehe\n");
    }
    else
    {
        printf("haha\n");
    }
//3-6,原本是小于0的,但是strlen是无符号的size_t,相减也是无符号数,一定是大于0的。
//一定要打印haha的话,可以强制类型转换(int)

字符串已经 '0' 作为结束标志,strlen函数返回的是在字符串中 '0' 前面出现的字符个数(不包含 '0' )。

    char arr[] = { 'a','b','c' };
    size_t sz = strlen(arr);
    printf("%u\n", sz);

这样写的话不知道字符c后面什么时候才会出现0,后面都是随机值

参数指向的字符串必须要以 '0' 结束。

strlen的模拟实现:

1.计数器

2.指针-指针

3.递归

已经模拟过很多次了,可以回顾下代码

2. 长度不受限制的字符串函数

因为遇到0才会停止,就会经常发生空间不足或者超出空间的情况,所以编译器会警告说不安全。

后面还有带n的版本,会相对比较安全。

2.1 strcpy

字符串拷贝

char * strcpy ( char * destination, const char * source );

拷贝到原字符串的0

拷贝的时候会吧0也拷贝进去,后面的都不会拷贝

原字符串没有0就会出问题

一直向后拷贝,访问了非法内存

    char arr1[] = "xxxxxxxxxx";
    char arr2[] = { 'a','b','c' };
    strcpy(arr1, arr2);

空间不足,常量修饰,都不行

目标空间必须足够大,而且不能是常量字符串

    const char* p = "xxxxxxxxxx";
    char arr1[] = "x";
    char arr2[] = "hello\0abc";
    strcpy(arr1, arr2);
    strcpy(p, arr2);
//都是错误的

还有之前使用scanf时提示的

#define _CRT_SECURE_NO_WARNINGS 1

strcpy其实也需要,因为他是个愣头青。

如果没有这条#define的话,编译器就会像使用scanf一样提出警告

表示函数不安全

我们可以查看下目标空间不足的情况

仍然会拷贝过去,拷贝完了,再报错。

作为一个合格的程序猿,我们必须保证目标空间放得下

strcpy的模拟实现

char* my_strcpy(char* dest, const char* scr)
{
    char* p = dest;
    //assert(dest != NULL);
    //assert(scr != NULL);
    assert(dest && scr);
    while (*dest++ = *scr++)
    {
        ;
    }
    return p;
}

int main()
{
    char p1[] = "1";
    char p2[] = "2";
    printf("%s\n", my_strcpy(p1, p2));
    return 0;
}

2.2 strcat

字符串连接 - 字符串追加

char * strcat ( char * destination, const char * source );
    char arr1[20] = "abc";//目标空间要足够
    strcat(arr1, "def");//必须有\0
    printf("%s\n", arr1);

    char arr1[20] = "abc";
    char arr2[] = { 'd','e','f','\0' };//没有\0是不行的
    strcat(arr1, arr2);
    printf("%s\n", arr1);

strcat的模拟实现

char* my_strcat(char* dest, char* src)
{
    //首先断言不是空指针
    assert(dest && src);
    //strcat返回的是目标空间的起始地址
    char* ret = dest;//存起来
    //1.找到目标字符串的末尾\0
    while (*dest)
    {
        dest++;
    }
    //2.追加源字符串到\0
    while (*dest++ = *src++)
    {
        ;
    }
    return ret;//把起始地址返回
}

int main()
{
    char arr1[20] = "abc";
    char arr2[] = { 'd','e','f','\0' };

    printf("%s\n", my_strcat(arr1, arr2));
    return 0;
}

2.3 strcmp

比较字符串内容大小

注意不是长度

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

第一个字符串小于第二个字符串,返回小于0的值;

第一个字符串等于第二个字符串,返回0;

第一个字符串大于第二个字符串,返回大于0的值。

遇到0或者已经发现不相等时,就会停下

    char arr1[] = "abc";
    char arr2[] = "ac";//c比b大,会返回小于0的值
    int ret = strcmp(arr1, arr2);
    printf("%d\n", ret);

strcmp的模拟实现

int my_strcmp(const char* s1, const char* s2)
{
    assert(s1 && s2);
    while (*s1 == *s2)
    {
        if (*s1 == '\0')
        {
            return 0;
        }
        s1++;
        s2++;
    }
    return *s1 - *s2;
}

int main()
{
    char arr1[] = "abc";
    char arr2[] = "ac";
    int ret = my_strcmp(arr1, arr2);
    if (ret<0)
    {
        printf("<\n");
    }
    else if (ret = 0)
    {
        printf("=\n");
    }
    else
    {
        printf(">\n");
    }
    return 0;
}

3. 长度受限制的字符串函数介绍

3.1 strncpy

char * strncpy ( char * destination, const char * source, size_t num );
    char arr1[] = "xxxxx";
    char arr2[] = "abc";
    strncpy(arr1, arr2, 3);//指定拷贝几个字符
    printf("%s\n", arr1);

如果指定数量超出了字符数量

不够就用0补

strncpy的模拟实现

带n版本的实现其实就很容易了,在strcpy的基础上添加指定数量即可。strncat和strncmp同理。

3.2 strncat

char * strncat ( char * destination, const char * source, size_t num );
    char arr1[] = "abc\0xxxxx";
    char arr2[] = "defg";
    strncat(arr1, arr2, 3);//只追加3个

可以看到他会从原来的0开始补,并且尾部会补上0

且如果要追加的数量大于原本字符的数量,那也只会补到0

strncat的模拟实现

带n版本的实现其实就很容易了,在strcat的基础上添加指定数量即可。strncpy和strncmp同理。

3.3 strncmp

int strncmp ( const char * str1, const char * str2, size_t num );
    char arr1[] = "abcdef";
    char arr2[] = "abce";
    strncmp(arr1, arr2, 3);//只比较前三个
    char arr1[] = "abcdef";
    char arr2[] = "abce";
    strncmp(arr1, arr2, 4);//比4个的话e比d大,就会返回一个小于0的数

strncmp的模拟实现

带n版本的实现其实就很容易了,在strcmp的基础上添加指定数量即可。strncpy和strncat同理。

编译器给的版本

strcpy_s

strcmp_s

strcat_s

以上带s的版本只是vs给的,不是C语言给的,所以换个编译器就没法用了。

而且使用方法也类似带n的版本,但是内部实现就各有各的了。

4. 字符串查找

4.1 strstr

const char * strstr ( const char * str1, const char * str2 );
      char * strstr (       char * str1, const char * str2 );
    char arr1[] = "i am kirito";
    char arr2[] = "kirito";
    char* ret = strstr(arr1, arr2);
    if (ret == NULL)
    {
        printf("找不到\n");
    }
    else
    {
        printf("%s\n", ret);
    }

strstr的模拟实现

这个函数的实现就有意思了

假设从abbbcd里找bbc

从a开始找,不行。

从第一个b开始找,唉?bbc的前2个b能找到,但是c就不匹配了

所以至少得3个指针,2个指向字符串的位置的指针。还要一个指针,保存目标字符串的寻找起始位置,不匹配的话就从下一个字符开始。

char* my_strstr(const char*str1,const char*str2)
{
    assert(str1 && str2);
    char* s1;
    char* s2;
    char* cp = str1;
    //设置3个指针
    if (*str2 == '\0')//str2如果原本就没有内容可以直接返回str1
    {
        return str1;
    }
        while (*cp)
        {
            s1 = cp;
            s2 = str2;
            //while (*s1 != '\0' && *s2 != '\0' && *s1 == *s2)
            while (*s1 && *s2 && *s1 == *s2)//可以简化,当s2匹配到\0时就停下
            {
                s1++;
                s2++;
            }
            if (*s2 == '\0')//其他的不重要,关键是s2匹配完了就可以返回了
            {
                return cp;//这里返回剩下的字符
            }
            cp++;
        }
        return NULL;//找不到
}

int main()
{
    char arr1[] = "abbcd";
    char arr2[] = "bbc";
    char* ret = my_strstr(arr1, arr2);
    if (ret == NULL)
    {
        printf("找不到\n");
    }
    else
    {
        printf("%s\n", ret);
    }
    return 0;
}

KMP算法

这里在网上没有谁具体讲过,我这里就发一个比特大博哥的:【完整版】终于有人讲清楚了KMP算法,Java语言C语言实现

4.2 strtok

切割字符串

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

会找到指定的字符,换成0

  • delimiters参数是个字符串,定义了用作分隔符的字符集合。
  • 第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记。
  • strtok函数找到str中的下一个标记,并将其用 0 结尾,返回一个指向这个标记的指针。
  • strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。
  • strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
  • 如果字符串中不存在更多的标记,则返回 NULL 指针。
    char arr1[] = "kirito@mywifeasuna.top";
    char arr2[100] = { 0 };//;临时数据
    char sep[] = "@.";
    strcpy(arr2, arr1);
    char* ret = NULL;
    for (ret = strtok(arr2, sep); ret != NULL; ret = strtok(NULL, sep))
    {
        printf("%s\n", ret);
    }

5. 错误信息报告

5.1 strerror

返回错误码对应信息

可以返回C语言内置的错误码对应的错误信息

char * strerror ( int errnum );

可以查看一下错误码对应的都是什么错误信息

    printf("%s\n", strerror(0));
    printf("%s\n", strerror(1));
    printf("%s\n", strerror(2));
    printf("%s\n", strerror(3));

C语言库函数调用失败的时候,会把错误码存储到errno变量中

还有一个函数:

perror - 打印+strerror

void perror ( const char * str );

举个栗子

    FILE* pf = fopen("test.txt", "r");
    //errno
    if (pf == NULL)
    {
        printf("%s\n", strerror(errno));
        perror("test");
    }
    else
    {
        printf("打开文件成功\n");
    }

6. 字符操作

函数

如果他的参数符合下列条件就返回真

  • iscntrl - 任何控制字符
  • isspace - 空白字符:空格‘ ’,换页‘f’,换行'n',回车‘r’,制表符't'或者垂直制表符'v'
  • isdigit - 十进制数字 0~9isxdigit 十六进制数字,包括所有十进制数字,小写字母a~f,大写字母A~F
  • islower - 小写字母a~z
  • isupper - 大写字母A~Z
  • isalpha - 字母a~z或A~Z
  • isalnum - 字母或者数字,a~z,A~Z,0~9
  • ispunct - 标点符号,任何不属于数字或者字母的图形字符(可打印)
  • isgraph - 任何图形字符
  • isprint - 任何可打印字符,包括图形字符和空白字符

内存函数

7. 内存操作函数

7.1 memcpy

void * memcpy ( void * destination, const void * source, size_t num );

内存拷贝

拷贝内存块

    int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
    int arr2[20] = { 0 };
    //拷贝的是整型数据
    memcpy(arr2, arr1, 10 * sizeof(int));
    int i = 0;
    for (i = 0; i < 20; i++)
    {
        printf("%d ", arr2[i]);
    }

memcpy的模拟实现

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

int main()
{
    int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
    int arr2[20] = { 0 };
    //拷贝的是整型数据
    //memcpy(arr2, arr1, 10 * sizeof(int));
    my_memcpy(arr2, arr1, 10 * sizeof(int));
    int i = 0;
    for (i = 0; i < 20; i++)
    {
        printf("%d ", arr2[i]);
    }
    return 0;
}

注意,my_memcpy这个函数,只要完成了不重叠的内存拷贝就完成任务。假设要把arr1的1234拷贝到3456的位置,就会变成12121278910。

my_memcpy(arr1 + 2, arr1, 16);//1 2 1 2 1 2 7 8 9 10

这只能考60分,而memmove可以考一百分

memmove可以做到

memmove(arr1 + 2, arr1, 16);//1 2 1 2 3 4 7 8 9 10

但是,库函数给的memcpy在实际使用的时候,你会发现他真的考出了100分的成绩,是可以打印出12123478910来的。但是我们应该自己,不去使用不重叠拷贝的memcpy。我们遇到了这样的需求,应该使用memmove来实现重叠拷贝的情况。

7.2 memmove

内存移动

可以根据上面的memcpy来理解,memmove就是移动内存块。而且可以重叠

memmove的模拟实现

需要考虑重叠的情况,那么就有3种情况

设定目标空间为dest,需要拷贝空间为src

dest在前面,src从前向后拷贝

dest在后面,src从后向前拷贝

dest在中间,src从后向前拷贝

这样看的话其实就容易许多了,从3分开写从前到后和从后到前2中情况即可。

void* my_memmove(void* dest, const void* src, size_t count)
{
        void* ret = dest;
    assert(dest && src);
    if (dest < src)
    {
        //前 -> 后
        while (count--)
        {
            *(char*)dest = *(char*)src;
            dest = (char*)dest + 1;//一个字节一个字节的拷贝
            src = (char*)src + 1;
        }
    }
    else
    {
        //后 -> 前
        while (count--)
        {
            *((char*)dest + count) = *((char*)src + count);
            //count每次都--进来,直接加count就可以从后开始拷贝了
        }
    }
    return ret;
}

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

7.3 memset

内存设置

void * memset ( void * ptr, int value, size_t num );
    int arr[] = { 1,2,3,4,5, };
    memset(arr, 0, 20);//全部初始化成0,在内存中查看

就这么简单

7.4 memcmp

字符串比较

int memcmp ( const void * ptr1, const void * ptr2, size_t num );

这就很简单了

    int arr1[] = { 1,2,3,4,5 };
    int arr2[] = { 1,2,3,5,6 };
    int ret = memcmp(arr1, arr2, 8);//前8个字节相同返回0。如果arr1小于arr2返回小于0的值,arr1大于arr2返回大于0的值。
    printf("%d\n", ret);

结尾

基本上就这样
小头图版权:《FGOサーヴァント『カーマ(アヴェンジャー)』》by ReDrop 2021年9月15日晚上9点02分 pid:92771184
《FGOサーヴァント『カーマ(アヴェンジャー)』》by ReDrop 2021年9月15日晚上9点02分 pid:92771184

广告位招租
最后修改:2021 年 09 月 16 日 11 : 33 PM
如果觉得我的文章对你有用,请喂饱我!(理直气壮)