常见字符串函数及其复写

简介:

@[TOC]

前言:

博主实力有限 ,博文有什么错误,请你斧正,非常感谢!
编译器:VS2019
本文介绍C语言<string,h>中那些常用的函数,及其复写。
因为C语言只给出了,函数的作用,对函数实现并不关心,因此在VS2019下某些字符串函数的行为可能与其它编译器不同,但是效果是大同小异的。
复写strstr时,博主目前掌握了BF算法,对于KMP算法将会在后续独立出一篇博客。

长度不受限制的函数

strlen

size_t strlen(const char* string);

  • 返回字符串中的字符数,不包括终止空字符(‘\0’)

    注意:strlen在计算字符数时,只要碰到‘\0’,就结束计算.这就可以认为当没碰到‘\0’,就不停止计算。因此说长度不受限制

image-20211019142905718

  • 返回值:无符号整形,因此在赋值时,某些编译器可能会报警告
  • 参数是:常量字符指针。(不可修改指向的数据)

strlen复写(3种,指针,递归,循环)

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <stdlib.h> 
#include <assert.h>
//我的:循环,递归,指针
size_t my_strlen1(const char* string)//循环
{
    assert(string);//防止传入NULL指针

    size_t cnt = 0;
    while (*string++)
    {
        ++cnt;
    }
    return cnt;
}
size_t my_strlen2(const char* string)//递归
{
    assert(string);
    return *string == '\0' ? 0 : 1 + my_strlen2(string + 1);
}
size_t my_strlen3(const char* string)//指针
{
    assert(string);

    const char* bigin = string;
    while (*string++)//注意这里当*string=='\0'时,string指向了'\0'后面的位置,因此返回时,要-1
    {

    }
    return (string - bigin-1);
}
int main()
{
    char arr[] = "hello";
    printf("%d\n", my_strlen1(arr));//循环
    printf("%d\n", my_strlen2(arr));//递归
    printf("%d\n", my_strlen3(arr));//指针



    return 0;
}

image-20211019150431695

strcpy

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

  • 将src指向的内容依次拷贝到 dest指向的内存,直到遇到str中的’\0‘,同时返回dest的首地址。如果没遇到‘\0’,也就是src不存在’\0‘,会访问非法内存,
  • 常量字符指针,不能更改str指向的内容。

strcpy复写

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <stdlib.h> 
#include <assert.h>
char* my_strcpy(char* dest, const char* src)
{

    assert(dest && src);//防止传入NULL指针
    char* ret=dest;
    while (*dest++=*src++)//当dest传入'\0',停止拷贝
    {
        ;
    }
    return ret;
}
int main ()
{
    char arr[20]={ 0 };
    printf("%s\n", my_strcpy(arr, "helloword"));
    return 0;
}

image-20211019150536294

strcat

char strcat(char dest , const char src);

  • 从dest的字符串结束标志’\0‘开始,将src中的内容追加到dest后面。直到遇到src的’\0‘.如果没遇到,会非法访问内存。因此长度不受限制,不安全
  • 追加字符串时不执行溢出检查。因此dest中 必须由程序执行者,预留足够空间
  • 返回 dest的首地址

strcat复写

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <stdlib.h> 
#include <assert.h>
char* my_strcat(char* dest, const char* src)
{
    assert(dest && src);//防止传入NULL指针
    char* ret = dest;
    while (*dest)//找dest的'\0'位置,不可while(*dest++),因为找到'\0'时,dest越界了
    {
        dest++;
    }

    while (*dest++ = *src++)
    {

    }

    return ret;

}
int main()
{
    char arr[20] = "hello";
    printf("%s\n", arr);

    my_strcat(arr, "word");
    printf("%s\n", arr);


    return 0;
}

image-20211019150314167

