【C语言学习】字符串函数

前面介绍完字符函数,相信小伙伴们一定对其有了充分的认识,紧接着,我们所要学习的是字符串函数。在字符串函数中,我们主要学习strlen、strcpy、strcat、strcmp、strncpy、strncat、strncmp、strstr、strtok、strerror。

1. strlen函数

首先,我们要介绍的是strlen函数,在刚开始接触C语言的时候我们就简单介绍了strlen函数,并且还与sizeof关键字进行了对比:字符串、转义字符及注释
那么今天我们需要更进一步的学习,边总结,边挖深这个函数的用法。

1.1 strlen函数计算字符串长度

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

int main()
{
	//strlen-求字符串长度
	//字符串的结束标志是\0
	//strlen统计的是\0之前出现的字符的个数
	char arr1[] = "abcdef";
	size_t len1 = strlen(arr1);
	printf("%u\n", len1);
	
	//strlen函数要正确获得字符串的长度,字符串中必须得有\0
	char arr2[] = { 'a', 'b', 'c' };
	size_t len2 = strlen(arr2);
	printf("%u\n", len2);
	return 0;
}

运行结果:

1.2 要注意strlen的返回值类型是size_t

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

int main()
{
	//要注意strlen的返回值类型是size_t
	//		3					6		
	//-3<0,但是如果是无符号整型,就得 >= 0
	if (strlen("abc") - strlen("abcdef") > 0)
	{
		printf(">\n");
	}
	else
	{
		printf("<=\n");
	}
	return 0;
}

运行结果:

以上都是strlen函数的基本用法,我还需要掌握更进阶的用法—strlen函数的模拟实现!

1.3 strlen函数的模拟实现

方法一:

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

//仿照strlen函数的参数,返回类型,功能写一个类似的函数

//assert
//const
//*指针解引用
//指针+整数

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

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


运行结果:

方法二:

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

//指针-指针得到的是中间元素的个数
size_t my_strlen(const char* str)
{
	const char* start = str;
	while (*str != '\0')
	{
		str++;
	}
	const char* end = str;
	return end - start;
}

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


运行结果:

除了上面两种方法外,我们也可以采用递归的方法来模拟实现。

方法三:

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

//第三种实现方法 ---> 递归的方式
//模拟实现strlen,不创建临时变量

//递归的思想,大事化小
//
//my_strlen("abcdef")
//1 + my_strlen("bcdef")
//1 + 1 + my_strlen("cdef")
//1 + 1 + 1 + my_strlen("def")
//1 + 1 + 1 + 1 + my_strlen("ef")
//1 + 1 + 1 + 1 + 1 + my_strlen("f")
//1 + 1 + 1 + 1 + 1 + 1 + my_strlen(" ")
//1 + 1 + 1 + 1 + 1 + 1 + 0

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

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



运行结果:

2. strcpy函数

2.1 strcpy函数的使用

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

//strcpy函数
//函数功能:拷贝字符串
//注意事项:
//1.源字符串必须包含\0,同时\0也会被拷贝到目标空间
//2.程序员要保证目标空间要足够大,能放得下拷贝来的数据
//3.保证目标空间必须可以修改

int main()
{
	char arr1[] = "abcd";
	char arr2[] = "xxxx";
	strcpy(arr1, arr2);
	printf("%s\n", arr1);


	//char arr1[] = { 'a', 'b', 'c', 'd', '\0' };
	//char arr2[] = "xxxx";
	//strcpy(arr1, arr2);
	//printf("%s\n", arr1);
	//
	return 0;
}



运行结果:

另外,如果把内容拷贝到常量字符串,程序会发生崩溃

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

//strcpy函数
//函数功能:拷贝字符串
//注意事项:
//1.源字符串必须包含\0,同时\0也会被拷贝到目标空间
//2.程序员要保证目标空间要足够大,能放得下拷贝来的数据
//3.保证目标空间必须可以修改

int main()
{
	//char arr1[] = "abcd";
	//char arr2[] = "xxxx";
	//strcpy(arr1, arr2);
	//printf("%s\n", arr1);
	//

	//char arr1[] = { 'a', 'b', 'c', 'd', '\0' };
	//char arr2[] = "xxxx";
	//strcpy(arr1, arr2);
	//printf("%s\n", arr1);
	//

	//程序崩溃
	char arr1[] = "abcd";//—常量字符串来创建数组,本质是数组,数组内容是可以改变的
	char* p = "wertoe";//常量字符串—不可修改
	strcpy(p,arr1 );
	printf("%s\n", p);
	return 0;
}

运行结果:

2.2 strcpy函数模拟实现

方法一:

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

void my_strcpy(char* dest, char* src)
{
	//拷贝的是'\0'之前的字符
	while (*src != '\0')
	{
		*dest = *src;
		src++;
		dest++;
	}
	//拷贝'\0'字符
	*dest = *src;
}

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




