常见字符串函数及其复写

文章目录

  • 前言:
  • 长度不受限制的函数
    • strlen
      • strlen复写(3种,指针,递归,循环)
    • strcpy
      • strcpy复写
    • strcat
      • strcat复写
    • strcmp
      • strcmp复写(2种,正常,vs特置)
  • 为什么说这些函数不受限制
  • 长度受限制的字符串函数
    • strncpy
      • strncpy复写
    • strncat
      • strcat复写
    • strncmp
      • strncmp复写
  • 内存操作函数
    • memcpy,memmove(内存拷贝)
      • memmove复写
    • memcmp(内存比较)
      • memcmp复写
    • memset(内存赋值)
      • memset复写
  • 字符串查找函数
    • strstr
      • 复写strstr
  • 切分字符串
    • strtok
      • strtok复写:
  • 错误信息报告
    • strerror,perror
  • 小结

前言:

博主实力有限 ,博文有什么错误,请你斧正,非常感谢!
编译器:VS2019
本文介绍C语言中那些常用的函数,及其复写。
因为C语言只给出了,函数的作用,对函数实现并不关心,因此在VS2019下某些字符串函数的行为可能与其它编译器不同,但是效果是大同小异的。
复写strstr时,博主目前掌握了BF算法,对于KMP算法将会在后续独立出一篇博客。

长度不受限制的函数

strlen

size_t strlen(const char string);*

  • 返回字符串中的字符数,不包括终止空字符(‘\0’)

注意:strlen在计算字符数时,只要碰到‘\0’,就结束计算.这就可以认为当没碰到‘\0’,就不停止计算。因此说长度不受限制

常见字符串函数及其复写_第1张图片

  • 返回值:无符号整形,因此在赋值时,某些编译器可能会报警告

  • 参数是:常量字符指针。(不可修改指向的数据)

strlen复写(3种,指针,递归,循环)

#define _CRT_SECURE_NO_WARNINGS

#include 
#include  
#include 
//我的:循环,递归,指针
size_t my_strlen1(const char* string)//循环
{
     
	assert(string);//防止传入NULL指针

	size_t cnt = 0;
	while (*string++)
	{
     
		++cnt;
	}
	return cnt;
}
size_t my_strlen2(const char* string)//递归
{
     
	assert(string);
	return *string == '\0' ? 0 : 1 + my_strlen2(string + 1);
}
size_t my_strlen3(const char* string)//指针
{
     
	assert(string);

	const char* bigin = string;
	while (*string++)//注意这里当*string=='\0'时,string指向了'\0'后面的位置,因此返回时,要-1
	{
     

	}
	return (string - bigin-1);
}
int main()
{
     
	char arr[] = "hello";
	printf("%d\n", my_strlen1(arr));//循环
	printf("%d\n", my_strlen2(arr));//递归
	printf("%d\n", my_strlen3(arr));//指针



	return 0;
}

常见字符串函数及其复写_第2张图片

strcpy

char * strcpy(char dest,const char src);

  • 将src指向的内容依次拷贝到 dest指向的内存,直到遇到str中的’\0‘,同时返回dest的首地址。如果没遇到‘\0’,也就是src不存在’\0‘,会访问非法内存,

  • 常量字符指针,不能更改str指向的内容。

strcpy复写

#define _CRT_SECURE_NO_WARNINGS

#include 
#include  
#include 
char* my_strcpy(char* dest, const char* src)
{
     

	assert(dest && src);//防止传入NULL指针
	char* ret=dest;
	while (*dest++=*src++)//当dest传入'\0',停止拷贝
	{
     
		;
	}
	return ret;
}
int main ()
{
     
	char arr[20]={
      0 };
	printf("%s\n", my_strcpy(arr, "helloword"));
	return 0;
}

常见字符串函数及其复写_第3张图片

strcat

char * strcat(char * dest , const char src);**

  • 从dest的字符串结束标志’\0‘开始,将src中的内容追加到dest后面。直到遇到src的’\0‘.如果没遇到,会非法访问内存。因此长度不受限制,不安全

  • 追加字符串时不执行溢出检查。因此dest中 必须由程序执行者,预留足够空间

  • 返回 dest的首地址

strcat复写

#define _CRT_SECURE_NO_WARNINGS

