一起来看看c语言中的字符串函数



字符串函数的秘密

1.浅谈字符串(c)

C语言中对字符和字符串的处理很是频繁,但是C语言本身是没有字符串类型的,字符串通常放在
常量字符串中或者字符数组 中。
字符串常量适用于那些对它不做修改的字符串函数.

2.常用字符串函数介绍及部分的模拟实现

2.1 strlen

返回 C 字符串 str 的长度。

C 字符串的长度由终止空字符确定:C 字符串的长度与字符串开头和终止空字符之间的字符数一样长(不包括终止空字符本身)。

这不应与保存字符串的数组的大小相混淆。例如:

char mystr[100]=“test string”;

定义了一个大小为 100 chars 的字符数组,但用于初始化 mystr 的 C 字符串的长度只有 11 个字符。因此,当 sizeof(mystr) 的计算结果为 100 时,strlen(mystr) 返回 11

标准库的声明

size_t strlen ( const char * str );
  • 字符串已经 ‘\0’ 作为结束标志,strlen函数返回的是在字符串中 ‘\0’ 前面出现的字符个数(不包
    含 ‘\0’ )。

  • 参数指向的字符串必须要以 ‘\0’ 结束。

  • 注意函数的返回值为size_t,是无符号的( 易错 )

    模拟实现:strlen的三种模拟实现

2.2 strcpy

标准库的声明

char * strcpy ( char * destination, const char * source );
  • 指向的 C 字符串复制到目标所指向的数组中,包括终止空字符(并在该点停止)。

    为避免溢出,目标所指向的数组的大小应足够长,以包含与相同的 C 字符串(包括终止空字符),并且内存中不应与重叠。

  • 源字符串必须以 ‘\0’ 结束。

  • 会将源字符串中的 ‘\0’ 拷贝到目标空间。

  • 目标空间必须足够大,以确保能存放源字符串

  • 目标空间必须可变。

模拟实现

char* my_strcpy( char* dest, const char* src) {
	assert(dest && src);
	char* ret = dest;
	while (*dest++ = *src++);
	return ret;
}

2.3 strcat

字符串的副本追加到目标字符串。目标中的终止空字符被的第一个字符覆盖,并且由目标中两者的串联形成的新字符串的末尾包含一个空字符。

目的地来源不得重叠。

标准库声明

char * strcat ( char * destination, const char * source );
  • 源字符串必须以 ‘\0’ 结束。
  • 目标空间必须有足够的大,能容纳下源字符串的内容
  • 目标空间必须可修改。

模拟实现

char* my_strcat(char* dest, const char* src) {
	assert(dest && src);
	char* ret = dest;
	while (*dest) {
		dest++;
	}
	while (*dest++ = *src++);
	return ret;
}

2.4 [strcmp](strcmp - C++ 参考 (cplusplus.com))

将 C 字符串 str1 与 C 字符串 str2 进行比较

此函数开始比较每个字符串的第一个字符。如果它们彼此相等,则继续使用以下对,直到字符不同或达到终止空字符。(比较的时字符的ASCII值得大小)

标准库说明

int strcmp ( const char * str1, const char * str2 );
返回值表明
0两个字符串的内容相等
,<0第一个不匹配的字符在 ptr1 中的值低于在 ptr2 中的值
>0第一个不匹配的字符在 ptr1 中的值大于在 ptr2 中的值

模拟实现

int my_strcmp(const char* str1, const char* str2) {
	assert(str1 && str2);
	while (*str1 == *str2) {
		if (*str1 == '\0') {
			return 0;
		}
		str1++;
		str2++;
	}
	if (*str1 > *str2) {
		return 1;
	}
	else {
		return -1;
	}
}

2.4 [strncpy](strncpy - C++ 参考 (cplusplus.com)),[strncat](strncat - C++ 参考 (cplusplus.com)),[strncmp](strncmp - C++ 参考 (cplusplus.com))

这三个函数与前面提到得strcpy,strcat,strcmp可以说是一一对应得,唯一的得区别时他们多了一个参数size_t num;这个参数可以说是约束这三个函数得,因为这三个函数在操作内存是是没有限制得,举个栗子,加入我们要把一个字符串复制到一个较小得数组空间中,就会发生越界得情况,因为这个函数只有遇到’\0’才会停下里,那么我们设置size_t得目的就是防止这种情况得发生,由我们程序员自己去设置需要操作得字节数!从而可有有效避免危险得发生!

