C升级——部分库函数介绍及模拟实现

好久不见呀,最近系统学习了一下常会用到的库函数,比如字符串函数strlen、strcmp、strcpy字符函数以及内存函数memcpy、memmove等等。其实前面也陆陆续续介绍过,只是一直没有作一个总结,这篇博客就把它们整理介绍一下,顺便用自己的方法去模拟实现,有兴趣的小伙伴来看看咩(预警!!!篇幅较长,可先收藏后看!!!)

一、字符串函数

1、求字符串长度的函数

(1)介绍

字符串中使用 strlen 函数来求字符串的长度,下面来看一下函数声明:

size_t strlen( const char *string );

这个函数的头文件是<string.h>,返回值为无符号整型 size_t ,形参为 const 修饰的字符串首元素地址。字符串以 ’ \0 ’ 为结束标志,返回的就是 ’ \0 ’ 前的字符个数,注意 ’ \0 ’ 不包括在内。形参指向的字符串必须要以 ’ \0 ’ 结束。

下面举一个实例:

int main()
{
	char arr[] = { "abcdef" };    //双引号引住的字符串会自动生成 '\0'
	printf("%d\n", strlen(arr));
	return 0;
}

(2)模拟实现

  • count计数器实现
int str_len(const char* str)
{
   assert(str != NULL);
   int count = 0;
   while (*str++)
   {
   	count++;
   }
   return count;
}
  • 函数递归实现
int str_len(const char* str)
{
	assert(str != NULL);
	while (*str++)
	{
		return 1 + str_len(str++);
	}
	return 0;
}
  • 函数指针的运算实现
int str_len(const char* str)
{
	assert(*str != NULL);
	const char* ret = str;
	while (*ret++)
	{
		;
	}
	return ret - str - 1;
}

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

(1)strcpy

  • 函数声明:

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

    头文件使用 <string.h> ,函数功能是将源字符串中的字符拷贝到目的字符串中(包括 ’ \0 ’ ),返回值返回拷贝后的目的字符串。

  • 注意事项
    源字符串必须以 ’ \0 ’ 结尾,防止程序陷入死循环;目标字符串必须空间足够大,并且是可修改的(为了把源字符串赋过来)

  • 实例:

    int main()
    {
        char str1[20];
        char str2[] = "zuoyawen";
        char* ret = strcpy(str1, str2);
        printf("%s\0", str1);
        return 0;
    }
    
  • 模拟实现

    char* str_cpy(char* dest, const char* src)
    {
        assert(dest != NULL);
        assert(src != NULL);
        char* ret = dest;
        while (*ret++ = *src++)
        {
    	    ;
        }
        return dest;
    }
    

(2)strcat

  • 函数声明

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

    头文件使用 <string.h> ,函数功能是将源字符串中的字符追加到目的字符串 ’ \0 ’ 之后(会覆盖目的字符串本来的结束标志),追加时会把源字符串的结束标志一起追加过来。返回值返回追加后的目的字符串。

  • 注意事项
    源字符串和目的字符串必须以 ’ \0 ’ 结束,防止函数陷入死循环;目标字符串必须空间足够大,并且是可修改的(为了把源字符串赋过来);这个函数不可以实现字符串自身的追加(后面会讲strncat,这个函数可以实现),因为会在一开始就覆盖掉结束标志,陷入死循环。

  • 实例

    int main()
    {
        char str1[20] = "hello ";
        char* str2 = "xiaozuo!";
        char* ret = strcat(str1, str2);
        printf("%s\n", ret);
        return 0;
    }
    
  • 模拟实现
    大体思路就是先把目的字符串遍历,找到结束标志,再采用strcpy的模拟实现方法实现即可。

    char* str_cat(char* dest, const char* src)
    {
        assert(dest != NULL);
        assert(src != NULL);
        char* ret = dest;
        while (*ret)
        {
    	    ret++;
        }
        while (*ret++ = *src++)
        {
    	    ;
        }
        return dest;
    }
    

