C语言——字符串函数

一.前言

我们在日常写代码的过程中,经常会对字符串进行处理的过程。而在C语言中的<string.h>中,包含了众多字符串函数,我们可以借助这些字符串函数来对其进行各种操作。

二.strlen函数

strlen函数的作用是求出所传字符串的长度。该函数的参数待求长度的字符串的地址,返回值为size_t类型的整数,因为一个字符串的长度不可能是负数,所以可以用无符号整型数据接收。该函数的逻辑是:从所传字符串首地址开始向后寻找,直到遇到'\0'结束,后返回'\0'到首地址之间的字符个数。

#include <stdio.h>
#include <string.h>

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

使用strlen函数的时候得包含对应头文件<string.h>。有上述代码可知,len的长度为6。注意:打印size_t类型的数据时,用%zd来打印。

2.1strlen函数的模拟实现

我们已经知道了strlen函数的使用方法,现在我们来模拟它的实现。要想实现它,我们得知道它的运行逻辑:我们先给它传了一个字符串的首地址,它就沿着该地址向后寻找,直到遇到'\0'停止,然后返回首地址与'\0'之间的字符个数。它的实现逻辑并不复杂,现在我们来模拟实现。

1.计数器法

我们可以创建一个变量用来统计字符的个数,然后通过while循环遍历该字符串的每一个字符,遇到'\0',则表示字符串遍历完毕,返回计数器。注意:当我们要使用该字符串时,应先判断该字符串是否为空。所以我们可以使用assert断言,使用assert得包含头文件<assert.h>

//1.计数器法

#include <stdio.h>
#include <assert.h>
size_t my_strlen(const char* arr)
{
    assert(arr);//判断传入的地址是否为空
	size_t count = 0;
	while (*arr != '\0')
	{
		count++;
		arr++;
	}
	return count;
}

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

2.不创建临时变量法(递归法)

不能创建临时变量,)意味着不能再使用计数器的方式来统计。这时,我们可以利用递归法来实现。这里我们在使用待求字符串前依旧得进行断言,判断地址是否有效。

//2.不能创建临时变量,递归法

#include <stdio.h>
#include <assert.h>

size_t my_strlen(const char* arr)
{
	assert(arr);//判断所传字符串是否为NULL
	if (*arr != '\0')
	{
		return 1 + my_strlen(arr + 1);
	}
	else
	{
		return 0;
	}
}

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

3.指针-指针

我们知道,两指针可以进行相减的操作,得出的结果的绝对值表示的是两指针之间的元素个数。所以我们可以将字符串首地址储存起来,在进行遍历,找到字符串的末尾,两者相减就是该字符串的元素个数。

//3.指针-指针

#include <stdio.h>
#include <assert.h>

size_t my_strlen(const char* arr)
{
	assert(arr);//判断所传地址是否为NULL

	char* p1 =(char*) arr;//记录首地址
	char* p2 = NULL;//记录末尾

	while (*arr++ != '\0')
	{
		;
	}
	p2 = (char*)arr - 1;//当退出循环后,arr指向的'\0'的位置,所以字符串的末尾应该是arr-1
	return p2 - p1;
}

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

三.strcpy函数

strcpy函数的参数为两个char*的指针,返回值也是char*类型的。该函数的作用为:将第二个指针所对应的字符串复制到第一个指针所指向的内容里。但复制的前提是目标地址的内存空间足够大。

#include <stdio.h>
#include <string.h>

int main()
{
	char arr1[] = "abcdef";
	char arr2[20] = "";
	char * ret = strcpy(arr2, arr1);
	printf("%s\n", ret);
	return 0;
}

打印结果为:

哪有的人就会问了,将第二个字符串复制给第一个指针是否会将第二个字符串的'\0'也复制过去?我们可以调试来看一下。

我们先将目标地址的字符串全部设置成x,并且x的数量是多于来源字符串的。如果我们在监视窗口看到前6个x被复制成abcdef,而第七个x被复制成'\0',那么就能说明strcpy复制的时候会将来源字符串的全部字符包括结尾的'\0'也复制给目标字符串。