#include 
#include  
#include 
char* my_strcat(char* dest, const char* src)
{
     
	assert(dest && src);//防止传入NULL指针
	char* ret = dest;
	while (*dest)//找dest的'\0'位置,不可while(*dest++),因为找到'\0'时,dest越界了
	{
     
		dest++;
	}

	while (*dest++ = *src++)
	{
     

	}

	return ret;

}
int main()
{
     
	char arr[20] = "hello";
	printf("%s\n", arr);

	my_strcat(arr, "word");
	printf("%s\n", arr);


	return 0;
}

常见字符串函数及其复写_第4张图片

strcmp

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

  • 比较字符串看的是ASICC,不是长度

  • 比较 字符串str1与字符串str2是否相等.并返回一个比较值(比较字符串,只能用它。)

  • 比较规则:

    • 先比较 str1与str2指向字符的ASSICC值,如果不同(代表字符串不等)就返回 2者差值。一旦相等就继续比较,直到str1与str2 同时都是‘\0’,(代表字符串相等)返回0;

  • 返回值。
    <0 字符串1小于字符串2。
    0 字符串1与字符串2相同。
    >0 字符串1大于字符串2

  • 在复写时,我发现vs2019返回的是三个确定的值。虽然符合返回值要求,但是不能代表全部。这只是 vs内置的结果。因此复写了2种strcmp

strcmp复写(2种,正常,vs特置)

#define _CRT_SECURE_NO_WARNINGS

#include 
#include  

#include 
//正常
int my_strcmp1(const char* string1, const char* string2)
{
     
	assert(string1 && string2);

	while (*string1 == *string2)
	{
     
		if (*string1 == '\0')//当*s1,*s2同时为'\0'时,才说明字符串相等,返回0
		{
     
			return 0;
		}
		++string1;
		++string2;//前置++比后置++快
	}

	return (*string1 - *string2);

}
// vs2019版
int my_strcmp2(const char* src, const char* dest)
{
     
	int ret = 0;
	assert(src);
	assert(dest);
	while (!(ret = *(unsigned char*)src - *(unsigned char*)dest) && *dest)
		//unsigned char* 是因为char是否为有符号,根据编译器,默认不同
		//妙!以ret接受差值,通过!非0的数都为真.
		  //同时&&&的使用,确保了相等的情况。
	{
     
		++src;
		++dest;
	
	}

	if (ret < 0)
	{
     
	
		ret = -1;
	}
	else if (ret > 0)
	{
     
		ret = 1;
	}
		return (ret);
}
int main ()
{
     
	
	char arr1[] = "helloword";
	char arr2[] = "hello";

	if (0 == my_strcmp1(arr1, arr2))
	{
     
		printf("相等\n");

	}
	else
	{
     
		printf("不相等\n");
	}
	if (0 == my_strcmp2(arr1, arr2))
	{
     
		printf("相等\n");

	}
	else
	{
     
		printf("不相等\n");
	}
	return 0;
}

常见字符串函数及其复写_第5张图片

为什么说这些函数不受限制

strlen,strcpy…这些字符串函数,只是执行代码,不关心会发生什么。有可能会访问非法内存。不安全。因此后面介绍长度受限制安全的strcnpy,一定程度上安全,但是不绝对

长度受限制的字符串函数

strncpy

char strncpy(char Dest,const char* Src,size_t count);**

  • 将 src前 count个字符拷贝到dest。但是如果count超过了src的字符个数时,会赋值’\0‘,

  • dest 的空间仍是程序调用者,自己控制

strncpy复写

#define _CRT_SECURE_NO_WARNINGS

#include 
#include  
#include 
#include 

char* my_strncpy(char* dest, const char* src, size_t cnt)
{
     

	assert(dest && src);
	char* ret = dest;
	if (strlen(dest) < (cnt - 1))//strlen的原因(不计数'\0'),因此cnt-1
	{
     
		printf("拷贝数目大于源字符串字节数,此行为是未定义的\n");
		exit(1);
	}

	//开始拷贝
	// 	   下面这种方式虽然可行,但是在vs调试中,我发现strncpy在 多余时,仍会对dest赋值'\0'
	//while ((cnt-- > 0)&&(*dest++ = *src++))//'\0'数值为0.因为 &&先左后右的求值顺序特点,
	//	                                  //当cnt为0时,可能还未到达src的末尾,其会再赋值一次非'\0'
	//{
     
	//	;
	//}
	//开始拷贝
	while (cnt > 0)
	{
     

		if (*dest = *src)//当*src为'\0'时,src不再++;
		{
     
			if (*src != '\0')
			{
     
				++dest;
				++src;

			}
			else
			{
     
				++dest;
			}
		}
		--cnt;
	}
	return ret;
}
int main()
{
     
	char arr1[20] = "hello";
	printf("%s\n", strncpy(arr1, "word", 8));
	char arr2[20] = "hello";
	printf("%s\n", my_strncpy(arr2, "word00", 5));
	char arr3[] = "hello";
	printf("%s\n", my_strncpy(arr3, "word", 4));
	return 0;
}