运行结果:

方法二:

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

void my_strcpy(char* dest, char* src)
{
	//拷贝的是'\0'之前的字符
	while (*src != '\0')
	{
		*dest++ = *src++;
	}
	//拷贝'\0'字符
	*dest = *src;
}

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

	my_strcpy(arr2, arr1);
	printf("%s\n", arr2);
	return 0;
}




运行结果:

方法三:

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

void my_strcpy(char* dest, char* src)
{
	assert(dest);
	assert(src);
	//拷贝的是'\0'之前的字符
	while (*dest++ = *src++)
	{
		;
	}
}

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

	my_strcpy(arr2, arr1);
	printf("%s\n", arr2);
	return 0;
}

运行结果:

方法四:

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

//dest指向的空间是需要改变的,但是src指向的空间是不期望被改变的
void my_strcpy(char* dest, const char* src)
{
	assert(dest);
	assert(src);
	//拷贝的是'\0'之前的字符
	while (*dest++ = *src++)
	{
		;
	}
}

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

	my_strcpy(arr2, arr1);
	printf("%s\n", arr2);
	return 0;
}

其实是在方法三的基础上进行优化。

方法五:

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

char* my_strcpy(char* dest, const char* src)
{
	char* ret = dest;
	//NULL
	assert(dest);
	assert(src);
	//拷贝的是'\0'之前的字符
	while (*dest++ = *src++)
	{
		;
	}
	return ret;//目标空间的起始地址
}

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

	char* p = my_strcpy(arr2, arr1);
	//链式访问
	printf("%s\n", p);
	return 0;
}

运行结果:

3. strcat函数

3.1 strcat函数的使用

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

//strcat—字符串追加
//1.目标空间中得有\0(从哪里开始追加),源头字符串中得有\0(追加什么时候结束)
//2.目标空间要足够大,目标可以修改

int main()
{
	char arr1[20] = "hello ";
	char* p = "world";

	strcat(arr1, p);
	printf("%s\n", arr1);
	return 0;
}

运行结果:

3.2 strcat函数模拟实现

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

//模拟实现
char* my_strcat(char* dest, char* src)
{
	char* ret = dest;
	//NULL
	assert(dest&&src);
	//找到目标空间中的\0
	while (*dest != '\0')
	{
		dest++;
	}
	//2.拷贝数据
	while (*dest++ = *src++)
	{
		;
	}
	return ret;
}

int main()
{
	char arr1[20] = "hello \0xxxxxxxx";
	char* p = "world";

	my_strcat(arr1, p);
	printf("%s\n", arr1);
	return 0;
}

运行结果:

3.3 strcat函数自追加

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <assert.h>

//strcat函数自己给自己追加
char* my_strcat(char* dest, char* src)
{
	char* ret = dest;
	//NULL
	assert(dest&&src);
	//找到目标空间中的\0
	while (*dest != '\0')
	{
		dest++;
	}
	//2.拷贝数据
	while (*dest++ = *src++)
	{
		;
	}
	return ret;
}