这时我们可以清楚的看到,当我们执行了strcpy函数之后,它会将来源字符串的全部字符包括结尾的'\0'全部复制给目标字符串。

注意:对于strcpy函数来说有几个注意点:

1.它会将来源字符串的全部包括'\0'都复制给目标

2.来源字符串必须包含'\0'

3.目标地址必须够大,以确保能够存放的下来源字符串

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

3.1.strcpy函数的模拟实现

为了实现strcpy函数,我们就得清楚其复制的逻辑:strcpy是将第二个来源字符串的全部字符包括'\0'也复制给目标字符串。所以我们可以从两个指针的首地址开始,将来源字符串的内容依次赋给目标字符串。既然是模拟实现strcpy函数,所以我们在函数返回值和参数上应保持一致,目标地址为char*类型,而来源地址为const char*类型。因为我们只希望将第二个指针的内容复制给一指针,并不希望二指针的内容被修改,于是用const修饰。

//strcpy函数的模拟实现

#include <stdio.h>
#include <assert.h>

char* my_strcpy(char* destination, const char* source)
{
	assert(destination && source);//判断传来的地址是否为空
	
	char* ret = destination;//记录目标字符串的首地址

	while (*destination++ = *source++)//将来源字符串的每个字符赋给目标字符串
	{
		;
	}
	return ret;
}

int main()
{
	char arr1[] = "abcdef";
	char arr2[20] = { 0 };
	char* ret = my_strcpy(arr2, arr1);
	printf("%s\n", ret);
	return 0;
}

在使用两个指针之前,我们需要断言,判断两指针是否为NULL。在此之后,我们先创建一个指针用来存储目标地址的首地址。后利用循环,对目标地址进行赋值,随后指针++,两指针走到下一个位置,后继续进行赋值,直到将来源地址的'\0'也赋值给目标地址后,循环判断条件为假,退出循环,复制结束,后将之前存放来目标地址的首地址的指针返回。

四.strcat函数

strcat函数的返回值为char*类型,参数分别为char*类型的目标字符串和const char* 类型的源字符串。它的功能是将目标字符串和源字符串连接起来(将源字符串复制在目标字符串的后面)。

#include <stdio.h>
#include <string.h>

int main()
{
	char arr1[20] = "abcdef";//目标字符串
	char arr2[] = "ghi";//来源字符串

	char* ret = strcat(arr1, arr2);
	
	printf("%s\n", ret);
	return 0;
}

运行结果为:

那是否可以利用该函数对字符串本身进行追加呢?

我们看到当我们在给字符串追加自己时,在调试的时候就会报错。说发生了访问冲突,其实就是数组越界了。这是因为你在给自己追加的时候,字符串在同时的发生变化。

我们看到,当指针指向arr数组的第一个元素时,会将它追加在目标字符串的位置,而目标字符串和源字符串是同一个,所以两者都发生了改变。随后指针向后走,指向源字符串的第二个元素,将其追加到目标字符串的后面,两者又同时发生改变。我们这时发现,字符串中已经没有了\0,故追加不会停止,直到追加到大于目标字符串可以承受的最大元素,导致越界。所以我们得出结论,不可以给字符串追加自身。

我们在使用strcat函数时,有几个注意点:

1.源字符串必须以\0结束;

2.目标字符串也必须包含\0,否则不知道从什么位置开始追加;

3.目标地址的空间必须足够大,能够容纳源字符串;

4.目标空间必须得可修改;

5.最好不要给自身追加,否则可能导致越界访问

4.1.strcat函数的模拟实现

模拟实现之前,我们得知道strcat函数到底是怎么个逻辑来实现的追加字符串。我们上面说到源字符串必须以\0结尾,所以说明当对源字符串解引用的时候等于\0就说明已经将源字符串全部追加到了目标字符串;上面还提到目标字符串也必须含有\0,否则不知道从哪里开始追加。所以要实现该函数,我们就得找到目标字符串的第一个\0位置,以此开始进行追加,直到将源字符串的所有字符都追加到目标字符串,就表明追加完成。

