基于字符函数和字符串函数的研究

本文详细介绍了C语言中处理字符串和字符的常用函数,包括strlen、strcpy、strcat、strcmp、strncpy、strncat、strncmp、strstr、strtok、strerror等。这些函数分别用于计算字符串长度、复制字符串、拼接字符串、比较字符串、查找子串、分割字符串和报告错误信息。同时,还探讨了字符分类函数memcpy、memmove、memcmp和memset的使用及其在处理内存拷贝时的注意事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

求字符串长度
strlen
长度不受限制的字符串函数
strcpy
strcat
strcmp
长度受限制的字符串函数介绍
strncpy
strncat
strncmp
字符串查找
strstr
strtok
错误信息报告
strerror
字符操作
内存操作函数

memcpy
memmove
memset
memcmp

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

1.strlen

int main()
{
	//int len = strlen("abcdef"); // 不算\0  算\0之前的字符的长度  
	char arr[] = { 'a','b','c','d','e','f' };
	//int len = strlen(arr);  // \0不知具体位置  为随机值
	printf("%d\n", len);
	return 0;
}

三种方法模拟实现strlen

1.计数器

2.递归

3.指针

//1.计数器
int my_strlen(const char* str)
{
	int count = 0;
	assert(str != NULL);
	while (*str != '\0') //可以直接换成while(*str)
	{
		count++;
		str++;  //地址++ 找到下一个字符
	}
	return count;
}
int main()
{
	int len = my_strlen("abcdef");//这里传的其实是a的地址
	printf("%d\n", len);
	return 0;
}
//函数递归的方法 - 不创建临时变量
int my_strlen(const char* str)
{
	if (*str == '\0')
		return 0;
	else
		return 1 + my_strlen(str + 1);
}
int main()
{
	char arr[] = "abcdef";
	int len = my_strlen(arr);
	printf("%d\n", len);

	return 0;
}
//3.指针-指针的方法
int my_strlen(char* str)
{
	//原理 地址是连续的
	char* p = str; // 存起始地址
	while (*p != '\0')
		p++;  // p+1  等价于 地址+1
	return p - str;

}
int main()
{
	int arr[] = "abcdef";
	int len = my_strlen(arr);
	printf("%d\n", len);
	return 0;
}

size_t是无符号十进制类型

 实际上是unsigned int 重命名来的

由于strlen求字符串长度的时候,不可能求出负数,所以规定为无符号整数,但这里返回值写成int也没有关系

总结

1.字符串已经 '\0' 作为结束标志,strlen函数返回的是在字符串中 '\0' 前面出现的字符个数(不包 含 '\0' )。

2.参数指向的字符串必须要以 '\0' 结束。

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

4.学会strlen函数的模拟实现

2.strcpy

strcpy - 字符串拷贝函数

char* my_strcpy(char* dest,const char* src)  //返回dest的起始位置
{
	assert(dest != NULL && src != NULL);//断言
	char* ret = dest; // 存dest的起始位置
	while (*dest++ = *src++)  //  拷贝完的结果又作为判断条件  赋值完后导致结果表达式为\0 才会停止循环
	{
		;
	}
	//返回dest的起始地址
	return ret;
}
int main()
{
	char arr1[] = "abcdefghi";
	char arr2[] = "bit";
	my_strcpy(arr1, arr2);
	printf("%s\n", arr1);
	return 0;
}
int main()
{
	//错误示范
	char arr1[] = "abcdefghi";
	char arr2[] = { 'b','i','t' };
	strcpy(arr1, arr2);
	return 0;
}

总结:

1.Copies the C string pointed by source into the array pointed by destination, including the terminating null character (and stopping at that point).

2.源字符串必须以 '\0' 结束,否则就会一直拷贝,直到遇到\0,就可能会导致越界访问。

3.会将源字符串中的 '\0' 拷贝到目标空间。

4.目标空间必须足够大,以确保能存放源字符串,否则也会越界访问。

5.目标空间必须可变。

