常见的一些字符串函数

前言

字符串的函数学习,我们大致要明白,学习一个新的函数时,我们首先要清楚它的功能、传参、返回类型、实际场景,了解前面这些后,我们想更深一步了解一个函数,我们可以试着去自己实现它,因此接下来介绍的一些关于字符串类型的函数,部分我会将实现过程和思路,以及我自己实现时候遇到的难点和易错点分享给大家,供大家参考和学习,如有错误也欢迎指出。

1.计算字符串大小的函数——strlen

定义:size_t    strlen   (const char* str)

ps:size_t   就是  unsigned int 

功能:计算字符串的大小

原理:一个个数字符,直到遇到‘\0’才会返回

易错点:

a.由于这个函数遇到‘\0’,才返回值,因此如果给过去的字符串没有\0,则返回随机值

b.函数传参时注意传过去的是字符串的地址

c.由于返回类型是无符号整形,strlen(arr1)- strlen(arr2)的值不是负数

关于my_strlen的两种实现方法:

非递归:

int my_strlen(const char* p)
{
	int count=0;//计数
	assert(p);
	while(*p++)//先解引用判断,然后在++
		count++;
	return count;
}

递归(不创建临时变量实现):

int my_strlen(const char* p)
{
	assert(p);
	if(*p)
		return 1+my_strlen(p+1);
	else
		return 0;
}

2.拷贝字符串函数——strcpy和strncpy

(1)函数—strcpy

定义:char* strcpy ( char*str1 , const char* str2 )

功能:将str2里的字符串拷贝到str1的字符串中

原理:在str2中逐一取一个字符往str1里放,直到在str2中遇到‘\0’为止,‘\0’也会被放到arr1,返回str1的初始地址

易错点:

a. str2要存在‘\0’

b. str1在定义时,空间要足够大,要比str2大,才能容下str2.

c.str1可修改,str2一般不可修改

d.返回地址是返回一开始str1的值,由于str1会被修改,因此一开始用一个临时变量先存放。

模拟实现 my_strcpy

char* my_strcpy(char* str1,const char* str2)
{
	char* ren=str1;
	assert(str1 && str2);
	while(*str1++=*str2++);//先赋值,再各自加一,当str2为\0时,完成\0的赋值后,赋值表达式返回0,不再执行循环
	return ren;
}

(2)函数—strncpy

定义:char* strncpy ( char*str1 , const char* str2 ,size_t num )

功能:同样是字符串拷贝,相比strcpy,它能够指定在str2中拷贝多少个元素放到str1中,但不会自动补‘\0’,当n大于str2的字符串长度时,多出来的赋值为‘\0’

易错点:

a.在你指定一个str2想拷贝到str1时,要知道strncpy是不会自动在末尾补'\0'的

b.在你n超过str2的时候,拷贝到str1的操作继续执行,拷贝‘\0’,直到执行完n次

模拟实现—my_strncpy

char* my_strncpy(char* str1,const char* str2,size_t n)
{
	char* ren=str1;
	assert(str1 && str2);
	while((*str1++=*str2++) && n--);//当str2或者n为0时,不再执行赋值操作
	while(n--)//若n还不为0,对str1继续补\0
		*str1++='\0';
	return ren;
}

3.追加字符串的函数—strcat 和 strncat

(1)函数—strcat

定义:char* strcat ( char* str1 , const char* str2 )

功能:在str1后面追加一个字符串str2,追加结束后自动补‘\0’

原理:找str1中‘\0’的位置,将str2的字符从str1中‘\0’的位置开始一个个放,直到str2遇到\0停下

易错点:

a.str1和str2不能是同一个字符串地址,也就是不能自己追加自己

b.str1要足够大,且要有\0存在

模拟实现—strcat

char* my_strcat(char* str1,const char* str2)
{
	char* ret=str1;
	assert(str1&&str2);
	while(*str1)
		str1++;//先让str1指在\0位置  
	while(*str1++ = *str2++);//追加
	return ret;
}

(2)函数—strncat

定义:char* strncat ( char* str1 , const char* str2 ,size_t n )

功能:可以指定追加的个数,追加结束后自动补‘\0’,当n超过str2时,不做其他的操作。

易错点:

a.追加结束后会自动补\0

b.n超过str2时,不做多余的操作,只会补一次\0

模拟实现—strncat

char* my_strncat(char* str1,const char* str2,size_t n)
{
	char* ret=str1;
	assert(str1&&str2);
	while(*str1)
		str1++;//先让str1指在\0位置  
	while(n&&(*str2))//当执行n次后或者str2已经指向\0时不再循环
	{
		*str1++=*str2++;
		n--;
	}	
	if(n==0  ||  (*str2 =='\0'))//补\0
		*str1='\0';
	return ret;
}

4.字符串比较字符—strcmp和strncmp