(3)strcmp

  • 函数声明

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

    头文件使用 <string.h> ,函数功能是比较两个字符串的ASCII码值。返回值返回比较后的结果,string1 > string2 时,返回一个正值;string1 = string2 时,返回0; string1 < string2 时,返回一个负值。

  • 注意事项
    在VS编译器下,该函数的返回值分别是1,0,-1,但是这并不是说明只有这一种情况,所以在使用时条件判断不要用 ==,而是采用大于小于这样的方式去判断,下面看一个实例。

  • 实例

    int main()
    {
        char* str1 = "hello ";
        char* str2 = "xiaozuo!";
        int ret = strcmp(str1, str2);
        if (ret > 0)
        {
    	    printf("%s > %s\n", str1, str2);
        }
        else if (ret = 0)
        {
    	    printf("%s = %s\n", str1, str2);
        }
        else
        {
    	    printf("%s < %s\n", str1, str2);
        }
        return 0;
    }
    
  • 模拟实现:找不同的字符,若未找到返回0,找到返回差值即可

  • 方法一

    int str_cmp(const char* str1, const char* str2)
    {
        while (*str1 == *str2)
        {
    	    if (*str1 == '\0')
    	    {
    		    return 0;
    	    }
    	    str1++;
    	    str2++;
        }
        //*str1 != *str2退出循环,跳到这里,返回两个ASCII的差值就好
        return *str1 - *str2;
    }
    
  • 方法二

    int str_cmp(const char* str1, const char* str2)
    {
        assert(str1 != NULL);
        assert(str2 != NULL);
        //判空不可以直接指针通过NULL判空,因为结束标志所在的地方不一定为空指针
        while (*str1 != '\0' && *str2 != '\0')
        {
    	    if (*str1 != *str2)
    	    {
    		    return *str1 - *str2;
    	    }
    	    str1++;
    	    str2++;
        }
        return 0;
    }
    
  • 方法三

    int str_cmp(const char* str1, const char* str2)
    {
    	assert(str1 != NULL);
        assert(str2 != NULL);
        int ret = 0;
        while (*str1 && !(ret = *str1 - *str2))
        {
    	    str1++;
    	    str2++;
        }
        return ret;
    }
    

    这种是小左认为最简单的一种~ 前两种都是循环嵌套一个条件判断,这种只需要做循环判断就好。怎么样,是不是很巧妙?

3、长度受限制的字符串函数

对于长度不受限制的字符串函数可能会出现安全性方面的问题,比如strcpy 、strcat 这样的函数,若是目的字符串开辟的内存空间不够大就可能会导致溢出的问题,所以就出现了这样一些长度受限制的字符串操作函数,规定操作的长度就可以规避这个问题。

由于处理的思想同无长度限制的字符串操作函数基本一致,这里就不再进行模拟实现,只进行实例演示。

(1)strncpy

  • 函数声明

    char *strncpy( char *strDest, const char *strSource, size_t count );
    

    同 strcpy 一样头文件是 <string.h> ,有目的操作数和源操作数,返回值为目的操作数首元素地址,区别是加了一个形参 count 。功能是把源操作数的前 count 个字符拷贝到目的字符串。

  • 注意事项
    若需要拷贝的字符个数 count 小于源字符串本身的个数num,只拷贝count个字节,不会主动追加结束标志 ’ \0 ’ ;若 count 大于 num 时则该函数会自动在拷贝完源字符串之后,在目的字符串后面追加 count - num 个 ’ \0 ’

  • 实例

    int main()
    {
        char str1[20] = {0};
        char* str2 = "hello xiaozuo!";
        strncpy(str1, str2, 10);
        printf("%s\n", str1);
        return 0;
    }
    

(2)strncat

  • 函数声明

    char *strncat( char *strDest, const char *strSource, size_t count );
    

    同 strcat 一样头文件是 <string.h> ,有目的操作数和源操作数,返回值为目的操作数首元素地址,区别是加了一个形参 count 。功能是把源操作数的前 count 个字符追加到目的字符串。

  • 注意事项

    追加时,不会追加 ’ \0 ',开辟空间的时候源字符串会自己将未赋值的内存空间全部默认为 ’ \0 ’ 。

  • 实例

    int main()
    {
        char str1[20] = "hello ";
        char* str2 = "xiaozuo!";
        strncat(str1, str2, 8);
        printf("%s\n", str1);
        return 0;
    }
    

(3)strncmp

  • 函数声明

    int strncmp( const char *string1, const char *string2, size_t count );
    

    同 strcmp 一样头文件是 <string.h> ,有两个操作数,返回值为比较后的结果,区别是加了一个形参 count 。功能是将两个操作数的前 count 个字符进行比较。

  • 实例

    int main()
    {
        char arr[][5] = { "R2D2","C3P0","R2A6" };
        int i = 0;
        for (i = 0; i < 3; i++)
        {
    	    if (strncmp(arr[i], "R2xx", 2) == 0)
    	    {
    		    printf("find it! -> %s\n",arr[i]);
    	    }
        }
        return 0;
    }
    

4、其余字符串函数