6.学会模拟实现。

3.strcat

strcat - 字符串追加

strcat(arr1, arr2)等价于 把arr2的内容 追加到 arr1后面

检测有没有追加\0

char* my_strcat(char* dest,const char* src)
{
	/*assert(dest != NULL && src != NULL);*/
	//等价于(dest && src)
	assert(dest && src);
	//1.找到dest字符串的\0
	//2.追加

	char* ret = dest;
	while (*dest != '\0')
	{
		dest++;
	}
	//while(*dest++)
	//{
	//;
	//}
	//拷贝的功能
	while (*dest++ = *src++)
	{
		;
	}
	return ret;
}
int main()
{
	char arr1[30] = "hello\0xxxxxxxxx";
	char arr2[] = "world";
	int ret = my_strcat(arr1,arr2);
	printf("%s\n", ret);
	
	return 0;
}

strcat定义

strcat在自己给自己追加的时候,会把\0覆盖,\0被赋值为'h',导致src找不到\0,导致死循环。

总结:

1.Appends a copy of the source string to the destination string. The terminating null character in destination is overwritten by the first character of source, and a null-character is included at the end of the new string formed by the concatenation of both in destination.

2.源字符串必须以 '\0' 结束,追加起点是\0。

3.目标空间必须有足够的大,能容纳下源字符串的内容。

4.目标空间必须可修改。

5.字符串自己给自己追加,如何? 

4.strcmp

int main()
{
	char* p1 = "abcdef"; 
	char* p2 = "sqwer";
	//if("abcdef" == "sqwer") // "abcdef"作为表达式结果是a的地址  "sqwer"同理 
	int ret = strcmp(p1, p2);  // ret == -1 
	printf("%d\n", ret); 
	//这里比的不是字符串长度  而是对应字符的ASCII码值  而且是一个一个比较
	//如果当前比较的字符相等,就比较下一对 
	//遇到\0停止比较  如果遇到\0之前都相等 那么两个字符串就相等   相等返回0

	return 0;
}

标准规定:

1.第一个字符串大于第二个字符串,则返回大于0的数字

2.第一个字符串等于第二个字符串,则返回0

3.第一个字符串小于第二个字符串,则返回小于0的数字

vs编译器下:

> 返回1

= 返回0

< 返回-1

int my_strcmp(const char* str1,const char* str2)
{
	assert(str1 && str2);//判断不能等于空指针
	//比较
	while (*str1 == *str2)
	{
		if (*str1 == '\0') //一直比到\0都相等 另外一个也是\0 说明两个字符串相等 (前提是两个字符串长度得相等)
			return 0;//相等
		str1++;
		str2++;
	}
	if (*str1 > *str2)
		return 1;//大于
	else if (*str1 < *str2)  //出while循环只有两种情况 大于或小于
		return -1;//小于
}
int main()
{
	char* p1 = "abcdef";
	char* p2 = "abqwe";
	int ret = my_strcmp(p1, p2);
	printf("%d\n", ret);

	return 0;
}

如果待比较的两个字符串长度不相等,必定有一个先到\0,然后就正常比, \0的ascii码值为0,与另外一个字符串对应的字符的ascii码值比较

如果要满足c库函数中的返回值要求:

> 返回大于0的数字

< 返回小于0的数字

= 返回0

将末尾改成:

return (*str1 - *str2);

以上的函数都是长度不受限制的字符串函数:遇到\0才会停止

以下是长度受限制的字符串函数:

4.strncpy

 

若不过拷贝,则不够的位置全补\0

总结:

1.Copies the first num characters of source to destination. If the end of the source C string (which is signaled by a null-character) is found before num characters have been copied, destination is padded with zeros until a total of num characters have been written to it.

2.拷贝num个字符从源字符串到目标空间。

3.如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个。

5.strncat

strncat也同理,参数多了一个追加的个数

 

同样的验证\0是否被追加

最后会主动放一个\0进去

如果不够,多余的不会补\0,

