c语言中的字符串函数与内存函数讲解及仿写

求字符串长度

strlen

在MSDN中查找一下strlen函数,我们会发现一个并不熟悉的类型:size_t

我们会发现返回类型为size_t,这种类型是我们不常见的。

size_t

size_t本质上是无符号类型,是专门给sizeof设计的的返回类型。

但使用size_t有不合理的地方:

int main()
{
    if(strlen("abc")-strlen("abcdef")>0)
      printf(">");
    else
      printf("<=");
    return 0;
}

Q:上面程序最终输出什么?

A:因为strlen函数的返回值为无符号数,所以无符号数与无符号数相减肯定不会得到负数-3,而是会输出一个很大的值。


长度不受限制的字符函数

strcpy

strcpy的作用为拷贝字符串。

strcpy的使用:

strcpy的使用要引用头文件<string.h>

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

int main()
{
    char arr1[] = "abcdefg";
    char arr2[20] = { 0 };
    strcpy(arr2, arr1);//将目的地的首元素地址放在第一个参数为上,将要拷贝过去的字符串地址传入
    printf("%s\n", arr2);//之后就可以直接打印出来,最终输出arr1拷贝过去的内容
    return 0;
}

Tip:拷贝是将字符串的'\0'也拷贝进去的

使用strcpy需要注意的情况:

1.可以对字符串拷贝,但记住一定要带上'\0'

例如下面

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>

int main()
{
    char arr1[] = { 'a','b','c','d','e','f' };
    char arr2[20] = { 0 };
    strcpy(arr2, arr1);
    printf("%s\n", arr2);
    return 0;
}

由于没有'\0',所以在输出的时候不知道要输出到什么时候截止。所以后面会输出乱码。

2.拷贝的目的地要够大,切忌越界访问。

就如这种情况,虽然arr2的字符串长度大于arr1的字符串长度,但是还是会依照函数copy过去给arr1,虽然有越界访问的情况出现,系统还是报错,但我们会发现还是copy了。

 strcpy的仿写:

char* my_strcpy(char* dest, const char* fir)
{
  char* str = dest;
  assert(dest && fir);
  while (*det++=*fir++)
  {
    ;
  }
  return str;
}

strcat

strcat——字符串链接

strcat的使用:

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

int main()
{
    char arr1[30] = "abcd";
    char arr2[] = "efg";
    strcat(arr1, arr2);
    printf("%s\n", arr1);
    return 0;
}

使用strcpy需要注意的情况:

1.初始化一定要加上'\0'

2.目标空间一定要足够大

在链接过程中同样遇上了栈溢出的情况,虽然也还是会链接上去,但是会报错。

3.目标空间要可修改

strcat的仿写:

#include <stdio.h>

char* my_strcat(char* dest, const char* str)
{
    while (*dest)//*
    {
        dest++;//*
    }
    while (*dest++ = *str++)
    {
        ;
    }
    return dest;
}

int main()
{
    char arr1[30] = "abcdef";
    char arr2[] = "ghijk";
    my_strcat(arr1, arr2);
    printf("%s\n", arr1);
    return 0;
}

注意!!!若把 //*的位置变为while(*dest++)是不行的,因为会把arr1中的'\0'也拷贝进去。


strcmp

先在MSDN中研究一下strcmp函数:

 

strcmp的功能是比较两个字符串,比较的非长度,而是对应位置上的字符大小,例如:

char arr1[ ]="abcdef"; char arr2[]="bbq";

在这里对应第一个元素,arr1中的'a'与arr2中的'b',比较,当比出来是b较大的时候,若代码为:strcmp(arr1,arr2); 则输出-1。

strcmp的仿写

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

int my_strcmp(const char* arr1, const char* arr2)//这里加const是为了防止在传参过程中两个数组被修改
{
    assert(arr1 && arr2);//这里是为了防止传入的是空指针,使用当传入空指针时assert函数会报错指出来同时停止程序的运行
    while (*arr1 == *arr2)
    {
        if (*arr1 == '\0')
        {
            return 0;
        }
        arr1++;
        arr2++;
    }
    if (*arr1 > *arr2)
    {
        return 1;
    }
    else
    {
        return -1;
    }
}

