字符串函数和内存函数

目录

1.字符串函数

1.1.字符串

1.2.strlen()

1.2.1.函数介绍

1.2.2使用时要注意的一些点

1.2.3.my_strlen()的实现

1.3.strcpy()

1.3.1.函数介绍

1.3.2使用时要注意的一些点

1.3.2.my_strcpy()的实现

1.4.strcat()

1.4.1.函数介绍

1.4.2使用时要注意的一些点

1.4.3.my_strcat()的实现

1.5.strcmp()

1.5.1.函数介绍

1.5.2使用时要注意的一些点

1.5.3.my_strcmp()的实现

1.6.strncmp()、strncpy()和strncat()

1.6.1.函数介绍

1.6.2使用时要注意的一些点

1.7.strstr()

1.7.1.函数介绍

1.7.2.my_strstr()的实现

1.8.字符分类函数

1.9.字符转换函数

2.内存函数

2.1.memcpy()

2.2.memmove()

2.3.memcmp()

2.4.memset()


1.字符串函数

此类函数的头文件为 <string.h>

1.1.字符串

在讲字符串函数前,我们先了解一下字符串,字符串是一串字符的集合,其特点是:以'\0'结尾;

这也是区分字符与字符串的关键。

字符串函数,字符串函数,意思就是对字符串进行操作的函数,为了安全起见,那么就要求参数得和字符串相关,那就得有字符串的标志:'\0'。

1.2.strlen()

1.2.1.函数介绍

unsigned int strlen(const char* str)

计算字符串有效长度的函数,只有一个参数,参数的类型是char*,指代要计算字符串的首元素地址,返回值类型是unsigned int,计算过程是从参数所指地址开始一个一个字节地向后访问,当被访问空间为 '\0' 时,就停止访问,返回 '\0' 前的字符个数。

1.2.2使用时要注意的一些点

当两个strlen()相减为负数的情况:前面我们提到了strlen()返回值的类型为 unsigned int ,两个这个类型的数进行加减运算时,计算的结果也会以%u 的形式解读,如果让两个strlen()函数相减,此时我们再判断其正负,就会得到,错误的结果,因为%u解读的数都是大于0的,不可能小于0!!!

1.2.3.my_strlen()的实现

实现的方法有三种:1.计数器法 2,指针法 3,不创建临时变量的递归法

由于计算字符串长度时,不需要进行修改,为了提高代码的健壮性,我们要在指针前加上const修饰。

1.计数器法

int My_strlen(const char* str)  
{
    int count = 0;
	while (*str++)
	{
		count++;
	}
	return count;
}

2.指针法

int My_strlen(const char* str)  
{
	char* p = str;
	while (*str)
	{
		str++;
	}
	return str - p;
}
//看到这要回忆一下 指针的运算 有哪些

3.递归实现

//这里不用创建临时变量
int My_strlen(const char* str)
{
	if (*str)
	{
		return 1 + My_strlen(++str);
	}
	else
	{
		return 0;
	}
}

1.3.strcpy()

1.3.1.函数介绍

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

在目标空间内复制字符串,有俩个参数,第一个参数的类型为char*,指向复制的目标空间,第二个参数的类型是char*,指向待复制内容的空间,返回值的类型是char*,指向目标空间的首元素地址,复制过程是:依次将待复制内容的数据赋给目标空间,当指向待复制内容的指针指向'\0'时,就结束。

1.3.2使用时要注意的一些点

空间大小问题:当目标空间的大小不足以存放待复制的内容的话,会越界访问,就存在数据被非法覆盖的危机了,所以在使用strcpy()前,请确定目标空间足够大。

1.3.2.my_strcpy()的实现

char* My_strcpy(char* dest, const char* sor)
{
	char* p = dest;
	while (*dest++ = *sor++)
	{
		;
	}
	return p;
}

1.4.strcat()

1.4.1.函数介绍

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

