C语言头文件<string.h>函数详解

2022-11-13 12:11:33 函数 头文件 详解

1. strlen —— 求字符串长度

1.1 strlen 的声明与用处

strlen ,我们有一些英语基础的话不难通过字面意思来知道这个函数是干嘛用的,str 表 string ,字符串的意思,len 表 length ,长度的意思。也就是说,strlen 是求字符串长度的函数。我们通过 cplusplus 这个网站来观察 strlen 的函数声明以及各个参数的意义。

我们翻译可以知道,strlen 所求的字符串长度是字符串结束标志之前的字符个数。即我们假设有字符串 "hello world" ,那么所求的字符串长度就是 '\0' 之前的所有字符个数,也就是 11 。那么这个字符串长度是通过返回值返回的,所以我们要知道所求的字符串长度是多少就必须定义一个变量来接收返回值。那么参数部分当然就是字符串的首地址了。

1.2 strlen 的用法

#include <stdio.h>
#include <string.h>
 
int main()
{
	char str1[] = "hello world";
	char* str2 = "hello world";
 
	int len1 = strlen(str1);//数组名表首元素地址
	int len2 = strlen(str2);//str2 指针变量存放的也是首元素地址
 
	printf("%d\n", len1);
	printf("%d\n", len2);
 
	//这种写法也可以输出长度
	//printf("%d\n", strlen(str1));
	//printf("%d\n", strlen(str2));
 
	return 0;
}

我们要注意的一点是,strlen 的返回值是一个 size_t 类型,即无符号整数。 它的意义在于求长度时是不可能求出负数的,所以一定程度上优化了内存(使用有符号整形的话会浪费掉用来存储负数的空间)。

 1.3 strlen 的模拟实现

我们上面已经分析过 strlen 的原理了,那么现在我们用自己所学过的只是来“创造”一个属于自己的 strlen 函数。

#include <stdio.h>
#include <assert.h>
unsigned int my_strlen(const char* str)//我们不改变字符串的内容,所以用 const 来进行修饰
{
	assert(str);//避免是一个空指针
	unsigned int count = 0;
	while (*str)
	{
		count++;
		str++;
	}
	return count;
}
int main()
{
	char str1[] = "hello world";
	char* str2 = "hello world";
 
	int ret1 = my_strlen(str1);
	int ret2 = my_strlen(str2);
 
	printf("%d\n", ret1);
	printf("%d\n", ret2);
 
	return 0;
}

2. strcpy —— 字符串拷贝

2.1 strcpy 的声明与用处

我们对其字面翻译进行理解,str 表 string ,即字符串,cpy 表 copy ,即拷贝。也就是 strcpy 这个函数是用来进行字符串拷贝的。那我们通过 cplusplus 这个网站来观察 strcpy 的声明以及各个参数的意义。

我们翻译过来就可以知道 strcpy 的功能,拷贝 source 指针指向的字符串至 destination 指针指向的数组当中,拷贝的内容包含字符串终止符。并且 destination 指针指向的数组必须有足够大的空间容纳拷贝之后的内容。 

由此我们知道, strcpy 需要两个参数,一个是数组,一个是字符串。并且数组的空间必须足够大。返回值是返回数组拷贝之前的地址。这么做的意义是防止拷贝之前的内容丢失。

2.2 strcpy 的用法

#include <stdio.h>
#include <string.h>
 
int main()
{
	char dest[20] = "row your boat";
	char src[] = "hello world";
	strcpy(dest, src);
	printf("%s\n", dest);
	return 0;
}

这是 strcpy 最最常用的用法,我们只需要记住一点即可:拷贝过去的内容是包含 '\0' 的。也就是说即使我拷贝过去的字符串长度没有数组原来的字符串长度长,但是我包含 '\0' 了,在 C 语言的角度来说 '\0' 之后的内容就不算从头往后看的字符串内容了。

2.3 strcpy 的模拟实现

#include <stdio.h>
#include <assert.h>
 
char* my_strcpy(char* dest, const char* src)//dest 指向的数组空间是要被改变的,但是 src 指向的字符串不需要改变
{
	assert(dest && src);//防止其中某个是无效指针
	char* ret = dest;
	while (*dest++ = *src++)
		;	//注意 while 循环执行了空语句
 
	return ret;
}
int main()
{
	char dest[30] = "gently down the stream";
	char src[] = "life is but a dream";
 
	my_strcpy(dest, src);
	printf("%s\n", dest);
	return 0;
}

