【C语言进阶】字符串函数模拟实现

  1. 求字符串长度

strlen

size_t strlen(const char*string);

头文件:<string.h>

功能:
计算字符串的长度,遇到'\0'便停止,统计'\0'之前字符的个数。

模拟实现:

//创建临时变量实现
int my_strlen(char* arr)
{
    int count = 0;
    while (*arr)
    {
        arr++;
        count++;
    }
    return count;
}

//指针-指针实现
int my_strlen(char* arr)
{
    char* str = arr;
    while (*str)
    {
        str++;
    }
    return str - arr;
}

//递归实现
int my_strlen(char* arr)
{
    if (*arr != '\0')
        return 1 + my_strlen(++arr);
    else
        return 0;
}

int main()
{
    char arr[] = "abcdef";
    int ret = my_strlen(arr);
    printf("%d\n", ret);
    return 0;
}
int main()
{
    char arr[] = "abcdef";
    int ret = my_strlen(arr);
    printf("%d\n", ret);
    return 0;
}
  1. 长度不受限制的字符串函数

strcpy

char*strcpy(char*strDestination,const char*strSource);

头文件:<string.h>

功能: 将strSource对应的字符串(源字符串)复制到strDestination对应的地址空间(目标空间)中
注意:
目标空间必须足够大
目标空间必须可以被修改
源字符串必须以 '\0' 结束
会将源字符串中的 '\0' 拷贝到目标空间

内存监视:会发现'\0'确实也拷贝到了新的数组中

模拟实现:

char* my_strcpy(char* arr1, char* arr2)
{
    char* str = arr1;
    while (*arr1++ = *arr2++)
    {
        ;
    }
    return str;
}

int main()
{
    char arr1[20] = "xxxxxxxxxx";
    char arr2[] = "abcd";
    char* str=my_strcpy(arr1, arr2);
    printf("%s\n", str);
    return 0;
}
strcat

char* strcat(char* strDestination,const char* strSource);

头文件:<string.h>

功能: 将strSource对应字符串(源字符串)连接到strDestination所指向字符串尾部
注意:
目标空间必须足够大
目标空间必须可变
连接会从目标空间的'\0'开始
源字符串必须以'\0'结束
源字符串的‘\0’也会被复制到目标空间
然而如果字符串自己给自己追加就会出现问题

模拟实现:

#include<assert.h>
char* my_strcat(char* arr1, const char* arr2)
{
    assert(arr1 && arr2);
    char* str1 = arr1;
    while (*arr1)
    {
        arr1++;
    }
    while (*arr1++ = *arr2++)
    {
        ;
    }
    return str1;
}

int main()
{
    char arr1[20] = "xxxxxx";
    char arr2[4] = "def";
    char* str = my_strcat(arr1, arr2);
    printf("%s", str);
    return 0;
}
strcmp

int strcmp(const char*string1, const char*string2);

头文件:<string.h>

功能 :比较字符串的大小(一个个字符依次比较)

string1>string2

返回大于0的数字

string1==string2

返回0

string1<string2

返回小于0的数字

而在VS这种编译器下,

string1>string2

返回1

string1==string2

返回0

string1<string2

返回-1

模拟实现:

int my_strcmp(const char* arr1, const char* arr2)
{
    assert(arr1&&arr2);
    if (*arr1 == *arr2 && *arr1 == '\0')
        return 0;
    while (*arr1 == *arr2 )
    {
        arr1++;
        arr2++;
    }
    if(*arr1>*arr2)
        return 1;
    else if(*arr1<*arr2)
        return -1;//VS上的写法
    //return *arr1 - *arr2;//其他编译器上的写法
}

int main()
{
    char arr1[] = "abcdef";
    char arr2[] = "abcq";
    int ret = my_strcmp(arr1, arr2);
    if (ret == 0)
        printf("arr1==arr2.\n");
    else if (ret > 0)
        printf("arr1>arr2.\n");
    else
        printf("arr1<arr2.\n");

    return 0;
}
  1. 长度受限制的字符串函数

strncpy

char*strncpy(char*strDest,constchar*strSource,size_tcount);

头文件:<string.h>

功能:
  • 从源字符串中拷贝count个字符到目标空间

  • 如果源字符串的长度小于count,则拷贝完后在目标的后面补'\0',直到count个为止

模拟实现:

char* my_strncpy(char* arr1, char* arr2, int num)
{
    assert(arr1 && arr2);
    char* dst=arr1;
    while (*arr2!='\0' && num>0)
    {
        *arr1++ = *arr2++;
        num--;
    }
    while (num)
    {
        *arr1 = '\0';
        arr1++;
        num--;
    }
    return dst;
}
int main()
{
    char arr1[] = "xxxxxxxx";
    char arr2[] = "abcd";
    char* str = strncpy(arr1, arr2, 6);
    printf("%s\n", str);
    return 0;
}
strncat

char* strncat(char*strDest,constchar*strSource,size_tcount);

头文件:<string.h>