在目标字符串的末尾追加字符串,有俩个参数,第一个参数的类型为char*,指向待被追加的目标空间,第二个参数的类型是char*,指向要追加内容,返回值的类型是char*,指向待被追加空间的首元素地址,追加的过程是:先访问待被追加的空间,当访问空间的内容为'\0'时停止向后访问,接着就从此位置开始,逐次将要追加的内容进行复制,复制的结束以要复制的空间内容为'\0'时停止。

1.4.2使用时要注意的一些点

在自己后面追加自己:当想使用strcat()实现在自己后头再追加自己时,就会出现一个bug,在清楚strcat()函数的执行逻辑后,在末尾进行复制追加的操作就会进入一个无尽循环,要追加内容永远不会时'\0'。

1.4.3.my_strcat()的实现

//有点像strlen()和strcpy()的结合
char* My_strcat(char* dest, const char* sor)
{
	char* p = dest;
	while (*dest)
	{
		dest++;
	}
	while (*dest = *sor)
	{
		dest++;
		sor++;
	}
	return p;
}

1.5.strcmp()

1.5.1.函数介绍

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

str1与str2的先后顺序没有太大关系,只有当两者不同时会影响返回值的正负。

字符串比较函数,比较的不是字符串长度,而是逐个比较字符的ascii码值的大小,如果两个字符串都比较到末尾'\0'时,就会返回0,表示两个字符串相等,反之如果遇到两个不相等的字符时,就返回参数1字符串与参数2字符串的差值,差值大于0说明两个不相等字符中,前者的ascii码值更大,反之说明后者的更大,类似于按姓名拼音进行排序的大小比较。

1.5.2使用时要注意的一些点

1.5.3.my_strcmp()的实现

int My_strcmp(const char* str1, const char* str2)
{
	while (*str1 == *str2)
	{
		if (*str1 == '\0') //这一步很关键
		{
			return 0;
		}
		str1++;
		str2++;
	}
	return *str1 - *str2;
}

---------------------------------------------------------------分割线---------------------------------------------------------

分割线以上的字符串操作函数都存在一个问题,就是当参数所指的“字符串”的最后如果不存在'\0',就会出现越界的风险,要是正好要操作的字符串后面是重要的数据,被函数一修改,那就麻烦了,

下面的函数在一定程度上降低了这种风险(加上了限制访问范围的n参数,统一加在最后)

1.6.strncmp()、strncpy()和strncat()

1.6.1.函数介绍

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

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

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

就是在原来的基础上加上参数n作为函数功能结束的控制条件,不再以'\0'作为“灯塔”,而是n字节后的位置为“灯塔”,函数操作字符串的过程基本是一样的。

补:strncpy()和strncat()中,如果原字符的长度小于count时,在拷贝完原字符串后,后面默认补0,直至count个

1.6.2使用时要注意的一些点

n的单位是字节,虽然在字符串中一个元素就是一个字节,但不要就把它当成元素的个数看了。

1.7.strstr()

1.7.1.函数介绍

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

在主串(string指向的字符串)中找子串(strCharSet指向的字符串)是否存在的函数,存在则返回子串的第一个字符在主串中的位置,不存在则返回NULL。

1.7.2.my_strstr()的实现

char* My_strstr(const char* str1, const char* str2)
{
	char* p1 = str1;  //用于每次比较后的复位
	char* p1_c = str1; //用于做比较
	char* p2 = str2;  //用于作比较

//循环的终点是主串被遍历完
//其实可以考虑先求出两个字符串的长度,然后就可以避免一些重复的比较了
//因为当主串中剩余未遍历的字符串长度小于子串时,就没有比较的必要了
//复位操作也能优化一下,让子串尽可能地“右移”(等我学完数据结构中的串后再来修改)
	while (*p1)
	{
		if (*p1 = *p2)
		{
			while (*p1_c == *p2)
			{
				p1_c++;
				p2++;
			}
			if (*p2 == '\0')
			{
				return p1;
			}
			p2 = str2;
		}
		p1++;
		p1_c = p1;
	}
	return NULL;
}

1.8.strerror()

char *strerror( int errnum )

识别错误码并返回表示错误信息的字符串,返回类型是char*,指向表示错误信息的字符串的第一个字符,参数的类型为int,需是能表示错误码的整型数。