int main()
{
    char arr1[] = "abcdef";
    char arr2[] = "abcdfg";
    printf("%d\n", (my_strcmp(arr1, arr2)));
    return 0;
}

也可以简化一点点:

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

int my_strcmp(const char* arr1, const char* arr2)//这里加const是为了防止在传参过程中两个数组被修改
{
    assert(arr1 && arr2);//这里是为了防止传入的是空指针,使用当传入空指针时assert函数会报错指出来同时停止程序的运行
    while (*arr1 == *arr2)
    {
        if (*arr1 == '\0')
        {
            return 0;
        }
        arr1++;
        arr2++;
    }
    return *arr1-*arr2;
}

int main()
{
    char arr1[] = "abcdef";
    char arr2[] = "abcdfg";
    printf("%d\n", (my_strcmp(arr1, arr2)));
    return 0;
}

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

strncpy

strncpy是选择要拷贝的个数。

strncpy的使用

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

int main()
{
    char arr1[] = "xxxxxxxxxxxxxx";
    char arr2[] = "hello world";
    strncpy(arr1, arr2, 5);//第一个参数的要拷贝的目的地地址,第二个位置是要拷贝的字符串地址,第三个是要拷贝的个数
    printf("%s\n", arr1);
    return 0;
}

strncpy的奇怪用法:

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

int main()
{
    char arr1[] = "xxxxxxxxxxxxxx";
    char arr2[] = "he";
    strncpy(arr1, arr2, 5);
    printf("%s\n", arr1);
    return 0;
}

如上,当我们能copy的字符串只有两个而硬要copy五个过去时,编译器会在剩余位置上补全'\0',在运行观察中打开监视器我们就可以发现了。

strncpy的模拟实现

//strncpy的仿写
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
#include <assert.h>

char* my_strncpy(char* dest, const char* ori, int n)
{
    assert(dest && ori);
    int i = 0;
    int sz = strlen(ori);
    int width = sz;
    if (n > sz)
    {
        while (n != width)
        {
            *(dest + width) = '\0';
            width++;
        }
        for (i = 0; i < sz; i++)
        {
            *(dest + i) = *(ori + i);
        }
    }
    else
    {
        for (i = 0; i < n; i++)
        {
            *(dest + i) = *(ori + i);
        }
    }
    return dest;
}

int main()
{
    char arr1[20] = "xxxxxxxxxxxxxxx";
    char arr2[] = "efg";
    printf("%s\n",my_strncpy(arr1, arr2, 5));
    return 0;
}

自己的实现代码太长了,看看编译器给的写法:(天秀)

char * __cdecl strncpy (
        char * dest,
        const char * source,
        size_t count
        )
{
        char *start = dest;

        while (count && (*dest++ = *source++) != '\0')    /* copy string */
                count--;

        if (count)                              /* pad out with zeroes */
                while (--count)
                        *dest++ = '\0';

        return(start);
}

strncat

会选择n个大小链接到目的字符串上,并在链接完后加上'\0'。

strcat的使用:

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>

int main()
{
    char arr1[20] = "hello\0xxxxxx";
    char arr2[] = "world";
    strncat(arr1, arr2, 3);
    printf("%s\n", arr1);
    return 0;
}

最终输出结果:

strncat的奇葩使用:

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>

int main()
{
    char arr1[20] = "hello\0xxxxxxxxxxxxx";
    char arr2[] = "world";
    strncat(arr1, arr2, 7);
    printf("%s\n", arr1);
    return 0;
}

本来最多arr2只有六个位置,但硬要用七个位置,那么此时编译器在链接'\0'后就不会再链接其他大小过去了,我们可以再程序运行的时候点开监视器观察:

strncat的仿写:(genius)

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <assert.h>

char* my_strncat(char* dest, const char* ori, int n)
{
    assert(dest && ori);
    int i = 0;
    char* str = dest;
    while (*dest++ != '\0')
    {
        ;
    }
    dest--;
    for (1; *ori;)
    {
        *dest++ = *ori++;
    }
    *dest = '\0';
    return str;
}