int main()
{
	char arr1[20] = "hello \0xxxxxxxx";
	char* p = "world";

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

运行结果:

我们发现,程序会发生崩溃!
这是为什么呢?
其实很简单,我们可以用下面这幅图来解释:

当dest走到 ‘\0’ 的位置时,'\0’会被替换成a,而src永远比dest走得慢,src永远在dest的后面,这就会导致后面src永远也不会遇到 ‘\0’,就会导致死循环。
而要解决这个问题,就需要用到strncat函数,这个我们后面再讲。

4.strcmp函数

4.1 strcmp函数的使用

strcmp—string compare,比较字符串的大小
比较两个字符串中对应位置上的字符——按字典序比较

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <assert.h>

int main()
{
	//比较2个字符
	int ret = strcmp("abwdef", "abq");
	if (ret > 0)
	{
		printf(">\n");
	}
	else if (ret == 0)
	{
		printf("==\n");
	}
	else
	{
		printf("<\n");
	}//这里不能用ret == 1 / ret == -1来判断,因为strcmp函数return的是大于/小于0的随机数。

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

运行结果:

4.2 strcmp函数模拟实现

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <assert.h>

int my_strcmp(char* str1, char* str2)
{
	assert(str1);
	assert(str2);
	while (*str1 == *str2)
	{
		if (*str1 == '\0')
		{
			return 0;
		}
		str1++;
		str2++;
	}
	if (*str1 > *str2)
	{
		return 1;
	}
	else
	{
		return -1;
	}
	//return *str1 - *str2;
	//如果*str1 == *str2,就进入到循环里面去

}

int main()
{
	int ret = my_strcmp("abcdef", "abc");
	if (ret > 0)
	{
		printf(">\n");
	}
	else if (ret == 0)
	{
		printf("==\n");
	}
	else
	{
		printf("<\n");
	}
	printf("%d\n", ret);

	return 0;
}

运行结果:

5. strcpy、strcmp、strcat—长度不受限制的字符串函数,不关心目标空间放不放的下,存在安全性问题

这里我们以strcpy函数举例说明:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

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

	return 0;
}

运行结果:

我们发现,str2中的“abcdef”依然能够拷贝到str1中,但是拷贝以后程序就崩溃了!所以这些函数在一定程度上是不安全的,所以C语言中提供了一组带n的函数(strncpy,strncmp,strncat),这些函数在参数部分也多了一个参数,这些函数称为长度受限制的函数

其中,n表示num,表示拷贝、追加、比较的字节数。

6. strncpy函数

6.1 strncpy函数的使用

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

int main()
{
	char arr1[10] = "xxxxxxxxxx";
	char arr2[] = "ab";
	strncpy(arr1, arr2, 5);//不够的话就补\0
	printf("%s\n", arr1);
	return 0;
}

运行结果:

拷贝num个字符从源字符串到目标空间。如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加 ‘\0’,直到num个。

6.2 strncpy函数的模拟实现

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

char* my_strncpy(char* dest, const char* src, size_t num)
{
	assert(dest&&src);
	char* start = dest;//目标字符串的起始地址
	while ((*src !='\0')&&(*dest !='\0'))// 拷贝字符串
	{
		*dest = *src;
		dest++;
		src++;
		num--;
	}
	if (num) // 当num大于src的长度时,将补充空字符
	{
		while (num)
		{
			*dest++ = '\0';
			num--;
		}
	}
	return start;
}

int main()
{
	char arr1[10] = "xxxxxxxxxx";
	char arr2[] = "ab";
	char* ret = my_strncpy(arr1, arr2, 11);//不够的话就补\0
	printf("%s\n", ret);
	return 0;
}

运行结果:

7. strncat函数

7.1 strncat函数的使用

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

int main()
{
	char arr1[10] = "abc \0xxxxx";
	char arr2[] = "abc";
	strncat(arr1, arr2, 5);//将dest字符串最后的'\0'覆盖掉,字符追加完成后,再追加空字符,即'\0'
	//如果num大于字符串src的长度,那么会将src全部追加到dest的后面;
	//如果num小于字符串src的长度,仅将前num个字符追加在dest末尾
	printf("%s\n", arr1);

	return 0;
}

运行结果:

7.2 strncat函数模拟实现

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

char* my_strncat(char * dest, const char * src, size_t num)
{
	assert(dest);
	assert(src);
	char* destination = dest + strlen(dest);//目标地址
	size_t s = strlen(src);//src的长度
	size_t i = 0;
	if (num > strlen(src))
	{
		for (i = 0; i < s; i++)
		{
			*(destination++) = *(src++);
		}
		*(destination) = '\0';
	}
	else
	{
		for (i = 0; i < num; i++)
		{
			*(destination++) = *(src++);
		}
		*(destination) = '\0';
	}
	return dest;
}

int main()
{
	char arr1[10] = "abc \0xxxxx";
	char arr2[] = "abc";
	char* ret = my_strncat(arr1, arr2, 0);
	printf("%s\n", ret);

	return 0;
}

运行结果:

8. strncmp函数

8.1 strncmp函数的使用

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

比较str1和str2的前num个字符,如果相等就继续往后比较,最多比较num个字符,如果提前发现不⼀样,就提前结束,大的字符所在的字符串大于另外一个。如果num个字符都相等,就是相等,返回0。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

int main()
{
	char arr1[] = "abcwert";
	char arr2[] = "abcrytg";
	int ret = strncmp(arr1, arr2, 3);
	printf("%d\n", ret);
	return 0;
}

运行结果:

当比较3个字符的时候,return 0,即相等,那么比较4个字符的时候呢?

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

int main()
{
	char arr1[] = "abcwert";
	char arr2[] = "abcrytg";
	int ret = strncmp(arr1, arr2, 4);
	printf("%d\n", ret);
	return 0;
}

运行结果:

我们发现运行结果变为了1,即arr1 > arr2,这是由于 ‘w’ 的ASCII码值大于 ‘r’。

8.2 strncmp函数模拟实现

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

int my_strncmp(const char* des, const char* src, size_t num)
{
	assert(des && src);
	while (num--)
	{
		if (*des == *src)
		{
			if ((*des == '\0') || (*src == '\0'))
			{
				return 0;
			}
			des++;
			src++;
		}
		else if (*des > *src)
		{
			return 1;
		}
		else
		{
			return -1;
		}
	}
	return 0;
}

int main()
{
	char arr1[] = "abcwert";
	char arr2[] = "abcrytg";
	int ret = my_strncmp(arr1, arr2, 4);
	printf("%d\n", ret);

	return 0;
}

运行结果:

9. strstr函数

9.1 strstr函数的使用

strstr函数功能是在字符串中找寻另一个字符串。

Returns a pointer to the first occurrence of str2 in str1, or a null pointer if str2 is not part of str1.
The matching process does not include the terminating null-characters, but it stops there.
翻译过来就是:
返回一个指针(位置),这个指针是str2在str1中首次出现的位置,如果str2不在str1中就返回一个空指针。
这个匹配过程不包括终止空指针,但是是在空指针处终止。

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

int main()
{
	char arr1[] = "abcdefabcdef";
	char arr2[] = "defq";
	char* ret = strstr(arr1, arr2);
	if (ret == NULL)
	{
		printf("找不到\n");
	}
	else
	{
		printf("%s\n", ret);
	}
	return 0;
}

运行结果:

9.2 strstr函数模拟实现

思路分析:

完整代码:

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

const char* my_strstr(const char* str1, const char* str2)
{
	assert(str1);
	assert(str2);
	const char* cp = str1;
	const char* s1 = NULL;
	const char* s2 = NULL;

	//如果字符串是空字符串,直接返回str1
	if (*str2 == '\0')
	{
		return str1;
	}

	while (*cp)
	{
		s1 = cp;
		s2 = str2;
		while (*s1 == *s2 && *s1 != '\0' && *s2 != '\0')
		{
			s1++;
			s2++;
		}
		if (*s2 == '\0')
		{
			return cp;
		}
		cp++;
	}
	return NULL;
}

int main()
{
	char arr1[] = "abcdefabcdef";
	char arr2[] = "defq";
	char* ret = my_strstr(arr1, arr2);
	if (ret == NULL)
	{
		printf("找不到\n");
	}
	else
	{
		printf("%s\n", ret);
	}
	return 0;
}

运行结果:

我们最好把const 修饰的指针传给const修饰的指针变量,不然编译器会报警告,如果把const修饰的指针交给非const修饰的指针,数据就变得不安全了。

10. strtok函数的使用

对于strtok函数,我们只需要学会它的用法即可,对于模拟实现我们不需要掌握。如果小伙伴们感兴趣的话,也可以自己去尝试模拟实现它,具体的模拟实现过程,我们后面再讲解。

在C语言库中,strtok函数定义为字符串分解函数,str 为一组字符串,delimiters 为分隔符。
该函数返回被分解的第一个子字符串,如果没有可检索的字符串,则返回一个空指针。

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

int main()
{
	char str[] = "192.168.6.111";
	char* sep = ".";
	char *token = NULL;

	//获取第一个子字符串
	token = strtok(str, sep);

	//继续获取其他的子字符串 
	while (token != NULL) {
		printf("%s\n", token);

		token = strtok(NULL, sep);
	}
	return 0;
}

运行结果:

11. strerror函数

11.1 strerror函数的使用

返回一个错误码所对应的错误信息字符串的起始地址。
在C语言的库函数中,我们设计了一些错误码,当我们库函数在调用的过程中发生了各种错误,要记录下来,这时候记录的就是错误码。
0 ——> no error
1 ——> 错误信息1
2 ——> 错误信息2
3 ——> 错误信息3

举例:

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

int main()
{
	int i = 0;
	for (i = 0; i < 10; i++)//打印10个错误码对应的信息
	{
		char* ret = strerror(i);
		printf("%d: %s\n", i, ret);
	}
	return 0;
}

运行结果:

0:没有错误
1:命令不被允许
2:没有这个文件或路径
3:没有这个进程
4:函数调用被中断
5:输入/输出错误
6:没有这个设备或地址
7:列表太长(参数过多)
8:格式错误
9:坏文件描述符

当库函数调用失败的时候,会将错误码记录到errno这个变量中,errno是一个全局变量。
举例:打开文件—读写文件前,需要打开文件。
读写文件前,需要打开文件,如果打开成功,需要文件是存在的,如果文件不存在,则打开失败,fopen返回NULL。

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

int main()
{
	FILE* pf = fopen("add.text", "r");
	if (pf == NULL)
	{
		printf("打开文件失败,失败的原因:%s\n", strerror(errno));
	}
	else
	{
		printf("打开文件成功\n");
	}
	return 0;
}

运行结果:

除了strerror函数外,我们还有一个叫做perror的函数,perror函数相当于⼀次将上述代码中的第10行完成了,直接将错误信息打印出来。

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

int main()
{
	FILE* pf = fopen("add.text", "r");
	if (pf == NULL)
	{
		perror("打开文件失败,失败的原因:");
	}
	else
	{
		printf("打开文件成功\n");
	}
	return 0;
}

运行结果:

好了,以上就是本期博客的全部内容了,创作不易,希望小伙伴们多多点赞支持。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值