具体实现:

 由于最开始的while循环里是后置++,多+了一次,最后foront--减回去

6.strncmp

多了个比较多少字节数的参数

int main()
{
	//strncmp  -  字符串比较
	const char* p1 = "abcdef";
	const char* p2 = "abcqwer";  //加上const 常量字符串无法被修改

	//int ret = strcmp(p1, p2);
	//printf("%d\n", ret);
	int ret = strncmp(p1,p2,3);//3 表示各自比较前三个字符
	printf("%d\n", ret);
	return 0;
}

7.strstr

strstr - 查找字符串

NULL - 空指针
NUL / Null - \0

int main()
{
	char* p1 = "abcdefghi";
	char* p2 = "def";
	//p1 里面找 "def" 找到返回d的地址  找不到返回空指针 
	char* ret = strstr(p1, p2);
	if (ret == NULL)
	{
		printf("字串不存在\n");
	}
	else
	{
		printf("%s\n", ret);
	}
	return 0;
}

若p1中含有多个p2,则strstr返回的是p1中第一次出现p2的地址.

char* p1 = "abcdefghidef";
char* p2 = "def";

 

模拟实现strstr:

char* my_strstr(const char* p1, const char* p2)
{
	assert(p1 && p2);//p1 p2不能为空指针
	//p2是空字符串 p2中只有一个\0 char* p2 就是\0字符
	if (*p2 == "\0")
	{
		return p1;
	}
	while (*p1)
	{
		while ((*p2 != '\0')&&(*p1 != '\0')&&(*p1 == *p2))// p1先到\0 说明没有字串  p2先到\0 说明查找到字串 
		{
			p1++;
			p2++;
		}
		if (*p2 == '\0')//p2 ++ 直到找到\0
		{
			return ? //没有变量记得找到第一个相同字符时的地址 此代码有问题
		}
		p1++;
	}
}

此代码直接让p1 ,p2++往后查找字符串,没有变量记得第一次字符相等时候的地址,无法返回首字符地址。

并且如果母串为"abbbcdef" 字串为"bbc",此代码会找不到字串,原因时:abbbc,中前两个b无法匹配字串后,母串接着从第三个b查找了,实际上应该返回第二个b再开始查找。

char* my_strstr(const char* p1, const char* p2)
{
	assert(p1 && p2);//p1 p2不能为空指针
	//p2是空字符串 p2中只有一个\0 char* p2 就是\0字符
	char* s1 = NULL;
	char* s2 = NULL;
	char* cur = p1;
	if (*p2 == "\0")
	{
		return p1;
	}
	while (*cur)
	{
		s1 = cur;
		s2 = p2;
		while ((*s1 != '\0')&&(*s2 != '\0')&&(*s1 == *s2))// p1先到\0 说明没有字串  p2先到\0 说明查找到字串 
		{
			s1++;
			s2++;
		}
		if (*s2 == '\0')//p2 ++ 直到找到\0
		{
			return cur;//找到字串
		}
		cur++;
	}
	return NULL;//找不到字串
}

此代码把p1和p2赋值给s1和s2,让s1和s2++往后走寻找字串,然后再增加一个cur变量来记住首次字符相等的地址。

图解:

代码优化:

char* my_strstr(const char* p1, const char* p2)
{
	assert(p1 && p2);//p1 p2不能为空指针
	//p2是空字符串 p2中只有一个\0 char* p2 就是\0字符
	char* s1 = NULL;
	char* s2 = NULL;
	char* cur = (char*)p1;//避免警告
	if (!*p2)// 若 p2 为\0 *p2就是\0 表达式结果为0  !*p2就是1 简洁写法
	{
		return (char*)p1;//因为const修饰了 直接赋值会报警告,所以这里强转为char*
	}
	while (*cur)
	{
		s1 = cur;
		s2 = (char*)p2;//强转避免警告
		//    \0的ASCII码值为0 所以可以直接写*s1   若s1 == s2 相减则为0
		while (*s1 && *s2 &&(*s1 == *s2))// p1先到\0 说明没有字串  p2先到\0 说明查找到字串 
		{
			s1++;
			s2++;
		}
		if (!*s1) //提前终止
		{
			return NULL;
		}
		// *s2 == '\0'的简洁写法
		if (!*s2)//p2 ++ 直到找到\0
		{
			return cur;//找到字串
		}
		cur++;
	}
	return NULL;//找不到字串
}

