C语言中字符串库函数

C语言中,本身没有字符串类型的,字符串通常以字符数组和常量字符串的形式出现。

而有一些库函数可以对字符串进行操作,使我们对字符串的处理可以简单许多,但是注意的是:这些库函数不可以修改常量字符串


1.求字符串长度

strlen

size_t strlen ( const char * str );
  • strlen函数计算的是字符串中'\0'前面出现的字符个数,不包含'\0'
  • 参数指向的字符串一定要以'\0'结尾,否则会计算出随机值
  • strlen是求字符串长度的,求出的长度是不可能为负数,所以返回size_t类型的值,size_t其实就是无符号的整形unsigned int

由于strlen返回无符号整形,所以这里是一个易错点,以接下来的代码为例

#include <stdio.h>
#include <string.h>
int main()
{
    
    
	char str1[] = "abcdefg";
	char str2[] = "abcdefghijk";
	
	if (strlen(str1) - strlen(str2) < 0)
		printf("str1长度小于str2长度");
	else
		printf("str1长度大于str2长度");

	return 0;
}

str1长度为7,str2长度为11,7-11 = -3,但是这里的7和11是无符号整形,他们俩相减会得到一个非常大的数,而不是-3,所以这个程序会输出str1长度大于str2长度


模拟实现strlen

这里有三种方法进行模拟实现

第一种:常规方法:

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

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

第二种:递归:

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

int my_strlen2(const char* str)
{
    
    
	assert(str != NULL);
	if (*str != '\0')
	{
    
    
		return 1 + my_strlen2(str+1);
	}
	else
	{
    
    
		return 0;
	}
}

第三种:指针相减:

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

int my_strlen3(const char* str)
{
    
    
	assert(str != NULL);
	const char* start = str;
	while (*str != '/0')
	{
    
    
		str++;
	}
	return str - start;
}

两个指向同一块空间的两个指针,两个指针相减得到这两个指针间的元素的个数


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

strcpy

char* strcpy(char * destination, const char * source );
  • 这个函数的作用是将source中的字符串拷贝到空间destination
  • destination是目标空间,函数将复制的字符串放到这个目标空间中
  • source是源字符串,函数会复制源字符串,因为只是对源字符串进行复制,并不会改变它,所以可以将源字符串写成const
  • 目标空间必须足够大,以确保能存放源字符串。
  • 目标空间必须可变。
  • 源字符串必须以 ‘\0’ 结束,因为源字符串读到\0就停止拷贝。
  • 函数会将源字符串中的 ‘\0’ 拷贝到目标空间。

strcpy的使用:

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

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

如果在源字符串中提前放一个\0,那么函数只会拷贝到\0

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

int main()
{
    
    
	char arr1[20] ="xxxxxxx";
	char arr2[] = "ab\0cdef";
	strcpy(arr1, arr2);
	return 0;
}

这里可以看到,在arr2ab后面放了一个\0,则在函数拷贝时只会拷贝ab\0

我们可以调试函数看一下:拷贝前的arr1
在这里插入图片描述
拷贝后的arr1

可以看到,只拷贝了ab\0


模拟实现strcpy

最常规写法:

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

char* my_strcpy(char* destination, const char* source)
{
    
    
	char* ret = destination;
	assert(destination && source );
	while (*source!='\0')
	{
    
    
		*destination =  *source;
		destination++;
		source++;
	}
	*destination = *source;//拷贝\0
	return ret;
}

这么要注意的一点就是,strcpy拷贝过程中会源字符串中最后的\0,但是如果以while (*source!='\0')为循环条件的话,最后的\0就不会被拷贝
所以要在循环外部额外再拷贝一次*destination = *source;

简化一点:

#include <stdio.h>
#include<assert.h>
#include <string.h>
char* my_strcpy(char* destination, char* source)
{
    
    
	char* ret = destination;
	assert(destination && source );
	while (*src != '\0')
	{
    
    
		*destination++ = *source++;
	}
	*destination = *source;//拷贝\0
	return ret;
}

再简化:

前面的两种写法都需要在循环外部额外再对\0进行拷贝,而下面的写法直接将\0的拷贝也放到了循环中进行

#include <stdio.h>
#include<assert.h>
#include <string.h>
char* my_strcpy(char* destination,const char* source)
{
    
    
	char* ret = destination;
	assert(destination && source );
	while (*destination++ = *source++)
	{
    
    
		;
	}
	return ret;
}

strcat

char * strcat ( char * destination, const char * source );
  • 这个函数的作用是将source中的字符串追加到destination的后面
  • 目标空间要有\0结尾,因为要直到从哪开始追加
  • 源字符串要有\0结尾,因为要知道何时结束追加
  • 目标空间必须有足够的大,能容纳下源字符串的内容。
  • 目标空间必须可修改。

