C语言——字符串和内存函数

目录

1. strlen函数:

1.1 strlen函数的特点:

1.2 模拟实现strlen函数:

2.长度不受限制的字符串函数:

2.strcpy函数:

2.1strcpy函数特点:

 2.2 模拟实现strcpy函数:

3. strcat函数:

3.1 strcat函数的特点:

3.2 模拟实现strcat函数:

4.strcmp函数:

4.1strcmp函数的返回值:

 4.2 模拟实现strcmp函数:

5.长度受限制的字符串函数:

 5.1 strncpy函数:

5.2 strncat函数 :

5.3 strncmp函数:

6. strstr函数:

 6.1 strstr函数的功能:

 6.2 模拟strstr代码实现:

7.strtok函数:

7.1 strtok函数解析及应用:

 8. 文章内容大体框架(思维导图):


之前的文章,不管是对指针的、数组进行介绍的文章,还是之前对于简单的循环语句等进行介绍的文章,都使用了一些函数,其中strlen和strcmp这两个针对于字符串的函数使用率很高,本篇文章对字符串函数进行一个相对系统的介绍 

1. strlen函数:

1.1 strlen函数的特点:

 C语言——字符串和内存函数_第1张图片

对于strlen的前两条性质,在之前的文章中多次提到,但是对于第三条性质则较为陌生。下面将给出代码进行相应的解释:

int main()
{
	char arr1[] = "abcde";
	char arr2[] = "abcdef";
	if (strlen(arr1) - strlen(arr2) > 0)
	{
		printf("a");
	}
	else
	{
		printf("b");
	}
	return 0;
}

在上面给出的代码中,创建了arr1和arr2两个字符数组用于存放字符串。如果strlen函数的返回值不是无符号的,则很明显if语句中的值是小于0的,所以应该打印字符'b'。但是打印出来的结果:

C语言——字符串和内存函数_第2张图片

 所以,可以证明,strlen函数的返回值是无符号的,在之前的文章中多次提到,数据在内存中的存储是以补码的性质进行存储的,而对于无符号的strlen函数的返回值,if语句中的第一个返回值结果是5,第二个则是6,二者相减结果为-1,-1的补码形式:11111111 11111111 11111111 11111110,对于无符号,可以理解为,补码的最高位,也就是象征着符号位的二进制位,对于无符号的数据是失效的,在进行运算时不看成符号位,而是看成普通的二进制位进行运算。

1.2 模拟实现strlen函数:

代码如下:

size_t my_strlen(char* s)
{
	int count = 0;
	while (*s++ != '\0')
	{
		count++;
	}
	return count;
}

int main()
{
	char arr[] = "abcde";
	size_t num = my_strlen(arr);
	printf("%u", num);
	return 0;
}

由于之前在对于函数递归进行介绍及应用时,已经用递归模拟strlen函数的实现,所以不再对代码进行过多的解释。下面同样给出用函数递归模拟的strlen函数:

size_t my_strlen(char* s)
{
	if (*s != '\0')
	{
		return 1 + my_strlen(s + 1);
	 }
}

int main()
{
	char arr[] = "abcedf";
	size_t num = my_strlen(arr);
	printf("%u", num);
	return 0;
}

2.长度不受限制的字符串函数:

长度不受限制的字符串函数,本文一共介绍三个,这三个函数的共同点是字符串结束的标志,即'\0',对于这三个函数很重要,这三个函数分别是: 

C语言——字符串和内存函数_第3张图片

2.strcpy函数:

2.1strcpy函数特点:

strcpy函数可以用于拷贝字符串,将一个空间的字符串拷贝到提前设定好的目标空间中。

C语言——字符串和内存函数_第4张图片

对于strcpy函数,重要的一点就在于源字符串的'\0',因为进行源字符像目标空间进行拷贝时,会把源字符的'\0'也拷贝到目标空间中。 下面将通过代码来进行说明:

int main()
{
	char arr[] = "xxxxxxxxxx";
	char arr1[] = "abcedf";
	strcpy(arr, arr1);
	printf("%s", arr);
	return 0;
}

字符数组arr中的内容是由10个'x‘组成的字符串,arr1的内容是连续的6字母组成的字符串,对于arr1中的字符串,第7位就是字符串的结束标志,即'\0',在调用strcpy函数的过程中,可以由编译器的监视窗口来观察arr中字符串的替代情况:

C语言——字符串和内存函数_第5张图片

 可以从上面的结果图看到,数组arr中标号为[6]的元素,被替代为'\0',而此编号的元素,恰好是这个数组中的第7个元素。对于字符串,'\0'是字符串的结束标志,对于strcpy函数则同理,'\0'是作为拷贝字符结束的标志,同时,对于没有'\0'的情况,例如在字符数组中输入了若干个字符,则不能正常的运行,但是在若干字符后面人为的加上'\0',则可以正常运行,例如:

int main()
{
	char arr[] = "xxxxxxxxxx";
	char arr1[] = {'a','b','c','d','\0'};
	strcpy(arr, arr1);
	printf("%s", arr);
	return 0;
}

打印结果依旧是对arr中字符串的内容进行替换:

C语言——字符串和内存函数_第6张图片

 对于第4条性质:目标空间必须可变,这里给出一个反例,创建一个常量字符串作为目标空间:

int main()
{
	const char* arr = "abcde";
	char arr1[] = {'a','b','c','d','\0'};
	strcpy(arr, arr1);
	printf("%s", arr);
	return 0;
}

此时的目标空间arr是一个常量字符串,如果将常量字符串的首元素地址作为参数传给strcpy函数,则系统会报错:

 2.2 模拟实现strcpy函数:

代码如下:

char* my_strcpy(char* dest, char* scr)
{
	char* p = dest;
	assert(dest != NULL);
	assert(scr != NULL);
	while (*dest++ = *scr++)
	{
		;
	}
	*dest = *scr;
	return p;
}
int main()
{
	char arr1[] = "xxxxxxxxx";
	char arr2[] = "abcde";
	my_strcpy(arr1, arr2);
	printf("%s", arr1);
	return 0;
}

之所以函数的类型是char*,是为了方便后面实现链式访问,并且,在cplusplus网站(strcpy - C++ Reference (cplusplus.com)中,也可以看到,strcpy这个库函数在进行定义时的类型就是char*

对于assert,则是检验传递过来的地址是否有效。在下面的while循环中,通过对地址传递过来的地址解引用并且后置++,可以将arr2中的字符对arr1中的字符进行代替。如果此时arr2的地址解引用后时'\0',则跳出while循环。并没有将arr2中的'\0'对arr1中的内容进行替换。所以,在下面额外的加上一行代码,(即上面的代码中,return p;的上一行

*dest = *scr;

此时的arr2的地址保存的正是'\0'的地址,可以将'\0'对arr1中的内容进行代替。

上面说到,为了实现链式访问,strcpy函数的类型是char*,此类型的返回值应返回目标空间的首元素地址,所以,在模拟实现这个函数时,首选用用一个指针将这个地址保存:

char* p = dest;

并在输入返回值时,输入这个指针即可。

3. strcat函数:

3.1 strcat函数的特点:

 在对strcat函数进行查询后,可以看到函数内的两个参数分别是两个地址,strcat函数的功能:正是把source地址对应的字符串;追加到destination地址对应的字符串后面。

C语言——字符串和内存函数_第7张图片

从上图中可以发现,strcat函数的特点与strcpy函数的特点基本相同,对于目标空间、源字符串的'\0'都有要求。

3.2 模拟实现strcat函数:

char* my_strcat(char* dest, const char* scr)
{
    assert(deat);
    assert(scr);
	char* p = dest;
	while (*dest)
	{
		dest++;
	}
	while (*dest++ = *scr++)
	{
		;
	}
	return p;
}

int main()
{
	char arr1[20] = "hello";
	char arr2[] = "world";
	my_strcat(arr1, arr2);
	printf("%s", arr1);
	return 0;
}

追加字符的思路和上面的strcpy中替换字符的思路是一样的,不同的是,由于arr2中的字符实在arr1的末尾追加的,所以,需要先找到arr1中'\0'的地址。

对于strcat,需要注意,尽量不要用strcat函数对一个字符串进行追加自己的操作,这样会导致字符串中的'\0'被代替,向后进行追加时,会因为找不到'\0'造成程序崩溃

4.strcmp函数:

4.1strcmp函数的返回值:

前面文章中多次用到strcmp函数,此函数被用来比较两个字符串的大小,由于多次使用,所以不过多解释,只给出strcmp函数的返回值,即如何模拟实现strcmp函数。

如下图所示,strcmp函数的返回值:

C语言——字符串和内存函数_第8张图片

 4.2 模拟实现strcmp函数:

int my_strcmp(char* s1, char* s2)
{
	while (*s1 == *s2)
	{
		if (*s1 == '\0')
		{
			return 0;
		}
		s1++;
		s2++;
	}
	return(*s1 - *s2);
}

int main()
{
	char arr1[] = "bcq";
	char arr2[] = "bcq";
	int ret = my_strcmp(arr1, arr2);
	if (ret > 0)
	{
		printf("大于");
	}
	else if( ret < 0)
	{
		printf("小于");
	}
	else
	{
		printf("相等");
	}
	return 0;
}

5.长度受限制的字符串函数:

对于长度受限制的字符串函数,会对照长度不受限制的字符串函数介绍三种,分别是:

C语言——字符串和内存函数_第9张图片

 5.1 strncpy函数:

对于strncpy函数,和strcpy函数的区别就在于二者的函数参数上:

C语言——字符串和内存函数_第10张图片

相对于strcpy函数,strncpy函数的参数中多了一个size_t num,这个参数的作用是限制函数作用与于字符串的长度,假设strncpy的size_t num这个参数的值为5,则对目标空间进行替换时,最多就替换五个元素。下面给出一个例子来演示此函数的作用:

例如下面给出的代码,在函数参数中,规定函数作用的字符数量 = 3

int main()
{
	char arr[20] = "abcdef";
	char arr2[] = "xxxxxxxxxxx";
	strncpy(arr, arr2, 3);
	printf("%s ", arr);
	return 0;
}

 通过监视窗口可以对arr中元素的替换情况进行查看:

C语言——字符串和内存函数_第11张图片

可以明确的看到,虽然arr2中的字符'x‘的数量远大于3,但是经过函数参数的限制后,arr中也只有3个元素被替换成了'x'。此时字符串中的元素个数>函数参数限制的个数。

当字符串中的元素个数 < 函数参数限制的个数时,例如:

int main()
{
	char arr[20] = "abcdef";
	char arr2[] = "xxx";
	strncpy(arr, arr2, 5);
	printf("%s ", arr);
	return 0;
}

 此时arr中的'x'个数为3,而函数参数规定的替换个数为5,通过监视看到arr中元素替换情况如下:

C语言——字符串和内存函数_第12张图片

此时,剩下的两个没有'x'去进行代替的arr中的字符串的元素的位置,会由'\0'进行替代。

5.2 strncat函数 :

如果把上面代码把strncpy改为strncat,同时为了方便后面对元素的替换情况进行查看,将字符串'abcdef'改为'abcdef\0zzzz'即:

int main()
{
	char arr[20] = "abcdef\0yyyy";
	char arr2[] = "xxx";
	strncat(arr, arr2, 5);
	printf("%s ", arr);
	return 0;
}

 从监视窗口查看arr中元素替换情况,即: 

C语言——字符串和内存函数_第13张图片

此时,第4个'y'因为没有足够的'x'进行替换,所以在后面补充了一个'\0',但是第5个’y'却不变。

如果再将替换的数量改为7,代码如下所示:

int main()
{
	char arr[20] = "abcdef\0yyyyyyy";
	char arr2[] = "xxx";
	strncat(arr, arr2, 5);
	printf("%s ", arr);
	return 0;
}

结果如下: 

C语言——字符串和内存函数_第14张图片

 可以看到,虽然 要求的替换数大于'x'的数量,但是不管大多少,替换后只会补充一个'\0',这和strncpy是不同的,strncpy函数对于缺少元素而无法在目标空间中被替换的元素,缺多少个,就补充多少个'\0',但是对于strncat函数,即使缺少的元素很多,也只是补充一个'\0'。

当规定替换的数量 >元素数时,即:

int main()
{
	char arr[20] = "abcdef\0yyyy";
	char arr2[] = "xxx";
	strncat(arr, arr2, 2);
	printf("%s ", arr);
	return 0;
}

结果如下:
C语言——字符串和内存函数_第15张图片

通过结果可以看到,arr中当替换完规定的数量的元素后,依旧就用‘\0'替换一个元素,用于作为一个字符串的结束标志。

5.3 strncmp函数:

对于strncmp,同样也是起了限制作用,这里的限制作用,指限制进行比较大小的字符数量,将由下面的代码进行演示:

代码如下,限制进行比较大小的字符数量为3

int main()
{
	char arr[] = "abbq";
	char arr2[] = "abbq";
	int ret = strncmp(arr, arr2,3);
	printf("%d", ret);

	return 0;
}

 结果如下:

C语言——字符串和内存函数_第16张图片

 可以验证,两个字符数组进行比较时,仅仅是比较前三个元素的大小。

6. strstr函数:

 6.1 strstr函数的功能:

 strstr函数的功能是从一个字符串中,寻找符合内容的子字符串。例如下面给出的例子:

int main()
{
	char arr1[20] = "abcccdgfbb";
	char arr2[] = "def";
	char* ret = strstr(arr1, arr2);
	if (ret != NULL)
	{
		printf("%s", ret);
	}
	else
	{
		printf("找不到");
	}
	return 0;
}

arr2中的内容便是需要去在arr1中寻找的子字符串,结果如下:

C语言——字符串和内存函数_第17张图片

 6.2 模拟strstr代码实现:

首先给出两个数组,如下列代码所示:

int main()
{
	char arr1[] = "abbbcdef";
	char arr2[] = "bbc";
}

假设用下面的图形代表两个数组及他们中的内容:

 如果想在arr1中找到arr2中的内容,第一步便是要定位首元素。

在定位首元素完成后,此时会有两种情况:

1.首元素后面的元素也相等

2.首元素后面的元素不相等

对应第一种情况,如果首元素后面的元素也相同,则继续向后比较,如果不相等,则同样对应了第二种情况,再如果,此时后面的元素是'\0',说明对于两个数组中的字符串比对已经完成,也就是找到了子句。

对应第二种情况,如果说在比对过程中两个元素不同,这并不能代表,arr1种没有符合arr2的子句,例如上面所举的例子,abbbcdef和bbc,如果从第一个字母’b'开始比较,则比较的字符串是bbb,而arr2中的字符串是bbc,此时,如果,从arr1的第2个‘b'开始比较,则二者进行比较的字符串是'bbc'和'bbc’所以,通过这个例子,可以说明,对于比对过程中出现了两个元素不同的情况是,应将arr1中进行比对的首元素的地址+1,如果此时arr1中进行比对的元素出现了'\0',此时才说明,arr1中没有arr2的子句。

至于如何进行比对元素不同后的地址+1操作,可以再设置一个变量。下面将给出流程图加文字的方式进行说明:

C语言——字符串和内存函数_第18张图片

 1. *s2和*s1对比,此时二者不相等,于是,arr1应从下个字符开始对比,所以:

cp++,s1++,s2不动

 2. *s2和*s1解引用对比,此时两者相等,所以,s2++,s1++,二者再次分别解引用进行对比,此时,*s2 == *s1,所以,s1++,s2++,此时 *s1 != *s2,所以,s2回归首元素地址,cp++,s1记录此时cp的地址

3.此时,cp地址为arr1中第三个元素,*s1和*s2(此时s2记录了arr2中的地址)对比,因为*s2 == *s1,s1++,s2++,此时再进行对比,*s1 == *s2, s1++,s2++,此时,*s1 == *s2,此时,再对s2进行++,则s2中存储着'\0'的地址,此时对比完成,表示可以在arr1中找到子句。

代码实现:

char* my_strstr(char* str1, char* str2)
{
	char* cp = str1;
	char* s1 = cp;
	char* s2 = str2;
	while (*cp)//用于检查arr1中,此时元素是否时'\0',是的话,则找不到子句
	{
		s1 = cp;
		s2 = str2;
		while ((*s1 && *s2) && (*s1 == *s2))//用于比对
			//并且检验arr1或者arr2是否是'\0',若arr1是,则找到不到子句
			//arr2是,则已经找到
		{
			s1++;
			s2++;
		}
		if (*s2 == '\0')//当*s2 == '\0'时,说明前面的元素对比都是相等,也就是说找到了
		{
			return cp;
		}
		cp++;
	}
	return NULL;
}
int main()
{
	char arr1[] = "abbbcdef";
	char arr2[] = "bbc";
	char * ret = my_strstr(arr1, arr2);
	if (ret!= NULL)
	{
		printf("%s", ret);
	}
	else
	{
		printf("找不到");
	}
}

7.strtok函数:

7.1 strtok函数解析及应用:

strtok函数有两个参数,分别是:

 第2个参数sep是一个字符串,定义了作为分隔符用的字符串的集合。

 第1个参数str是一一个指向字符串的指针,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记。

对于strtok函数的特点及使用,将由一幅图给出:

C语言——字符串和内存函数_第19张图片

 为了进一步验证及理解strtok函数运行,将给出一段代码进行解释:

int main()
{
	char arr1[] = "[email protected]";
	char copy[20];
	strcpy(copy,arr1);
	char sep[] = "@.";
	char* ret = strtok(copy, sep);
	printf("%s\n", ret);
	ret = strtok(NULL, sep);
	printf("%s\n", ret);
	ret = strtok(NULL, sep);
	printf("%s", ret);

	return 0;
}

因为strtok函数会改变被操作的字符串,所以,先用strcpy函数将arr1中的内容拷贝到copy中,再规定参数sep。

在第一次调用strtok函数时,strtok 函数会通过第一个参数copy来找到copy中的字符串,因为字符串中存在sep中用来分割字符串的字符'@',所以,将'@"用'\0'替代,返回这个字段的起始位置.。(即返回分隔符号之前的字符串的开头)。同时,因为第一个参数不是NULL,所以,在找到'@'后,会保存这个符号的地址。

第二次调用strtok函数时,此时函数的第一个参数为NULL,所以,函数将在上一次函数运行时被保存的位置开始向后查找,字符串中包含sep中的字符'.',所以,和第一次调用时一样,将‘.’用'\0进行代替,并返回此字符串的起始位置。

第三次调用同理,不过多说明。

结果如下:

C语言——字符串和内存函数_第20张图片

 8. 文章内容大体框架(思维导图):

C语言——字符串和内存函数_第21张图片

你可能感兴趣的:(c语言进阶知识简介,c语言,开发语言,算法,蓝桥杯)