(1)函数—strcmp

定义:int strcmp ( const char* str1,const char* str2 )

功能:实现字符串str1和str2的比较

比较规则:

两个字符串都从第一个开始,逐个逐个比较,若第一个相同则同时找到下一个字符比较,直到两个字符串都遇到‘\0’,也就是比完的情况,都相同则认为是两个字符串相等;

当某次比较出现不同时,比较结束,大小按字符的acsll码值算;

当其中一个字符串前面都相同,但其中一个较短,例如“abc\0”和“abcdf\0”,则在比到\0时也会视为前者小于后者,因此是较短的字符小

当str1  >  str2时,返回一个大于0的数

当str1  =  str2时,返回0

当str1  <  str2时,返回一个小于0的数

根据上面的比较规则,我们来试着自己设计这个函数

模拟实现—my_strcmp

int my_strcmp(const char* str1,const char* str2)
{
	assert(str1 && str2);
	while(*str1 == *str2)
	{
		if(*str1 == '\0')
			return 0;//顺利比完,str1和str2都指向‘\0’
		str1++;
		str2++;
	}
	//比较出现不同
	return *str1-*str2;
}

(2)函数—strncmp

定义:int strncmp ( const char* str1,const char* str2, size_t n )

功能:指定两个字符串的前n位进行比较,比较规则和strcmp一样

模拟实现—my_strncmp

int my_strncmp(const char* str1,const char* str2,size_t n)
{
	assert(str1 && str2);
	while(n&&*str1 == *str2)//当比完n次或者提前比较出结果了
	{
		n--;//n--一定要放在判断前面
		//当n次比完后,还能进入循坏,说明前面的都相等,因此先n--
		if(*str1=='\0'||n==0)//若n为0则说明比完n次,且都相同
			return 0;
		str1++;
		str2++;
	}
	return *str1 - *str2;
}

5.找字符创子串的函数—strstr

定义:char* strstr ( const char* str1,const char* str2 )

功能:在str1里找是否存在子串和str2相同,如果找到了,则返回第一次在str1里找到str2的起始位置的地址,如果没找到返回空指针。

模拟实现—my_strstr

设计思路:
    //实现在str1中找到str2的首字符
    //当找到以后开始判断是否为子串
    //当不是子串时,指针位置返回:str1返回到刚刚的下一个,str2返回到首字符
    //继续执行查找,若是找到子串,则str2会指向\0,若始终没找到,str1会指向\0

char* my_strstr(const char* str1,const char* str2)
{
	const char* str_1=str1;//作为记住str1开始判断时的地址的一个指针
	const char* str_2=str2;//str_2作为不变量,始终指向str2的首元素
	assert(str1 && str2);
	if(*str2 == 0)
		return (char*)str1;//若str1或str2是‘\0’则直接返回str1首元素地址
	while(*str1)//str1指向\0时意为着找完了也没有
	{
		while(*str1!=*str2)//在str1中找到str2的首字符
		{
			if(*str1 == 0)
				return NULL;//若一开始就不存在和str2匹配的字符,直接返回NULL
			str1++;
		}

		str_1=str1;//记住起始位置
		while(*str1==*str2)
		{
			str1++;
			str2++;
			if(*str2 == 0)//找到了返回str1中开始判断的起始位置
				return (char*)str_1;
		}
		//没找到时,str1重新指向开始判断时的下一个字符,str2指向首字符
		str1=str_1+1;
		str2=str_2;
	}
	return NULL;
}

6.分割字符串函数—strtok

定义:char* strtok ( char* str1, const char* str2 )

功能:用来切分字符串,str1是要被切割的字符串,str2是告诉函数要切哪些地方(从哪下刀的问题)

使用方法:这个函数能够记住上一次切完的位置,只需要第一次给数组首地址str1,每次执行切一段即返回,会改变str1里面的数据,因此通常将拷贝后的数据放到函数里切。

例子:123.452*[email protected]

我如果想把里面的数字和字母单独拿出来,我调用strtok去切,第一次str1给这个字符串的首地址,str2给“.*@”(要以什么为分割),第一次调用结果返回会得到:

123\0452*[email protected]

因此只会打印123,此时它会记录指针(不销毁的参数)指向4的位置,若再次调用,可切出452,并把*改成\0。

巧用for循环去使用这个函数会方便的多

int main()
{
	char str1[100]="123.456&1325@com";//被分割的字符串
	const char *p=".@&";//分割点
	char *len;//接受返回值

    //for的第一个语句只执行一次,并且完成切割并把结果给len
    //如果len不为‘\0’,说明切割下了一段数据,将其可以用printf输出
    //输出后再到第三个条件语句,此时用空指针作为str1的参数,再次进行切割操作,切割完成后赋值给len
    //随后再次进行判断,len若为‘\0’,说明刚刚字符串已经切完了,跳出循环,若不是,则继续执行
	for(len=strtok(str1,p);len;len=strtok(NULL,p))
	printf("%s\n",len);
	system("pause");
	return 0;
}