(1)strstr

  • 函数声明

    char *strstr( const char *string, const char *strCharSet );
    

    函数头文件是 <string.h> ,形参为两个 const 修饰的字符串,其中第一个为原字符串,第二个需要判断是不是第一个的子字符串,返回值为 char* 类型的指针,若是找到了该子串,返回找到第一个子字符串的原字符串的地址;若是未找到,返回一个NULL即可。

  • 注意事项

    当原字符串中有多个子字符串时,只返回第一个所在的地址,若要继续找后面的,可以以第一次返回的字字符串串为新的原字符串,再去寻找。

  • 实例
    这里使用该函数实现判断字符串是否旋转,基本思想:利用strncat()函数构造一个新的字符串,通过strstr()函数直接找到子字串,能找到说明就是旋转得到的。

    int judge_spin(const char* str, const char* spin_str)
    {
        int num = strlen(str);
        char* str1 = strncat(str,str, num);
        if (strstr(str1, spin_str) != NULL)
        {
    	    return 1;
        }
        return 0;
    }
    int main()
    {
        char str1[30] = "abcde";
        char str2[30] = "eabcd";
        int ret = judge_spin(str1, str2);
        if (ret)
        {
    	    printf("yes\n");
        }
        else
        {
    	    printf("no\n");
        }
        return 0;
    }
    

(2)strtok

  • 函数声明

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

    函数头文件是 <string.h> ,形参为两个字符串,其中第一个是被分隔的字符串,第二个是分隔的字符集合。当使用该函数找到下一个分隔标记,将其替换为 ’ \0 ’ ,并返回指向这个分隔标记的指针(即下一个分隔标记前的内容);当第一部分参数为NULL时,直接从下一个开始;若再无标记时,返回NULL。

  • 注意事项

    该函数会改变原字符串内容,所以使用时都是放到临时拷贝的内存中,同时保证其可修改。

  • 实例
    由于这一个函数的实现过程比较复杂,口述不够形象,小左就把它的执行过程截图附到后面,方便理解。
    代码1:

     int main()
    {
        char str[] = ".xiao.zuo.xiao.chen";
        char* pstr;
        pstr = strtok(str, ".");
        while (pstr != NULL)
        {
    	    printf("%s\n", pstr);
    	    //固定格式。第一次使用原字符串名,后面就直接NULL就好
    	    pstr = strtok(NULL, ".");    
        }
        return 0;
    }
    

    执行过程:
    执行过程
    代码2:

    有了代码1的基础,代码2就不再展示执行过程,基本同1一致,只是通过 for 循环去实现,具体见注释。

    int main()
    {
        char* ip = "192.168.0.1";
        const char* sign = ".";
        //创建临时变量来进行切分,防止改变被操作数
        char str1[30];
        strcpy(str1, ip);
        //创建一个指向每部分字符串的指针
        char* str = strtok(str1, sign);
        //循环走完所有的分隔符除了上面初始化用原字符串,其余使用这个函数时,原字符串为承接上一个的NULL
        for (str; str != NULL; str = strtok(NULL, sign))
        {
    	    printf("%s\n", str);
        }
        return 0;
    }
    

(3)strerror

  • 函数声明

    char *strerror( int errnum );
    

    函数头文件是 <string.h> ,形参为整型变量错误序号,使用时要包含头文件 <errno.h>,返回值是一个指向错误信息字符串的指针。

  • 实例

    int main () 
    { 
         FILE * pFile; //定义一个文件
         pFile = fopen ("unexist.doc","r"); //以只读的方式打开该文件
         if (pFile == NULL)
         {
             printf("Error information ->  %s\n", strerror(errno));
             //perror("Error information");
         }
         return 0; 
    }
    
  • 改良——perror( )

    直接把错误提示信息以字符串的形式写到形参上,也不需要写错误信息编号,一定程度上优化了代码,所以实际编程时都写的是 perror( ) 函数。

二、字符函数

注意字符函数都需要一个头文件 <ctype.h>,这部分内容没有什么技术含量,所以只做总结,不作具体实现。

1、字符分类函数

函数参数符合条件返回真
iscntrl任何控制字符
isspace空白字符:空格‘ ’,换页‘\f’,换行’\n’,回车‘\r’,制表符’\t’或者垂直制表符’\v’
isdigit十进制数字 0至9
isxdigit十六进制数字,包括所有十进制数字,小写字母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任何可打印字符,包括图形字符和空白字符

2、字符转换函数

注意,转换只是返回改变后的 ASCII 值,并不会改变字符本身!!

int tolower ( int c ); //大写字母转换成小写
int toupper ( int c ); //小写字母转换成大写

三、内存函数