功能:将源字符串的count个字符追加到目标空间对应字符串末尾(从'\0'开始)

  • 若追加元素个数小于源字符串的长度,会在追加count个字符之后再加上一个'\0'

模拟实现:

char* my_strncat(char* arr1, const char* arr2, int num)
{
    assert(arr1 && arr2);
    char* str = arr1;
    while (*arr1 != '\0')
    {
        arr1++;
    }
    while (*arr2!='\0'&&num>0)
    {
        *arr1++ = *arr2++;
        num--;
    }
    if(num==0)
        *arr1='\0';
    while (num--)
    {
        *arr1++ = '\0';
    }
    return str;
}

int main()
{
    char arr1[20] = "xxxxxxxxx";
    char arr2[] = "abdes";
    char* dst = my_strncat(arr1, arr2, 6);
    printf("%s\n", dst);
    return 0;
}
strncmp

int strncmp(constchar*string1,constchar*string2,size_tcount);

头文件:<sting.h>

功能:比较两个字符串count个字符的大小(一个个字符依次进行比较)

模拟实现:

int my_strncmp(const char* arr1, const char* arr2, int num)
{
    assert(arr1 && arr2);
    while (*arr1 == *arr2&&num>0)
    {
        if (*arr1 == '\0')
            return 0;
        //if (num == 0)
        //    return *arr1 - *arr2;
        arr1++;
        arr2++;
        num--;
    }
    //情况1:*arr1!=*arr2跳出循环
    if (num != 0)
        return *arr1 - *arr2;
    //情况2:由于num==0跳出循环
    else if (num == 0)
        return 0;
}

int main()
{
    char arr1[] = "abcdptr";
    char arr2[]="abcdeh";
    int ret = my_strncmp(arr1,arr2,5);
    if (ret > 0)
        printf("arr1>arr2\n");
    else if (ret == 0)
        printf("arr1==arr2\n");
    else
        printf("arr1<arr2\n");
    return 0;
}
  1. 字符串查找

strstr

char*strstr(constchar*str1,constchar*str2);

头文件:

功能: 在str1对应字符串中寻找str2对应字符串是否存在,若存在则返回str2在str1中对应位置的起始位置,若不存在则返回NULL

模拟实现:

char* my_strstr(const char* arr1, const char* arr2)
{
    char* pc1 = (char*)arr1, *pc2 = (char*)arr2;
    if (*arr2 == '\0')
            return pc1;
    while (*arr1 != '\0')
    {
            while (*arr1 == *arr2)
            {
                arr1++;
                arr2++;
                if (*arr2 == '\0')
                {
                    return pc1;
                }
            }
            arr2 = pc2;
            pc1++;
            arr1 = pc1;
    }
    return NULL;
}
int main()
{
    char arr1[] = "abbcdef";
    char arr2[] = "bcde";
    char* ret=my_strstr(arr1, arr2);
    if (ret != NULL)
        printf("%s\n", ret);
    else
        printf("no find!\n");
    return 0;
}
strtok

char*strtok(char*strToken,constchar*strDelimit);

头文件:<string.h>

功能:分割字符串,其中 strDwlimit是存放分隔符的字符串;若找到其中的分隔符则将分隔符改为'\0'
返回值:1.字符串 strToken不为空,如果找到对应的分隔符,则返回分隔符之前字符串的首地址(指向 标记开头的指针);如果到达字符串末尾'\0'则返回NULL;
2.若字符串 strToken为空,则返回空指针NULL
注意:第一次传参时第一个字符指针传递字符串的地址,但在第二次及之后传参时,传递NULL,因为在找到分隔符后函数会记录并标记相关地址,以便后续查找。

  1. 错误信息报告

strerror

char*strerror(interrnum);

头文件:<string.h>

功能: 打印错误信息
补充:C语言的库函数在运行时,如果发生错误,就会将错误码存在一个变量中,这个变量是:errno 错误码是一些数字:1 2 3 4 5 ,而我们为了明白错误,需要将错误码翻译成错误信息。使用strerroe函数就可以将错误信息的地址返回,从而打印出错误信息。

同时有一个和strerror类似的也是打印错误信息的函数perror

perror=printf+error

void perror(const char *string);

头文件:<stdio.h>or<stdlib.h>

功能: 直接打印错误信息,但在打印错误信息之前会打印自定义的信息。

strerror与perror用法比较:

int main()
{
    //打开文件
    FILE* pf = fopen("test.txt", "r");
    if (pf == NULL)//判断是否读取成功
    {
        printf("%s\n", strerror(errno));
        //使用strerror函数要将错误码作为参数传参
        perror("fopen");
        //使用perror函数则直接传递自定义信息即可
    }
    //读文件

    //关闭文件
    fclose(pf);
    pf = NULL;
    return 0;
}
  1. 字符操作

字符分类函数

字符转换函数

int tolower(int c);//大写变小写
int toupper(int c);//小写变大写

来看一个题目:

如何将一串字符串都变为小写?

如何将一串字符串都变为大写?

  1. 内存操作函数

因此字符串函数是只针对字符串而言的,不具有普遍性,因此引出针对所有类型而言的内存函数

