C语言基础11——字符串函数、内存函数。字符串函数模拟实现、字符函数讲解、内存函数模拟实现

目录

求字符串长度函数

strlen()函数

不可设置长度的字符串函数

strcpy()函数

strcat()函数

strcmp()函数

可设置长度的字符串函数

strncpy()函数

strncat()函数

strncmp()函数

字符串中查找字符

strchr()

strrchr()

字符串查找函数

strstr函数

strtok()函数

错误信息报告

strerror()函数

perror()函数

字符函数

字符分类函数

字符转换函数

内存函数

memcpy()函数

memmove()函数

memcmp()函数

memset()函数


C语言中对字符和字符串的处理很频繁,但是C语言本身没有字符串类型,字符串通常放在常量字符串或字符数组中。常量字符串适用于那些对它不做修改的字符串函数

/*
 * 字符串
 * - 以'\0'结尾的一串字符。0与'\0'相同,但是与'0'不同。
 * - \0标志着字符串的结束,但其不是字符串的一部分。
 *   计算字符串长度的时候,就不包含'\0'
 * - 字符串多以数组形式存在,以数组或指针的形式访问
 *   更多是以指针的形式。
 * - string.h有很多处理字符串的函数。
 */
  • 字符串特性

    int main()
    {
        //C语言中,如果两个相邻的字符串中间什么都没有,那么就会被看作是一个字符串。
        printf("abc""def\n");//abcdef
    
        char a[] = "a""c";
        printf("%s",a);//ac
    
        // 这里字符串中的\,表示换行。换行后字符串前的空格也算入字符串中
        printf("abc\
               def\n");//acabc           def
    
       printf("abc\
    def");//acabcdef
    }
    
  • 字符串常量

    int main()
    {
        int i;
        char *s = "hello world";
        char *s2 = "hello world";
    
        //s与s2都是指针,初始化为指向一个字符串常量。
        //由于这个常量所在的地方,所以实际上s与s2是const char*。但是如果不写const,编译器也不报错。
        //所以我们就需要自己注意,不能去修改常量
        //*s = 'B';     //该操作是要去修改常量,常量不允许修改,所以编译报错。
    
        //我们发现字符串常量存放的位置,与局部变量存放的位置不同。
        //局部变量的地址与字符串常量的地址,相距很远。
        //并且s与s2指向了同一个字符串常量。这是因为字符串常量是存在于代码段中。
        //并且对于代码段中的数据,我们只有读取的权限,没有写入的权限,这就是为什么常量一旦定义之后就不可以修改的原因。
        //因为s于s2指向相同的字符串常量,所以他们实际上是指向了同一个字符串常量。
        printf("%p\n",&i);  //000000A53B9FF72C
        printf("%p\n",s);   //00007FF7967BA000
        printf("%p\n",s2);  //00007FF7967BA000
    
        //如果定义的字符串需要修改,则应使用数组
        char s3[] = "Hello world!";
        
        return 0;
    }
    
  • char*是字符串吗?

    /*
     * 1.字符串可以表达为char*的形式
     * 2.char*不一定是字符串
     *   char*本意上是指向字符的指针,可能指向的是字符数组
     *   只是说char*所指向的字符数组有字符串结束符时,才能说是指向的是字符串。
     */
    
  • 空字符串

    //""是一个空字符串,空字符串以\0结尾,也就是说buffer[0] = '\0'
    char buffer[100] = "";
    
    //该数组长度是1,其中存储一个字符串结束符\0
    char buffer[] = "";
    
  • main()函数的参数有两个

    //表示数组argv有argc个元素。
    int main(int argc,char const* argv[])
    {
        //argv[0]是命令本身。当使用Unix的符号链接时,反应符号链接的名字。
        int i;
        for(i=0 ; i<argc ; i++)
        {
            printf("%d——%s\n",i,argv[i]);
            //windows下:0——D:\Program Files (x86)\CLion 2021.3.2\Project\First\cmake-build-debug\First.exe
    
        }
    }
    
    //实际上用于Linux系统下,输入指令运行该程序时,所有输入的内容,都以空格隔开,存储在argv()数组中
    //例如输入指令调用以上程序(以上程序写入文件,文件名是a.out,linux下可执行程序以.out为后缀)。
    //指令: ./a.out 123 abc haha hehe 
    //程序执行输出:0——./a.out 1——123 2——abc 3——haha 4——hehe
    

求字符串长度函数

strlen()函数

  • 函数原型

    /*
     * strlen()函数
     * 函数原型:size_t strlen(const char* str);
     * 作用:返回字符串str的长度。字符串的长度由终止空字符'\0'决定。只计算字符的个数,直到出现\0。长度不包括结束符'\0'。
     * 参数 —— str:要计算长度的字符串。
     * 返回值:返回字符串的长度。
     * 注意:参数指向的字符串必须要以\0结束。如果没有\0,则会一直向后取,直到碰到\0位置,因为其后出现\0的位置并不能确定,所以此时的字符串长度是个随机值。
     *      size_t是服务号整型
     */
    
  • strlen()函数使用

    #include <string.h>
    #include <stdio.h>
    
    int main()
    {
        //arr1数组中实际上有四个元素:a b c \0
        char arr1[] = "abc";
        //arr2数组中没有结束符\0
        char arr2[] = {'a','b','c'};
    
        int len1 = strlen(arr1);
        int len2 = strlen(arr2);
    
        printf("%d\n",len1);//3
        //此时的长度是个随机值,因为没有碰到\0会一直向后取。
        printf("%d\n",len2);//6
        return 0;
    }
    
  • strlen()函数模拟实现

    计数器方式,使用中间变量

    #include <string.h>
    #include <stdio.h>
    #include <assert.h>
    
    int my_strlen(const char * str)  //因为只是计算长度,所以为str指针增加一个不可修改的属性。
    {
        assert(str);  //str不是空指针的时候执行程序
        int count = 0;
        while(*str != '\0')
        {
            count++;
            str++;
        }
        return count;
    }
    
    int main()
    {
        char arr1[] = "abcdddef";
        char arr2[] = {'a','b','c'};
        //模拟实现strlen函数
        int len1 =  my_strlen(arr1);
        int len2 =  my_strlen(arr2);
    
        printf("%d\n",len1);//8
        printf("%d\n",len2);//11,这个11是个随机值
        return 0;
    }
    

    递归实现

    /*
     * 以下程序执行过程:
     * - main()函数调用,char数组创建,其中存储数据dog,printf("%d\n",my_strLen(arr));执行
     * - my_strLen(arr)执行,其中存储的是数组首元素的内存地址,其中存储的'd'!='\0',输出,并执行return 1+my_strLen(str+1);
     *   注意这里传进来的是str,也就是dog'\0',比较的时候用的指针变量,*str则表示其是首元素地址,也就是第一个字符。
     * - 1+my_strLen(str+1)执行,str+1表示’d‘的下一个内存地址。此时函数内的str就变成了og'\0',*str是'o'!='\0'
     *   输出并执行return 1+my_strLen(str+1);
     * - 1+my_strLen(str+1)执行,str+1表示’o‘的下一个内存地址。此时函数内的str就变成了g'\0',*str是'g'!='\0'
     *   输出并执行return 1+my_strLen(str+1);
     * - 1+my_strLen(str+1)执行,str+1表示’g‘的下一个内存地址。此时函数内的str就变成了'\0',*str是'\0'='\0'
     *   此时if中条件为0,不再进行函数递归。return0
     * - return 0+1之后,再次return 1+1,再次return 1+2,就返回3。
     */
    #include <stdio.h>
    
    //因为是计算数组中字符的个数,所以应该是返回一个数。因为是要读取数组本身,所以参数要传入指针变量。
    int my_strLen(char* str)
    {
        //如果传递进来的这个地址对应的不是'\0'字符串结尾字符,就递归调用
        //*str就表示我们传递进来的元素的内存地址,这里如果写成str,就
        if(*str != '\0')
        {
            //因为str是一个指针变量,其中存储的是数组首元素的内存地址
            //又因为数组中存储的元素内存地址都是连续的,所以str+1就表示下一个元素的内存地址。
            //这里不是*str,是因为str+1本身就是地址,我们传入之后,函数直接操作就可以。
            return 1+my_strLen(str+1);
        }
    
        //如果str中读取的是\n,也就是说读取到字符串末尾了,则返回0
        return 0;
    }
    
    int main()
    {
        char arr[] = "dog";
        printf("%d\n",my_strLen(arr));
    }
    

    指针-指针

    #include <stdio.h>
    
    //数组指针相减求长度的方法
    int myStrLen(char * str)
    {
        //将数组首元素的地址保存到s中
        char * s = str;
        //如果不是\0,就一直循环,直到str对应的地址是最后一个元素的地址,也就是'\0'的地址
        while(*str != '\0')
        {
            str++;
        }
        //最后一个元素的地址-第一个元素的地址,就是这两个元素之间的个数,也就是字符串的长度
        return str-s;
    }
    
    int main()
    {
        char arr[] ="abcdef";
        int len = myStrLen(arr);
        printf("%d\n",len);
        return 0;
    }
    
  • 需要注意的地方:strlen()函数返回size_t类型,是无符号整型

    #include <string.h>
    #include <stdio.h>
    
    int main()
    {
        //因为strlen的返回类型是size_t,这两个无符号类型的值进行加减,返回的也是一个无符号类型的数。
        //第一个长度是3,第二个长度是6。3-6本来应该是-3。但是因为是无符号整型,所以最高位1被解析为数值位。
        //-3原码:10000000 00000000 00000000 00000011
        //-3反码:11111111 11111111 11111111 11111100
        //-3补码:11111111 11111111 11111111 11111101
        //strlen("abc")-strlen("abcdef")表达式执行后,其结果最后为size_t无符号整型,所以一定是比0大的。
        //-3在内存中存储为补码,如果是无符号整型,则最高位1被解析为数值位。就是4294967293。这个数是远大于0的,所以输出大于。
        if(strlen("abc")-strlen("abcdef") >0)
        {
            //%u以无符号整形打印。
            printf("%u\n",strlen("abc")-strlen("abcdef"));//4294967293
            printf("大于");
        }
        else
        {
            printf("小于");
        }
    }
    