内存函数这部分,它的头文件 也是<string.h> ,实现的功能其实也和字符串函数一样,而它的优势是操作数不再是字符串,而是内存单元。这就意味着它可以把这些功能实现在更多的数据类型中。内存函数只说四个使用频率较高的,其余小伙伴们可自行学习,这里就不作介绍~

1、memcpy

  • 函数声明

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

    函数形参是两个 void* 的指针(分别指向目的操作数,源操作数)以及一个 size_t 的值 count (表示需要拷贝的字符字节数);返回值也是 void* 的指针,返回一个指向目的操作数的指针。

  • 注意事项

    这个函数在遇到 ‘\0’ 的时候并不会停下来只能实现非重叠拷贝(先拷贝的数据可能会覆盖后拷贝的数据)

  • 实例

    #include <stdio.h>
    #include <string.h>
    //实现整数数组的拷贝
    int main()
    {
        int arr1[10] = { 1,2,3,4,5 };
        int arr2[20];
        memcpy(arr2,arr1,12);
        int i = 0;
        for (i = 0; i < 3; i++)
        {
    	    printf("%d ", arr2[i]);
        }
        return 0;
    }
    //实现结构体的拷贝
    struct human
    {
        char name[20];
        int age;
    };
    int main()
    {
        struct human h1 = { "xiaozuo",21 };
        struct human h2;
        memcpy(&h2, &h1, sizeof(h1));
        printf("name:%s age:%d \n", h2.name, h2.age);
        return 0;
    }
    
  • 模拟实现

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

2、memmove

  • 函数声明

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

    这个函数就弥补了 memcpy 不可以实现重叠拷贝的缺点,函数形参是两个 void* 的指针(分别指向目的操作数,源操作数)以及一个 size_t 的值 count (表示需要拷贝的字符字节数);返回值也是 void* 的指针,返回一个指向目的操作数的指针。

  • 注意事项

    和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的;如果源空间和目标空间出现重叠,就得使用memmove函数处理。

  • 实例

    //整数数组的重叠拷贝
    int main()
    {
        int arr1[10] = { 1,2,3,4,5 };
        memmove(arr1, arr1 + 2, 12);
        int i = 0;
        int sz = sizeof(arr1) / sizeof(arr1[0]);
        for (i = 0; i < sz; i++)
        {
    	    printf("%d ", arr1[i]);
        }
        return 0;
    }
    //字符串的重叠拷贝
    int main()
    {
        char str[30] = "abcdefg hijkiloveyou";
        memmove(str, str + 12, 9);
        puts(str);
        return 0;
    }
    
  • 模拟实现

    由于这个函数一般是专门来实现重叠拷贝的,为了防止未拷贝数据就被覆盖,具体还需要分情况讨论:

    情况1:目的空间地址在源空间地址之前有交叠,从前向后拷贝;
    情况2:目的空间地址在源空间地址之后有交叠,从后向前拷贝;
    情况3:当两个地址空间无交叠或者完全重叠时,不存在覆盖问题,拷贝顺序都可。
    

    所以实现方式有两种,本篇文章做模拟实现的时候采用(情况1 + 情况2、3)这种,下面看实现:

    void* mem_move(void* dest, const void* src, size_t count)
    {
        assert(dest);
        assert(src);
        void* ret = dest;
        //从前往后拷贝,和memcpy一样
        if (dest < src)
        {
    	    while (count--)
    	    {
    		    *((char*)dest)++ = *((char*)src)++;			
    	    }
        }
        //其余情况都采用从后往前拷贝
        else
        {
    	    while (count--)
    	    {
    		    *((char*)dest + count) = *((char*)src + count);
    	    }
        }
        return ret;
    }
    

3、memset

  • 函数声明

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

    函数形参是一个 void* 的指针(指向目的操作数),一个准备设置的字符 c 以及一个 size_t 的值 count (表示需要设置的字符字节数);返回值也是 void* 的指针,返回一个指向目的操作数的指针。

  • 注意事项

    虽然这个函数可以设置字符,但对于数字而言只能设置 0 ,因为它是按字节统一去设置,无法实现四个字节为单位的一致。

  • 实例

    int main()
    {
        char str[30];
        memset(str, 'z', 3);
        str[5] = 0;//为了看是不是只设置了3个
        puts(str);
        return 0;
    }
    

4、memcmp

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

函数形参是两个const void* 的指针(分别指向两个需要比较的操作数)以及一个 size_t 的值 count (表示需要比较的字符字节数);返回值是 int 型的,通过返回的值确定大小。功能基本和strncmp类似,这里就不再做实现~

综上,这次的分享又结束了~欢迎评论区讨论指正!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值