3. strcmp —— 字符串比较

3.1 strcmp 的声明与用处

也是一样,我们可以通过字面翻译来大致了解这个函数是干嘛用的。str 表 string ,即字符串,cmp 表 compare ,即比较。我们同样通过 cplusplus 这个网站观察这个函数的声明以及各个参数的意义。

我们翻译即可知道这个函数的原理。其原理就是:都是从两个字符串的第一个字符开始比较,如果这两个字符相等,那么这两个字符串都会进行下一对字符比较,直到两个字符不相等,然后进行比较大小。如果第一个字符小于第二个字符,就会返回一个小于 0 的整数,等于则返回 0 ,大于则返回一个大于 0 的整数。

其两个参数都是要比较的字符串。为了方便理解,我们通过画图的形式来理解。

 3.2 strcmp 的用法

#include <stdio.h>
#include <string.h>
 
int main()
{
	char* str1 = "abbbcd";
	char* str2 = "abbbdd";
	int ret = strcmp(str1, str2);
	if (ret > 0)
		printf("str1 > str2\n");
	else if (ret < 0)
		printf("str1 < str2\n");
	else
		printf("str1 == str2\n");
	return 0;
}

 3.3 strcmp 的模拟实现

#include <stdio.h>
#include <assert.h>
 
int my_strcmp(const char* str1, const char* str2)//两个字符串的内容都不需要修改,用 const 修饰
{
	assert(str1 && str2);//防止是无效指针
 
	while (*str1 == *str2)//如果相等则进入循环
	{
		if (*str1 == '\0')//*str1 == '\0' 了并且进入循环了,说明两个字符串比较完成了,没有不相等的字符
			return 0;
		str1++;
		str2++;
	}
	return *str1 - *str2;//两个字符的差作为返回值
}
int main()
{
	char* str1 = "abbbcd";
	char* str2 = "abbbdd";
	int ret = my_strcmp(str1, str2);
	if (ret > 0)
		printf("str1 > str2\n");
	else if (ret < 0)
		printf("str1 < str2\n");
	else
		printf("str1 == str2\n");
	return 0;
}

4. strcat —— 字符串追加

4.1 strcat 的声明与用处

 我们通过 cplusplus 这个网站来观察 strcat 的声明以及各个参数的意义。

翻译一下即可直到 strcat 函数的用法:将 source 指针指向的字符串追加到 destination 指针指向数组并且有足够大的空间(本人尝试追加到字符串,但是失败了),其追加的位置是 destination 指针指向的字符串的 '\0' 处,即从这个位置拷贝。需要注意的是,追加的 '\0' 的位置第一个 '\0' 的位置。

4.2 strcat 的用法 

#include <stdio.h>
#include <string.h>
 
int main()
{
	char str1[50] = "row row row your boat,";
	char* str2 = "gently down the stream";
	strcat(str1, str2);
	printf("%s\n", str1);
	return 0;
}

4.3 strcat 的模拟实现 

#include <stdio.h>
#include <assert.h>
 
char* my_strcat(char* dest, const char* src)//src 指向的字符串是不改变内容的,所以用 const 修饰
{
	assert(dest && src);//确保两个指针有效
	char* ret = dest;
	while (*dest)
		dest++;//先找到 dest 指向的数组的第一个 '\0' 的位置
	while (*dest++ = *src++)//拷贝
		;
	return ret;
}
int main()
{
	char str1[50] = "row row row your boat,";
	char* str2 = "gently down the stream";
	my_strcat(str1, str2);
	printf("%s\n", str1);
	return 0;
}

5. strncpy —— 长度受限制的字符串拷贝

5.1 strncpy 的声明与用处

strncpy 与 strcpy 的区别就是 strncpy 多了一个参数。这个参数是一个无符号的整数,即可以自定义拷贝多少个字节的内容。这就大大的方便了我们的使用,提高了 C 语言的灵活性。

5.2 strncpy 的用法

#include <stdio.h>
#include <string.h>
 