8.strtok

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

1.sep参数是个字符串,定义了用作分隔符的字符集合

2.第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记。

3.strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针(注:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改.)
4.strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。
5.strtok函数的第一个参数为 NULL ,函数将在同一个字符串中w被保存的位置开始,查找下一个标记。
6.如果字符串中不存在更多的标记,则返回 NULL 指针。

三次分割:

优化代码,巧妙的利用for循环:

for循环的初始化部分指挥执行一次,也就是传arr调用strtok只会调用一次,以后都传空指针。

直到ret = strtok(NULL,p),ret被赋值给空指针,然后判断跳出for循环。

 

小注意点:

 

分隔符集合p只要包含arr中的所有分隔符就行了,是分隔符集合(互异性)。

9.strerror

strerror - 错误报告(错误信息)函数

char * strerror ( int errnum );

 返回错误码,所对应的错误信息。

int main()
{
	char* str = strerror(0);
	printf("%s\n", str);

	return 0;
}

 

 把错误码,翻译成对应的错误信息

当c库函数执行发生错误的时候,strerror就会主动把错误码主动放在errno中,对应的错误信息即可了解错误的原因。

比如:

int main()
{
	//打开文件
	FILE* pf = fopen("text.txt", "r"); // "r" - read 读文件   
	if (pf == NULL)
	{
		printf("%s\n", strerror(errno));
	}
	else
		printf("open file success\n");

	return 0;
}

 

字符分类函数:

 

 

 是小写字母就返回一个大于0的数字,否则返回0。

字符转换:

int tolower ( int c );
int toupper ( int c );

tolower - 大写转小写 大写不动

toupper - 小写转大写 小写不动

示例:

10.memcpy

strcpy遇到\0就会停止拷贝,拷贝不了整型/浮点型/结构体数组

(小端储存,arr1数组在内存中的布局:01 00 00 00 02 00 00 00 ....,strcpy是一个字符一个字符的拷贝的,直到拷贝到01后面的0,strcpy以为是\0,结束拷贝)

为了弥补strcpy的缺陷,出现了memcpy函数,内存拷贝函数        

memcpy参数为void*,可以接收任意类型的指针   void* - 无类型的指针        

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

size_t 拷贝的字节数,单位字节

int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[5] = { 0 };

	/*strcpy(arr2,arr1);*/
	memcpy(arr2, arr1, sizeof(arr1));

	return 0;
}

struct s
{
	char name[20];
	int age;
};
int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[5] = { 0 };
	struct s arr3[3] = { {"张三",20},{"李四",30}};
	struct s arr4[4] = { 0 };

	/*strcpy(arr2,arr1);*/
	//memcpy(arr2, arr1, sizeof(arr1));
	memcpy(arr4, arr3, sizeof(arr3));

	return 0;
}

 模拟实现:

struct s
{
	char name[20];
	int age;
};
void* my_memcpy(void* dest,const void* src,size_t num)//size_t - unsigned int
{
	assert(dest && src);//判断指针有效性  != NULL
	void* ret = dest;
	while (num--)
	{
		//类比bubble_sort
		*(char*)dest = *(char*)src;
		++(char*)dest; //前置++    先转化在++  ++优先级高
		++(char*)src;
	}
	return ret;
}
int main()
{
	//int arr1[] = { 1,2,3,4,5 };
	//int arr2[5] = { 0 };
	struct s arr3[] = { {"张三",20},{"李四",20} };
	struct s arr4[3] = {0};
	my_memcpy(arr4, arr3, sizeof(arr3));
	
	return 0;
}

 

 

 