strcat的使用:

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

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

}

当一个字符串,自己对自己追加的时候会有问题:
以字符串abcdef追加自己为例

在这里插入图片描述
从\0开始追加,a追加到f,本来f后面是\0,到了\0就停止追加,但是前面追加时已经就将\0覆盖了,所以不会停止
在这里插入图片描述

所以当一个字符串自己追加自己,会发生死循环


模拟实现strcat

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

char* my_strcat(char* des, char* src)
{
    
    
	assert(des && src);
	char* ret = des;
	while (*des != '\0')
	{
    
    
		des++;
	}
	while (*des++ = *src++)
	{
    
    
		;
	}
	return ret;
}

strcmp

int strcmp ( const char * str1, const char * str2 );
  • 标准规定(在非VS环境中):

    • 第一个字符串大于第二个字符串,则返回大于0的数字
    • 第一个字符串等于第二个字符串,则返回0
    • 第一个字符串小于第二个字符串,则返回小于0的数字
  • 而在VS环境中:

    • 第一个字符串大于第二个字符串,则返回1
    • 第一个字符串等于第二个字符串,则返回0
    • 第一个字符串小于第二个字符串,则返回-1

对于两个字符串的大小,比较的并不是字符串的长度,而是一个一个比较组成字符串的字符,字符对应ASCII码值大的字符大。例如:'z'大于'a'


模拟实现strcmp

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

int my_strcmp(char* str1,char* str2)
{
    
    
	assert(str1 && str2);
	while (*str1 == *str2)
	{
    
    
		if (*str1 == '\0')
		{
    
    
			return 0;
		}
		str1++;
		str2++;
	}
	//VS环境下:
	if (*str1 > *str2)
		return 1;
	else
		return -1;

	//非VS环境下:
	//return *str1-*str2;
}

3.长度受限制的字符串函数介绍

上面介绍的都是没有长度限制字符函数,它们都是进行到\0就停止,而接下来的三个函数有长度限制

strncpy

char * strncpy ( char * destination, const char * source, size_t num );
  • strncpystrcpy的区别就是:strcpy会将源字符串一直拷贝到\0,而strncpy会拷贝前num个字符
  • strncpy是不会在拷贝后自动加\0
//验证strncpy是不会在拷贝后自动加'\0'的
#include <stdio.h>
#include <string.h>

int main()
{
    
    
	char arr1[20] = "xxxxxxx";
	char arr2[] = "abcdef";
	strncpy(arr1, arr2,2);
	return 0;
}

arr2前2个字符拷贝到arr1中,在监视中看到:
在这里插入图片描述
所以可知:strncpy不会自动添加\0

  • num比源字符串要长,多出来的部分用\0来补
//验证num比源字符串要长,多出来的部分用\0来补
#include <stdio.h>
#include <string.h>

int main()
{
    
    
	char arr1[20] = "xxxxxxxxxxxxx";
	char arr2[] = "abc";
	strncpy(arr1, arr2,6);
	return 0;
}

在这里插入图片描述


模拟实现strncpy

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

char* my_strncpy(char* str1, char* str2, int n)
{
    
    
	char* ret = str1;
	assert(str1 && str2);
	if (n < 0) return;
	while (n>0)
	{
    
    
		if (*str2 == '\0')
		{
    
    
			*str1 = '\0';
			str1++;
			n--;
		}
		else
		{
    
    
			*str1 = *str2;
			str1++;
			str2++;
			n--;
		}
	}
	return ret;
}

这里模拟实现时,要注意当num大于源字符串长度时需要补\0这点,其余地方很简单


strncat

char * strncat ( char * destination, const char * source, size_t num );
  • source字符串中的前num个字符追加到destination后面
  • 追加后,函数会在追加后的destination后面自动加\0
  • 如果num大于source的大小,则多余的部分补\0

模拟实现strncat

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

char* my_strncat(char* str1, char* str2, int n)
{
    
    
	char* ret = str1;
	assert(str1 && str2);
	if (n < 0) return;
	while (*str1 != '\0')
	{
    
    
		str1++;
	}
	while (n > 0)
	{
    
    
		if (*str2 == '\0')
		{
    
    
			*str1 = '\0';
			str1++;
			n--;
		}
		else
		{
    
    
			*str1 = *str2;
			str1++;
			str2++;
			n--;
		}
		
	}
	*str1 = '\0';
	return ret;
}

strncmp

int strncmp ( const char * str1, const char * str2, size_t num );
  • 比较到出现另个字符不一样或者一个字符串结束或者num个字符全部比较完。
  • 返回值的情况与strcmp相同

模拟实现strncmp

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