#include <stdio.h>
#include <string.h>
#include <assert.h>

char* my_strcat(char* destination, const char* source)
{
	assert(destination && source);//判断所传两指针是否为NULL

	char* ret = destination;//将目标字符串的首地址储存起来

	//找到目标字符串中第一次出现\0的位置
	while (*destination)
	{
		destination++;
	}

	//从该位置开始,将源字符串追加到目标字符串
	while (*destination++ = *source++)
	{
		;
	}

	return ret;
}

int main()
{
	char arr1[30] = "abcdef";//目标字符串
	char arr2[] = "ghijk";//源字符串

	char * ret = my_strcat(arr1, arr2);

	printf("%s\n", ret);
	return 0;
}

运行结果:

我们在使用指针的时候最好先进行判断,以防该指针为NULL。

五.strcmp函数

strcmp也是C语言中字符串函数之一,它的参数为两个 const char*类型的指针,返回值为int。该函数的作用为:比较两字符串的大小。返回值的大小由比较结果来定。如果:str1 > str2,则返回大于0的数字;str1 = str2,则返回0;str2<str2,则返回小于0的数字。

那strcmp是如何比较两个字符串呢?

strcmp函数进行两字符串间的比较是同位之间进行比较,比较的是字符所对应的ASCII码值,ASCII码值大的该字符就大。

需要注意的是,字符串的大小与其长度没有关系,只跟相同位置上的字符所对应的ASCII码值有关。

5.1.strcmp函数的模拟实现

我们在介绍strcmp函数的时候就已经解释它的比较逻辑,我们只需要根据其比较逻辑写出代码即可:

#include <stdio.h>
#include <assert.h>

int my_strcmp(const char* p1, const char* p2)
{
	//使用指针前先对其进行断言,判断其是否可用
	assert(p1 != NULL);
	assert(p2 != NULL);

	while (*p1 == *p2)
	{
		//如果出现\0,说明两个字符串完全相同
		if (*p1 == '\0')
		{
			return 0;
		}

		//使指针后移,指针下一个元素
		p1++;
		p2++;
	}

	//字符减法相当于用ASCII码值进行减法,如果p1>p2,则返回一个大于0的数,反之返回一个小于0的数
	return *p1 - *p2;
}

int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "abcdef";

	int ret = my_strcmp(arr1, arr2);

	printf("%d\n", ret);
	return 0;
}

六.strncpy函数

从函数名可以看出来,它与strcpy只有一个n的区别,那区别究竟在哪里呢?

我们发现,两者的区别就在于参数的不同,strncpy比strcpy多了一个参数 size_t 类型的num。这个参数其实就是n。

该函数的作用与strcpy类似,是复制源字符串的前n个元素到目标地址。

那如果源字符串没有num个元素,为怎么样呢?

我们看到,arr2只有五个元素,如果num=5的话,刚好把arr1中前五个x改变,后面不改变;但当num =6的时候,arr2不够6个元素,此时进行复制的时候,会在第五个元素后面补0,直到够num个元素。

而对于字符数组来说,0就相当于\0,所以arr1中第六个元素的位置就会被复制成\0,导致不能打印后面的x。

注意:在使用strncpy函数的时候同样得保证目标地址的内存空间足够大。

七.strncat函数

我们看到,两者的区别也在于一个size_t类型的num参数。该函数的功能就是追加源字符串中的前num个字符到目标地址。

与strncpy相同,如果num大于源字符串的长度,就会在后面补0:

注意:目标地址的内存大小必须足够大。

八.strncmp函数

该函数中的size_t num,与上面两者有点不同,num的作用是比较str1 和 str2 的前num个字符。

如图所示,如果比较的是前四个字符,则arr1和arr2相同,返回0;如果比较前5个字符,则arr1 < arr2,返回小于0的值。