不可设置长度的字符串函数

strcpy()函数

  • 函数原型

    /*
     * strcpy()函数
     * 函数原型:char *strcpy(char *dest,const char *src)
     * 作用:将src指向的C字符串复制到dest指向的数组中,包括终止的空字符(并在复制终止符后停止)。
     * 参数:dest——用于复制内容的目标数组;      src——要复制的字符串
     * 需要注意:
     * - 如果目标数组的dest不够大,而src中的字符串长度又太长,可能会造成缓冲溢出的情况。要保证dest足够大,以确保能放下源字符串
     * - 目标空间必须可变。如:dest是一块数组空间,不能是指向常量字符串的指针,因为常量字符串是常量,不可修改。
     * - 目标数组中如果有内容,则会被源字符串覆盖掉。
     * - 因为遇到'\0'才会停止拷贝,所以源字符串必须以'\0'结束。如果没有'\0',则可能得到意料之外的结果。
     * 返回值:返回一个指向最终的目标字符串dest的指针
     */
    
  • 使用

    #include <stdio.h>
    #include <string.h>
    
    int main()
    {
        //数组拷贝
        char arr1[20] = {0};
        char arr2[] = "hello world!";
    
        strcpy(arr1,arr2);
        printf("%s\n",arr1);//hello world!
    
        //字符串拷贝
        char arr3[20] = {0};
    //    char* p ="abcdefg";
    //    strcpy(arr3,p);
        //可以简化为:
        strcpy(arr3,"you are good");
        printf("%s\n",arr3);//you are good
    
        //程序会挂掉,因为常量字符串不能修改。
    //    char* str = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
    //    char* p ="abcd";
    //    strcpy(str,p);
    //    printf("%s\n",str);
    
        //目标字符串中如果有内容,则会被源字符串覆盖掉。
        char arr4[20] = {'c','c','c'};
        char arr5[] = {'a','b','c'};
        //源字符串如果没有以'\0'结尾,则会一直拷贝,直到碰到\0。
        strcpy(arr4,arr5);
        printf("%s\n",arr4);//abcabc,如果c后有\0,则正常拷贝应该是abc,但是没有\0,所以出现了abcabc。abc后是随机字符。
        return 0;
    }
    
  • strcpy()函数模拟实现

    #include <stdio.h>
    
    void myStrCpy(char* dest,char* src)
    {
        //当不是字符串结尾的时候拷贝
        while(*src != '\0')
        {
            *dest++ = *src++;
        }
        //执行到这里,*src中存储的就是\0了,*dest也++了,虽然此时循环停止了,但是我们可以手动拷贝\0到dest中
        *dest =*src;
    }
    int main()
    {
        char arr1[20] = "abcdefgh";
        char arr2[] = "hello";
        myStrCpy(arr1,arr2);
        printf("%s",arr1);
        return 0;
    }
    

    简化

    #include <stdio.h>
    
    void myStrCpy(char* dest,char* src)
    {
        //当没有到达字符串结尾的时候,循环一直进行。这里是后置++,先赋值再运算。
        //()中表达式的结果就是*src的结果。例如:b=3,a=b这个表达式的结果应该是a,但是a=b,也就是说b就是这个表达式的结果。
        //当src指向的是'h'时,它对应的ASCII码是:104。非零条件为真,执行空语句。此时后置++生效,继续判断()中的结果
        //只有当src='\0'的时候,也就是dest='\0',字符串结尾符'\0'拷贝了过去,字符串拷贝完成。而'\0'的ascii码就是0,也就是条件为假,循环结束。
        while(*dest++ = *src++);
    
        //参考:
        //int i = 1;
        //printf("%d\n",i='\0'); //0
    }
    int main()
    {
        char arr1[20] = "abcdefgh";
        char arr2[] = "hello";
        myStrCpy(arr1,arr2);
        printf("%s",arr1);
        return 0;
    }
    

    使用断言,并使用const修饰

    #include <stdio.h>
    #include <assert.h>
    
    //库函数中的字符串拷贝函数是char * strcpy ( char * destination, const char * source );
    //它的形参在源数组前加上了一个const,为什么要加这个东西呢?
    //我们拷贝是要把src指向的内容放入dest指向的空间中,从本质上讲,是希望dest指向的内容被修改,而src指向的内容不修改。
    //也就是*src不能被修改,所以在*的左边加上了const,表示*src不能被修改。
    void myStrCpy(char* dest,const char* src)
    {
        //当程序执行到断言的位置时,对应的断言应该为真。若断言不为真时,程序会中止执行,并给出错误信息。
        //当传入的两个数组中有一个是空指针,则输出错误信息
        assert(dest != NULL);
        assert(src != NULL);
        //const表示被修饰的变量不可被修改。防止我们在编写的过程中写反。
        // 也就是说如果我们这里写反了,那么编译都不会过去,报错:assignment of read-only location '*src++'
        //while(*src++ = *dest++);
        while(*dest++ = *src++);
    
    }
    int main()
    {
        char arr1[20] = "abcdefgh";
        char arr2[] = "hello";
        myStrCpy(arr1,arr2);
        printf("%s",arr1);
        return 0;
    }
    

    最终版

    #include <stdio.h>
    #include <assert.h>
    //库函数中的字符串拷贝函数是char * strcpy ( char * destination, const char * source );
    //库函数返回类型是一个指针,返回的是目标的起始地址。
    char* myStrCpy(char* dest,const char* src)
    {
        assert(dest);
        assert(src);
        //我们定义一个指针,接收传入函数的目标起始地址,保存到变量中。
        //使用ret进行拷贝地址的变化。
        char* ret = dest;
        while(*ret++ = *src++);
        //拷贝完成后返回起始地址。
        return dest;
    }
    int main()
    {
        char arr1[20] = "abcdefghjklmn";
        char arr2[] = "hi baby!";
        printf("%s",myStrCpy(arr1,arr2));
        return 0;
    }
    