执行结果:

常见的一些字符串函数_第1张图片

本函数要求会用即可,暂时不进行模拟实现

7.错误码转译函数—strerror

定义:char* strerror(int errnum)

 功能:在一些程序执行过程中,可能出现一些错误是我们无法知道的,但代码又能顺利跑,可实际又达不到我们要的效果,于是,要想知道具体哪一部分出了问题,我们通过系统返回的错误码,使用这个函数去转译成错误信息,方便找到bug

ps:要使用错误码得引用头文件#include

暂时来说这个函数还用不上,目前理解就行

8.字符转换函数—tolower 和 toupper

功能:字面意思,tolower就是将大写字母转换成小写字母,tolower就是将小写变大写,如果你是小写的放到tolower里去,结果不变。

没啥好说的这个,要求遇到这两个函数能知道干嘛的就ok,由于功能过于简单,自己用的时候忘了也随便能写一个,但不要遇到了却不知道干嘛的就行。

9.字符分类函数表

函数                 如果他的参数符合下列条件就返回真
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              任何可打印字符,包括图形字符和空白字符

ps:遇到时认得出来就ok

10.数组拷贝函数—memcpy 和 memmove

(1)函数—memcpy

定义:void* memcpy(void* arr1,const void* arr2,size_t sz)

功能:基本功能和strcpy一样是拷贝数据的函数,但功能上扩大了,这个函数能拷贝不同类型的数据,因此参数和返回类型也是void*这个万金油,sz的单位是字节,在使用时要额外注意。

易错点:当两个数组地址出现重叠时,不能进行交叉拷贝

模拟实现—my_memcpy

void* my_memcpy(void* arr1,const void* arr2,size_t sz)
{
	void* ren=arr1;
	assert(arr1&&arr2);
	//进入循环sz为字节数,循环执行多少次,arr1被赋值多少次
	while(sz--)
	{
		*(char*)arr1 = *(char*)arr2;
		arr1=(char*)arr1+1;
		arr2=(char*)arr2+1;
	}
	return ren;
}

(2)函数—memmove

定义:void* memmove(void* arr1,const void* arr2,size_t sz)

功能:也是拷贝数据,但它能实现交叉拷贝,当arr1和arr2有交叉的时候也能拷贝。

原理:通过画图可以知道,之所以不能交叉拷贝是因为拷贝可能会篡改后面还未执行拷贝的数据,因此要分情况讨论从前向后拷贝,还是从后向前拷贝:

arr1>arr2时        arr2的指针位于arr1前面时        从后向前拷贝

arr1=arr2时        arr2的指针位于arr1同一位置        随便拷

arr1

模拟实现—my_memmove

void* my_memmove(void* arr1,const void* arr2,size_t sz)
{
	char* ren=(char*)arr1;
	assert(arr1 && arr2);
	//从后向前拷贝
	if(arr1>arr2)
		while(sz--)//此处sz先减1,刚好指向最后一个元素的位置
			*((char*)arr1+sz)=*((char*)arr2+sz);
	//从后向前
	else
		while(sz--)
		{
			*(char*)arr1=*(char*)arr2;
			arr1=(char*)arr1+1;
			arr2=(char*)arr2+1;
		}
	return ren;
}

11.比较两个数据大小的函数—memcmp

定义:int memcmp(const void* arr1,const void* arr2,size_t sz)

功能:和strncmp的比较规则是一样的,这里所有的数据类型都可以放进去比,一个字节一个字节的比较,比较内存中的ascll码值的大小,需要注意的是,第三个参数是字节为单位的。

模拟实现—my_memcmp

int my_memcmp(const void* arr1,const void* arr2,size_t sz)
{
	assert(arr1 && arr2);
	while(sz--&&(*(char*)arr1==*(char*)arr2))//要么比较sz次后跳出循环,要么遇到不同提前跳出
	{
		arr1=(char*)arr1+1;
		arr2=(char*)arr2+1;
	}
	//如果循环被执行了sz次,则此时*arr1和*arr2相同,相减返回0
	//如果提前跳出,相减可估计返回值比大小
	return *(char*)arr1 - *(char*)arr2;
}

12.内存设置函数—memset

定义:void* memset(void* arr,int c,size_t sz)

功能:在arr中的内存里每次取一个字节的c放到arr内存中,按顺序一直放,放sz次

易错点:

a.每次放c中的一个字节的内容

b.第三个参数单位是字节

总结

本章学习了字符串有关的一些函数,并通过理解原理和尝试模拟实现函数去加深对函数的理解和记忆,希望这篇文章看了对你能有所帮助,有哪里不足和错误也欢迎指出。

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