C语言——常用字符串函数的实现

用C语言实现常用的字符串函数

注:凡是要使用库里面的字符串函数,都需要引用头文件<stdio.h>

一、常用字符串函数介绍


1.strlen

size_t strlen(const char* str);
  • 功能是计算字符串的长度。字符串以’\0’作为结束标志,strlen的返回值是在’\0’前(不包括\0)的字符数

    char arr[] = "abc";
    printf("%d\n", strlen(arr));//3
    

请添加图片描述

  • 需要保证参数中的字符串有’\0’,不然返回的值是不确定的

    char arr[] = {'a', 'b', 'c'};
    printf("%d\n", strlen(arr));//随机值,因为字符没有结束标志,\0不在arr数组内而在数组后某个未知的位置
    
  • strlen函数返回值是size_t类型的无符号整数。因此以strlen(a)-strlen(b)为判断条件时,如果字符串a的长度小于b,计算结果不会是负值,而是一个极大的正值,需要注意
    请添加图片描述

2.strcat

char* strcat(char* dest, const char* src);
  • 功能是追加字符串。找到目标空间的’\0’,然后从’\0’处开始,逐字符替换为源字符串的内容,拷贝过程直到遇到源字符串的’\0’结束,返回目标字符串的首地址

    char arr1[20] = "hello ";
    char arr2[] = "world";
    strcat(arr1, arr2);
    printf("%s\n", arr1);//hello world
    

请添加图片描述

  • src代表源字符串首字符地址,dest代表目标字符串首字符地址(后面也一样)

  • 目标空间必须足够大,能容纳源字符串的内容

  • 目标空间必须可修改

    char* p = "abcdef";//指针所指向的空间不可修改
    char p[] = "abcdef";//数组空间可修改
    
  • 不能自己给自己追加字符串

    char* arr[] = "abc";
    printf("%s\n", strcat(arr, arr));//Error
    

    原因在strcat的实现原理,如图:

请添加图片描述

拷贝过程中,arr的首字符a会先被拷贝到原来的\0处,这导致源字符串的内容发生改变。

请添加图片描述

当需要将\0拷贝到后面的内存块中时,\0的内容已被覆盖,而源字符串的拷贝工作要遇到\0才会停止,结果可想而知,字符串会无限地以"abcabc"的形式拷贝下去,陷入死循环。这也是为什么源字符串前面要用关键字const修饰,因为源字符串的内容发生改动会带来很大的风险

长度有限制的同类字符串函数:strncat

char* strncat(char* dest, const char* src, size_t n);
  • 功能是在目标字符串末尾追加源字符串的前n个字符,并在后面补上空字符’\0’,返回目标字符串的首地址
  • 如果n的值大于源字符串长度,也只拷贝到源字符串的第n个字符为止,不会追加多余内容
char arr1[20] = "hello ";
char arr2[] = "world";
strncat(arr1, arr2, 3);
printf("%s\n", arr1);//hello wor

3.strcpy

char* strcpy(char* dest, const char* src);
  • 功能是复制字符串。将源字符串的全部内容复制到目标字符串空间中,包括空字符’\0’

    char arr1[20] = "aaa";
    char arr2[] = "bbb";
    strcpy(arr1, arr2);
    printf("%s\n", arr1);//bbb
    
  • 目标空间必须足够大,能容纳源字符串的内容

  • 目标空间必须可修改

    长度有限制的同类字符串函数:strncpy

    char* strncpy(char* dest, const char* src, size_t n);
    
    • 功能是从源字符串复制前n个字符到目标字符串空间中

      char arr1[20] = "aaa";
      char arr2[] = "bbb";
      strncpy(arr1, arr2, 2);
      printf("%s\n", arr1);//bba
      
    • 如果n的值大于源字符串长度,则在拷贝完源字符串所有内容后,在目标的后面追加0,直到n个

    例:

    char arr1[20] = "aaaaaaaaaaaaa";
    char arr2[] = "bbb";
    strncpy(arr1, arr2, 8);
    printf("%s\n", arr1);//bbb
    

    对于以上代码,arr1拷贝前的存储信息:

    请添加图片描述
    拷贝后:

    请添加图片描述