int main()
{
	char str1[20] = "row your boat";
	char str2[] = "hello world";
	strncpy(str1, str2, 3);//我们从 str2 中拷贝三个字节的内容到 str1 去
	printf("%s\n", str1);
	return 0;
}
 

 

 我们可以看到输出的结果非常奇怪,这是因为当我们只拷贝三个字节的内容时,strncpy 是不会在后面补 '\0' 的(即要多少内容就拷多少内容),这就导致了我们能够看到 str1 数组里面拷贝之前的内容。

5.3 strncpy 的模拟实现

#include <stdio.h>
#include <assert.h>
 
char* my_strncpy(char* dest, const char* src, unsigned int num)
{
	assert(dest && src);//确保两个指针有效
	char* ret = dest;
 
	while (num--)//拷贝几个字节
	{
		*dest = *src;
		dest++;
		src++;
	}
 
	return ret;
}
int main()
{
	char str1[20] = "row your boat";
	char str2[] = "hello world";
	my_strncpy(str1, str2, 3);
	printf("%s\n", str1);
	return 0;
}

6. strncmp —— 长度受限制的字符串比较

6.1 strncmp 的声明与用处

strncmp 与 strcmp 的功能是一模一样的,原理也就是一模一样的。也就是说掌握了 strcmp 就能够掌握 strncmp 。strncmp 只是相较于 strcmp 多了一个参数,这个参数是一个无符号整数,代表的是字节。即我们想要比对多少字节。我们通过 cplusplus 这个网站可以观察这个函数的声明以及各个参数的意义。

6.2 strncmp 的用法

#include <stdio.h>
#include <string.h>
 
int main()
{
	char* str1 = "abbbcd";
	char* str2 = "abbbdd";
	int ret=strncmp(str1, str2,3);//我们只想比较字符串的前三个字节
	if (ret > 0)
		printf("str1 > str2\n");
	else if (ret < 0)
		printf("str1 < str2\n");
	else
		printf("str1 == str2\n");
	return 0;
}

6.3 strncmp 的模拟实现

#include <stdio.h>
#include <assert.h>
 
int my_strncmp(const char* str1, const char* str2, unsigned int num)//两个字符串的内容都不需要变,所以用 const 修饰
{
	assert(str1 && str2);
	while (num-- && *str1 == *str2)
	{
		if (*str1 == '\0')
			return 0;
		str1++;
		str2++;
	}
	return *str1 - *str2;
}
int main()
{
	char* str1 = "abbbcd";
	char* str2 = "abbbdd";
	int ret = my_strncmp(str1, str2, 3);//我们只想比较字符串的前三个字节
	if (ret > 0)
		printf("str1 > str2\n");
	else if (ret < 0)
		printf("str1 < str2\n");
	else
		printf("str1 == str2\n");
	return 0;
}

7. strncat —— 长度受限制的字符串追加

7.1 strncat 的声明与用处

同理,掌握了 strcat 就能掌握 strncat 。strncat 其参数多了一个无符号整形的参数,其意义代表字节数。即我们可以自定义想要追加多少个字符至另一个字符串的后面。我们可以通过 cplusplus 这个网站观察 strncat 的声明以及各个参数的意义。

7.2 strncat 的用法

#include <stdio.h>
#include <string.h>
 
int main()
{
	char str1[50] = "row row row your boat,";
	char str2[] = "gently down the stream";
	strncat(str1, str2, 6);//我们只想追加 6 个字节的字符到 str1 中
	printf("%s\n", str1);
	return 0;
}

 在这里如果我们注意观察的话,就会发现一个问题。我们追加过去的 6 个字节的字符并不包含 '\0' ,但是在最后输出打印的时候好像在字符串尾又被赋予了 '\0' 。那么这就不得不提及 strncat 的特性,即 strncat 会在要追加的字符串后面补 '\0' 。

 7.3 strncat 的模拟实现

#include <stdio.h>
#include <assert.h>
 
char* my_strncat(char* dest, const char* src, unsigned int num)//str2 中的字符串不需要被修改,所以用 const 修饰
{
	assert(dest && src);//确保两个指针是有效指针
	char* ret = dest;//记录返回值
 
	while (*dest)//先找到 dest 指向的 '\0' 的位置
		dest++;
	while (num-- && (*dest++ = *src++) )//拷贝限制的字节数
		;
	*dest = '\0';
 
	return ret;
 
}
int main()
{
	char str1[50] = "row row row your boat,";
	char str2[] = "gently down the stream";
	my_strncat(str1, str2, 8);
	printf("%s\n", str1);
	return 0;
}

小结