memcpy

void*memcpy(void*dest,constvoid*src,size_tcount);

头文件:<string.h>

功能:实现任意类型数据的拷贝,以src指向的地址为起点,将连续的n个字节数据,复制到以dest指向的地址为起点的内存中。
返回值:目标空间起始地址

模拟实现:

void* my_memcpy(void* dest, const void* src, size_t count)
{
    assert(dest && src);
    void* ret = dest;
    //写法1:通过char*型的指针分别接收源地址和目标地址,再进行操作
    char* dst = (char*)dest;
    char* str = (char*)src;
    while (count)
    {
        *dst = *str;
        dst++;
        str++;
        count--;
        
    }
    //写法2:通过直接强制类型转换进行操作
    //while (count)
    //{
    //    *(char*)dest = *(char*)src;
    //    dest = (char*)dest + 1;
    //    src = (char*)src + 1;
    //    //(char*)dest++;
    //    //这种强制类型转换只是临时的,在转换之后便会复原
    //    //因此是错误的
    //    count--;
    //}
    return ret;
}
int main()
{
    int arr1[10] = { 1,2,3,4,5,6,7 };
    int arr2[10] = { 0 };
    void* ret = my_memcpy(arr2, arr1, 16);
    for (int i = 0; i < 4; i++)
    {
        printf("%d ", arr2[i]);
    }
    return 0;
}

这种写法很可能在某些情况下出现问题:

当src与dest所指向的内容有所重叠的时候,如果还是按照从前向后的顺序可能拷贝不成功

eg:将arr1数组中的1,2,3,4,5拷贝至3,4,5,6,7时

出现这种情况的原因是在从前向后拷贝时会把还未拷贝的数据覆盖,从而导致无法达到预期效果

我们来分析一下dest与src位置与拷贝顺序的关系,会发现可以分为三种情况:

  1. 当dest在src之前时,由前至后拷贝

  1. 当dest在src所指向要拷贝内容的起始位置与终端位置之间时,由后至前拷贝

  1. 当dest在src所指向要拷贝内容的末端位置之后时,由前至后拷贝与由后至前拷贝都是可行的

由此我们引出memmove函数

memmove

void*memmove(void*dest,constvoid*src,size_tcount);

头文件:<string.h>

功能:拷贝src在内存对应内容的count个字节到dest中,可以实现重叠内存的拷贝。

模拟实现:由于上面提到三种情况中,第二种和第三种都可以用由后->前的思路来实现,为了简便我们分为dest<src和dest>=src两种情况考虑

void* my_memcpy(void* dest, const void* src, size_t count)
{
    assert(dest && src);
    void* ret = dest;
    //由前->后
    if (dest < src)
    {
        while (count)
        {
            *(char*)dest = *(char*)src;
            dest = (char*)dest + 1;
            src = (char*)src + 1;
            count--;
        }
    }
    //由后->前
    else
    {
        
        while (count--)
        {
            *((char*)dest + count) = *((char*)src + count);
        }
    //最后因此count为1判断为真,之后--变为0进入循环,第一个字节也有拷贝过去
    }
    return ret;
}
int main()
{
    int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
    void* ret = my_memcpy(arr1 + 2, arr1, 20);
    for (int i = 2; i < 7; i++)
    {
        printf("%d ", arr1[i]);
    }
    return 0;
}

但很多编译器上也将memcpy设置为可重叠拷贝的函数,使用时可以选择都用memmove函数,也可以选择在重叠时用memmove,不重叠用memcpy。

memset

void* memset(void* dest,int c,size_t count);

头文件:<string.h>or<memory.h>

功能:内存设置函数,以字节为单位来设置内存中的数据,将dest在内存中对应count个字节的内容改为c

使用:

int main()
{
    char arr[] = "hello world";
    memset(arr, 'x', 5);
    printf("%s\n", arr);
    memset(arr+6, 'y', 5);
    printf("%s\n", arr);

    //int arr[10] = { 0 };
    //memset(arr, 0, 40);
    //一般初始化为0如果初始化内容超过一字节在使用时可能会出现问题
    return 0;
}
memcmp

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

头文件:<string.h>or<memory.h>

功能: 比较buf1与buf2内存块的count个字节的内容
前两个参数就是要比较的内存块的地址;
第三个参数就是要比较的字节数。
int main()
{
    int arr1[] = { 1,2,3,4,5 };
    int arr2[] = { 1,2,3,0,0 };
    int ret = memcmp(arr1, arr2, 12);
 
    printf("%d\n", ret);
 
    return 0;
}
//前12个字节的内容都一样,所以返回0
  • 注意:这里比较的以字节为单位的,所以是要牵扯到大小端存储的

不太了解大小端的宝宝们可以看看这篇文章http://t.csdn.cn/5QaTY

以arr1数组为例:

若是小端存储则:

若是大端存储则:

12字节无论是大端还是小端都是相等的

若是访问11字节内容:

小端存储:

大端存储:

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

伱恏呀呀呀呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值