int my_strncmp(char* str1, char* str2, int n)
{
    
    
	assert(str1 && str2);
	if (n < 0) return;
	while (n > 0)
	{
    
    
		while (*str1 == *str2)
		{
    
    
			if (n == 1)
			{
    
    
				return 0;
			}
			str1++;
			str2++;
			n--;
		}
		if (*str1 > *str2)
			return 1;
		else
			return -1;
	}
}

4.字符串查找

strstr

char * strstr ( const char *str1, const char * str2);
  • str1字符串中查找str2字符串
  • 返回str2str1中出现的一个位置的地址

模拟实现strstr

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

void* my_strstr(char* str1, char* str2)
{
    
    
	assert(str1 && str2);
	if (*str2 == '0')
	{
    
    
		return (char*)str1;
	}
	const char* s1 = str1;
	const char* s2 = str2;
	const char* cp = str1;
	while (*cp)
	{
    
    
		s1 = cp;
		s2 = str2;
		while (*s1 != '\0' && *s2 != '\0' && *s1 == *s2)
		{
    
    
			s1++;
			s2++;
		}
		if (*s2 == '0')
		{
    
    
			return (char*)cp;
		}
		cp++;
	}
	return NULL;
}

strtok

char * strtok ( char * str, const char * sep );
  • sep参数是个字符串,定义了用作分隔符的字符集合
  • 第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记。
#include <stdio.h>
#include <string.h>

int main()
{
    
    
	char arr1[] = "abc#defa|gh";
	char arr2[] = "#|";
	strtok(arr1,arr2);
}

有一个字符串abc#defa|gh,可以看出它被字符#|分隔开,strtok的作用是取出被字符#|分隔开的每块字符串

  • strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。)
#include <stdio.h>
#include <string.h>
int main()
{
    
    
	char arr1[] = "abc#defa|gh";
	char arr2[] = "#|";
	char arr3[20] = {
    
     0 };
	strcpy(arr3, arr1);//防止strtok直接修改被操作的字符串,所以需要拷贝一份
	char* ret = strtok(arr3, arr2);
	printf("%s", ret);
	return 0;
}

取出第一块被分隔的字符串abc并且将#改为\0,此时的arr3abc\0defa|ghretabc的地址,输出ret为:
在这里插入图片描述

  • strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。
  • strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
  • 如果字符串中不存在更多的标记,则返回 NULL 指针。
#include <stdio.h>
#include <string.h>
int main()
{
    
    
	char arr1[] = "abc#defa|gh";
	char arr2[] = "#|";
	char arr3[20] = {
    
     0 };
	strcpy(arr3, arr1);//防止strtok直接修改被操作的字符串,所以需要拷贝一份
	char* ret = strtok(arr3, arr2);
	printf("%s\n", ret);
	ret = strtok(NULL, arr2);//为了取出剩下部分的字符串,第一个参数需要改为NULL
	printf("%s\n", ret);
	ret = strtok(NULL, arr2);
	printf("%s\n", ret);
	return 0;
}

如果一个字符串被某些字符分隔为很多部分,如果我们一个一个地取出就会使代码冗长,这里我们可以使用循环解决

#include <stdio.h>
#include <string.h>
int main()
{
    
    
	char arr1[] = "abc#defa|gh";
	char arr2[] = "#|";
	char arr3[20] = {
    
     0 };
	strcpy(arr3, arr1);
	char* ret = NULL;
	for (ret = strtok(arr3, arr2); ret != NULL; ret = strtok(NULL, arr2))
	{
    
    
		printf("%s\n", ret);
	}
	return 0;
}

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


5.错误信息报告

strerror

char * strerror ( int errnum );
  • C语言的库函数在运行的时候,如果发生一些错误,就会将错误码放到一个变量中:erron
  • 错误码是一些数字:0 1 2 3 4 5……
  • strerror就是返回这些错误码对应的字符串的首字符地址

我们可以看一下这些错误码对应的错误信息

int main()
{
    
    
	printf("%s\n", strerror(0));
	printf("%s\n", strerror(1));
	printf("%s\n", strerror(2));
	printf("%s\n", strerror(3));
	printf("%s\n", strerror(4));
	printf("%s\n", strerror(5));
	return 0;
}

在这里插入图片描述
当然,平常使用时并不需要我们将具体数字传入函数中

我们通常将变量errno传入函数中strerror(errno)
而这个errno变量在头文件errno.h中,使用前必须添加这个头文件。

另外还有一个perror函数,使用这个函数,可以直接将错误信息打印出来,并且如果perror中传了字符串,这个函数会先将传过去的字符串打印出来,再打印一个冒号,最后打印错误信息


猜你喜欢

转载自blog.csdn.net/weixin_64116522/article/details/128765544