常见字符串函数及其复写_第6张图片

strncat

*char strncat(char Dest,const char Src,size_t count );

  • 如果count大于src的长度,就将src全部追加到dest,包括‘\0’。反之,追加src前 count的字符。并再追加一个‘\0’

strcat复写

#define _CRT_SECURE_NO_WARNINGS

#include 
#include  
#include 
#include 


char* my_stcncat(char* dest, const char* src, size_t cnt)
{
     
	
	assert(dest && src);
	char* ret = dest;
	while (*dest)//定位源字符串的'\0'指针
	{
     
		dest++;

	}

	if (cnt > strlen(src))//当cnt大的时侯,就是完整拷贝。
	{
     
	
		while (*dest++=*src++)
		{
     
			;

		}

	}
	else {
     

		while ((cnt-->0)&&(*dest++=*src++))
		{
     
			;
		}
		*dest = '\0';//因为cnt小的原因,根据要求,追加完后,需要再追加一个'\0'
	}
	return ret;


}
int main()
{
     
	char arr1[20] = "helloxx\0xxxxxxxxxx";
	char arr2[20] = "helloxx\0xxxxxxxxxx";

	printf("%s\n", my_stcncat(arr1, "word", 3));
	printf("%s\n", strncat(arr2, "word", 3));

	return 0;
}



常见字符串函数及其复写_第7张图片

strncmp

int strncmp( const char* str1, const*char str2,size_t cnt )

  • 当 cnt 大于 str1与str2 中长度最大的时,效果与 strcmp相同。反之 比较 长度最大的前cnt为。
  • 比较规则同strcmp
  • 注意复写时,我是以vs2019 为结论编写程序,返回值不是一个定值。

strncmp复写

#define _CRT_SECURE_NO_WARNINGS

#include 
#include  
#include 
#include 

int my_strncmp(const char* str1, const char* str2, size_t cnt)
{
     
	assert(str1 && str2);
	int ret = 0;
	while (!(ret=(*str1-*str2))&&(*str1)&&(*str2)&&(cnt>0))//只要当str1,str2其中为'\0',或者cnt为0就停止
	{
     
		--cnt;
		++str1;
		++str2;
	}
	if (ret == 0)
	{
     
		return 0;
	}else if(ret>0)
	{
     
		return 1;
	}
	else
	{
     
		return -1;
	}
	
}
int main ()
{
     
	char* arr1 = "hello";

	char* arr2 = "hello";
	
	printf("%d\n", strncmp(arr1, arr2, 20));
	printf("%d\n", my_strncmp(arr1, arr2, 20));

	return 0;
}

常见字符串函数及其复写_第8张图片

内存操作函数

memcpy,memmove(内存拷贝)

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

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

  • 这2个函数都是内存拷贝函数,效果一样。区别在于 memcpy只管拷贝(不考虑重叠拷贝),而memmove

    在拷贝时会注意重叠拷贝的问题。因此称memcpy是半拷 贝,而memmove是全拷贝。

  • 注意VS2019中的memcpy,memmove都是全拷贝。

  • 因此在复写时,我选择了复写一个memmove

memmove复写

常见字符串函数及其复写_第9张图片

#define _CRT_SECURE_NO_WARNINGS

#include 
#include  
#include 
#include 


