C语言应用——字符串函数

常用字符串函数模拟

目录

常用字符串函数模拟

0、常用字符串汇总

1:字符串信息提取函数

2:内存操作函数

3:字符串操作函数

4:字符分类与操作函数(分类函数判断条件为真时,返回真(1))

1、字符串信息提取函数

1:strlen

1. 原理 

2. 模拟代码

3. 测试与结果分析

2:strstr

1. 原理

2. 暴力求解法模拟代码和结果测试分析

3. KMP法模拟代码和结果测试分析

2、内存操作函数

1:memcpy

1. 原理

2. 模拟代码

3. 测试与结果分析

2:memmove

1. 原理

2. 模拟代码

3. 测试与结果分析

3:memset

1. 原理

2. 模拟代码

3. 测试与结果分析

4:memcmp

1. 原理

2. 模拟代码

3. 测试与结果分析

3、字符串操作函数

1:strcpy和strncpy

1. 原理

2. strcpy模拟代码和测试

3.strncpy模拟代码和测试

2: strcat和strncat

1. 原理

2. strcat模拟代码和测试

3.strncat模拟代码和测试

3:strcmp和strncmp

1. 原理

2. strcmp模拟代码和测试

3.strncmp模拟代码和测试

 

5、结束语和一些相似函数的对比

1: 拷贝函数memcpy、memmove、strcpy和strncpy

2: 比较函数memcmp、strcmp和strncmp

3:结束语


0、常用字符串汇总

C语言的字符串都是放在常量字符串字符数组中。

常用字符串函数如下:

1:字符串信息提取函数

(1)strlen :求字符串长度(模拟实现)

(2)strstr:字符串查找(模拟实现)

2:内存操作函数

(1)memcpy:内存拷贝函数(模拟实现)

(2)memmove:内存拷贝函数(模拟实现)

(3)memset:内存初始化函数(模拟实现)

(4)memcmp:内存比较函数(模拟实现)

3:字符串操作函数

(1)strcpy:字符串复制函数(长度不受限制)(模拟实现)

(2)strcat:字符串连接函数(长度不受限制)(模拟实现)

(3)strcmp:字符串比较函数(长度不受限制)(模拟实现)

(4)strncpy:字符串复制函数(长度受限制)(模拟实现)

(5)strncat:字符串连接函数(长度受限制)(模拟实现)

(6)strncmp:字符串比较函数(长度受限制)(模拟实现)

4:字符分类与操作函数(分类函数判断条件为真时,返回真(1))

(1)iscntrl:判断字符是否为控制字符

(2)isspace:判断字符是否为空白字符

(3)isdigit:判断字符是否为十进制数字字符

(4)isxdigit:判断字符是否为十六进制数字字符

(5)islower:判断字符是否为小写字母

(6)isupper:判断字符是否为大写字母

(7)isalpha :判断字符是否为字母(不分大小写)

(8)isalnum:判断字符是否为字母(不分大小写)或数字字符

(9)ispunct:判断字符是否为标点符号(不属于数字字符、字母和图像字符的字符都叫标点字符)

(10)isgraph:判断字符是否为图像字符

(11)isprint:判断字符是否可打印

(12)tolower:将大写字母转化为小写字母(如果字符不是大写字母,则不转换)

(13)toupper:将小写字母转化为大写字母(如果字符不是小写字母,则不转换)

本文对部分字符串信息提取函数、内存操作函数字符串操作函数进行原理讲解和模拟实现。

1、字符串信息提取函数

1:strlen

1. 原理 

函数作用:计算字符串长度(不包括字符串结束符:'\0'

函数结构:size_t strlen( const char *string );

函数参数
参数介绍(源于MSDN)
string目标字符串起始地址。数据类型:const char*
返回值字符串长度。数据类型:int

需要注意的点:strlen碰到'\0'则停止计数。

2. 模拟代码

这里给出递归和计数器方法模拟strlen的代码

计数器模式:

int Str_len(const char *arr)
{
	char* p = arr;
	while (*(p++) != '\0');
	return p - arr - 1;
}

递归方式:

unsigned int Str_len(char* strs)
{

		if (*strs == '\0')
		{
			return 0;
		}
		else
		{
			return 1 + Str_len(strs + 1);
		}
	}

}

3. 测试与结果分析

测试代码如下:

int main()
{
	char arr[] = "abscjdksuslaj asdl";
	printf("%d", Str_len(arr));
	return 0;
}

程序运行结果:

 

2:strstr

1. 原理

函数作用:查找子字符串

函数结构:char *strstr( const char *string, const char *strCharSet );

函数参数
参数介绍
string目标字符串首地址。参数类型:const char *
strCharSet子字符串首地址。参数类型:const char *
返回值如果目标字符串中存在子字符串,则返回子字符串第一次出现时目标字符串的地址。
函数概述:如果目标字符串中不存在子字符串,则返回NULL;如果子字符串为NULL,则返回目标字符串的首地址。

说白了,strstr就是求解子串的问题。(假设只有一个目标字符串和一个子字符串)

求解思路:(1)暴力求解法(2)KMP算法

(1)暴力求解法