strcmp

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

  • 比较字符串看的是ASICC,不是长度
  • 比较 字符串str1与字符串str2是否相等.并返回一个比较值(比较字符串,只能用它。)
  • 比较规则:

    • 先比较 str1与str2指向字符的ASSICC值,如果不同(代表字符串不等)就返回 2者差值。一旦相等就继续比较,直到str1与str2 同时都是‘\0’,(代表字符串相等)返回0;

  • 返回值。
    <0 字符串1小于字符串2。
    0 字符串1与字符串2相同。
    \>0 字符串1大于字符串2
  • 在复写时,我发现vs2019返回的是三个确定的值。虽然符合返回值要求,但是不能代表全部。这只是 vs内置的结果。因此复写了2种strcmp

strcmp复写(2种,正常,vs特置)

#define _CRT_SECURE_NO_WARNINGS

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

#include <assert.h>
//正常
int my_strcmp1(const char* string1, const char* string2)
{
    assert(string1 && string2);

    while (*string1 == *string2)
    {
        if (*string1 == '\0')//当*s1,*s2同时为'\0'时,才说明字符串相等,返回0
        {
            return 0;
        }
        ++string1;
        ++string2;//前置++比后置++快
    }

    return (*string1 - *string2);

}
// vs2019版
int my_strcmp2(const char* src, const char* dest)
{
    int ret = 0;
    assert(src);
    assert(dest);
    while (!(ret = *(unsigned char*)src - *(unsigned char*)dest) && *dest)
        //unsigned char* 是因为char是否为有符号,根据编译器,默认不同
        //妙!以ret接受差值,通过!非0的数都为真.
          //同时&&&的使用,确保了相等的情况。
    {
        ++src;
        ++dest;
    
    }

    if (ret < 0)
    {
    
        ret = -1;
    }
    else if (ret > 0)
    {
        ret = 1;
    }
        return (ret);
}
int main ()
{
    
    char arr1[] = "helloword";
    char arr2[] = "hello";

    if (0 == my_strcmp1(arr1, arr2))
    {
        printf("相等\n");

    }
    else
    {
        printf("不相等\n");
    }
    if (0 == my_strcmp2(arr1, arr2))
    {
        printf("相等\n");

    }
    else
    {
        printf("不相等\n");
    }
    return 0;
}

image-20211019212500645

为什么说这些函数不受限制

strlen,strcpy....这些字符串函数,只是执行代码,不关心会发生什么。有可能会访问非法内存。不安全。因此后面介绍长度受限制安全的strcnpy,一定程度上安全,但是不绝对

长度受限制的字符串函数

strncpy

char strncpy(char Dest,const char* Src,size_t count);

  • 将 src前 count个字符拷贝到dest。但是如果count超过了src的字符个数时,会赋值’\0‘,
  • dest 的空间仍是程序调用者,自己控制

strncpy复写

#define _CRT_SECURE_NO_WARNINGS

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

char* my_strncpy(char* dest, const char* src, size_t cnt)
{

    assert(dest && src);
    char* ret = dest;
    if (strlen(dest) < (cnt - 1))//strlen的原因(不计数'\0'),因此cnt-1
    {
        printf("拷贝数目大于源字符串字节数,此行为是未定义的\n");
        exit(1);
    }

    //开始拷贝
    //        下面这种方式虽然可行,但是在vs调试中,我发现strncpy在 多余时,仍会对dest赋值'\0'
    //while ((cnt-- > 0)&&(*dest++ = *src++))//'\0'数值为0.因为 &&先左后右的求值顺序特点,
    //                                      //当cnt为0时,可能还未到达src的末尾,其会再赋值一次非'\0'
    //{
    //    ;
    //}
    //开始拷贝
    while (cnt > 0)
    {

        if (*dest = *src)//当*src为'\0'时,src不再++;
        {
            if (*src != '\0')
            {
                ++dest;
                ++src;

            }
            else
            {
                ++dest;
            }
        }
        --cnt;
    }
    return ret;
}
int main()
{
    char arr1[20] = "hello";
    printf("%s\n", strncpy(arr1, "word", 8));
    char arr2[20] = "hello";
    printf("%s\n", my_strncpy(arr2, "word00", 5));
    char arr3[] = "hello";
    printf("%s\n", my_strncpy(arr3, "word", 4));
    return 0;
}



image-20211019214757386

strncat