void* my_memmove(void* dest, const void* src, size_t count)
{
     
	assert(dest && src);//断言,防止NULL指针

	if (dest <= src)//dest在前,从前往后拷贝
	{
     
		for (size_t i = 0; i < count; ++i)
		{
     
			*((char*)dest+i) =*((char*)src+i);
		}
	
	
	}
	else {
     

		for (size_t i = count-1; i>0; --i)//dest在后,从后往前拷贝,注意因为无符号的问题,
			                              //要考虑溢出问题。防止死循环。因此i=0的位置单独赋值
		{
     
			*((char*)dest + i) = *((char*)src + i);
		}
		*(char*)dest = *(char*)src;
	}

	return dest;

}
int main ()
{
     
	char arr1[20] = "hellowordxxxxx";
	char arr2[20] = "hellowordxxxxx";

	printf("%s\n", arr1);
	my_memmove(arr1, arr1 + 3, 5);//dest在前
	printf("%s\n", arr1);
	
	printf("%s\n", arr2);
	my_memmove(arr2+3, arr2 , 5);//dest在后
	printf("%s\n", arr2);
	return 0;
}

memcmp(内存比较)

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

  • 比较内存中的元素,与strcmp道理相同。只是以内存角度比较字节。

  • memcmp是将内存中的补码看成无符号型,。

常见字符串函数及其复写_第10张图片

memcmp复写

#define _CRT_SECURE_NO_WARNINGS

#include 
#include  
#include 
#include 


int my_memcmp(const void* buf1, const void* buf2, size_t count)
{
     

	assert(buf1 && buf2);
	int ret = 0;
	//注意一定是无符号的char*,因为memcmp看的是无符号
	while (count&&(!(ret=*(unsigned char*)buf1 -*(unsigned char*)buf2))) //因为&&的原因,count要在前,提前结束,一旦比较元素不相等就停止。
	{
                                                     //相等就继续比较.因为不同于字符串,直到count为0.
		buf1 = ( unsigned char*)buf1 + 1;                       // 防止比较多了。
		buf2 = ( unsigned char*)buf2 + 1;                   
		--count;
	}
	if (ret > 0)
	{
     
	
		return 1;

	}
	else if(ret<0)
	{
     
		return -1;
	
	}
	else
	{
     
	
		return 0;
	}



}
int main ()
{
     
	int a = 10001;
	int b = 10;
	
	
	printf("%d\n", my_memcmp(&a, &b, 4));

	printf("%d\n", memcmp(&a, &b, 4));
	
	return 0;
}

常见字符串函数及其复写_第11张图片

memset(内存赋值)

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

  • 将缓冲区(内存)设置为指定的字符c。
  • 缓冲区的大小由调用者设置,要合适。

memset复写

#define _CRT_SECURE_NO_WARNINGS

#include 
#include  
#include 
#include 
void* my_memset(void* dest, int c, size_t count)
{
     
	assert(dest);
	void* ret = dest;
	while (count--)
	{
     
		*(char*)dest = c;
		dest = (char*)dest + 1;
	}

	return ret;
}
int main ()
{
     
	char arr[20] = "xxxxxword";
	printf("%s\n", arr);

	my_memset(arr, 'h', 5);
	printf("%s\n", arr);
	
	
	return 0;
}

常见字符串函数及其复写_第12张图片

字符串查找函数

strstr

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

  • 在str1中搜索str2.要求str1于str2都是NULL结尾的字符串,否则行为未定义

  • 返回一个指针,指向字符串 中首次出现的str2,如果字符串中没有出现str2,则返回NULL。如果str2指向长度为零的字符串,则函数返回字符串。

  • 使用的是BF(暴力查找法)复写。KMP会在后续单独一篇博客

复写strstr

#define _CRT_SECURE_NO_WARNINGS

#include 
#include  
#include 
char* my_strstr(const char* string, const char* strCharSet)
{
     
	assert(string && strCharSet);
	if (*strCharSet == '\0')//传入空的时候,就返回string
	{
     

		return (char*)string;//返回值的类型是 char*,但是string是const char*,因此需要强制为char*
	}
	while (*string)
	{
     
		//进行匹配活动
		const char* s1 = string;
		const char* s2 = strCharSet;
		while ((*s2 != '\0') && (*s1 != '\0') && (*s1 == *s2))//&&的妙用。一旦一个假就停止遍历。
		{
     
			s1++;
			s2++;
		}
		if (*s2 == '\0')//我们关注的是:是否找到,必须先判断s2的情况,一旦成立就找到了。其它情况不需要考虑。
		{
     

			return (char*)string;
		}

		if (*s1 == '\0')//当*s1为空时就没有必要再次循环,后面不会出现匹配的情况。
						//但是当字符数组中没有一个匹配的字符时,*string为'\0'的特殊情况
		{
     

			return NULL;
		}

		string++;
	}
	return NULL;//当
}
int main()
{
     
	char arr[] = "abcdef";
	if (my_strstr(arr, "def")!=NULL)
	{
     
		printf("找到了\n");
	}
	else
	{
     
		printf("找不到了\n");

	}
	return 0;
}