int main()
{
    char arr1[20] = "abcdef\0xxxxxxxx";
    char arr2[] = "ghij";
    printf("%s\n",my_strncat(arr1, arr2, 6));
    return 0;
}

编译器的参考写法:(感觉上差不多)

char * __cdecl strncat (
        char * front,
        const char * back,
        size_t count
        )
{
        char *start = front;

        while (*front++)
                ;
        front--;

        while (count--)
                if ((*front++ = *back++) == 0)
                        return(start);

        *front = '\0';
        return(start);
}

strncmp

与strcmp差别不大,就是可以指定要对比大小的个数。

 strncmp的仿写

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
#include <assert.h>

int my_strncmp(const char* arr1, const char* arr2, int n)
{
    assert(arr1 && arr2);
    int i = 0;
    for (i = 0; i < n; i++)
    {
        if (*(arr1+i) > *(arr2+i))
        {
            return 1;
        }
        if (*(arr1 + i) < *(arr2 + i))
        {
            return -1;
        }
        if (*(arr1 + i) == *(arr2 + i))
        {
            if (i == (n - 1))
            {
                return 0;
            }
        }
    }
}

int main()
{
    char arr1[] = "abcdef";
    char arr2[] = "abcefg";
    printf("%d\n",my_strncmp(arr1, arr2, 3));
    return 0;
}


字符串查找

strstr

strstr为字符串查找字符串。当找到后会从找到的位置开始往后打印,为第一次找到的起始位置为准。如果找不到,则会返回空指针。

strstr的使用

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

int main()
{
    char arr1[] = "abcdefg";
    char arr2[] = "bcd";
    char* str=strstr(arr1, arr2);
    if (NULL == str)
    {
        printf("找不到");
    }
    else
    {
        printf("%s\n", str);
    }
    return 0;
}

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

int main()
{
    char arr1[] = "abcdefg";
    char arr2[] = "bbcd";
    char* str=strstr(arr1, arr2);
    if (NULL == str)
    {
        printf("找不到");
    }
    else
    {
        printf("%s\n", str);
    }
    return 0;
}

strstr的仿写:

在仿写的时候我们要考虑遍历整个字符串来查找,若第一个元素相同则要继续往下对比,如果全部找到后则可以一直走到要查找字符串的'\0'位置,这时就算查找到了要查找的字符串,便返回查找字符串的首元素对应被查找字符串的地址。但如果没有被找到,有不同,则直接跳过本次循环,从被查找字符串的下一个位置开始查找。若一直遍历完到'\0'后还是找不到,则返回NULL,表示找不到该字符串。

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <assert.h>

char* my_strstr(const char* arr1, const char* arr2)
{
    assert(arr1 && arr2);
    const char* str1 = arr1;
    const char* str2 = arr2;
    if (*arr2 == ' ')//当输入查找的为空字符串时,直接输出arr1
    {
        return (char*)arr1;
    }
    while (*arr1)
    {
        str1 = arr1;
        str2 = arr2;
        if (*str1 == *str2)
        {
            while (*str2)
            {
                str1++;
                str2++;
                if (*str1 != *str2)
                {
                    if (*str2 == '\0')
                    {
                        return (char*)arr1;
                    }
                    arr1++;
                    break;
                }
                else
                {
                    continue;
                }
            }
        }
        else
        {
            arr1++;
        }
    }
    return NULL;
}

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

这样子的效率仍然不够高,在以后可以研究一下KMP算法。

编译器的参考写法:

char * __cdecl strstr (
        const char * str1,
        const char * str2
        )
{
        char *cp = (char *) str1;
        char *s1, *s2;

        if ( !*str2 )
            return((char *)str1);

        while (*cp)
        {
                s1 = cp;
                s2 = (char *) str2;

                while ( *s2 && !(*s1-*s2) )//这里是判断相同,如果相同,则继续往下判断相同,当不相同或为s2为'\0'时,退出循环
                        s1++, s2++;

                if (!*s2)//这里是判断此次从要对比的字符串的位置开始比到s2是否比完,如果比完了(也就是s2为'\0'时),就直接返回找到的那个位置
                        return(cp);

                cp++;//这里是已经判断了比到s2并没有比完,说明要从cp位置的下一个字符开始比较,直至比到了或者比到字符串都遍历了一遍还没有找到
        }

        return(NULL);//当遍历完要对比字符串的所有元素都没有办法找到的时候,我们就会返回空指针,代表该字符串中没有我们要查找的字符串

}