char strncat(char Dest,const char *Src,size_t count );

  • 如果count大于src的长度,就将src全部追加到dest,包括‘\0’。反之,追加src前 count的字符。并再追加一个‘\0’

strcat复写

#define _CRT_SECURE_NO_WARNINGS

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


char* my_stcncat(char* dest, const char* src, size_t cnt)
{
    
    assert(dest && src);
    char* ret = dest;
    while (*dest)//定位源字符串的'\0'指针
    {
        dest++;

    }

    if (cnt > strlen(src))//当cnt大的时侯,就是完整拷贝。
    {
    
        while (*dest++=*src++)
        {
            ;

        }

    }
    else {

        while ((cnt-->0)&&(*dest++=*src++))
        {
            ;
        }
        *dest = '\0';//因为cnt小的原因,根据要求,追加完后,需要再追加一个'\0'
    }
    return ret;


}
int main()
{
    char arr1[20] = "helloxx\0xxxxxxxxxx";
    char arr2[20] = "helloxx\0xxxxxxxxxx";

    printf("%s\n", my_stcncat(arr1, "word", 3));
    printf("%s\n", strncat(arr2, "word", 3));

    return 0;
}


image-20211019222600438

strncmp

int strncmp( const char str1, constchar str2,size_t cnt )

  • 当 cnt 大于 str1与str2 中长度最大的时,效果与 strcmp相同。反之 比较 长度最大的前cnt为。
  • 比较规则同strcmp
  • 注意复写时,我是以vs2019 为结论编写程序,返回值不是一个定值。

strncmp复写

#define _CRT_SECURE_NO_WARNINGS

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

int my_strncmp(const char* str1, const char* str2, size_t cnt)
{
    assert(str1 && str2);
    int ret = 0;
    while (!(ret=(*str1-*str2))&&(*str1)&&(*str2)&&(cnt>0))//只要当str1,str2其中为'\0',或者cnt为0就停止
    {
        --cnt;
        ++str1;
        ++str2;
    }
    if (ret == 0)
    {
        return 0;
    }else if(ret>0)
    {
        return 1;
    }
    else
    {
        return -1;
    }
    
}
int main ()
{
    char* arr1 = "hello";

    char* arr2 = "hello";
    
    printf("%d\n", strncmp(arr1, arr2, 20));
    printf("%d\n", my_strncmp(arr1, arr2, 20));

    return 0;
}

image-20211019225750664

内存操作函数

memcpy,memmove(内存拷贝)

void memcpy(void dest,const void* src, size_t count )

void memmove(void dest,const void *src,size_t count);

  • 这2个函数都是内存拷贝函数,效果一样。区别在于 memcpy只管拷贝(不考虑重叠拷贝),而memmove

    在拷贝时会注意重叠拷贝的问题。因此称memcpy是半拷 贝,而memmove是全拷贝。

  • 注意VS2019中的memcpy,memmove都是全拷贝。
  • 因此在复写时,我选择了复写一个memmove

memmove复写

image-20211020152530870

#define _CRT_SECURE_NO_WARNINGS

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


void* my_memmove(void* dest, const void* src, size_t count)
{
    assert(dest && src);//断言,防止NULL指针

    if (dest <= src)//dest在前,从前往后拷贝
    {
        for (size_t i = 0; i < count; ++i)
        {
            *((char*)dest+i) =*((char*)src+i);
        }
    
    
    }
    else {

        for (size_t i = count-1; i>0; --i)//dest在后,从后往前拷贝,注意因为无符号的问题,
                                          //要考虑溢出问题。防止死循环。因此i=0的位置单独赋值
        {
            *((char*)dest + i) = *((char*)src + i);
        }
        *(char*)dest = *(char*)src;
    }

    return dest;

}
int main ()
{
    char arr1[20] = "hellowordxxxxx";
    char arr2[20] = "hellowordxxxxx";

    printf("%s\n", arr1);
    my_memmove(arr1, arr1 + 3, 5);//dest在前
    printf("%s\n", arr1);
    
    printf("%s\n", arr2);
    my_memmove(arr2+3, arr2 , 5);//dest在后
    printf("%s\n", arr2);
    return 0;
}

memcmp(内存比较)