常见字符串函数及其复写_第13张图片

切分字符串

strtok

char strtok(char str,const char sep)*

  • sep是分隔符的集合

  • str指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记,标记是一个非包含sep的字符串。

  • 如果传入的参数str不为NULL,那么strtok会返回第一个标记首地址,并将分隔符置为‘\0’,同时strtok会记住这个分隔符的位置。如果传入的参数为NULL,strtok会从记住的位置开始分割字符串。

  • 也就是说我们想将一个字符彻底切分完,在第二次调用时要传入NULL

  • 如果字符串中不存在更多的标记,则返回 NULL 指针

  • 下面距离说明:

常见字符串函数及其复写_第14张图片

strtok复写:

#define _CRT_SECURE_NO_WARNINGS

#include 
#include  
#include 
#include 

int isfind( char* str, const char* sep)//BF查找。二分需要排序。另外esp中字符少
{
                                           
	for (size_t i = 0; i < strlen(sep); ++i)
	{
     
		if (*str == sep[i])
		{
     
			return 1;
		}
	}
	return 0;
}
char* my_strtok(char* str, const char* sep)
{
     
	 char* ret = NULL;
	 static char* sp = NULL;
	 int Flage = 0;//帮助记录标记的起始地址
	if (str != NULL)//传入非NULL指针,就找第一个标记
	{
     
		
		for (size_t i = 0; i < strlen(str); ++i)
		{
     
			if ((isfind(str + i, sep) == 1)&&(Flage==1))//只有当记录了标记起始地址,才返回值。
			{
     
				sp = str + i+1;//+1,是为了方便查找.
				*(str + i) = '\0';
				return ret;
			}
			else if((isfind(str + i, sep)==0)&&(Flage==0))
			{
     
				ret = str + i;
				Flage = 1;//一旦Flage为1后,就不需要担心再次对ret赋值。
			
			}

		}
		return NULL;//没找到标记就返回NULL
	}
	else if(str==NULL&&sp!=NULL)
	{
     
		char* s = sp;
		while (*s!='\0')
		{
     

			if (isfind(s, sep) == 1&&Flage==1)
			{
     
				
				*s= '\0';
				sp = s + 1;
				return ret;
			}
			else if(isfind(s, sep) == 0 && Flage == 0)
			{
      
				ret = s;
				Flage = 1;
			}
			s++;
		}
	}
	sp = NULL;//当遇到'\0'时,需要返回这之前的标记地址。但是需要将记住的静态指针置为NULL.
	          // 因为最终需要返回一个NULL指针结束main的循环。
	return ret;
}
int main ()
{
     
	char arr[] = "- This, a sample string.";
	char* p = NULL;
	for (p = my_strtok(arr, "- ,"); p != NULL; p = my_strtok(NULL, "- ,"))
	{
     
		printf("%s\n", p);
	
	}

	return 0;
}

常见字符串函数及其复写_第15张图片

错误信息报告

strerror,perror

char * strerror(int errnum);

*void*perror(const char string);

  • 编译器会将程序中的出现所有错误(如少分号,少{}等)对应一个码,称谓错误码。每个错误码对应一个错误信息字符串。而错误码存储在errno中(< errno,h >)

image-20211020215258086

  • strerror会返回错误码对应字符串的首地址。不会主动打印错误信息,因此如果需要打印错误信息,需要printf函数

  • perror不仅·可以自动打印错误信息,还可以人为的添加一些信息,与错误信息组成新的字符串。

常见字符串函数及其复写_第16张图片

小结

  • 函数的复写过程,必须考虑到很多情况,提高函数的鲁棒性。可能BF算法笨(我就是。。。。。),但是算法都是一步一步优化的。
  • 关于字符串函数就介绍到这了。如果想了解其它字符串函数,可以去网站:https://en.cppreference.com/w/

你可能感兴趣的:(c语言,c语言,开发语言,后端)