4.strcmp

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

计算机内的数据是以AscⅡ码的形式存储的,因此字符串比较实际是对一个一个字符的AscⅡ码进行比较,比如字符’b’的AscⅡ码98大于字符’a’的AscⅡ码97,因此’b’>‘a’

  • 功能是比较两个字符串。从两个字符串首地址处逐字符比较,当该次比较的两个字符相同时继续比较两个字符串的下一个字符,当出现相异字符或其中一个字符串结束时比较结束

    例:字符串str1"abcdef"和字符串str2"abqa"的比较

    请添加图片描述

    比较结果相同,指针p1和p2都往后移一个字符,继续比较下一个字符,直到遇到不同的字符或’\0’(字符串结束标志)

    请添加图片描述

    c<q,因此str1<str2,比较结束

  • 返回值:如果比较结果是str1(第一个参数)>str2(第二个参数),则strcmp函数会返回一个大于0的整型值;如果比较结果是str1<str2,则会返回一个小于0的整型值;如果str1=str2,即两个字符串完全相同,会返回0

    (图片来源于MSDN)

char arr1[] = "abcdef";
char arr2[] = "abqa";
int ret = strcmp(arr1, arr2);
printf("%d\n", ret);//小于0的数

除了比较字符串,strcmp函数最常用的应用还是将返回值作为判断条件来判断两个字符串是否相等

长度有限制的同类字符串函数:strncmp

int strncmp(const char* str1, const char* str2, size_t n);
  • 功能是比较两个字符串的前n个字符。和strcmp相似,比较到出现相异字符或其中一个字符串结束或n个字符全部比较完时结束

    char arr1[] = "abcdef";
    char arr2[] = "abqa";
    int ret = strncmp(arr1, arr2, 2);
    printf("%d\n", ret);//0
    

5.strstr

char* strstr(const char* str1, const char* str2)
  • 功能是检索字符串。在str1里寻找str2的内容,判断str2是否为str1的子串。如果str2是str1的子串,则返回str2在str1里首次出现时的指针,如果不是则返回NULL

    请添加图片描述

char arr1[] = "abbbcd";
char arr2[] = "bbc";
char* ret = strstr(arr1, arr2);
printf("%s\n", ret);//bbcd

6.strerror

char* strerror(int errnum);
  • 功能是打印错误信息。不同的错误码对应不同的错误信息,返回错误码对应的错误信息,可以告知用户或程序员错误原因

    请添加图片描述

这里要说到一个用于存放C语言全局错误码的变量——errno。当程序运出错时,会自动将对应的错误码存放在这个全局变量内,当程序运行出错我们又不知道原因时,就可以尝试将这个变量作为参数传递给strerror,让其打印我们所需要的错误信息

使用errno需要引用头文件<errno.h>

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

int main()
{
    int* p = (int*)malloc(INT_MAX);
	if(p == NULL)//出错
	{
    	printf("%s\n", strerror(errno));//打印错误信息
    	return 1;//中断以保护程序的安全
	}
    
    //业务处理
    free(p);
    p = NULL;
    return 0;
}

7.strtok