strcat()函数

  • 函数原型

    /*
     * 字符串拼接函数strcat()
     * 函数原型:char * strcat(char* dest,const char* src);
     * 作用:把src指向的字符串,追加到dest所指向的字符串的结尾。dest字符串中“第一个字符串结束符”被src第一个字符覆盖。
     *      将整个源字符串,包括结束符\0,都拼接到dest字符串后。如果拼接的源字符串没有结束符\0,则程序会down掉。
     *      按理说:如果一个数组,自己拷贝自己。数组中存放的字符串结束符\0就会被替换掉,但是在运行的时候,并没有被替换。而是正常拷贝,并且拷贝后的字符串有结束符。但是如果拷贝的是一个没有结束符的字符数组,则程序就会down掉。
     * 参数:dest:指向目标数组,该数组中包含了一个C字符串,并且足够容纳追加后的字符串。src:要追加的字符串,该字符串不会覆盖目标字符串。
     * 返回值:返回dest字符串指针
     * 注意:dest必须可以修改,不能是是指向常量字符串的指针。源字符串必须有字符串结束符\0
     */
    
  • 使用

    #include <string.h>
    #include <stdio.h>
    
    int main()
    {
        char arr1[20] = "hello \0aaa";
        strcat(arr1,"word!");
    
        printf("%s\n",arr1);//hello word!
        return 0;
    }
    
  • strcat()函数的模拟实现

    #include <string.h>
    #include <stdio.h>
    
    char* my_strcat(char* dest,const char* src)
    {
        //因为要返回目标指针,所以我们在附加之前就保存其指针。
        char* ret = dest;
        //1.找字符串dest中的\0
        while(*dest) //当*dest中不是\0的时候,循环就一直运行。当其指向了\0时,*dest=\0,这个表达式的结果就是0,循环结束。
        {
            dest++;
        }
        //追加字符串,包含'\0',当*src='\0'时,*dest就='\0',而\0就是0,循环结束。当拷贝了字符串结束符之后,循环也停止。
        while(*dest++ = *src++);
        return ret;
    }
    
    int main()
    {
        char arr1[20] = "hello \0";
        printf("%s\n",my_strcat(arr1,"world!"));//hello word!
        return 0;
    }
    

strcmp()函数

  • 函数原型

    /*
     * strcmp()函数
     * - 用于对两个字符串进行比较
     * - 语法: int strcmp(const char* str1 , const char* str2);
     *   参数str1与参数str2是参与比较的两个字符串
     * - strcmp()会根据ASCII编码依次比较str1和str2中的每一个字符,直到出现不到的字符,或者达到字符串末尾(遇到\0)
     * - 返回值:
     *     ——如果返回值 < 0 ,则表示str1 < str2
     *     ——如果返回值 > 0 ,则表示str1 > str2
     *     ——如果返回值 = 0 ,则表示str1 = str2
     * - 比较这两个字符串相同位置的字符的ASCII码,一旦出现不匹配的,就比较这两个字符。ASCII码低的就小,高的就大。
     * - 如abcq与adc进行比较,两个字符串第一个字符相同,比较第二个字符,d比b大,所以adc>abcq。比较的是对应位置的字符的大小,而不是比较字符串长度。
     */
    
  • 使用

    #include <stdio.h>
    #include <string.h>
    
    int main()
    {
        char* p = "obc";
        char* q = "abcdef";
        //字符串比较不能使用运算符比较,要使用相应的字符串函数。
        //比较两个字符串对应位置的字符的ASCII码。
        int ret = strcmp(p,q);
        if(ret > 0)
        {
            printf("p大");
        }
        else if(ret < 0)
        {
            printf("q大");
        }
        else
        {
            printf("p=q");
        }
        return 0;
    }
    
  • strcmp()函数模拟实现

    #include <stdio.h>
    #include <string.h>
    #include <assert.h>
    
    int my_strcmp(const char* s1 ,const char* s2)
    {
        assert(s1 && s2);//如果s1或s2有一个是空指针,程序就不会继续执行。
        //如果对应位置的字符相等,那么地址+1,指向下一个字符。直到这两个字符不相等,或比较完。
        while(*s1 == *s2)
        {
            //如果其中一个是字符串结束符,那么说明这两个字符串完全相等。相等返回0。
            if(*s1 == '\0')
            {
                return 0;
            }
            s1++;
            s2++;
        }
    
        //如果是abc与abcd比较,那么c比完之后。就是d与\0比较,d肯定比0大,所以是abcd>abc
        //如果指向的字符不相等,则进行比较,看哪个大。
        //s1比s2大,就返回>0的数。//如果s1<s2,就返回<0的数。
        return *s1 - *s2;
    }
    
    int main()
    {
        char* p = "abce";
        char* q = "abcdef";
        //比较两个字符串对应位置的字符的ASCII码。
        int ret = my_strcmp(p,q);
        if(ret > 0)
        {
            printf("p大");
        }
        else if(ret < 0)
        {
            printf("q大");
        }
        else
        {
            printf("p=q");
        }
        return 0;
    }
    

可设置长度的字符串函数

strncpy()函数

  • 函数原型

    /*
     * strcnpy()函数
     * 函数原型 char* strncpy(char* dest,const char* src,size_t num);
     * 作用:把src的前num个字符复制到dest。如果num>src长度,多余的用\0填充,直到总共写入了num个字符。如果num<sre,则不会在目标末尾隐式追加空字符。
     * 参数:
     * - dest:指向要存储复制内容的目标数组指针。
     * - src:要复制的字符串
     * - num:要从src复制的最大字符数。size_t是无符号整型。
     * 注意:
     * - dest与src不能重叠。也就是说不能自己复制自己。
     * - 如果dest中本来就有内容,并且src中没有空字符、src>num,此时dest前num个字符是从src中复制过来的,剩下的是dest中的内容。
     *   除非从src中复制了空字符\0,此时dest中长于src的部分才不会显示。
     * 返回值:返回dest
     */
    
  • 使用

    #include <string.h>
    #include <stdio.h>
    
    int main()
    {
        char arr1[20] = "abcdef";
        char arr2[] = "qwer";
        //复制qw到arr2中,因为没有空字符,所以arr1前两个字符就变为:qw,接着arr1中本来就有的cdef。就成为:qwcdef
        strncpy(arr1,arr2,2);
        printf("%s\n",arr1); //qwcdef
        return 0;
    }
    
  • strncpy()函数模拟实现

    #include <stdio.h>
    char* my_strncpy(char* dest,const char* src,size_t num)
    {
        //将dest保存,最后返回
        char* start = dest;
        //条件:当num不为0、*src没有到\0的时候进行拷贝
        //情况1:当num不为0,当*src='\0'时,*dest = '\0',\0就是0,此时循环停止。//因为拷贝\0也是一次拷贝,而拷贝了一次,没有执行循环,num少减了一次1。
        //情况2:当*src不为0,而num为0时,说明要拷贝的次数已经完成。
        //后置++,每次拷贝完之后,dest与src都向后指一个位置,方便进行拷贝。
        while(num && (*dest++ = *src++))
        {
            num--;//循环每执行一次,num-1
        }
    
        //num为0后,说明次数拷贝完了,不用再做操作
        //而如果num不为0,而*src为字符串结束符\0了。此时num为几,就要在后面追加几个\0。
        //1、如果num不为0,而我们上面的程序num少减了一次1,如果使用后置++,则会多追加一个\0
        //2、使用前置++,先减1再执行程序,为dest之后追加0。这样看似合理。但是当num=0时,0-1=-1,因为num是无符号整型。-1就被解析为一个很大的数,循环一直追加\0,此时程序就会陷入死循环。
        //所以我们不能直接进行操作,而是要操作前判断一下num是不是为0,如果不为0再进行操作
        //while(++num){*dest++ = '\0';}
        if(num)
        {
            //如果num不为0,则继续为dest追加空字符
            while(--num){
                //此时是num不为0,而*dest=*src=\0的情况,因为是后置++,此时的dest指向的就是拷贝的\0的后面,直接*进行追加\0操作
                //而后置++,同样是为了下一次追加更方便。
                *dest++ = '\0';
            }
        }
    	return start;
    }
    int main()
    {
        char arr1[20] = "abcdefg";
        char arr2[] = "qwe";
        //复制qw到arr2中,因为没有空字符,所以arr1前两个字符就变为:qw,接着arr1中本来就有的cdef。就成为:qwcdefg
        my_strncpy(arr1,arr2,2);
        printf("%s\n",arr1); //qwcdefg
    
        //当num>src时,如果想要验证拷贝的\0个数是否正确,可以加断点,Debug进行查看。
        return 0;
    }
    