strncmp

int strncmp ( const char * str1, const char * str2, size_t num );

比较两个字符串的字符

将 C 字符串 str1数目与 C 字符串 str2 的数目进行比较。
此函数开始比较每个字符串的第一个字符。如果它们彼此相等,则继续使用以下对,直到字符不同,直到达到终止空字符,或者直到两个字符串中的数字字符匹配,以先发生者为准。

strncpy

char * strncpy ( char * destination, const char * source, size_t num );

从字符串中复制字符

源**的第一个字符复制到目标。如果在复制 num 个字符之前找到 C 字符串(由空字符表示)的末尾,则用零填充目标,直到向其写入了总共 num 个字符。

如果长于 num,则不会在目标末尾隐式追加空字符。因此,在这种情况下,不应将目标视为空终止的C字符串(读取它会使溢出)。

目标不得重叠(请参阅 memmove 了解重叠时更安全的替代方法)。

strncat

char * strncat ( char * destination, const char * source, size_t num );

从字符串追加字符

的第一个字符追加到目标,以及终止空字符。

如果中 C 字符串的长度小于 num,则仅复制终止空字符之前的内容。

模拟实现

这3函数得实现基本与相对应得无限制函数相似,需要注意一些特殊情况下得细节,思路有了,其它得就是自由发挥了。这里我就不展示代码了,有兴趣得小伙伴可以去写一写,不算太难。

2.5 strstr

const char * strstr ( const char * str1, const char * str2 );//母串不可改变(值)
   char * strstr (       char * str1, const char * str2 );//母串可改变(值)

查找子字符串

返回指向 str1 中第一次出现的 str2 的指针,如果 str2 不是 str1 的一部分,则返回空指针。

匹配过程不包括终止空字符,但它止步于此。

模拟实现

char* my_strstr(const char* str1, const char* str2) {
	assert(str1 && str2);
	char* p2 = str2;//P2是记录需查找子字符串的首地址,便于后续循环更新使用
	while (*str1 != '\0') {
		char* p1 = str1;//p1可以理解为一个活动指针,每一次当得到与母字符串的地址相等时,我们记录这个地址,并进行后续的比较
		while (*p1 == *str2) {
			char* ret = str1;//ret记录待查找的母字符串第一个匹配的地址;
			if (*str1 != '\0') {//进行后续判断
				p1++;
				str2++;
			}
			if (*str2 == '\0') {//当str达到'\0',说明匹配成功了,返回ret
				return ret;
			}
		}
		str2 = p2;//匹配失败,刷新str2的值;
		str1++;//str1向后移动,继续判断
	}
	return NULL;//遍历了整个母字符串,没有找到,返回空指针;
}

这里还有一种很厉害得算法叫kmp算法,大家可以看一下 比特大博哥

2.6 strtok

char * strtok ( char * str, const char * delimiters );

将字符串拆分为标记

对此函数的一系列调用将 str 拆分为标记,这些标记是由作为分隔符一部分的任何字符分隔的连续字符序列。

在第一次调用时,该函数需要一个 C 字符串作为 str 的参数,其第一个字符用作扫描令牌的起始位置。在后续调用中,该函数需要一个空指针,并将紧跟在最后一个令牌结束之后的位置用作扫描的新起始位置。

若要确定令牌的开头和结尾,该函数首先从起始位置扫描分隔包含的第一个字符(该字符将成为令牌的开头)。然后从令牌的开头开始扫描分隔符中包含的第一个字符,该字符将成为令牌的结尾。如果找到终止空字符,扫描也会停止。

令牌的此末尾将自动替换为空字符,令牌的开头由函数返回。

在对 strtok 的调用中找到 str 的终止空字符后,对此函数的所有后续调用(以空指针作为第一个参数)将返回空指针。

找到最后一个令牌的点由函数在内部保留,以便在下一次调用时使用(不需要特定的库实现来避免数据争用)。

模拟实现:

static char* ret2 = NULL;//设置一个全局的指针变量;记录母字符串当前的位置
char* my_strtok(char* str1, const char* str2) {
	if (str1!=NULL) {
		char* ret = str1;
		char* p2 = str2;//理解为一个活动指针,记录起始位置
		begin:
		while (*str1 != '\0') {
			while (*str1 != *p2) {//一一匹配在当前str1指向得情况下,是否有匹配字符
				if (*p2 != '\0') {
					p2++;
				}
				else {
					str1++;
					p2 = str2;
					goto begin;
				}
			}
			*str1 = '\0';
			ret2 = str1;
			return ret;
		} 
		return ret;
	}
	else {//传入参数为NULL时得情况,这种情况和第一次得情况很像,有点递归得意思,但地址变换比较复杂,要时刻记住,自己变得什么,存得什么,要复原什么,第一感觉可以递归实现,但没写出来,也没去看库函数的源码,欢迎大佬指教。
		str1 = ret2+1;
		if (*str1 == '\0') {
			return NULL;
		}
		char* ret = str1;
		char* p2 = str2;
	begin1:
		while (*str1 != '\0') {
			while (*str1 != *p2) {//一一匹配在当前str1指向得情况下,是否有匹配字符
				if (*p2 != '\0') {
					p2++;
				}
				else {
					str1++;
					p2 = str2;
					goto begin1;
				}
			}
			*str1 = '\0';
			ret2 = str1;
			return ret;
		}
		ret2 = str1 - 1;
		return ret;
	}
}

总体实现思路:分两大类情况,第一类是参数不为NULL时,在str1指向的字符串中去查找str2指向的字符串是否包含其丹个字符,第一印象就是循环的嵌套了,第一层,str1指向目标,第二层,str2指向目标,通过基本的控制语句实现简单的逻辑就可以了;到最后返回的时候,我们利用全局指针变量去记录上一个我们达到目标的位置;第二类是指针为空指针时,这时候需要我们第一次记录的地址去变换得到新的str1,后面的步骤就有点类似了;但是要注意一些细节,特殊情况的考虑;但由余小编水平有限,可能会有很多考虑不周的情况,大家有其它好的想法也可以评论区留言哦。

附一张vs2019测试图:image-20220708021258842

2.7 [strerror](斯特雷罗尔 - C++参考 (cplusplus.com))

char * strerror ( int errnum );

获取指向错误消息字符串的指针

解释 errnum 的值,生成一个字符串,其中包含一条消息,该消息描述错误条件,就好像库的函数设置为 errno 一样。

返回的指针指向静态分配的字符串,程序不得修改该字符串。对此函数的进一步调用可能会覆盖其内容(不需要特定的库实现来避免数据争用)。

strerror 生成的错误字符串可能特定于每个系统和库实现

2.8 字符分类函数

image-20220708024215249

2.9 memcpy

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

复制内存块

num 字节值从指向的位置直接复制到目标指向的内存块。

指针和目标指针所指向的对象的基础类型与此函数无关;结果是数据的二进制副本。

该函数不检查中是否有任何终止空字符 - 它始终精确地复制数字字节。

为避免溢出,目标参数和参数所指向的数组的大小应至少为 num 个字节,并且不应重叠(对于重叠的内存块,memmove 是一种更安全的方法)。

  • 函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。
  • 这个函数在遇到 ‘\0’ 的时候并不会停下来
  • 如果source和destination有任何的重叠,复制的结果都是未定义的

模拟实现:

void my_memcpy(void* dest, const void* src, size_t num) {//需要用到void*,因为我们不知道用户需要操作什么类型的数据
	while (num--) {
		*(char*)dest = *(char*)src;
		((char*)dest)++;
		((char*)src)++;
	}
}

2.10 memmove

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

移动内存块

num 字节值从指向的位置复制到目标指向的内存块。复制就像使用中间缓冲区一样进行,从而允许目标重叠。

指针和目标指针所指向的对象的基础类型与此函数无关;结果是数据的二进制副本。

该函数不检查中是否有任何终止空字符 - 它始终精确地复制数字字节。

为避免溢出,目标参数和参数所指向的数组的大小应至少为 num 个字节。

模拟实现:

void my_memmove(void* dest, const void* src, size_t num) {
	char c[100];//设置缓冲池
	my_memcpy(c, src, num);
	my_memcpy(dest, c, num);
}

毒鸡汤

念念不忘,必有回响,知识也一样,时常想起积累的小知识点,总有一天你会发现自己的思维居然真的能实现成代码,但在实现的路上可能会有无数bug,这也是见怪不怪的,哈哈。但总的来说就是思维不清晰或者基础知识不扎实,前者靠锻炼,后者靠积累,大家加油!最后关注小编不迷路,后期不定时更新c的知识哦!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

WindFall1314

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

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

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

打赏作者

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

抵扣说明:

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

余额充值