可是要在程序运行后才能知道错误码是多少啊,我们怎么能在敲程序代码的时候就未卜先知了呢?这个时候就需要引用一个特殊的全局变量:errno,这个变量的作用是存放上次程序运行错误对应的错误码,如果程序运行过程中没有错误,则其值为默认的初始值0,而错误代码0指的是 no error,注意!在使用这个变量的时候,得引用一个库:<errno.h>。

1.9.strtok()

char* strtok(char* str, const char div)

 字符串分割函数,将字符串按所给字符进行分割,返回的是遇到此次的第一个分割符处之前的字符串,分割参数div可以是单个字符,或者是分割符的字符串,字符串的每个字符都代表一个分割符。

注意:如果你上次引用strtok(),strtok()会记忆上次分割过的字符串位置,当想要获取第二段分割串时,参数1char* str,传NULL即可,当字符串不能再分割时,函数返回NULL。

1.9.字符分类函数

所需要引用的头文件为<ctype.h>

这类函数以 is 开头

函数结构:int isxxx(int x)

相当于英语中的疑问句,用于判断字符的属性:

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 任何可打印字符,包括图形字符和空白字符

上述函数的返回类型为bool类型,只有一个参数,类型为int,参数如果符合相应条件就返回ture,反之返回false。

1.10.字符转换函数

所需要引用的头文件为<ctype.h>

用来转换 字符 的大小写的功能:

int tolower ( int c );

int toupper ( int c );

如果参数合理,则返回转化后的字符对应的ascii码值。

------------------------------------------------------内存函数---------------------------------------------------------------

2.内存函数

所需引用的头文件:<string.h>

相较于字符串操作函数,内存函数在使用上显得更加顺手一点,因为其操作的对象是更加底层的内存,也就意味着它能操作任何存储在内存中的数据(只要有权限的话)

补:下列标红的文字

1.void*

注意函数返回值为void*时,虽然void*可以转化成任意类型的指针,但也不能直接用带类型的指针去接收,而是应该把返回值强制类型转化一下再赋给对应类型的指针。

2.size_t num

这里的单位时字节,不是指元素的个数!!!

2.1.memcpy()

void * memcpy ( void * destination, const void * source, size_t num )

从低地址到高地址,将source指向内存块的后size_t个字节空间的数据,拷贝到destination指向的内存块中,类似strncpy()。

这样的拷贝方式存在一个缺陷,试分析以下代码输出的结果:

int main()
{
	char string[10] = "memcpy";
	memcpy(string+2, string, 4);
	printf("%s\n", string);
	return 0;
}

 这是因为destination指向的内存空间与source指向的内存空间发生了重叠,在顺序拷贝的过程中,也将source指向的内存空间进行了修改,但是你不先备份一份数据,在不申请额外空间的条件下,想将自身的某个部分拷贝到自身,这种现象是不可避免的吧?

其实是有解决办法的,下面的函数memmove()就是不申请额外空间,但是完成了这个要求。

2.2.memmove()

void * memmove ( void * destination, const void * source, size_t num )

与memcpy()所不同的是,memmove()函数不是一贯以从左向右进行拷贝的,而是先判断dest指向空间的地址与source指向空间的地址的先后顺序,如果dest指向的空间的地址低于source指向的地址,就从左向右进行拷贝,反之则从右向左进行拷贝。

规则巧记:

如果记不住这个规则,我这里做个比喻,将拷贝的动作想象成向目标地埋雷,埋雷者不能经过自己埋过雷的地方,否则就会有危险,当要埋雷的区域与埋雷者活动的区域重叠时,埋雷者应从自己活动范围的两端中选则在重叠部分的一端开始埋雷,这样就不会踩到自己埋的雷了。

2.3.memcmp()

int memcmp ( const void * ptr1, const void * ptr2, size_t num )

                            同strncmp()

2.4.memset()

void * memset ( void * ptr, int value, size_t num )

内存设置函数,在ptr指向地址后num个字节的空间中,将每一字节空间的数据设置为value。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值