C语言--字符串函数

该文章已生成可运行项目,


在编程的过程中,会经常处理字符串,为了方便操作处理,C语言标准库中提供了许多字符串函数给我们使用,下面就让我们来学习一下。

先开门见山:所有的字符串函数都要包含一个头文件#include <string.h>

一、strlen的使用和模拟实现

1.1 strlen的使用

函数原型:

size_t strlen ( const char * str );

功能:求字符串的长度
参数str:需要计算长度的字符串(本质是首元素地址)
返回类型:size_t
注:字符串是以'\0'为结束标志,所以strlen计算的是字符串开头到'\0'之间(不包括’\0')的字符个数

使用:

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

int main()
{
	char str[] = "hello world!";
	size_t len = strlen(str);
	printf("%zd\n", len);//结果是12
	return 0;
}

注意:strlen库函数是以’\0’为结束标志来计算字符串个数的,所以如果字符串中含有’\0’,strlen只会计算从开始到第一个’\0’之间(不包括’\0’)的字符个数

下面这段代码就是一个例子:

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

int main()
{
	char str[] = "hello\0world!";
	size_t len = strlen(str);
	printf("%zd\n", len);//结果是5
	return 0;
}

还有一点值得注意的是strlen库函数的返回类型是size_t

int main()
{
	if (strlen("abc") - strlen("abcdefg") > 0)
	{
		printf(">");
	}
	else
	{
		printf("<");
	}
	return 0;
}

这段代码许多人肯定会认为strlen("abc")结果是3;strlen("abcdefg")的结果是6,3 - 6= - 3 < 0 ,会打印<。其实是错误的。
虽然一个是3,一个是6,但是它们是size_t类型的数字,本质上是一种无符号类型,是恒大于0的。
所以会打印>。

1.2 strlen的模拟实现

方法一:计数器
思路:从字符串开始向后遍历,如果没有遇到'\0',计数器count就+1。

size_t my_strlen(const char* str)//不需要对str本身改变,所以使用const修饰
{
	assert(str);//防止str为NULL
	size_t count = 0;
	while (*str)//*str != '\0'
	{
		str++;
		count++;
	}
	return count;
}

int main()
{
	char str[] = "hello world!";
	size_t len = my_strlen(str);
	printf("%zd\n", len);
	return 0;
}

方法二:
递归法(不创建临时变量法)

思路:如果计算字符串"abcd"的长度,就等价于计算 “a” + “bcd” 的长度(1 + my_strlen(str + 1)字符是char类型,所以+1一次跳过一个字符),以此类推,最后就等价与计算 “a” + “b” + “c” + “d” + "\0"的长度。

size_t my_strlen(const char* str)
{
	assert(str);
	if (*str == '\0')
		return 0;
	else
		return 1 + my_strlen(str + 1);
}

int main()
{
	char str[] = "hello world!";
	size_t len = my_strlen(str);
	printf("%zd\n", len);
	return 0;
}

方法三:指针 - 指针法

思路:在之前学习指针时,我们知道,一个char*类型的指针变量+n代表跳过n个字节,一个char类型的变量就是一个字节
,也就是+n跳过n个字符。
指向前面的指针 + n(== n个字节 == n个字符) = 指向后面指针;
所以用指向后面的指针 - 指向前面的指针 = 字符个数

size_t my_strlen(const char* str)
{
	assert(str);
	const char* end = str;
	while (*end != '\0')
	{
		end++;
	}
	return end - str;
}

int main()
{
	char str[] = "hello world!";
	size_t len = my_strlen(str);
	printf("%zd\n", len);
	return 0;
}

二、strcpy的使用和模拟实现

2.1 strcpy的使用

函数原型:

char * strcpy ( char * destination, const char * source );

功能:将源字符串(source指向的字符串)复制拷贝到目标字符串(destination指向的字符串)
source:源字符串 类型是const char* ,即source指向的字符串的内容不能被修改
destination:目标字符串
返回目标字符串destination(的首地址),类型是char*
注:为了避免溢出,destination指向的字符串的空间应足够大,以满足source字符串拷贝

使用:

int main()
{
	char str1[] = "hello world!";
	char str2[20] = { 0 };
	strcpy(str2, str1);
	printf("%s\n", str2);
	return 0;
}

注:
1.源字符串末尾的’\0’也会被拷贝到目标字符串

int main()
{
	char str1[] = "hello world!";
	char str2[20] = "xxxxxxxxxxxxxxxx";
	strcpy(str2, str1);
	printf("%s\n", str2);
	return 0;
}

在这里插入图片描述
2.目标字符串必须是可修改的,不能是常量字符串

int main()
{
	char str1[] = "hello";
	char* p = "I love you";
	strcpy(p, str1);
	printf("%s\n", p);
	return 0;
}

上述代码中p指向的字符串是常量字符串,不能修改,所以使用strcpy复制拷贝就不会成功。

2.2 strcpy的模拟实现

char* my_strcpy(char* dest, const char* src)
{
	assert(dest && src);//防止dest和src为NULL;
	char* ret = dest;//记录目标字符串的起始位置,因为后面dest会变化
	while (*src != '\0')
	{
		*dest = *src;
		dest++;
		src++;
	}
	*dest = *src;//拷贝'\0'
	return ret;
}

我们会发现这段代码的核心部分会有些冗余,可以对其优化:

char* my_strcpy(char* dest, const char* src)
{
	assert(dest && src);//防止dest和src为null;
	char* ret = dest;//记录目标字符串的起始位置,因为后面dest会变化
	while (*dest++ = *src++)
		;
	return ret;
}

当循环拷贝到*src == '\0'的时候,while中条件判断表达式*dest++ = *src++还会执行一遍,表达式的结果作为条件判断的结果,从而'\0'也完成了拷贝。

三、strcat的使用和模拟实现

3.1 strcat的使用

函数原型:

char * strcat ( char * destination, const char * source );

功能:将source直线的字符串连接到destination指向的字符串的后面;
source:源字符串,类型是const char*,即source指向的字符串的内容不可以被修改
destination:目标字符串
返回destination指向的字符串(的首字符地址),类型是char*
注意destination指向的字符串数组需要有足够大的空间,以用来存放连接后的字符串

dest(destination简写)指向的字符串中需包含'\0',strcat会从'\0'开始连接,dest指向的字符串末尾的'\0'会被source的第一个字符覆盖。

使用:

int main()
{
	char str1[20] = "hello ";
	char str2[] = "world!";
	strcat(str1, str2);
	printf("%s\n", str1);
	return 0;
}

结果:
在这里插入图片描述

strcat函数进行字符串追加的时候,会把source指向的字符串的末尾'\0'也追加到destination指向的字符串后面。

int main()
{
	char str1[20] = "xxxxxxxx\0xxxxxxxxx";
	char str2[] = "world!";
	char* ret = strcat(str1, str2);
	printf("%s\n", str1);
	printf("%s\n", ret);

	return 0;
}

从第一个'\0'开始向后追加,world!'\0'都追加在后面
在这里插入图片描述

注意:destination指向的字符串和source指向的字符串不能有重叠,也就不能自己给自己追加。

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

int main()
{
	char str1[] = "hello world!";
	strcat(str1, str1 + 3);
	return 0;
}

str1 和 str1 有重叠的部分,就无法得出正确结果。

3.2 strcat的模拟实现

char* my_strcat(char* dest, const char* src)
{
	assert(dest && src);
	char* ret = dest;
	//1.找到dest的末尾'\0'
	while (dest)
	{
		dest++;//注意++不能直接上面条件判断表达式中写,
		       //当*dest == '\0'的时候,它还是会+1,但是我们要找到的是'\0'这个位置
	}
	//2.连接
	while (*dest++ = *src++)
		;
	return ret;
}

int main()
{
	char str1[20] = "hello ";
	char str2[] = "world!";
	strcat(str1, str2);
	printf("%s\n", str1);
	return 0;
}

四、strcmp的使用和模拟实现

4.1 strcmp的使用

函数原型

int strcmp ( const char * str1, const char * str2 );

功能:将字符串str1和str2进行比较
str1,str2:两个要比较的字符串
比较逻辑:此函数开始比较每个字符串的第一个字符。如果它们彼此相等,则继续处理以下对,直到字符不同或到达终止字符'\0'

返回值类型:int
如果第一个不相等的字符在str1中的值 > str2中的,返回一个大于0的数;
str1 == str2 ,返回0;
str1 < str2 ,返回一个小于0的数。

使用:

int main()
{
	char str1[] = "abcd";
	char str2[] = "abcf";
	int ret = strcmp(str1, str2);
	if (ret > 0)
	{
		printf("str1大\n");
	}
	else if (ret < 0)
	{
		printf("str2大\n");
	}
	else
	{
		printf("相等\n");
	}
	return 0;
}

结果:前面三个字符abc都一样,因为f > d,所以返回一个小于0的数。
在这里插入图片描述

4.2 strcmp的模拟实现

//strcmp的模拟实现

int my_strcmp(const char* str1, const char* str2)
{
	assert(str1 && str2);
	
	while (*str1 == *str2)
	{
		if (*str1 == '\0')
			return 0;//当比较到'\0'的时候说明两字符串已经相等,返回0
		str1++;
		str2++;
	}
	return *str1 - *str2;//如果存在不相等,返回字符差值,就可以直接表示二者大小
}

int main()
{
	char str1[] = "abcd";
	char str2[] = "abcf";
	int ret = my_strcmp(str1, str2);
	printf("%d\n", ret);
	return 0;
}

五、strncpy的使用

函数原型:

char * strncpy ( char * destination, const char * source, size_t num );

功能:将源字符串(source指向的字符串)的前num个字符,拷贝到目标字符串(destination指向的字符串)中
source:源字符串 类型是const char* ,即source指向的字符串的内容不能被修改
destination:目标字符串
num:需要复制拷贝的最大字符个数,类型是size_t
返回目标字符串destination(的首地址),类型是char*

使用:

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

int main()
{
	char str1[] = "hello ";
	char str2[] = "world!xxxx";
	char* ret = strncpy(str1, str2, 3);
	printf("%s\n", str1);
	printf("%s\n", ret);
	return 0;
}
  1. 如果源字符串source的字符个数 >= num,则直接拷贝num个字符(注意:不包含'\0')覆盖到目标字符串destination中
#include <stdio.h>
#include <string.h>

int main()
{
	char str1[] = "hello xxx";
	char str2[] = "world!xxxx";
	char* ret = strncpy(str1, str2, 3);
	printf("%s\n", str1);//worlo xxx
	printf("%s\n", ret);
	return 0;
}
#include <stdio.h>
#include <string.h>

int main()
{
	char str1[] = "hello xxx";
	char str2[] = "world!";
	char* ret = strncpy(str1, str2, 6);//只拷贝覆盖6个字符,不包含'\0'
	printf("%s\n", str1);//world!xxx
	printf("%s\n", ret);
	return 0;
}
  1. 如果源字符串source的字符个数 < num,则会在源字符串后面添加’\0’,直到num个
#include <stdio.h>
#include <string.h>

int main()
{
	char str1[] = "hello xxxxxx";
	char str2[] = "world!";
	char* ret = strncpy(str1, str2, 8);
	printf("%s\n", str1);//world!
	printf("%s\n", ret);
	return 0;
}

在这里插入图片描述

六、strncat的使用

函数原型:

char * strncat ( char * destination, const char * source, size_t num );

功能:将源字符串(source指向的字符串)的前num个,追加到目标字符串(destination指向的字符串)之后,再追加一个'\0'
source:源字符串 类型是const char* ,即source指向的字符串的内容不需要被修改
destination:目标字符串
num:需要追加的个数,类型是size_t
返回目标字符串destination(的首地址),类型是char*

使用:和strcat函数一样,也是从字符串的第一个'\0'开始向后追加。

int main()
{
	char str1[20] = "hello ";
	char str2[] = "world!";
	char* ret = strncat(str1, str2, 3);
	printf("%s\n", str1);//hello wor
	printf("%s\n", ret);
	return 0;
}
  1. 如果源字符串source的字符个数 >= num,则在追加完num个字符后,会在末尾添加上一个'\0';
  2. 如果源字符串source的字符个数 < num,因为已经把源字符串的'\0'已经包括在内了,不会在添加'\0'
int main()
{
	char str1[20] = "hello \0xxxxxxxxxxx";
	char str2[] = "world!";
	char* ret = strncat(str1, str2, 8);
	printf("%s\n", str1);//hello wor
	printf("%s\n", ret);
	return 0;
}

在这里插入图片描述

七、strncmp的使用

函数原型:

int strncmp ( const char * str1, const char * str2, size_t num );

功能:将字符串str1和str2的前num个字符进行比较
str1,str2:两个要比较的字符串
比较逻辑:此函数开始比较每个字符串的第一个字符。如果它们彼此相等,则继续处理以下对,直到字符不同或前num个字符比较完成都相等到达终止字符'\0'

返回值类型:int
如果第一个不相等的字符在str1中的值 > str2中的,返回一个大于0的数;
str1 == str2 ,返回0;
str1 < str2 ,返回一个小于0的数。

使用:

int main()
{
	char str1[] = "abcdef";
	char str2[] = "abhdef";
	int ret = strncmp(str1, str2, 3);
	printf("%d\n", ret);//-1
	return 0;
}

八、strstr的使用和模拟实现

8.1 strstr的使用

函数原型:

char * strstr ( const char * str1, const char * str2 );

功能:查找子字符串,在str1字符串中查找str2字符串
如果存在,则返回str2在str1中第一次出现的位置(指针);
如果不存在,则返回空指针NULL
返回类型是char*

使用:

int main()
{
	char str1[] = "This is an apple";
	char str2[] = "is";
	char* ret = strstr(str1, str2);
	printf("%s\n", ret);//is is an apple
	return 0;
}

8.2 strstr的模拟实现

思路: 首先,既然是字符串中找子字符串,那么就会需要通过两个指针一步一步向前走来进行比较。

  1. 第一种情况:从第一个字符开始向后匹配,str1的某一个位置开始匹配,匹配成功时,我们需要返回str2在str1中第一次出现的指针,但是此时指针的当前位置在末尾,所以需要一个指针来记录是从str1字符串的哪里开始匹配的。
    在这里插入图片描述

  2. 第二种情况:当匹配过程中,发现从str1该位置开始的匹配失败 在这里插入图片描述
    str1可以根据记录开始位置的指针回去,然后从下一个位置再开始匹配,但是此时str2同样也需要回到起始位置,所以还需要一个额外的指针记录str2字符串的起始位置。
    在这里插入图片描述

  3. 第三种情况:从str1的所有位置开始的匹配都不成功,所以在str1中不存在str2子串,返回空指针NULL; 在这里插入图片描述

模拟实现:

char* my_strstr(const char* str1, const char* str2)
{
	assert(str1 && str2);
	const char* s1 = NULL;//使用s1,s2代替str1,str2进行匹配
	const char* s2 = NULL;
	const char* cur = str1;//当前str1匹配的起始位置

	if (*str2 == '\0')//如果str2传的是空字符串(注意不是NULL),则返回str1的地址
		return (char*)str1;

	while (*cur != '\0')
	{
		s1 = cur;//让str1开始下一个位置的匹配
		s2 = str2;//让str2回到起始位置
		while (*s1 != '\0' && *s2 != '\0'&& *s1 == *s2)//如果相等,开始匹配
		{
			s1++;
			s2++;
		}
		if (*s2 == '\0')//当子串str2遇到末尾'\0',说明匹配成功
		{
			return (char*)cur;//返回当前开始匹配的位置(指针)
		}
		//没有遇到末尾'\0'就出循环,说明不相等,从该cur位置匹配失败,后面一位开始匹配
		cur++;
	}
	//当str1遇到末尾'\0'还没有找到,说明不存在str2子串
	return NULL;
}

int main()
{
	char str1[] = "this is an apple";
	char str2[] = "is";
	char* ret = strstr(str1, str2);
	if (ret)
	{
		printf("%s\n", ret);
	}
	else
	{
		printf("找不到");
	}
	return 0;
}

九、strtok的使用

函数原型:

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

功能:拆分含特定分隔符的字符串
str:要被拆分的字符串
delimiters:包含特定分隔符的字符串
每一次调用,返回一段字符串的起始地址,类型是char*

该函数会改变字符串的内容,所以使用strtok操作的字符串一般都是临时拷贝的内容并且可修改。

该函数与其他字符串函数的调用方式有所差异:
第一次调用时,需要参数str,第一个字符作为起始标记,函数会记住这个起始标记。
然后从起始位置开始扫描,直到遇到第一个分隔符,然后将该分隔符置为'\0',返回该段字符串的起始位置(地址);

后面再次调用时,str参数就只需要传NULL,因为函数会记录上一段从哪里截断拆分的,会从上一段结束的'\0'的后面进行下一次的拆分,每一次拆分都会将每一段的后面字符串的分隔符置为'\0',返回该段字符串的起始位置(地址)。

如果遇到str的末尾,则拆分结束,后续的调用函数都将返回空指针NULL

简单使用:

int main()
{
	char str1[] = "abc.def@ghi*jkl";
	char str2[] = ".@*";

	char* ret = strtok(str1, str2);
	printf("%s\n", ret);

	ret = strtok(NULL, str2);
	printf("%s\n", ret);

	ret = strtok(NULL, str2);
	printf("%s\n", ret);

	ret = strtok(NULL, str2);
	printf("%s\n", ret);

	ret = strtok(NULL, str2);
	if (ret != NULL)
	{
		printf("%s\n", ret);
	}
	else
	{
		printf("已遇到str末尾,不需要拆分\n");
	}
	return 0;
}

调试:每一次函数调用拆分都会将分隔符置为’\0’
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

可是如果需要对特别长的包含分隔符的字符串进行拆分的话,一个一个写函数调用岂不是太麻烦?

所以我们可以使用for循环来解决:
初始化进行第一次调用切分ret = strtok(str1, str2),条件判断是调用返回值不为NULL,如果不为NULL,就可以进行下一次调用切分ret = strtok(NULL, str2)

int main()
{
	char str1[] = "abc.def@ghi*jkl";
	char str2[] = ".@*";
	char* ret = NULL;
	for (ret = strtok(str1, str2); ret != NULL; ret = strtok(NULL, str2))
	{
		printf("%s\n", ret);
	}
	return 0;
}

十、strerror的使用

函数原型:

char * strerror ( int errnum );

功能:获取指向错误码对应的错误信息字符串的指针
errnum:错误码
返回错误信息字符串(的指针),类型是char*

在不同的系统和C语言标准库中都规定了一些错误码,一般会放在errno.h这个头文件中说明,C语言程序启动后会有一个全局的errro变量来记录当前程序的错误码,当程序启动后无差错时,错误码errno为0。
当程序运行过程中出现错误,不同的错误会将不同的错误码存放在errno中。单凭错误码难以查明错误原因,而每一个错误码都会对应着有一个错误信息,该strerrno函数就是可以将错误码所对应的错误信息的字符串(的地址)返回。

使用变量errno必须要包含头文件#include <errno.h>

比如在文件操作中,如果文件不存在,以读的形式打开该文件,就会产生错误码:

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

int main()
{
	//以读的形式打开文件
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)//文件不存在,打开失败
	{
		printf("%d %s", errno, strerror(errno));
		return 1;
	}
	
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

在这里插入图片描述
我们也可以看看其他错误码所对应的字符串:

int main()
{
	int i = 0;
	for (i = 0; i < 20; i++)
	{
		printf("%d : %s\n", i, strerror(i));
	}
	return 0;
}

在这里插入图片描述

拓展:perror

如果打印错误信息需要printf和strerror函数,有点繁琐,所以有一个perror函数的作用相当于二者之和。

函数原型

void perror ( const char * str );

perror有直接打印error错误信息的能力

使用perror需要传一个字符串(任意),然后打印的时候会先打印该字符串,在打印冒号和空格,最后打印错误信息

不仅效果一样,而且还能添加字符串说明哪里出现的错误

int main()
{
	//以读的形式打开文件
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)//文件不存在,打开失败
	{
		perror("fopen");
		printf("%s", strerror(errno));
		return 1;
	}
	
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

在这里插入图片描述

该函数在后面的学习中,用来打印错误信息回经常使用到。

结语:C语言-- 字符串函数 章节到这里就结束了。
本人才疏学浅,文章中有错误和有待改进的地方欢迎大家批评和指正,非常感谢您的阅读!如果本文对您又帮助,可以高抬贵手点点赞和关注哦!

在这里插入图片描述

本文章已经生成可运行项目
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值