废话不说,直接上图:

暴力求解子串问题

这里需要说明的是:这种方法比较简单,其时间复杂度为O(M*N)。

(2)KMP算法求解

KMP是专门求解子串包含问题的一种算法,其优势在于KMP对子字符串信息挖掘较多。

本人也找过KMP算法的介绍,大多给出一堆公式和大段文字进行讲解,感觉把人将蒙了。这是因为:KMP算法原理不难,但是叙述起来比较麻烦。所以,这里就不叙述了,直接上图。

如果还是感觉过于复杂或看不懂,推荐直接看代码,代码不长,把判断条件看明白就行。另外,本人网上查资料时碰见几个将KMP的博客,这里给出链接。

https://www.cnblogs.com/zhangboy/p/7635627.html

https://mp.weixin.qq.com/s?src=11&timestamp=1609923375&ver=2811&signature=JkII*3KBCxVOWGu9yYO6c60uiAZFSDNIGe4L4E3IsmU7fdtSzxlxJKT3CHyeyi3VPAkwUb9UY6Kqb84uBBKiyoRftixRPpA-K8aXGDcCiyyJzBfAUC9hNmoqkhiewdjd&new=1

2. 暴力求解法模拟代码和结果测试分析

//暴力求解法
char* my_strstr1(const char* string, const char* strcharset)
{
	int i = 0;//目标字符串下标
	int j = 0;//子字符串下标
	if (*strcharset == '\0')
	{
		return string;
	}
	else
	{
		int num = 0;
		char* s = strcharset;
		while (*s++)
		{
			num++;
		}
		while (1)
		{
			if (*(string + i + num - 1) == '\0') //目标字符串到结尾了还没跳出(说明目标字符串不含子串)
			{
				return NULL;
			}
			else
			{
				if (*(strcharset + j) == '\0') //b已全部判断完毕,目标字符串中包含子串
				{
					return string + i;
				}
				else
				{
					if (*(string + i + j) == *(strcharset + j)) //匹配到字符
					{
						j++;
					}
					else
					{
						j = 0;
						i++;
					}
				}
			}
		}
	}
}

测试代码:

int main()
{
	char a[] = "abcdefa";
	char b[] = "def";
	char c[] = "";
	char d[] = "acef";
	printf("测试1\n");
	if (my_strstr1(a, b) == &a)
	{
		printf("b为空字符串\n");
	}
	else if (my_strstr1(a, b) == NULL)
	{
		printf("目标字符串中不包含子字符串\n");
	}
	else
	{
		printf("目标字符串包含子字符串,起始位置为:%p,\n在目标字符串中的:%c处起始\n", my_strstr1(a, b), *my_strstr1(a, b));
	}
	printf("测试2\n");
	if (my_strstr1(a, c) == &a)
	{
		printf("b为空字符串\n");
	}
	else if (my_strstr1(a, c) == NULL)
	{
		printf("目标字符串中不包含子字符串\n");
	}
	else
	{
		printf("目标字符串包含子字符串,起始位置为:%p,\n在目标字符串中的:%c处起始\n", my_strstr1(a, c), *my_strstr1(a, c));
	}
	printf("测试3\n");
	if (my_strstr1(a, d) == &a)
	{
		printf("b为空字符串\n");
	}
	else if (my_strstr1(a, d) == NULL)
	{
		printf("目标字符串中不包含子字符串\n");
	}
	else
	{
		printf("目标字符串包含子字符串,起始位置为:%p,\n在目标字符串中的:%c处起始\n", my_strstr1(a, d), *my_strstr1(a, d));
	}
	return 0;
}

结果分析:分析比较简单,这里直接给出程序运行结果图

3. KMP法模拟代码和结果测试分析

KMP代码:代码比较长是因为我加了很多额外条件判断,KMP的关键代码注释:关键代码

//前后缀法求解Next数组
void KMP_Next(const char* string, int* next)  
{
	*next = -1;
	int k = -1;
	int j = 0;
	while (*(string + j + 1) != '\0') //判断结束
	{
		if (k == -1 || *(string + j) == *(string + k))
		{
			*(next + j + 1) = k + 1;
			k++;
			j++;
		}
		else
		{
			k = *(next + k);
		}
	}
}




//KMP算法模拟strstr
char* my_strstr_KMP(const char* string, const char* strcharset)
{
	//先判断子字符串是否为空?  如果是返回string
	if (*strcharset == '\0')
	{
		return string;
	}

	char* s1 = string;
	char* s2 = strcharset;
	int sit = 0;//报错信息
	//先计算子字符串的长度(因为要定义next数组)
	int num = 0;
	while (*s2++)
	{
		num++;
	}
	int* next = NULL;
	while (next == NULL)
	{
		next = malloc(num * 4);  //动态开辟内存(最后要释放)
		sit++;
		if (sit == 10) //连续10次都无法成功开辟,则报错,直接返回
		{
			perror("无法为NEXT数组开辟内存!");
		}
	}
	//Next数组求解
	KMP_Next(strcharset,next);
	//KMP算法
	int i = 0;
	int j = 0;
	while (*(string + i) != '\0' && *(strcharset + j) != '\0')  //结束标志
	{
		if (j == 0 || *(string + i) == *(strcharset + j))  //KMP关键判断语句(看这里,看这里,看这里,看这里,看这里,看这里,看这里)
		{
			i++;
			j++;
		}
		else
		{
			j = next[j]; //KMP关键判断语句(看这里,看这里,看这里,看这里,看这里,看这里,看这里)
		}
	}
	if (*(strcharset + j) == '\0') //匹配成功,返回地址信息
	{
		free(next);
		next = NULL;
		return string + i - num;
	}
	else if (*(string + i) == '\0')
	{
		free(next);
		next = NULL;
		return NULL; //表示目标字符串不含子串
	}
}