strncat()函数

  • 函数原型

    /*
     * 指定长度字符串拼接函数strncat()
     * 函数原型:char * strncat(char* dest,const char* src,size_t num);
     * 作用:将src的前num个字符附加到dest结尾。dest字符串中第一个的字符串结束符被src第一个字符覆盖。并且在拼接后的字符串末尾加上字符串结束符
     *      如果src中字符串的长度小于num,则仅复制直到终止空字符串的内容(包括终止字符串)。
     * 注意:因为当长度<num时,strncat并不会为其添加结束符,只有碰到结束符才会结束。此时如果追加的字符数组没有结束符\0时,在追加了其长度的字符之后的内容,都是随机的,因为只有碰到结束符\0才会结束。所以我们在为其追加时,src字符串一定要带有\0。
     * 参数:
     * - dest:指向目标数组,应该包含一个C字符串,且足够容纳追加后的字符串,包括额外的空字符
     * - src:要追加的字符串
     * - num:要附加的最大字节数。size_t无符号整型。
     * 返回值:返回一个只想最终目标字符串dest的指针。
     *
    
  • 使用

    #include <string.h>
    #include <stdio.h>
    
    int main()
    {
        char arr1[20] = "hello ";
        char arr2[] ="world!";
    
        //将arr2的前3个字符附加到arr1结尾。arr1中第一个字符串结束符被arr2第一个字符覆盖。并且在拼接后的字符串末尾加上字符串结束符\0
        strncat(arr1,arr2,3);
        printf("%s\n",arr1); //hello wor
    
        char arr3[20] = "hi ";
        char arr4[] ="baby!";
        //arr4中字符串的长度小于20,则仅复制直到终止空字符串的内容(包括终止字符串)。
        strncat(arr3,arr4,20);
        printf("%s\n",arr3); //hi baby!
        return 0;
    }
    
  • strncat()函数模拟实现(模拟库函数实现方法实现)

    #include <stdio.h>
    
    char* my_strncat(char* dest, char* src, size_t num)
    {
        //保存dest,最后返回
        char* start = dest;
        //首先将dest定位到其字符串结束符\0的位置。
        //*dest=\0时,条件为0,循环结束。后置++,此时dest指向\0后的位置。
        while(*dest++);
        //dest-1,让dest指向字符串结束符\0。
        dest--;
        //num为几就循环几次
        while(num--)
        {
            //进行拷贝。
            //*dest++ = *src++;
            //如果num>src长度,那么如何结束呢?加入判断,在*src == \0的时候,结束方法
            //if(*src == '\0'){return start;}
    
            //简化:只有当拷贝到字符串结束符的时候结束结束方法。
            //情况1:num<src长度时,追了num个字符,循环结束会结束。结束后会为其添加字符串结束符。
            //情况2:num>src长度,在*src == \0的时候,结束方法。
            if((*dest++ = *src++) == '\0')
            {
                return start;
            }
        }
    
        //为结尾添加字符串结束符\0
        *dest = '\0';
    
        //返回dest。
        return start;
    }
    
    int main()
    {
        char arr1[20] = "hello ";
        char arr2[] ="world!";
    
        //将arr2的前3个字符附加到arr1结尾。arr1中第一个字符串结束符被arr2第一个字符覆盖。并且在拼接后的字符串末尾加上字符串结束符\0
        my_strncat(arr1,arr2,3);
        printf("%s\n",arr1); //hello wor
    
        char arr3[20] = "hi ";
        char arr4[] ="baby!";
        //arr4中字符串的长度小于20,则仅复制直到终止空字符串的内容(包括终止字符串)。
        my_strncat(arr3,arr4,20);
        printf("%s\n",arr3); //hi baby!
    
        return 0;
    }
    

    实现注意:

    /*
     * - 因为当长度<num时,strncat并不会为其添加结束符,只有碰到结束符才会结束。
     *   此时如果追加的字符数组没有结束符\0时,在追加了其长度的字符之后的内容,都是随机的,因为只有碰到结束符\0才会结束。
     *   所以我们在为其追加时,src字符串一定要带有\0。
     * - 解决:重新实现strncat
     *
     * - 实现思路:增加一个参数,用于传递字符数组元素个数/字符串的长度。
     *   这样在实现的时候,加入判断。
     *   - 如果长度>指定追加字符数num,那么就以num--为条件进行循环
     *   - 如果长度<num,那么我们以(长度--)为条件进行循环,然后循环结束后为其添加结束符\0。
     *
     * - char * my_strncat(char* dest,const char* src,size_t length,size_t num);
     *   dest:指向目标数组,应该包含一个C字符串,且足够容纳追加后的字符串,包括额外的空字符
     *   src:要追加的字符串/字符
     *   length:字符串/字符数组长度
     *   num:要附加的最大字节数。size_t无符号整型。
     *   返回值:返回一个只想最终目标字符串dest的指针。
     */
    

strncmp()函数

  • 函数原型

    /*
     * strncmp()函数
     * 函数原型:int strncmp(const char* str1,const char* str2,size_t num);
     * 作用:比较str1与str2的前n个字符。比较每个字符串的对应字符,如果相等,则比较下一个位置的字符,直到字符不同/到达终止空字符/到达第num个字符。
     * 参数:str1 —— 要比较的字符串;   str2 —— 要比较的字符串;  num —— 要比较的最大字符数,size_t是无符号整型。
     * 返回值:
     * - 如果返回值 < 0 ,表示第一个不匹配的字符在str1中的值低于在str2中的值。str1 < str2
     * - 如果返回值 > 0 ,表示第一个不匹配的值在str1中的值大于在str2中的值。str1 > str2
     * - 如果返回值 = 0 ,表示两个字符串前num个字符相等。str1 = str2
     */
    
  • 使用

    #include <string.h>
    #include <stdio.h>
    
    int main()
    {
        char* p = "abcdef";
        char* q ="abcqwe";
        int ret = strncmp(p,q,3);
        printf("%d\n",ret);//0 表示前三个字符相等
    
        int ret1 = strncmp(p,q,4);
        printf("%d\n",ret1);//-1,表示i前三个字符,p<q
    
        char* p1 = "abc";
        char* q1 ="abc";
        int ret2 = strncmp(p1,q1,6);
        printf("%d\n",ret2);//0,表示相等。num>长度,比较到字符串结束符就停止。
    
        char* p2 = "aec";
        char* q2 ="abc";
        int ret3 = strncmp(p2,q2,6);
        printf("%d\n",ret3);//1,表示p2>q2。虽然num为6,但是在比较到第二个字符时,出现了不匹配的字符。比较出了大小,所以停止。
        return 0;
    }
    