int memcmp(const void buf1,const void buf2,size_t count);

  • 比较内存中的元素,与strcmp道理相同。只是以内存角度比较字节。
  • memcmp是将内存中的补码看成无符号型,。

    image-20211020162606752

memcmp复写

#define _CRT_SECURE_NO_WARNINGS

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


int my_memcmp(const void* buf1, const void* buf2, size_t count)
{

    assert(buf1 && buf2);
    int ret = 0;
    //注意一定是无符号的char*,因为memcmp看的是无符号
    while (count&&(!(ret=*(unsigned char*)buf1 -*(unsigned char*)buf2))) //因为&&的原因,count要在前,提前结束,一旦比较元素不相等就停止。
    {                                                //相等就继续比较.因为不同于字符串,直到count为0.
        buf1 = ( unsigned char*)buf1 + 1;                       // 防止比较多了。
        buf2 = ( unsigned char*)buf2 + 1;                   
        --count;
    }
    if (ret > 0)
    {
    
        return 1;

    }
    else if(ret<0)
    {
        return -1;
    
    }
    else
    {
    
        return 0;
    }



}
int main ()
{
    int a = 10001;
    int b = 10;
    
    
    printf("%d\n", my_memcmp(&a, &b, 4));

    printf("%d\n", memcmp(&a, &b, 4));
    
    return 0;
}

image-20211020162859221

memset(内存赋值)

void memset(voiddest,int c,size_t count);

  • 将缓冲区(内存)设置为指定的字符c。
  • 缓冲区的大小由调用者设置,要合适。

memset复写

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <stdlib.h> 
#include <string.h>
#include <assert.h>
void* my_memset(void* dest, int c, size_t count)
{
    assert(dest);
    void* ret = dest;
    while (count--)
    {
        *(char*)dest = c;
        dest = (char*)dest + 1;
    }

    return ret;
}
int main ()
{
    char arr[20] = "xxxxxword";
    printf("%s\n", arr);

    my_memset(arr, 'h', 5);
    printf("%s\n", arr);
    
    
    return 0;
}

image-20211020163920111

字符串查找函数

strstr

char strstr(const charstr1,const char * str2);

  • 在str1中搜索str2.要求str1于str2都是NULL结尾的字符串,否则行为未定义
  • 返回一个指针,指向字符串 中首次出现的str2,如果字符串中没有出现str2,则返回NULL。如果str2指向长度为零的字符串,则函数返回字符串。
  • 使用的是BF(暴力查找法)复写。KMP会在后续单独一篇博客

复写strstr

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <stdlib.h> 
#include <assert.h>
char* my_strstr(const char* string, const char* strCharSet)
{
    assert(string && strCharSet);
    if (*strCharSet == '\0')//传入空的时候,就返回string
    {

        return (char*)string;//返回值的类型是 char*,但是string是const char*,因此需要强制为char*
    }
    while (*string)
    {
        //进行匹配活动
        const char* s1 = string;
        const char* s2 = strCharSet;
        while ((*s2 != '\0') && (*s1 != '\0') && (*s1 == *s2))//&&的妙用。一旦一个假就停止遍历。
        {
            s1++;
            s2++;
        }
        if (*s2 == '\0')//我们关注的是:是否找到,必须先判断s2的情况,一旦成立就找到了。其它情况不需要考虑。
        {

            return (char*)string;
        }

        if (*s1 == '\0')//当*s1为空时就没有必要再次循环,后面不会出现匹配的情况。
                        //但是当字符数组中没有一个匹配的字符时,*string为'\0'的特殊情况
        {

            return NULL;
        }

        string++;
    }
    return NULL;//当
}
int main()
{
    char arr[] = "abcdef";
    if (my_strstr(arr, "def")!=NULL)
    {
        printf("找到了\n");
    }
    else
    {
        printf("找不到了\n");

    }
    return 0;
}

image-20211020170233962

切分字符串

strtok