测试代码:

这里单独测试了Next求解函数的正确性,然后分别举例测试KMP

int main()
{
	//测试1:测试前后缀法求解Next数组
	//对于字符串:"DABCDABD " 其Next数组={-1,0,0,0,0,1,2,3};
	char aa[] = "DABCDABD";
	int a_next[8] = {0};
	KMP_Next(aa,a_next);
	printf("测试1\n\n");
	printf("子字符串:DABCDABD 的Next数组元素为: ");
	for (int i = 0; i < 8; i++)
	{
		printf("%d ", a_next[i]);
	}
	printf("\n");
	//测试2:KMP测试
	char a[] = "abcdefa";
	char b[] = "def";
	char c[] = "";
	char d[] = "acef";
	printf("\n测试2\n\n");
	if (my_strstr_KMP(a, b) == &a)
	{
		printf("b为空字符串\n");
	}
	else if (my_strstr_KMP(a, b) == NULL)
	{
		printf("目标字符串中不包含子字符串\n");
	}
	else
	{
		printf("目标字符串包含子字符串,起始位置为:%p,\n在目标字符串中的:%c处起始\n", my_strstr_KMP(a, b), *my_strstr_KMP(a, b));
	}
	printf("\n测试3\n\n");
	if (my_strstr_KMP(a, c) == &a)
	{
		printf("b为空字符串\n");
	}
	else if (my_strstr_KMP(a, c) == NULL)
	{
		printf("目标字符串中不包含子字符串\n");
	}
	else
	{
		printf("目标字符串包含子字符串,起始位置为:%p,\n在目标字符串中的:%c处起始\n", my_strstr_KMP(a, c), *my_strstr_KMP(a, c));
	}
	printf("\n测试4\n\n");
	if (my_strstr_KMP(a, d) == &a)
	{
		printf("b为空字符串\n");
	}
	else if (my_strstr_KMP(a, d) == NULL)
	{
		printf("目标字符串中不包含子字符串\n");
	}
	else
	{
		printf("目标字符串包含子字符串,起始位置为:%p,\n在目标字符串中的:%c处起始\n", my_strstr_KMP(a, d), *my_strstr_KMP(a, d));
	}
	return 0;
}

程序运行结果为:

 

 

2、内存操作函数

1:memcpy

1. 原理

函数作用:内存复制函数。
函数结构为:void *memcpy( void *dest, const void *src, size_t count );

函数参数介绍
参数介绍(源于MSDN)
dest目标内存空间的起始地址。参数类型:void *
src源空间的起始地址。参数类型:const void *
count复制内存空间大小(单位:字节)。参数类型:unsigned int
返回值返回dest的值

函数功能:从源空间起始地址开始,将往后的count个字节的内容复制到目标

内存空间之中。(要求:dest<=src或 src+count<=dest)

 

需要注意的一点是:使用本函数时,必须保证  dest<=src或 src+count<=dest。