字符串中查找字符

strchr()

/*
 * strchr()函数
 * 函数原型:csont char* strchr(const char* str,int char);
 * 作用:定位字符串中第一次出现的字符
 * - 返回指向C 字符串str中第一次出现的字符的指针。
 *   终止的空字符被认为是 C 字符串的一部分。因此,也可以定位它以检索指向字符串末尾的指针。
 * 参数:
 * - str:C字符串
 * - char:要定位的字符,作为int整型提升传递,但在内部转换为char进行比较。
 * 返回值:指向str中第一次出现该字符的指针。如果未找到该字符,则该函数返回一个空指针。
 *
 */

int main()
{
    char s[] = "hello";
    //查找第一个l
    char *p = strchr(s,'l');
    printf("%s\n",p);//llo

    //查找第二个l
//    p = strchr(p+1,'l');
//    printf("%s\n",p);//lo

    //拷贝找到的字符之后的所有字符到另一个字符串
    char *t = (char*)malloc(strlen(s)+1);
    strcpy(t,p);
    printf("%s\n",t);//llo

    //拷贝找到的字符之前的所有字符到另一个字符串
    *p = '\0';
    strcpy(t,s);
    printf("%s\n",t);//he

    return 0;
}

strrchr()

/*
 * strrchr()
 * 函数原型:const char* strrchr(const char* str,int char);
 * 作用:查找字符串中最后一次出现的字符
 * - 返回指向字符串str中最后一次出char字符的指针。
 *   终止空字符被认为是C字符串中的一部分。因此,也可以定位它来检索指向字符串结尾的指针。
 * 参数:
 * - str:字符串
 * - char:要定位的字符,作为int整型提升传递,但在内部转换为char进行比较。
 * 返回值:指向str中最后一次出现该字符的指针。如果未找到该字符,则该函数返回一个空指针。
 */

int main()
{
    char s[] = "world dlrow";
    char *p = strrchr(s,'r');
    printf("%s\n",p);//row
    return 0;
}

字符串查找函数

strstr函数

  • 函数原型

    /*
     * 定位子串函数strstr()
     * 函数原型:const char* strstr(const char* str1,const char* str2);
     *       或char* strstr(char* str1,const char* str2);
     * 作用:在str1中查找第一次出现str2的指针,如果str2不是str1的一部分,则返回空指针。匹配过程中不包括字符串结束符'\0',但它会停在那里。
     * 参数:- str1:要扫描的长字符串。   -str2:短字符串,在str1中查找是否出现的字符串
     * 返回值:该函数返回str2第一次出现在str1中的指针。如果str2不在str2中,也就是未在str1中找到str2,就返回NULL。
     */
    
  • 使用

    #include <string.h>
    #include <stdio.h>
    
    int main()
    {
        char arr1[] = "abcdefabcdef";
        char arr2[] = "bcd";
        char* ret = strstr(arr1,arr2);
        ret == NULL? printf("没找到"): printf("找到了:%s\n",ret);
        return 0;
    }
    
  • strstr函数的模拟实现

    #include <stdio.h>
    #include <assert.h>
    
    char* my_strstr(const char* str1,const char* str2)
    {
        assert(str1 && str2);
    
        //strstr规定,如果要在某个字符串中查找空字符串(""):其中存储了字符串结束符,那么就返回str1字符串
        if(*str2 == '\0')
        {
            return (char*)str1;
        }
    
        /*
         * 比较逻辑:
         * - 需要把短的字符串(bbc)第一个字符与长字符串(acbbbcdef)的第一个字符比较:a≠b。指针向后指1
         * - bbc第一个字符与acbbbcef中第二个字符比较:b≠c。指针向后指1
         * - bbc第一个字符与acbbbcef中第三个字符比较:b=b;指针向后指1
         *   继续比较:bbc第二个字符与acbbbcef中第四个字符比较,b=b;指针向后指1
         *   继续比较:bbc第三个字符与acbbbcef中第五个字符比较,c≠b。指针返回指向开始比较时的地址。
         *   注意这里:acbbbcef指向第五个字符的指针,此时需要指向的是第四个字符。也就是说,我们需要有一个指针保存str1在每次比较前所指向的位置。
         * - bbc第一个字符与acbbbcef中第四个字符比较:b=b;指针向后指1
         *   继续比较:bbc第二个字符与acbbbcef中第五个字符比较,b=b;指针向后指1
         *   继续比较:bbc第三个字符与acbbbcef中第六个字符比较,c=c;指针向后指1
         *   继续比较:bbc中的结束符与acbbbcef中第六个字符比较,此时返回acbbbcef中第四个指针的地址。
         */
    
        //当不匹配时用于每次向后走1个位置,匹配时使用s1进行比较。
        //如果只是前几个字符匹配,出现不匹配字符的时候,将repeat指向下一个位置,并且赋给s1。
        //如果全部匹配,则说明此时repeat指向的位置就是str2在str1中首次出现的位置,返回即可。
        const char* repeat = str1;
        //因为查找子串,最后需要返回子串的指针。所以我们将其赋给s1、s2。操作s1、s2,这样str1、str2中的值就不会被改变了。
        const char* s1 = str1;//长字符串指针,用于每次比较
        const char* s2 = str2;//短字符串指针,要用于每次比较。
    
        //repeat每次向后指1,直到长字符串匹配完(匹配到终止空字符)时循环结束
        while(*repeat)
        {
            //每次循环,s1中指向的是repeat指向的位置。
            s1 = repeat;
            //s2指向的就是短字符串。
            s2 = str2;
    
            //进行匹配,如果*s1=*s2,则s1与s2都指向下一个位置。
            //情况1:在abcd中找cd。c=c,d=d,\0=\0,此时s1与s2++之后都指向\0之后的内容了。加入判断
            //情况2:在abcde中找efg,e=e,\0≠f,此时跳出循环repeat++,直到repeat=\0循环停止。
            //我们可以加上判断:如果s1=\0了,就返回NULL。这样就不用判断之后的了。
    //        while(*s1 == *s2)
    //        {
    //            s1++;
    //            s2++;
    //        }
            //s2++后,如果s2是字符串结束符,说明s1中包含了s2,那么我们在循环后加条件让其返回repeat指针
            /*
            while(*s2 && *s1 == *s2)
            {
                s1++;
                s2++;
            }
            if(*s2 == '\0')
            {
                return repeat;
            }*/
    
            while(*s1 == *s2)
            {
                s1++;
                s2++;
                //当*s1=*s2时,如果s2++为空,说明s2已经到了字符串结束符。
                //也就是说,此时repeat中存储的指针就是s1中第一次出现s2的位置。
                /*
                 * 注意:这个判断要在循环内。
                 * 例如:当abcd中找cd时,如果判断在循环外。则c=c,d=d,\0=\0,然后s1与s2++,此时他们都指向字符串结束符之后的位置
                 * - 等循环出来,s2指向的已经不是结束符的位置了。所以判断要放在循环内。
                 */
                if(*s2 == '\0')
                {
                    return repeat;
                }
            }
            /*
             * - 情况1:abcd中找cd,s1与s2都为\0,但是s2为\0的时候就已经返回了指针,不考虑这种情况。
             * - 情况2:abcd中找def,d=d,\0≠e,跳出循环之后进入判断,因为s1已经找完字符串结尾,在这之后肯定不可能找到def字符串了
             *   所以我们直接返回NULL。加这个判断,只是为了提升效率。也可以不加,跳出循环后repeat+1,然后传入s1。只是会多判断几次再返回NULL
             */
            if(*s1 == '\0')
            {
                return NULL;
            }
            repeat++;
        }
        return NULL;
    }
    
    int main()
    {
        char arr1[] = "acbbbcef";
        char arr2[] = "bbc";
        char* ret = my_strstr(arr1,arr2);
        ret == NULL? printf("没找到"): printf("找到了:%s\n",ret);
        return 0;
    }
    