九.strstr函数

strstr函数的功能是在str1中查找str2,如果找到了,就返回str2在str1中第一个字符出现的地址;如果找不到则返回NULL。

9.1strstr函数的模拟实现

#include <stdio.h>
#include <assert.h>

char* my_strstr(const char* str1, const char* str2)
{
	assert(str1 && str2);

    //如果str2为空字符串,则直接返回str1
	if (!*str2)
	{
		return (char*)str1;
	}

	char* ch = (char*)str1;
	char* s1;
	char* s2;

	while (*ch)
	{
		s1 = ch;
		s2 = (char*)str2;

		//s1、s2都不是\0,并且两者相等
		while (*s1 && *s2 && !(*s1 - *s2))
		{
			//相等之后,指针向后走继续比较
			s1++;
			s2++;
		}

		//在此判断*s2是否为\0,若为\0则说明已经在str1中找到了str2
		if (!*s2)
		{
			return ch;
		}

		ch++;
	}

	//如果ch遇到\0,说明已经将str1查找完了,也没有找到str2,这时返回NULL
	return NULL;
}

int main()
{
	char arr1[] = "This is a apple";
	char arr2[] = "is";

	char* ret = my_strstr(arr1, arr2);

	printf("%s\n", ret);
	return 0;
}

十.strtok函数

strtok函数的功能为将str分解成一系列字符串,以其中的分隔符来进行分离。delimiters指的是所有分隔符所存在的字符串。下面给出一个例子:

值得注意的是,在使用完一次strtok函数之后,它会记住第一次分隔符出现的地方,下次调用的时候第一个参数传一个空指针,第二个参数依旧时分隔符字符串,该函数会从第一次分隔符的下一个位置开始扫描。

但是如果我们想要分开一个字符串就得写多个这样的调用和输出语句,非常麻烦,我们可以利用循环来实现多次调用及打印:

法一:

#include <stdio.h>
#include <string.h>

int main()
{
	char arr[] = "xia-si-cheng.12138.com";
	char deli[] = "-.";

	char* str = NULL;
	for (str = strtok(arr,deli); str != NULL;str = strtok(NULL,deli))
	{
		printf("%s\n", str);
	}
	return 0;
}

法二:

#include <stdio.h>
#include <string.h>

int main()
{
	char arr[] = "xia-si-cheng.12138.com";
	char deli[] = "-.";

	char* ret = strtok(arr, deli);
	
	while (ret)
	{
		printf("%s\n", ret);

		ret = strtok(NULL, deli);
	}
	return 0;
}

注意:使用strtok函数后str1的内容会改变,所有分隔符都会被修改成\0。

十一.strerror函数

该函数的功能是返回错误信息,通过错误数字代码,返回一个错误信息指针,指针所指向的内容就是错误信息。该错误信息取决于开发平台和编译器。

总结:

字符串函数的内容就到此结束,如果文章出错还请大家帮我揪出问题。

相关推荐
西猫雷婶2 小时前
python学opencv|读取图像(十九)使用cv2.rectangle()绘制矩形
开发语言·python·opencv
liuxin334455663 小时前
学籍管理系统:实现教育管理现代化
java·开发语言·前端·数据库·安全
码农W3 小时前
QT--静态插件、动态插件
开发语言·qt
ke_wu3 小时前
结构型设计模式
开发语言·设计模式·组合模式·简单工厂模式·工厂方法模式·抽象工厂模式·装饰器模式
code04号4 小时前
python脚本:批量提取excel数据
开发语言·python·excel
小王爱吃月亮糖4 小时前
C++的23种设计模式
开发语言·c++·qt·算法·设计模式·ecmascript
hakesashou4 小时前
python如何打乱list
开发语言·python
网络风云4 小时前
【魅力golang】之-反射
开发语言·后端·golang
Want5955 小时前
Java圣诞树
开发语言·python·信息可视化
运维小文5 小时前
python之打印、变量、格式化输出
开发语言·python·python基础·hello world