我们上面介绍了许多长度不受限制的字符串函数和长度受限制的字符串函数。那么受限制和不受限制有什么区别呢? 首先不受限制的字符串函数的灵活性是比较低的,因为只能操作整个字符串。但是受限制的字符串函数的灵活性是比较高的,可以自定义操作的字符个数。并且,长度受限制的字符串函数相对于长度不受限制的字符串函数来说是比较安全的。注意是受限制的相对于不受限制的比较安全。

8. strstr —— 字符串查找

8.1 strstr 的声明与用处

strstr 通过字面翻译过来就是两个字符串。那么其意义是在其中一个字符串里面查找另外一个字符串。我们可以通过 cplusplus 这个网站来观察 strstr 的声明以及各个参数的意义。

我们翻译一下即可知道 strstr 的某些原理。如果在字符串 1 里面查找到了字符串 2 ,那么就返回字符串 2 出现在字符串 1 中的首地址。如果没有找到字符串,就返回一个空指针。

8.2 strstr 的用法

#include <stdio.h>
#include <string.h>
 
int main()
{
	char* str1 = "row row row your boat,gently down the stream";
	char* str2 = "row your boat";
	char* ret = strstr(str1, str2);//在 str1 中查找 str2 
	printf("%s\n", ret);
	return 0;
}

可以看到返回值是返回 str2 第一次出现在 str1 中的首地址。 

8.3 strstr 的模拟实现

#include <stdio.h>
#include <assert.h>
 
char* my_strstr(const char* str1, const char* str2)//不需要改变其内容,用 const 修饰
{
	assert(str1 && str2);//避免是无效指针
	const char* s1 = str1;
	const char* s2 = str2;
	const char* cp = str1;//这个指针变量是至关重要的
	while (*cp)
	{
		s1 = cp;
		s2 = str2;
 
		//在 str1 中查找 str2 的核心循环
		while (*s1 && *s2 && *s1 == *s2)
		{
			s1++;
			s2++;
		}
		if (*s2 == '\0')//查找完成
			return (char*)cp;
 
		cp++;
	}
	return NULL;
}
int main()
{
	char* str1 = "row row row your boat,gently down the stream";
	char* str2 = "row your boat";
	char* ret = my_strstr(str1, str2);
	printf("%s\n", ret);
	return 0;
}

我们来通过画图来详解一下思路。 

 9. strtok —— 字符串切割

9.1 strtok 的声明与用处

对于这个函数,我们只要了解了基本的使用方法即可。我们通过 cplusplus 这个网站来观察 strtok 的声明以及各个参数的意义。

这段英文的篇幅太长了,我来解释一下核心部分:我们提供两个参数给 strtok ,一个是字符串,一个是要切割的标记。如果在这个字符串当中出现了这个标记,那么这个位置将被修改为 '\0' ,并且返回这个标记之前的字符串地址。如果要进行二次传参,那么只需要传一个空指针即可。

9.2 strtok 的用法

#include <stdio.h>
#include <string.h>
 
int main()
{
	char* str1 = "row@row~row%your^boat,gently@down~the^stream";
	char* str2 = "@~%^";
 
	//将 str1 拷贝至 tmp 数组,这样不会丢失 str1 的原始数据
	char tmp[50] = { 0 };
	strcpy(tmp, str1);
 
	char* ret = NULL;
	for (ret = strtok(tmp, str2); ret != NULL; ret = strtok(NULL, str2))
	{
		printf("%s ", ret);
	}
	return 0;
}

10. strerror —— 错误码解析

10.1 strerror 的声明与用处

我们需要普及的一个点是,在 C 语言中,程序发生错误的时候,是有一个隐藏的全局变量 errno 的,这个变量是一个整数的,譬如 0 ,1,2,3,....等都代表不同的错误信息,而 strerror 的作用就是翻译这个错误码。我们可以通过 cplusplus 这个网站来观察 strerror 的声明以及各个参数的意义。 

10.2 strerror 的用法

#include <stdio.h>
#include <string.h>
 
int main()
{
	FILE* p;
	p = fopen("test.txt", "r");//在我们的工程目录下并没有 test.txt 这个文件
	if (p == NULL)//那么打不开 p 就是一个空指针
	{
		printf("%s\n", strerror(errno));//这里就会解释为什么是空指针的原因
	}
	return 0;
}

相关文章