strtok()函数

  • 函数原型

    /*
     * strtok()函数
     * 函数原型:char* strtok(char* str,const char* delimiters);
     * 作用:使用标记拆分str字符串。这些标记由delimiters(分隔符)中的任何字符分割的连续字符序列。
     * - 在第一次调用时,该函数需要一个字符串作为str的参数,str的第一个字符用做扫描的起始位置。
     * - 函数首先从起始位置扫描不是分隔符的第一个字符,然后从此字符开始扫描分隔符。
     *   如:##abc#def#ggg#haha\0中以#为分隔符分割字符串,那么会从a开始扫描其后的分隔符。
     * - 在其后扫描到的第一个分隔符,该字符被替换为字符串结束符\0,并且起始扫描位置由函数返回;或者扫描到终止空字符,扫描也会停止。
     *   如:##abc#def#ggg#haha\0以#为分隔符分割字符串,从a开始扫描分隔符,扫描到c后面的#,会将其替换为\0,成为:##abc\0def#ggg#haha\0
     *   此时扫描的起始位置由函数返回,也就是指向str的第一个非分隔符字符'a'的指针。
     * - 如果扫描到分割符,那么在之后的调用中,该函数需要一个空指针,并使用前一个分隔符之后的位置作为新的扫描位置。
     *   例:也就是##abc\0def#ggg#haha\0中,c后面的那个空字符\0之后的位置,会保存到函数中,以便在下次调用中使用(不需要特定的库实现,来避免数据争用)
     * - 如果扫描到了分隔符,那么在之后的调用中,strtok函数的第一个参数为NULL,函数将在同一个字符串中被保存的位置开始,查找下一个分隔符。
     *   如:##abc\0def#ggg#haha\0,继续调用strtok(NULL,"#"),此时会从c后的\0后的d开始扫描分隔符,返回指向d的指针。
     *   扫描到f后#的位置,将其替换为空字符\0,成为:##abc\0def\0ggg#haha\0。然后f后\0之后的位置,会保存在函数中,以便于在下次调用中使用。
     *   ......再经历一次strtok(NULL,"#"),成为:##abc\0def\0ggg\0haha\0,g后的\0之后的位置被保存到函数中。
     * - 一旦找到str终止空字符,对该函数的所有后续调用(以空指针作为第一个参数)都会返回空指针。
     *   如:##abc\0def\0ggg\0haha\0,从h开始扫描,扫描到haha后的\0,返回指向h的指针,扫描停止。
     *   在此之后所有对strtok(NULL,"#")的调用,都将返回空指针。
     *
     * 注意:
     * - strtok会改变被操作的字符串,所以使用此函数分割字符串时,一般是拷贝源字符串后,对拷贝出的字符串进行函数调用。
     * - strtok函数的第一个参数不为NULL时,函数在str中找到第一个分隔符,并保存它在字符串中的位置。
     *   是在函数内部,有一个静态变量来保存其位置。在下一次调用的时候,使用这个位置后的那个位置作为开始扫描的位置,继续扫描分隔符。
     * - 也可以使用空格作为分隔符。strtok(str," ");
     *   但是不能使用\0作为分隔符,因为碰到\0strtok函数会以为遇到了字符串结束符,会下一次调用就会返回空指针。
     *
     * 参数:
     * - str:要被分解的字符串。注意:这个字符串是通过拆分成更小的字符串(标记)来修改的。或者可以指定空指针,在这种情况下,函数继续扫描先前成功调用结束的位置。
     * - delimiters:包含分割字符的C字符串。
     *
     * 返回值:
     * - 该函数返回被分解的第一个子字符串,如果没有可检索的字符串,则返回空指针。
     *   如果找到标记,则会显示指向该标记开头的指针;否则返回空指针。在扫描的字符串中,到达字符串结束符时,始终返回空指针。
     */
    
  • 使用

    #include <string.h>
    #include <stdio.h>
    
    int main()
    {
        char arr[] = "##abc#def#ggg#haha";
        char* p = "#";
    
        //strtok会改变被操作的字符串,所以使用此函数分割字符串时,我们将arr数组拷贝进tmp数组,对tmp进行函数调用。
        char tmp[20] = {};
        strcpy(tmp,arr);
        //strtok函数返回每一个分割后的字符串的起始字符的指针,我们使用char*类型指针变量q来接收
        char* q = NULL;
    
    //    q = strtok(tmp,p);
    //    printf("%s\n",q);//abc
    //
    //    q = strtok(NULL,p);
    //    printf("%s\n",q);//def
    //
    //    q = strtok(NULL,p);
    //    printf("%s\n",q);//ggg
    //
    //    q = strtok(NULL,p);
    //    printf("%s\n",q);//haha
    
        //简化
        //初始化:q=strtok(tmp,p)
        //判断条件:当q不为空指针时,说明还没有分隔到字符串结尾。
        //更新:每次打印了分割的字符串之后,进行更新  q=strtok(NULL,p)
        //当遇到tem中的字符串结束符\0时,扫描停止,下一次strtok(NULL,p)调用返回空指针。此时q为空指针,循环结束。
        for(q=strtok(tmp,p) ; q!=NULL; q=strtok(NULL,p))
        {
            printf("%s\n",q);
        }
    
        //循环中更新表达式已经执行,但是条件为假循环结束。此时我们打印查看发现q确实已经为为空指针
        //printf("%s\n",q);//(null)
        return 0;
    }
    

错误信息报告

strerror()函数

  • 函数原型

    /*
     * strerror()函数
     * 函数原型:char* strerror(int errnum);
     *
     * 作用:
     * - 使用库函数的时候,如果库函数调用失败,都会设置错误码:其函数中有一个全局变量int errno;
     * - strerror()从内部数组中搜索错误号errnum,生成带有描述错误信息的字符串,strerror生成的错误字符串取决于开发平台和编译器。
     *   返回的指针指向一个静态分配的字符串,程序不能修改。对该函数的进一步调用可能会覆盖其内容(不需要特定的库实现来避免数据争用)
     * 参数:
     * - errnum:错误号,通常传入的是errno。
     * - error是一个全局变量,C语言定义好的一个全局变量,所有人都可以使用。需要引入头文件<error.h>
     * 返回值:返回指向描述错误号errnum的错误字符串的指针。
     */
    
  • 使用

    #include <stdio.h>
    #include <string.h>
    #include <errno.h>
    
    int main()
    {
    //    printf("%s\n",strerror(0));//No error —— 没有错误
    //    printf("%s\n",strerror(1));//Operation not permitted —— 不允许操作:操作被拒绝
    //    printf("%s\n",strerror(2));//No such file or directory —— 没有这个文件/文件夹
    //    printf("%s\n",strerror(3));//No such process —— 没有这个进程
    //    printf("%s\n",strerror(4));//Interrupted function call —— 函数调用被中断
    //    printf("%s\n",strerror(5));//Input/output error —— 输入/输出错误
    
        //fopen()函数用于打开文件,test.txt是文件名,r表示以只读形式打开。
        FILE* pf = fopen("test.txt","r");
        if(pf == NULL)
        {
            //error是一个全局错误码,是一个全局变量。当fopen函数调用失败之后,会将error设置为对应值。
            //我们将error传入strerror()函数,就会打印对应的错误信息。
            //因为我们打开文件传入的文件名是一个没有创建的文件,所以错误信息是:没有这个文件
            printf("%s\n",strerror(errno));//No such file or directory
        }
        return 0;
    }
    