由于memcpy与memmove原理相似,memcpy的原理详解放在了memmove小节中(2.2 memmove。这里只给出模拟代码和测试。

 

2. 模拟代码

void* my_memcpy(void* dest, const void* src, uint count)
{
	char* des = (char*)dest;
	if (src >= dest || (char*)src + count <= (char*)dest)
	{
		while (count--)
		{
			*(char*)des = *(char*)src;
			des = (char*)des + 1;
			src = (char*)src + 1;
		}
	}
	else
	{
		printf("源空间和目标空间有重叠,复制会出错。请使用memmove函数,本函数不会进行复制。\n");
	}
	return dest;
}

 

 

 

3. 测试与结果分析

测试代码为:

int main()
{	
	//测试字符串
	char a[] = "12345678901234567890";
	char b[] = "abcdefghijklmnopqrst";
	char c[] = "abcdefghij1234567890";
	printf("测试情况1\n更改前:%s\n", a);
	my_memcpy(a + 9, a + 4, 5);  //dest>=src+n
	printf("更改后:%s\n", a);
	printf("测试情况2\n更改前:%s\n", b);
	my_memcpy(b, b + 5, 5); //dest<=src
	printf("更改后:%s\n", b);
	printf("测试情况3\n更改前:%s\n", c);
	my_memcpy(c + 4, c, 10);  //src+n>dest>src
	printf("更改后:%s\n", c);
	return 0;
}

输出分析:

(1)对于情况1的测试 

my_memcpy(a + 9,a + 4,5);  //dest>=src+n

从a[4]起始的5个字节的内容("56789")复制到a[9]起始的5个字节的空间(“01234”)。此时dest>=src+n,从低地址往高地址正常复制即可,复制完应为:“123456789(a[0]到a[8]不动)56789(a[9]到a[13]为a[4]到a[8]粘贴的内容)567890(a[14]到a[19]不动)”,故结果为:"12345678956789567890"

(2)对于情况2的测试 

my_memcpy(b, b+5, 5); //dest<=src

从b[5]起始的5个字节的内容("fghij")复制到b[0]起始的5个字节空间("abcde")。此时dest<=src,从低地址往高地址正常复制即可,复制完应为:"fghij(被粘贴的内容)fghijklmnopqrst(后面的不变)",故结果为:"fghijfghijklmnopqrst"

(3)对于情况3的测试 

my_memcpy(c+4, c, 10);  //src+n>dest>src

不满足my_memcpy的使用条件,所以直接报错,并且不对字符串c进行任何操作。

程序运行结果:

 

2:memmove

1. 原理

函数作用:内存复制函数。

函数结构为:void *memmove( void *dest, const void *src, size_t n);

函数参数介绍
参数介绍(源于MSDN)
dest目标内存空间的起始地址。参数类型: void *
src源空间的起始地址。参数类型:const void *
n复制内存空间大小(单位:字节)。参数类型:unsigned int
返回值返回dest的值
函数功能:从源空间起始地址开始,将往后的count个字节的内容复制到目标内存空间之中。

内存复制之中存在以下三种情况:

问题:为什么情况1和情况2对memmove来说是一样的?

因为memmove是将源空间的内容复制到目标空间,那么对于源空间来说,源空间没有完成复制的空间是不允许被更改的。就比如对于情况3来说:一开始,源空间src地址处的内容会被复制,并粘贴到目标空间的dest地址处。但dest地址也属于源空间,并且此时dest地址处的内容并未被复制,所以这种行为是不被允许的。对于情况2来说,虽然源空间和目标空间也有重叠,但目标空间中src地址处空间被粘贴内容时,源空间src地址处的内容已经被复制完成了,所以没有影响。(简而言之:如果一块空间既属于源空间,有属于目标空间,那么这块空间的内容在被复制之前,不可对其进行粘贴操作

怎么解决?

我们要注意到的是:传统意义上的复制和粘贴都是从低地址开始的,这是因为函数参数就是源空间和目标空间的起始地址。但对于情况3而言,这种从低地址到大地址的复制方法不可行,那么就反其道而行,从大地址往小地址进行复制(即对于情况3而言:首先复制src+n地址处的内容,粘贴到dest+n中,然后往低地址进行复制和粘贴操作

2. 模拟代码

void* my_memmove(void* dst, const void* src, uint count)
{
	void* ret = dst;
	if (dst <= src || (char*)dst >= ((char*)src + count)) {
		//情况1和情况2
		while (count--) {
			*(char*)dst = *(char*)src;
			dst = (char*)dst + 1;
			src = (char*)src + 1;
		}
	}
	else {
		//内存空间重叠
		dst = (char*)dst + count - 1;
		src = (char*)src + count - 1;
		while (count--) {
			*(char*)dst = *(char*)src;
			dst = (char*)dst - 1;
			src = (char*)src - 1;
		}
	}
	return(ret);
}

3. 测试与结果分析

int main()
{
	//测试字符串
	char a[] = "12345678901234567890";
	char b[] = "abcdefghijklmnopqrst";
	char c[] = "abcdefghij1234567890";
	printf("测试情况1\n更改前:%s\n", a);
	my_memmove(a + 9,a + 4,5);  //dest>=src+n
	printf("更改后:%s\n",a);
	printf("测试情况2\n更改前:%s\n", b);
	my_memmove(b, b+5, 5); //dest<=src
	printf("更改后:%s\n", b);
	printf("测试情况3\n更改前:%s\n", c);
	my_memmove(c + 4, c, 10);  //src+n>dest>src
	printf("更改后:%s\n", c);
	return 0;
}

输出分析:

(1)对于情况1的测试 

my_memmove(a + 9,a + 4,5);  //dest>=src+n

从a[4]起始的5个字节的内容("56789")复制到a[9]起始的5个字节的空间(“01234”)。此时dest>=src+n,从低地址往高地址正常复制即可,复制完应为:“123456789(a[0]到a[8]不动)56789(a[9]到a[13]为a[4]到a[8]粘贴的内容)567890(a[14]到a[19]不动)”,故结果为:"12345678956789567890"

(2)对于情况2的测试 

my_memmove(b, b+5, 5); //dest<=src

从b[5]起始的5个字节的内容("fghij")复制到b[0]起始的5个字节空间("abcde")。此时dest<=src,从低地址往高地址正常复制即可,复制完应为:"fghij(被粘贴的内容)fghijklmnopqrst(后面的不变)",故结果为:"fghijfghijklmnopqrst"

(3)对于情况3的测试 

my_memmove(c+4, c, 10);  //src+n>dest>src

从c[0]起始10个字节的空间("abcdefghij")被复制到c[4]起始10个字节的空间("efghij1234"),此时src+n>dest>src,需要从高地址往低地址进行复制,复制完应为:"abcd(c[0]到c[3]不变) abcdefghij(被粘贴的内容)567890(c[14]到c[19]不变])"

程序运行结果:

 

3:memset

1. 原理

函数作用:将内存块设置为指定字符(初始化)

函数结构为:void *memset( void *dest, int c, size_t count );

函数参数介绍
参数介绍(源于MSDN)
dest目标内存空间的起始地址。参数类型: void *
c初始化字符的ASCII值(具体可查ASCII表,举例:'0'=48),参数类型:int
count初始化内存空间大小(单位:字节)。参数类型:unsigned int
返回值返回dest的值
函数功能:从目标空间起始地址开始,将往后的count个字节的内容设置为c对应的字符。

这里给出ASCII表:

ASCII表

2. 模拟代码

void* my_memset(void* dest, int c, uint count)
{
	char* des = dest;
	while (count --)
	{
		*des = c;
		des++;
	}
	return dest;
}

3. 测试与结果分析

测试代码:

int main()
{
	char a[11]="1234567890";
	printf("未初始化之前的字符串:%s\n",a);
	printf("前5个字符初始化为字符*,后5个字符初始化为字符0\n");
	my_memset(a, '*', 5);
	printf("初始化之后的字符串:%s\n", a);
	my_memset(a + 5, 48, 5);
	printf("初始化之后的字符串:%s\n", a);
	return 0;
}

这里要说的就一点:函数传参时,无论函数参数是int型还是char型,我们都可以传递字符或数字,函数会通过ASCII表自己进行匹配。

比如:测试代码中my_memset的第二个参数为int型,如果我们调用时传递字符(my_memset(a, '*', 5)),函数会自动将 '*' 转化为42。反之如果函数参数为char型,我们传递int型数字时,函数会自动将数字转化为对于的ASCII字符。

测试结果:

 

4:memcmp

1. 原理

函数作用:内存比较函数

函数结构为:int memcmp( const void *buf1, const void *buf2, size_t count );

函数参数
参数介绍(源于MSDN)
buf1一块内存空间的起始地址。参数类型:const void *
buf2另一块内存空间的起始地址。参数类型:const void *
count内存比较长度(单位:字节)。参数类型:unsigned int
返回值返回比较结果(*buf1-*buf2:相等返回0 不相等返回正数或负数)
函数功能:比较二块内存空间的大小

内存比较原理:

首先从buf1和buf2空间的起始地址开始比较,判断起始地址处的字符是否相等。如果相等,地址增加,继续判断二块空间的字符是否相等。如果不相等,返回此地址字符相减的结果(二个字符相减,相当于二个字符ASCII对应的数字相减,得到的数字作为int型数据,并将此数值返回)。

所以函数功能可概述为:判断二块内存空间的字符串是否相等,如果相等返回0。如果不相等,则返回第一个不相等地址处字符的差值。

2. 模拟代码

int my_memcmp(const void* buf1, const void* buf2, uint count)
{
	while (count--)
	{
		if (*(char*)buf1 == *(char*)buf2)
		{
			buf1=(char*)buf1 + 1;
			buf2 = (char*)buf2 + 1;
		}
		else
		{
			return (int)(*(char*)buf1 - *(char*)buf2);
		}
	}
	return 0; 
}

3. 测试与结果分析

测试代码:

int main()
{
	//测试1 相等字符串
	char a[] = "abcde";
	char b[] = "abcde";
	if (my_memcmp(a, b, 5) == 0)
	{
		printf("二个字符串相等\n");
	}
	else
	{
		printf("二个字符串不相等,返回结果为:%d\n", my_memcmp(a, b, 5));
	}
	//测试2 不相等字符串
	char c[] = "abcdefg";
	char d[] = "abcde!";
	if (my_memcmp(c, d, 6) == 0)
	{
		printf("二个字符串相等\n");
	}
	else
	{
		printf("二个字符串不相等,返回结果为:%d\n", my_memcmp(c, d, 6));
	}
	return 0;
}

结果分析:

测试1:测试二个相等字符串,这没什么好说的。输出肯定为:二个字符串相等、

测试2:二个字符串的第6个字符不相同,c[5]='f'对应ASCII数字为102,d[5]='!'对应ASCII数字为33,。那么返回值应为:102-33=69。

程序运行结果:

 

 

3、字符串操作函数

字符串操作主要有:复制、连接、和比较。

1:strcpy和strncpy

1. 原理

函数作用:将一个字符串复制到另一个字符串之中。

函数结构:

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

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

二个函数都是将strSource起始位置的字符串复制到strDestination起始的空间中。

二者区别:

(1)strcpy复制时,如果*strSource =='\0',则停止复制(把最后的'\0'也复制过去),否则strSource ++。最后返回strDestination。

(2)strncpy复制是,可以指定复制的长度。但如果strncpy至strncpy+count空间内存在'\0',则'\0'之后的字符都被替换为'\0'。最后返回strDest。

(3)与内存复制函数不同,在使用strcpy和strncpy时,必须保证二块空间没有重叠,否则会出现意外的错误。

2. strcpy模拟代码和测试

模拟代码:

char* my_strcpy(char *arr2,const char *arr1)
{
	char* s = arr2;
	while (*( s ++) = *( arr1 ++));
	return arr2;
}

测试程序:

int main()
{
	char arr1[200] = { '0' };
	char arr2[200] = { '0' };
	int i = 0;
	int j = 0;
	printf("请输入一个字符串\n");
	while (scanf("%c", &arr1[i]) && arr1[i] != '\n')//将键入的字符串存放到arr中
	{
		i++;
	}
	printf("共键入%d个字符\n",i);
	printf("粘贴前,arr2的前%d个字符为:\n", i);
	for (j = 0; j <= i; j++)
	{
		printf("%c", arr2[j]);
	}
	printf("\n粘贴后,arr2的前%d个字符为:\n", i);
	my_strcpy(arr2,arr1);
	for (j = 0; j <= i; j++)
	{
		printf("%c",arr2[j]);
	}
}

程序运行结果:

3.strncpy模拟代码和测试

模拟代码:

char* my_strncpy(char* str1, const char* str2, uint num)
{
	char* s = str1;
	while (num--)
	{
		if (*str2 != '\0')
		{
			*s = *str2;
			s++;
			str2++;
		}
		else //num>str2的长度,直接终止
		{
			break;
		}
	}
	return str1;
}

参数程序:

int main()
{
	char a[20]= "abcdefghiss";
	printf("原字符串为:%s\n要粘贴的字符串为:123\n 粘贴长度为4,粘贴起始位置为f\n粘贴完成后的字符串为:%s",a, my_strncpy(a+5,"123",4)-5);
	return 0;
}

程序运行结果:

 

2: strcat和strncat

1. 原理

函数作用:将一个字符串粘贴到另外一个字符串后面。

函数结构:

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

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

二个函数都是将strSource起始位置的字符串粘贴到strDestination起始空间的后面。

二者区别:

(1)无论是strcat还是strncat,都要求strDestination起始的字符串结尾有'\0'。而strcat还要求strSource 的结尾也有'\0',否则就停不下来了。

(2)使用strcat时,必须要保证strDestination的空间够大,必须能放得下strSource 起始的字符串

(3)无论是strcat都不能用来粘贴自身,因为这样会把字符串结尾的'\0'给覆盖度,导致意外的现象。而strncat可以。

2. strcat模拟代码和测试

模拟代码:

char* my_strcat(char* strdest, const char* strsource)
{
	char* s = strdest;
	while (*s != '\0')
	{
		s++;
	}
	while (*strsource != '\0')
	 {
		*s = *strsource;
		s++;
		strsource++;
	 }
	return strdest;
}

参数程序:

int main()
{
	char a[20] = "abcdefg..!";
	char b[] = "zah scabv";
	printf("原字符串为:%s\n%s\n粘贴长度为:8\n粘贴后的字符串为:%s", a, b, my_strcat(a, b));
	return 0;
}

程序运行结果:

3.strncat模拟代码和测试

模拟代码:

char* my_strncat(char * strdest, const char *strsource, uint count)
{
	char * s= strdest;
	while (*s != '\0')
	{
		s++;
	}
	while (count--)
	{
		if (*strsource != '\0')
		{
			*s = *strsource;
			s++;
			strsource++;
		}
		else  //count>strsource的长度   直接终止
		{
			break;
		}
	}
	return strdest;
}

参数程序:

int main()
{
	char a[20] = "abcdefg..!";
	char b[] = "zah scabv";
	printf("原字符串为:%s\n%s\n粘贴长度为:8\n粘贴后的字符串为:%s",a,b, my_strncat(a,b,8));
	return 0;
}

程序运行结果:

 

3:strcmp和strncmp

1. 原理

函数作用:比较字符串

函数结构:

int strcmp( const char *string1, const char *string2 );

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

二个函数都是将从左往右比,比的是字符对应ASCII的大小。碰见第一个不相同的字符则停止比较,返回二个字符ASCII对应数字的差,返回值数据类型为:int

返回值= (int)(*string1 - *string2 )

二者区别:strncmp可以规定比较范围,而strcmp会全部比完或碰见不同字符时停止。

2. strcmp模拟代码和测试

模拟代码:

int my_strcmp(const char* buf1, const char* buf2)
{
	while (*buf1++ == *buf2++)
	{
		if (*buf1 == '\0')  //字符串相等
			return 0;
	}
	return (int)(*buf1 - *buf2);
}

参数程序:

int main()
{
	//测试1 相等字符串
	char a[] = "abcde";
	char b[] = "abcde";
	if (my_strcmp(a, b, 5) == 0)
	{
		printf("二个字符串相等\n");
	}
	else
	{
		printf("二个字符串不相等,返回结果为:%d\n", my_strcmp(a, b));
	}
	//测试2 不相等字符串
	char c[] = "abcdefg";
	char d[] = "abcde!";
	if (my_strcmp(c, d) == 0)
	{
		printf("二个字符串相等\n");
	}
	else
	{
		printf("二个字符串不相等,返回结果为:%d\n", my_strcmp(c, d));
	}
	return 0;
}

程序运行结果:

3.strncmp模拟代码和测试

模拟代码:

int my_strncmp(const char* buf1, const char* buf2, uint count)
{
	while (count--)
	{
		if (*buf1 == *buf2)
		{
			buf1 ++;
			buf2 ++;
		}
		else
		{
			return (int)(*buf1 - *buf2);
		}
	}
	return 0;
}

参数程序:

int main()
{
	//测试1 相等字符串
	char a[] = "abcde";
	char b[] = "abcde";
	if (my_strncmp(a, b, 5) == 0)
	{
		printf("二个字符串相等\n");
	}
	else
	{
		printf("二个字符串不相等,返回结果为:%d\n", my_strncmp(a, b, 5));
	}
	//测试2 不相等字符串
	char c[] = "abcdefg";
	char d[] = "abcde!";
	if (my_strncmp(c, d, 6) == 0)
	{
		printf("二个字符串相等\n");
	}
	else
	{
		printf("二个字符串不相等,返回结果为:%d\n", my_strncmp(c, d, 6));
	}
	return 0;
}

程序运行结果:

 

 

5、结束语和一些相似函数的对比

1: 拷贝函数memcpy、memmove、strcpy和strncpy

memcpy和memmove都是将一块内存块的内容复制到另一块内存中。唯一的区别是:如果二个内存块的空间重叠,必须使用memmove。当二个内存块空间不重叠时,二个函数的功能没有区别。(就相当于,如果你想把一个桌子从一个房间搬到另外一个房间,memcpy和memmove都可以帮你实现这个效果。但如果你只是想把桌子往旁边挪一挪,那么就必须使用memmove)。而strcpy和strncpy是字符串操作函数,理论为strcpy和strncpy能干的事情,memcpy和memmove都能干,反之不一定。

区别1:memcpy、memmove和strncpy都需要输入拷贝字符的数量,而strcpy则不需要,其碰见字符串中的'\0'自动停止,所以在使用字符数组时,一定要确定字符串最后有个'\0'。

区别2:strcpy和strncpy使用时一定要注意二个字符串空间不能重叠,否则会出现意外。

区别3:strncpy在复制时碰见'\0',后面都会复制为'\0'。

2: 比较函数memcmp、strcmp和strncmp

strcmp和strncmp是比较字符串是否相同,唯一的区别的strncmp可以控制比较范围。

memcmp是内存比较函数,与字符比较函数的区别是:strcmp和strncmp在比较时,需要注意字符串是否结束,即使是strncmp也不会比较'\0'后面的字符。而memcmp不管那么多,哪怕是'\0',也会进行比较。

3:结束语

文章较长,这是因为知识点太多。还有很多文中没有涉及的字符串函数,后期本人会继续对本文进行完善。

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
第1章概述 1.1单片机的结构与应用 1.1.1单片机的定义、分类与内部组成 1.1.2单片机应用系统的结构及其工作过程 1.1.3单片机的应用 1.2单片机基础知识 1.2.1数制与数制间的转换 1.2.2单片机中数的表示方法及常用数制的对应关系 1.2.3逻辑数据的表示 1.2.4单片机中常用的基本术语 1.3单片机入门的有效方法与途径 1.4学习单片机的基本条件 1.4.1软件条件 1.4.2硬件条件 习题与实验 第2章单片机开发软件及开发过程 2.1仿真软件Proteus的使用 2.1.1Proteus的主要功能特点 2.1.2实例1:功能感受——Pmteus仿真单片机播放《渴望》主题曲 2.1.3Proteus软件的界面与操作介绍 2.1.4实例2:Proteus仿真设计快速入门 2.2KeilC51的使用 2.2.1单片机最小系统 2.2.2实例3:用Kei1C51编写点亮一个发光二极管的程序 2.3程序烧录器及烧录软件的使用 习题与实验 第3章逐步认识单片机基本结构 3.1实例4:用单片机控制一个灯闪烁 3.1.1实现方法 3.1.2程序设计 3.1.3用Proteus软件仿真 3.1.4延时程序分析 3.2实例5:将P1口状态送入P0口、P2口和P3口 3.2.1实现方法 3.2.2程序设计 3.2.3用Proteus软件仿真 3.2.4用实验板试验 3.2.5I/O口功能介绍 3.2.6I/O口的结构分析 3.3实例6:使用P3口流水点亮8位1ED 3.3.1实现方法 3.3.2程序设计 3.3.3用Proteus软件仿真 3.3.4用实验板试验 3.4实例7:通过对P3口地址的操作流水点亮8位1ED 3.4.1实现方法 3.4.2程序设计 3.4.3用Proteus软件仿真 3.4.4用实验板试验 3.5MCS-51单片机存储器的基本结构 3.5.1程序存储器 3.5.2数据存储器 3.6单片机的复位电路 习题与实验 第4章单片机C语言开发基础 4.1C语言源程序的结构特点 4.2标志符与关键字 4.3C语言数据类型与运算符 4.3.1数据类型 4.3.2运算符 4.3.3实例8:用不同数据类型的数据控制1ED的闪烁 4.3.4实例9:用P0口、P1口分别显示加法和减法运算结果 4.3.5实例10:用P0口、P1口显示乘法运算结果 4.3.6实例11:用P1口、P0口显示除法运算结果 4.3.7实例12:用自增运算控制P0口8位1ED的闪烁花样 4.3.8实例13:用P0口显示逻辑“与”运算结果 4.3.9实例14:用P0口显示条件运算结果 4.3.10实例15:用P0口显示按位“异或”运算结果 4.3.11实例16:用P0口显示左移运算结果 4.3.12实例17:“万能逻辑电路”实验 4.3.13实例18:用右移运算流水点亮P1口8位1ED 4.4C语言的语句 4.4.1概述 4.4.2控制语句 4.4.3实例19:用if语句控制P0口8位LED的点亮状态 4.4.4实例20:用swtich语句控制PO口8位LED的点亮状态 4.4.5实例21:用for语句设计鸣笛报警程序 4.4.6实例22:用while语句控制PO口8位LED闪烁花样 4.4.7实例23:用dOwhile语句控制PO口8位LED流水点亮 4.5C语言的数组 4.5.1数组的定义和引用 4.5.2实例24:用字符型数组控制PO口8位LED流水点亮 4.5.3实例25:用PO口显示字符串常量 4.6C语言的指针 4.6.1指针的定义与引用 4.6.2实例26:用PO口显示指针运算结果 4.6.3实例27:用指针数组控制PO口8位LED流水点亮 4.6.4实例28:用数组的指针控制PO口8位LED流水点亮 4.7C语言函数 4.7.1函数的定义与调用 4.7.2实例29:用PO口、P1口显示整型函数返回值 4.7.3实例30:用有参函数控制PO口8位LED流水速度 4.7.4实例3l:用数组作函数参数控制PO口8位LED流水点亮 4.7.5实例32:用指针作函数参数控制PO口8位LED流水点亮 4.7.6实例33:用函数型指针控制PO口8位LED流水点亮 4.7.7实例34:用指针数组作为函数的参数显示多个字符串 4.7.8实例35:字符软件ctype.h中的isalpha()函数应用举例 4.7.9实例36:内部函数库文件intrins.h中的_cml_()函数应用举例 4.7.10实例37:标准函数库文件stdlib.h中的rand()函数应用举例 4.7.1l实例38:字符串函数库文件string.h中的strcmp()函数应用举例 4.8C语言的编译预处理 4.8.1常用预处理命令介绍 4.8.2实例39:宏定义应用举例 4.8.3实例40:文件包含应用举例 4.8.4实例41:条件编译应用举例 习题与实验 第5章单片机的定时器/计数器 5.1定时器,计数器的基本概念 5.2定时器/计数器的结构及工作原理 5.2.1定时器/计数器的结构 5.2.2定时器,计数器的工作原理 5.3定时器,计数器的控制 5.3.1定时器/计数器的方式控制寄存器(TMOD) 5.3.2定时器/计数器控制寄存器(TCON) 5.3.3定时器/计数器的4种工作方式 5.3.4定时器/计数器中定时/计数初值的计算 5.4定时器/计数器应用举例 5.4.1实例42:用定时器T0查询方式控制P2口8位LED闪烁 5.4.2实例43:用定时器T1查询方式控制单片机发出1kHz音频 5.4.3实例44:用计数器TO查询的方式计数,结果送P1口显示 习题与实验 第6章单片机的中断系统 6.1中断系统的基本概念 6.2中断系统的结构及控制 6.2.1中断系统的结构 6.2.2中断系统的控制 6.3中断系统应用举例 6.3.1实例45:用定时器TO的方式1控制LED闪烁 6.3.2实例46:用定时器TO的方式1实现长时间定时 6.3.3实例47:用定时器T1的方式1控制两个LED以不同周期闪烁 6.3.4实例48.用计数器T1的中断方式控制发出1kHz音频 6.3.5实例49:用定时器TO的方式O控制播放《好人一生平安》 6.3.6实例50.用计数器TO的方式2对外部脉冲计数 6.3.7实例51:用定时器TO的门控制位测量外部正脉冲宽度 6.3.8实例52:用外中断INT0测量负跳变信号累计数 6.3.9实例53-用外中断控制INT0控制P1口LED亮灭状态 6.3.10实例54:用外中断INT0中断测量外部负脉冲宽度 习题与实验 第7章串行通信技术 7.1串行通信的基本概念 7.2串行通信口的结构 7.3串行通信口的控制 7.3.1串行控制寄存器SCON 7.3.2电源控制寄存器PCON 7.3.3四种工作方式与波特率的设置 7.4串行通信口应用举例 7.4.1实例55.将方式0用于扩展并行输出控制流水灯 7.4.2实例56.基于方式1的单工通信 7.4.3实例57:基于方式3的单工通信 7.4.4实例58:单片机向计算机发送数据 7.4.5实例59:单片机接收计算机送出的数据 习题与实验 第8章接口技术 第9章新型串行接口芯片应用介绍 第10章常用功能器件应用举例 第11章高级综合应用技术

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值