数组自己拷贝自己出现的问题:

int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };

	my_memcpy(arr+2,arr,5*sizeof(int));

	int i = 0;
	for ( i = 0; i < 10; i++)
	{
		printf("%d\n", arr[i]);
	}
	return 0;
}

 

 

原因:拷贝的过程中改变了arr

解决方法:先挪5,然后4... 最后在挪1,不过这种方法也是有问题的

 当数组整体要往前拷贝的时候

这时候从最后一个元素开始拷贝也会出现覆盖的问题。

这时候就要使用memmove函数,专门用来处理重叠拷贝

11.memmove

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

参数与memmove一样

int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };

	//my_memcpy(arr+2,arr,5*sizeof(int));
	memmove(arr + 2, arr, 5 * sizeof(int));//处理重叠拷贝的情况

	int i = 0;
	for ( i = 0; i < 10; i++)
	{
		printf("%d\n", arr[i]);
	}
	return 0;
}

c语言规定:
memcpy只要处理 不重叠的内存拷贝即可
memmove 处理重叠的内存拷贝

但vs编译器中的memcpy也能处理重叠拷贝的问题

模拟实现memmove:

1.自己实现:

void* my_memmove(void* dest,const void* src,size_t num)
{
	//1.存一份src的临时拷贝,把临时拷贝 拷贝到 dest里
	//2.dest落在src左边 从前向后拷贝    落在src右边  从后向前拷贝
	//  dest < src                        dest > src
	assert(dest && src);
	void* ret = dest;
	if (*(char*)dest == *(char*)src) //相当于没有拷贝
		return (char*)dest;
	else if ((char*)dest < (char*)src) //dest 地址小于 src  等价于 dest在左侧  //从前往后拷贝  包含两种情况  有重叠和没重叠
	{
		while (num--)
		{
			*(char*)dest = *(char*)src;
			++(char*)dest;
			++(char*)src;
		}
	}
	else//dest在右侧 从后往前拷贝  包含有重叠和没重叠
	{
		char* src2 = (char*)src + num-1; 
		char* dest2 = (char*)dest + num-1;//这里是加num-1  如果加num 直接跳到整个要拷贝的数组后面的了  实际上要跳到 要拷贝数组的最后一个元素
		while (num--)
		{
			*(char*)dest2 = *(char*)src2;
			--(char*)dest2;
			--(char*)src2;
		}
	}
}
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };

	//my_memcpy(arr+2,arr,5*sizeof(int));
	//memmove(arr + 2, arr, 5 * sizeof(int));//处理重叠拷贝的情况
	//memcpy(arr+2,arr,5*sizeof(int));
	my_memmove(arr+2,arr,5*sizeof(int));

	int i = 0;
	for ( i = 0; i < 10; i++)
	{
		printf("%d\n", arr[i]);
	}
	return 0;
}

 dest在右侧,从后向前拷贝还有另外一种简洁写法;

else//dest在右侧 从后往前拷贝  包含有重叠和没重叠
	{
		while (num--)
		{
            *((char*)dest+num) = *((char*)stc+num);
		}
	}

c库里的分类方式:

 

12.memcmp

int main()
{
	//01 00 00 00 02 00 00 00 03 00 00 00  ..
	//01 00 00 00 02 00 00 00 05 00 00 00  ..
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 1,2,4,3,5 };
	int ret = memcmp(arr1, arr2, 9);//相等返回0  第一个大返回一个>0的数字  第一个小 返回一个<0的数字
	//vs 是 1 0 -1	
	printf("%d\n", ret);

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

第三个参数是比较的字节数

13.memset

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

int c是所要设置的字符对应的ASCII码值

size_t count 所要改的字节数

int main()
{
	int arr[10] = { 0 };
	memset(arr, 1, 10);//错误的改法
	return 0;
}

原因:memset是以字节为单位进行修改:

将arr前10个字节的内容改为 01 01 01 01  01 01 01 01  01 01

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值