strtok

用来分隔字符串的函数

char* strtok(char* str,const char* sep)

strtok的使用解释: 

  • sep参数是个字符串,定义了用作分隔符的字符集合
  • 第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记。
  • strtok函数找到str中的下一个标记,并将其用\0结尾,返回一个指向这个标记的指针。(注:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改)
  • strtok函数的第一个参数不为NULL,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。
  • strtok函数的第一个参数为NULL,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
  • 如果字符串中不存在更多的标记,则返回NULL指针。

strtok函数的使用

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

int main()
{
    const char* p = "@.";//遇到要分割的标志
    char arr[] = "zhengxiaolv@qq.com";
    char buf[50] = { 0 };//一般为了不改变原来的字符串,会选择copy到另一数组上来
    strcpy(buf, arr);
    char* str = NULL;
    for (str = strtok(buf, p); str != NULL; str = strtok(NULL, p))
    {
        printf("%s\n", str);
    }
    return 0;
}

(为防止保存的位置在使用完函数后被销毁,strtok在保存的时候会有一个静态的变量来储存上一个保存的位置,用来下一次查找下个标记)


错误信息报告

strerror

c语言中规定了一些错误信息

错误码——错误信息

0-“No Error”

1-

2-

....

在这里,strerror将错误码翻译出对应的错误信息。

c语言可以操作文件

当库函数使用的时候,发生错误会把error这个全局的错误变量设置为本次执行库函数产生的错误码

errno是c语言提供的一个全局变量,可以直接使用,放在errno.h的头文件中。


字符分类函数

 在对字符分类时,以前的我们是使用例如(ch>='A'&&ch<='Z')的方式来判定ch字符是否为大写字符,在判断是否为其他类型字符是也是用的这种形式,代码量大且有些冗长,而现在我们可以通过字符分类函数来简化我们的代码:

字符分类函数的使用举例:

#include <stdio.h>
int main()
{
    printf("%d\n",isspace('!'));
    return 0;
}

如果返回非0的数,则为空白字符,反之,返回0的话则为空白字符。


字符操作

字符转换

小写→大写:toupper() 大写→小写:tolower()

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <ctype.h>

int main()
{
    char ch = 0;
    ch = getchar();
    if (islower(ch))
    {
        printf("%c\n", toupper(ch));
    }
    else
    {
        printf("%c\n", tolower(ch));
    }
    return 0;
}


内存操作函数

memcpy

strcpy是拷贝字符串的,而拷贝int等类型时不行,这时候就需要用到memcpy,任何大小都可以拷贝。

void* memcpy(void* destination,const void* source,size_t num);
///size_t num——拷贝字节的个数
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
int main()
{
    int i = 0;
    int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
    int p[10] = { 0 };
    memcpy(p, &arr[5], 5 * sizeof(int));
    for (i = 0; i < 5; i++)
    {
        printf("%d ", p[i]);
    }
    return 0;
}

memcpy的仿写:

//my_memcpy的实现
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <assert.h>

void* my_memcpy(void* dst,const void* ori, int sz)
{
    assert(dst && ori);
    void* str = dst;
    int i = 0;
    for (i = 0; i < sz; i++)
    {
        *((char*)dst + i) = *((char*)ori + i);
    }
    return str;
}

int main()
{
    int i = 0;
    int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
    int arr2[5] = { 0 };
    my_memcpy(arr2, arr1, sizeof(arr1[0]) * 5);
    for (i = 0; i < 5; i++)
    {
        //printf("%d\n", *((int*)my_memcpy(arr2, arr1, sizeof(arr1[0]) * 5)+i));
        printf("%d\n", arr2[i]);
    }
    return 0;
}

还有一个天才仿写法:

void* my_memcpy(void* dst, const void* ori, size_t sz)
{
    assert(dst&&ori);
    void* str = dst;
    while (sz--)
    {
        *(char*)dst = *(char*)ori;
        ((char*)dst)++;
        ((char*)ori)++;
    }
    return str;
}

过渡:

创建一个int型数组,里面装有{1,2,3,4,5,6,7,8,9,10},此时把1,2,3,4,5的位置移动到3,4,5,6,7位置,此时用我们自己写的my_memcpy会发现打印出的结果和我们想象中的不一样。不是我们想要的,因为在原来代码中,拷贝会覆盖掉。

而在我们使用编译器给出的memcpy函数来尝试时,发现并不会出现这样的错误,那么我们的仿写是不是出现了错误呢?

其实,c语言只要求:memcpy能拷贝不重叠的内存空间就可用来,memmove去出来纳西重叠拷贝,都是发现vs的memcpy也能实现重叠拷贝。所以我们在仿写memcpy时只需要做到copy不重叠的就好。


memmove

memmove处理重叠的内存拷贝。

与memcpy差不多用法,但是可以直接覆盖重复的地方。

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
int main()
{
    int i = 0;
    int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
    memmove(arr + 2, arr, 5 * sizeof(int));
    for (i = 0; i < 10; i++)
    {
        printf("%d ", arr[i]);
    }
    return 0;
}

memmove的仿写:

在外面实现仿写前,先思考这样几种情况:

  1. 覆盖内容包含(或不包含)部分被覆盖内容,并且在被覆盖内容前方
  2. 覆盖内容包含(或不包含)部分被覆盖内容,并且在被覆盖内容后方

上面的情况可以分为以下图解,我们通过图解来一一解决:

当出现情况一时:

经过研究后可以发现,当在这种情况下将要移动的元素从后面开始移动,就能解决被覆盖掉的情况出现。

 

当出现情况二时:

和情况一相反,当在这种情况下将要移动的元素从前面开始移动,就能解决被覆盖掉的情况出现。这里就不用图解来解释了。

在经过对两种情况的分析后,终于可以写出如下代码:

//memmove的仿写
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <assert.h>

void* my_memmove(void* dst, void* ori, int sz)
{
    void* str = dst;
    int i = 0;
    assert(dst && ori);
    if (dst > ori)
    {
        for (i = sz-1; i >= 0; i--)
        {
            *((char*)dst + i) = *((char*)ori + i);
        }
    }
    else
    {
        for (i = 0; i <sz; i++)
        {
            *((char*)dst + i) = *((char*)ori + i);
        }
    }
    //另一种写法
    /*int i = 0;
    for (i = sz-1; i >= 0; i--)
    {
        *((char*)dst + i) = *((char*)ori + i);
    }*/
    /*if (dst > ori)
    {
        while (sz--)
        {
            *((char*)dst + sz) = *((char*)ori + sz);
        }
    }
    else
    {
        while (sz--)
        {
            *(char*)dst = *(char*)ori;
            ((char*)dst)++;
            ((char*)ori)++;
        }
    }*/
    return str;
}

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

 memset

设置内存为特定的字符,但是都是放置的以字节为单位的

因为是以字节为单位来改变的,所以其实使用上不太方便

void* memset(void* dest,int c,size_t count);//这里的c可以为字符或者整型
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
int main()
{
    char arr[] = "wojiushishizhegezemexie";
    memset(arr, 'x', 10);
    printf("%s\n", arr);
    return 0;
}

memcmp

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

比较从ptr1和ptr2指针开始的num个字节

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
int main()
{
    int arr1[] = { 1,2,3,7,5 };
    int arr2[] = { 1,2,3,7,5 };
    if (memcmp(arr1, arr2, 16) > 0)
    {
        printf("前者大\n");
    }
    else if (memcmp(arr1, arr2, 16) < 0)
    {
        printf("后者大\n");
    }
    else
    {
        printf("一样大\n");
    }
    return 0;
}

在学习了这么多字符函数与内存函数后要多多使用和仿写,来加深一下记忆。

收获满满的一天~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值