perror()函数

  • 函数原型

    /*
     * perror()函数与strerror()函数的区别:
     * - strerror()函数是把错误码转换为错误信息,需要的时候才打印其对应的错误信息
     * - perror()会直接打印错误信息,并且还可以选择在其信息之前加上执行的自定义信息。
     * perror()函数
     * 函数原型:void perror(const char* str);
     * 作用:
     * - 将error的值解释为错误信息,并将其打印到标准错误输出流,通常是打印到控制台。可以选择在其前面加上str中指定的自定义消息。
     * - errno是一个整型变量,其值描述了调用库函数产生的错误条件或诊断信息。
     *   C标准库的任何函数都可以为errno设置值(即使没有明确指定,即使没有发生错误)。
     * - 产生的错误信息与使用的开发平台及编译器有关。
     * - 如果str传入空指针NULL,则会直接打印错误信息,错误信息后跟换行符\n。
     *   如果str不是空指针,则打印str指向的字符串,后跟一个英文冒号(:),再跟一个空格( ),然后加上错误信息,再加上换行符\n。
     * - perror应该在错误信息产生之后立即调用,否则error可能会在调用其他库函数时被覆盖。
     *
     * 参数 —— str:要打印在错误信息之前的自定义的字符串。如果传入空指针,则无自定义信息,直接打印错误信息。
     * 返回值:无返回值。
     */
    
  • 使用

    #include <stdio.h>
    
    int main()
    {
        //fopen()函数用于打开文件,test.txt是文件名,r表示以只读形式打开。
        FILE* pf = fopen("test.txt","r");
        if(pf == NULL)
        {
            //因为我们打开文件传入的文件名是一个没有创建的文件,所以错误信息是:没有这个文件
            //如果传入的是空指针,则直接打印错误信息,后跟换行符\n。
            perror(NULL);//No such file or directory
            //如果是传入一个字符串,则是str+冒号+空格+错误信息+换行符\n
            perror("fopen");//fopen: No such file or directory
    
        }
        return 0;
    }
    

字符函数

字符分类函数

函数如果是则返回真;反之,返回假
iscntrl()是否为控制字符
isspace()是否是空白字符:空格’ ‘、换页’\f’、换行’ \n’、回车’\r’、制表符’\t’、垂直制表符’\v’
isdigit()是否为数字字符:0~9 (十进制)。注意是数字字符,而不是数字。
isxdigit()是否为十六进制数字。包括所有十六进制数字,小写字母af,大写:AF
islower()是否为小写字母:a~z
isupper()是否为大写字母:A~Z
isalpha()是否为字母:az、AZ
isalnum()是否为字母或数字:az、AZ、0~9
ispunct()是否为标点符号。任何不属于数字或字母的可打印的图形字符
isgraph()是否为图形字符
isprint()是否为可打印字符,包括图形字符和空白字符
  • 简单使用

    #include <stdio.h>
    #include <ctype.h>
    
    int main()
    {
        //isdigit(),如果是数字字符返回非0的值,如果不是数字字符,返回0。
        char ch = '1';
        printf("%d\n",isdigit(ch));//1
    
        //islower(),如果是小写字母字符返回非0的值,如果不是,返回0。
        ch = 'a';
        printf("%d\n",islower(ch));//2
        return 0;
    }
    

字符转换函数

tolower()函数

  • 函数原型

    /*
     * tolower()函数
     * 函数原型:int tolower(int c)
     * 作用:
     * - 如果c是大写字母,并具有其小写字母,则将c转换为对应的小写字母。
     *   如果不能进行这种转换,则返回的值c不变
     * - 注意:被视为字母的内容可能取决于使用的语言环境。在默认的”C“语言环境中。
     *   大写字母可以是以下任意一种:A B C D E F G H J K L M N O P Q R S T U V W X Y Z
     *   它们分别转换为:a b c d e f g h i j k l m n o p q r s t u v w x y z
     * - 在其他语言环境中,如果一个大写字符有多个对应的小写字符,则此函数总是为相同的c值返回相同的字符。
     *
     * 参数c:要被转换为小写的字符。
     * 返回值:如果c有对应的小写字母,则函数返回c的小写字母,否则返回未更改的c。该值作为int值返回,可以隐式转换为char类型
     */
    
  • 使用

    #include <stdio.h>
    #include <ctype.h>
    
    int main()
    {
        char arr[20] = {0};
        scanf("%s",arr);
        int i =0;
        //可以加一个判断,如果其为大写字母,则转换为小写,并放入数组。
    //    while(arr[i] != '\0')
    //    {
    //        if(isupper(arr[i]))
    //        {
    //            arr[i] = tolower(arr[i]);
    //        }
    //        printf("%c ",arr[i]);
    //        i++;
    //    }
        //如果其值如果不能被转换为小写,或已经是小写,则不会改变其值。所以我们也可以省略判断。
        while(arr[i] != '\0')
        {
            arr[i] = tolower(arr[i]);
            printf("%c ",arr[i]);
            i++;
        }
        printf("%s",arr);
        return 0;
    }
    

toupper()函数

  • 函数原型

    /*
     * toupper()函数
     * 函数原型:int toupper(int c)
     * 作用:
     * - 如果c是小写字母,并具有其大写字母,则将c转换为对应的大写字母。
     *   如果不能进行这种转换,则返回的值c不变
     * - 注意:被视为字母的内容可能取决于使用的语言环境。在默认的”C“语言环境中。
     *   小写字母是:a b c d e f g h i j k l m n o p q r s t u v w x y z
     *   它们分别转换为:A B C D E F G H J K L M N O P Q R S T U V W X Y Z
     * - 在其他语言环境中,如果一个小写字符有多个对应的大写字符,则此函数总是为相同的c值返回相同的字符。
     *
     * 参数c:要被转换为大写的字符。
     * 返回值:如果c有对应的大写字母,则函数返回c的大写字母;否则返回未更改的c。该值作为int值返回,可以隐式转换为char类型
     */
    
  • 使用

    #include <stdio.h>
    #include <ctype.h>
    
    int main()
    {
        char arr[20] = "aAbBcCdDEe12EeDdgG";
        int i =0;
        //如果其值如果不能被转换为大写,或已经是大写,则不会改变其值。所以我们也可以省略其是否小写字母,如果是再转换的判断。
        while(arr[i] != '\0')
        {
            arr[i] = toupper(arr[i]);
            i++;
        }
        printf("%s",arr);//AABBCCDDEE12EEDDGG
        return 0;
    }
    

内存函数

memcpy()函数

  • 函数原型

    /*
     * memcpy()函数
     * 函数原型:void* memcpy(void* dest,const void* src,size_t num);
     * 作用:
     * - 将src中前num个字节复制到dest指向的内存块。指针指向的数据的类型与函数无关。
     * - 该函数不检查源中的任何终止空字符,他总是精确复制num个字节。为了避免溢出,dest与src指向的数组大小应该至少为num个字节。
     *   并且dest与src不能指向同一内存块。对于重叠的内存块,memmove是一种更安全的方法。
     *
     * 参数:
     * - dest:指向存储复制内容的目标数组指针,类型强制转换为void*指针。
     * - src:只想要复制的数据源的指针,类型强制转换为void*指针。
     * - num:要复制的字节数。size_t是无符号整型。
     *
     * 返回值:该函数返回指向dest的指针。
     */
    
  • 使用

    #include <memory.h>
    
    int main()
    {
        int arr1[10] = {0,1,2,3,4,5,6,7,8,9};
        int arr2[10] = {0};
        //
        //memcpy(arr2,arr1,20);
        //指定断点,调试查看执行结果
        return 0;
    }
    
  • 模拟实现

    #include <assert.h>
    
    void* my_memcpy(void* dest,const void* src,size_t num)
    {
        assert(dest && src);
        void* ret = dest;
        while(num--)
        {
            //void*是无具体类型,需要将其强制转换为char*类型,然后解引用。一个字节一个字节拷贝。
            *(char*)dest = *(char*)src;
            //拷贝后,指针向后走一个字节,就需要先转换为char*类型,然后+1。
            dest = (char*)dest + 1;
            src = (char*)src + 1;
        }
        return ret;
    }
    
    int main()
    {
        int arr1[10] = {0,1,2,3,4,5,6,7,8,9};
        int arr2[10] = {0};
        my_memcpy(arr2,arr1,20);
        //将arr1中前5个元素0,1,2,3,4放到2,3,4,5,6的位置,期望结果:0,1,0,1,2,3,4,7,8,9
        my_memcpy(arr1+2,arr1,20);//0,1,0,1,0,1,0,7,8,9 ,如果库函数中也是我们这样实现的,那么在使用时,dest与src就不能指向同一个数组。
        
        //指定断点,调试查看执行结果
        return 0;
    }
    