char* strtok(char* str, char* sep);
  • 功能是切割字符串

  • str是需要切割的目标字符串的首字符地址。sep是一个字符串,包含了用作分隔符(字符串切割依据)的字符集合

  • 目标字符串中包含了一个或多个sep中的分隔符标记。strtok函数会将目标字符串逐字符与sep中的存在的分隔符标记进行对比,每在目标字符串中找到一个分隔符标记就将其替换为’\0’。显然这个操作会改变目标字符串内容,因此在切割的目标字符串往往都是临时拷贝的一个与目标字符串相同且可修改的字符串

  • strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置;strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记;如果字符串中不存在更多的标记,则返回 NULL 指针

    人话是只有第一次切割时传的参数是目标字符串首元素地址和分隔符字符串,第二次及以后切割都只传NULL和分隔符字符串就行了,因为strtok函数会保存上一次切割时的分隔断点

    请添加图片描述

    可以用循环来巧妙限制切割次数:

    char arr[100] = "805975991@dabuding.com";//待切割的字符串
    char sep[] = "@.";//分隔符为'@'和'.'
    
    //临时拷贝一份一样的字符串
    char tmp[100] = { 0 };
    strcpy(tmp, arr);
    
    //切割
    char* p = NULL;
    for (p = strtok(tmp, sep); p != NULL; p = strtok(NULL, sep))
    //p为NULL时,代表已无分隔符可切割,循环结束
    {
        printf("%s\n", p);
    }
    

    请添加图片描述

    切割结果

二、常用字符串函数的实现


提示:设计函数时善用const关键字和assert函数可以有效增强代码的健壮性

有写错的或更好的实现方法欢迎指出!|ू・ω・` )Thanks♪

1.strlen

size_t my_strlen1(const char* str)//计数器
{
	assert(str);
	int n = 0;
	while (*str++)
	{
		n++;
	}
	return n;
}

size_t my_strlen2(const char* str)//递归
{
	assert(str);
	if (*str == '\0')
		return 0;
	else
		return (my_strlen2(++str) + 1);

}

2.strcat

char* my_strcat(char* dest, const char* src)
{
	assert(dest && src);
	char* p = dest;

	//找到目标字符串的\0
	while (*dest != '\0')
	{
		dest++;
	}

	//将目标字符串从\0位置处开始替换为源字符串,直到检测到源字符串的\0
	while (*dest++ = *src++)
	{
		;
	}

	return p;//实现链式访问
}

3.strncat

char* my_strncat(char* dest, const char* src, size_t n)
{
	assert(dest && src);
	char* p = dest;
	while (*dest)
	{
		dest++;
	}
	while (n--)
	{
		if ((*dest++ = *src++) == '\0')
		{
			return p;
		}
	}
	*dest = '\0';//记得额外补上\0
	return p;
}

4.strcpy

char* my_strcpy(char* dest, const char* src)
{
	assert(dest && src);
	char* p = dest;
	while (*src != '\0')
	{
		*dest++ = *src++;
	}
	*dest++ = '\0';
	return p;
}

5.strncpy

char* my_strncpy(char* dest, const char* src, size_t n)
{
	assert(dest && src);
	char* p = dest;
	while (n && (*dest++ = *src++) != '\0')
	{
		n--;
	}
	while (n--)
	{
		*dest++ = '\0';
	}
	return p;
}

6.strcmp

int my_strcmp(const char* str1, const char* str2)
{
	assert(str1 && str2);
	while (*str1 == *str2)
	{
		if (*str1 == '\0')//指针内容相等且都为\0,表示两个字符串相等
		{
			return 0;
		}
		str1++;
		str2++;
	}
	return *str1 - *str2;//指针内容不等时,返回值的正负取决于str1和str2指向字符的大小
}

7.strncmp

int my_strncmp(const char* str1, const char* str2, size_t n)
{
	assert(str1 && str2);
	while (n-- && (*str1 != '\0' && *str2 != '\0'))
	{
		if ((*str1 - *str2) != 0)
			return (*str1 - *str2);
		else
		{
			str1++;
			str2++;
		}
	}
	return 0;
}

8.strstr

char* my_strstr(const char* dest, const char* src)
{
	assert(dest && src);
	const char* s1 = dest;
	const char* s2 = src;
	char* p = dest;
	while (*p)//*p不为\0时
	{
		s1 = p;
		s2 = src;
		while (*s1 != '\0' && *s2 != '\0' && *s1 == *s2)
		{
			s1++;
			s2++;
		}
		if (*s2 == '\0')
			return p;
		p++;
	}
	return NULL;
}
  • 10
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 16
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

大 布 丁

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

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

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

打赏作者

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

抵扣说明:

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

余额充值