char strtok(char str,const char *sep)

  • sep是分隔符的集合
  • str指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记 ,标记是一个非包含sep的字符串。
  • 如果传入的参数str不为NULL,那么strtok会返回第一个标记首地址,并将分隔符置为‘\0’,同时strtok会记住这个分隔符的位置。如果传入的参数为NULL,strtok会从记住的位置开始分割字符串。
  • 也就是说我们想将一个字符彻底切分完,在第二次调用时要传入NULL
  • 如果字符串中不存在更多的标记,则返回 NULL 指针
  • 下面距离说明:

image-20211020202141568

strtok复写:

#define _CRT_SECURE_NO_WARNINGS

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

int isfind( char* str, const char* sep)//BF查找。二分需要排序。另外esp中字符少
{                                      
    for (size_t i = 0; i < strlen(sep); ++i)
    {
        if (*str == sep[i])
        {
            return 1;
        }
    }
    return 0;
}
char* my_strtok(char* str, const char* sep)
{
     char* ret = NULL;
     static char* sp = NULL;
     int Flage = 0;//帮助记录标记的起始地址
    if (str != NULL)//传入非NULL指针,就找第一个标记
    {
        
        for (size_t i = 0; i < strlen(str); ++i)
        {
            if ((isfind(str + i, sep) == 1)&&(Flage==1))//只有当记录了标记起始地址,才返回值。
            {
                sp = str + i+1;//+1,是为了方便查找.
                *(str + i) = '\0';
                return ret;
            }
            else if((isfind(str + i, sep)==0)&&(Flage==0))
            {
                ret = str + i;
                Flage = 1;//一旦Flage为1后,就不需要担心再次对ret赋值。
            
            }

        }
        return NULL;//没找到标记就返回NULL
    }
    else if(str==NULL&&sp!=NULL)
    {
        char* s = sp;
        while (*s!='\0')
        {

            if (isfind(s, sep) == 1&&Flage==1)
            {
                
                *s= '\0';
                sp = s + 1;
                return ret;
            }
            else if(isfind(s, sep) == 0 && Flage == 0)
            { 
                ret = s;
                Flage = 1;
            }
            s++;
        }
    }
    sp = NULL;//当遇到'\0'时,需要返回这之前的标记地址。但是需要将记住的静态指针置为NULL.
              // 因为最终需要返回一个NULL指针结束main的循环。
    return ret;
}
int main ()
{
    char arr[] = "- This, a sample string.";
    char* p = NULL;
    for (p = my_strtok(arr, "- ,"); p != NULL; p = my_strtok(NULL, "- ,"))
    {
        printf("%s\n", p);
    
    }

    return 0;
}

image-20211020214318375

错误信息报告

strerror,perror

char * strerror(int errnum);

voidperror(const char string);

  • 编译器会将程序中的出现所有错误(如少分号,少{}等)对应一个码,称谓错误码。每个错误码对应一个错误信息字符串。而错误码存储在errno中(< errno,h >)

    image-20211020215258086

  • strerror会返回错误码对应字符串的首地址。不会主动打印错误信息,因此如果需要打印错误信息,需要printf函数
  • perror不仅·可以自动打印错误信息,还可以人为的添加一些信息,与错误信息组成新的字符串。

image-20211020220656077

小结

  • 函数的复写过程,必须考虑到很多情况,提高函数的鲁棒性。可能BF算法笨(我就是。。。。。),但是算法都是一步一步优化的。
  • 关于字符串函数就介绍到这了。如果想了解其它字符串函数,可以去网站:https://en.cppreference.com/w/
相关文章
|
5月前
|
Java
方法的重载
方法的重载
50 1
|
5月前
|
编译器 Linux C++
string类的函数讲解
string类的函数讲解
36 1
|
5月前
String类及相应的字符串操作方法
String类及相应的字符串操作方法
96 1
C++重载、重写、重定义
C++重载、重写、重定义
87 0
|
11月前
|
C++
54 C++ - 重写 重载 重定义
54 C++ - 重写 重载 重定义
30 0
函数的重载
函数的重载
27 0
字符串函数(一)之常见用法
计算字符串长度 但应注意 使用 string.h头文件 strlen函数返回值为 unsigned int
138 0
字符串处理方法
字符串处理方法
107 0
字符串处理方法