memmove()函数

  • 函数原型

    /*
     * memmove()函数
     * 函数原型:void* memmove(void* dest,const void* src,size_t num);
     * 作用:将src中的前num个字节复制到dest指向的内存块。复制就像使用中间缓冲区一样进行。允许src与dest重叠。
     * 参数:
     * 参数:
     * - dest:指向存储复制内容的目标数组指针,类型强制转换为void*指针。
     * - src:只想要复制的数据源的指针,类型强制转换为void*指针。
     * - num:要复制的字节数。size_t是无符号整型。
     *
     * 返回值:该函数返回指向dest的指针。
     */
    
  • 使用

    #include <string.h>
    
    int main()
    {
        int arr[10] = {1,2,3,4,5,6,7,8,9,10};
        //将arr数组中,1,2,3,4,5放到3,4,5,6,7的位置
        memmove(arr+2,arr,20);
        //加断点,调试查看结果
        //1,2,1,2,3,4,5,8,9,10
        return 0;
    }
    
  • memmove()函数模拟实现

    #include <assert.h>
    
    /*
     * 实现思路:
     * - dest与src是不重叠,则怎么拷贝都可以。
     *   dest与src指向同一个数组的不同位置,并且src的前num个字节包含了dest指向的位置,此时就需要注意将src内容拷贝到dest时,是不是修改了src中靠后的几个字节的内容。
     *   如:将a[10]中前5个元素a[0]~a[4]放到a[2]~a[6]的位置,
     *   如果是从前往后拷贝:a[2]=a[0],a[3]=a[1],a[4]应该=a[2]原来的值,但是因为a[2]=a[0]了,
     *   src中的内容还没有被拷贝到dest中,就被改变了,结果就不是我们想要的结果了。
     * - dest与src重叠,dest<src,并且src的前num个字节有与dest重叠的部分。
     *   如:1 2 3 4 5 6 7 8 9中将6 7 8拷贝到5 6 7的位置,从前往后拷贝
     *   6放入5的位置,成为:1 2 3 4 6 6 7 8 9
     *   7放入6的位置,,成为:1 2 3 4 6 7 7 8 9
     *   8放到7的位置,成为:1 2 3 4  6 7 8 8 9
     * -dest与src重叠,src<dest,并且src的前num个字节有与dest重叠的部分。
     *   如:1 2 3 4 5 6 7 8 9中将5 6 7拷贝到6 7 8的位置,期望结果:1 2 3 5 5 6 7 9
     *   此时如果从前向后拷贝,5放到6的位置成为:1 2 3 4 5 5 7 8 9 ( src中的6还没有拷贝就被修改了。 )
     *   6位置的数据放入7位置:1 2 3 4 5 5 5 8 9
     *   7位置的数据放入8位置:1 2 3 4 5 5 5 5 9 ,与七万结果不同,src中的数据还未拷贝到dest中对应位置就被修改了。
     *   如果我们从后往前拷贝,也就是将7放入8的位置:1 2 3 4 5 6 7 7 9
     *   6放入7的位置:1 2 3 4 5 6 6 7 9
     *   5放入6的位置:1 2 3 4 5 5 6 7 9 ,与期望结果相同
     * - 如果是dest与src重叠,dest<src或dest>src。但src的前num个字节有与dest没有重叠的部分。
     *   此时不管从前向后或从后向前拷贝,都可以。因为没有重叠的部分,所以src中的内容不会被修改,可以完全拷贝到dest中
     * - 如果dest与src指向不同的数组,则前向后或后向前都可以。
     *
     * 结论:
     * - dest<src时,从前往后拷贝即可。也就是*(char*)dest=*(char*)src,拷贝完后dest与src向后走一个字节,然后继续拷贝....
     * - src<=dest时,从后往前拷贝,也就是*((char*)dest+num) = *((char*)src+num),拷贝完之后dest与src向前走一个字节,然后继续拷贝...
     */
    char* my_memmove(void* dest,const void* src,size_t num)
    {
        assert(dest && src);
        void* ret = dest;
        if(dest < src)
        {
            //前——>后
            while(num--)
            {
                *(char*)dest = *(char*)src;
                dest = (char*)dest + 1;
                src =(char*)src + 1;
            }
        }
        else
        {
            //后——>前。
            while(num--)
            {
                //每次拷贝完,向前走一个字节,就是-1。但是num每次就减1,所以不用再做操作。
                *((char *) dest + num) = *((char *) src + num);
            }
        }
        return ret;
    }
    
    int main()
    {
        int arr[10] = {1,2,3,4,5,6,7,8,9,10};
        my_memmove(arr+2,arr,20);
        //加断点,调试查看结果。
        return 0;
    }
    

memcmp()函数

  • 函数原型

    /*
     * memcmp()函数
     * 函数原型:int memcmp(const void* ptr1,const void* ptr2,size_t num);
     * 作用:将prt1与prt2的前num个字节进行比较。与strcmp不同,该函数找到空字符后不会停止比较。
     * 参数:
     * - ptr1:指向内存块的指针。
     * - ptr2:指向内存块的指针。
     * - num:要比较的字节数
     * 返回值:
     * - 返回值<0,表示两个内存块中不匹配的第一个字节,在prt1中值低于ptr2,也就是ptr1 < ptr2。
     * - 返回值等于0,两个内存块的前num个字节相同。
     * - 返回值>0,表示两个内存块中不匹配的第一个字节,在prt1中值大于ptr2,也就是ptr1 > ptr2。
     */
    
  • 使用

    #include <stdio.h>
    #include <string.h>
    
    int main()
    {
        int arr1[] = {1,2,3};
        int arr2[] = {1,3};
        printf("%d\n",memcmp(arr1,arr2,4));
        printf("%d\n",memcmp(arr1,arr2,5));
        return 0;
    }
    

memset()函数

  • 函数原型

    /*
     * memset()函数
     * 函数原型:void * memset(void * ptr,int value,size_t num);
     * 作用:将ptr指向的内存块的前num字节设置为指定值。
     *  - ptr:指向要填充的内存块的指针
     *  - value:要设置的值。该值作为int传递,但该函数使用该值的无符号字符转换填充内存块。
     *  - num:被设置为value值的字节数。size_t是无符号整数类型。
     */
    
  • 使用

    #include <string.h>
    #include <stdio.h>
    
    int main()
    {
        char arr[] ="hello world";
        //将arr数组的前5个字节替换为x
        memset(arr,'x',5);
        printf("%s",arr); //xxxxx world
    
        int arr1[10] = {0};
        memset(arr1,1,20);//以字节为单位设置内存
        //调试查看arr数组在内存中的存储,会发现arr数组的前20个字节,每个字节都是01
        return 0;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值