字符函数和字符串函数


大家好!这里是笨鸟先飞,这次为大家介绍一下C语言中经常使用的字符函数和字符串函数

C语言对字符和字符串的使用很频繁,但是C语言本身是没有字符串类型的,字符串通常放在常量字符串中或者字符数组中。

字符串常量适用于那些对他不做修改的字符串函数数

1.函数介绍

1.1strlen

>size_t strlen(const char *str)
  • 使用时头文件不能忘记 #include<string.h>

  • 字符串以‘\0’作为结束标志,strlen函数返回的是在字符串中‘\0’前面出现的字符个数不包括‘\0’

  • 参数指向的字符串必须要以‘\0’结束。

  • 该函数的返回值是size_t,是无符号的

演示strlen的使用:

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
//使用时需要包含头文件
#include <string.h>
int main()
{
	char arr[] = "hello word!";
	int sz =strlen(arr);
	printf("%d\n", sz);
    //链式访问输出
    //printf("%d\n", strlen(arr));
	return 0;
}
//最后输出得到的字符个数是11

模拟实现strlen

//第一种方法
#include <assert.h>
int my_strlen(const char *pc)//我们只是用来求长度,并不改变他的内容,所以使用const
{
	//断言
	assert(pc);
	//用来计数
	int k = 0;
	//当*pc的值不为0时,++找到下一个元素知道*pc为'\0'时停止
	while (*pc)
	{
		pc++;
		k++;
	}
	return k;
}
int main()
{
	char arr[] = "hello word";
	//求数组元素的个数包括了'\0'
	int sz=sizeof(arr) / sizeof(arr[0]);
	//使用sizeof时'\0'的长度不计算进来
	int k=my_strlen(arr);
	printf("%d\n", sz);
	printf("%d\n", k);
}
//第二种方法
//不能创建临时变量计数器
int my_strlen(const char * str)
{
 if(*str == '\0')
 return 0;
 else
 return 1+my_strlen(str+1);
}

image-20221003172957240

1.2strcmp

int strcmp(const char *str1, const char *str2)
  • str1 – 要进行比较的第一个字符串。
  • str2 – 要进行比较的第二个字符串。
  • 如果返回值小于 0,则表示 str1 小于 str2。
  • 如果返回值大于 0,则表示 str1 大于 str2。
  • 如果返回值等于 0,则表示 str1 等于 str2。

演示strcmp的使用:

image-20221003174542636

模拟实现strcmp:

#include <string.h>
#include <assert.h>
int my_strcmp(const char* str1, const char* str2)
{
    //断言,穿空指针时系统会崩溃
	assert(str1 && str2);
	while (*str1 == *str2)
	{
		if (*str1 == '\0')
			return 0;
		str1++;
		str2++;
	}
	return *str1 - *str2;
}
int main()
{
	char arr1[] = "abcdefg";
	char arr2[] = "hello bit";
	int sz = my_strcmp(arr1, arr2);
	printf("%d\n", sz);
	return 0;
}

1.3strcpy

char *strcpy(char *dest, const char *src)
  • 源字符串必须以 ‘\0’ 结束。
  • 会将源字符串中的 ‘\0’ 拷贝到目标空间。
  • 目标空间必须足够大,以确保能存放源字符串。
  • 目标空间必须可变。

演示strcpy的使用:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LGz4DqP4-1665664842921)(./image-20221006215003037.png)]

模拟实现strcpy:

//char*为返回类型,可以实现链式访问
char* my_strcpy(char* dest, const char* src)
{
	char* pc = dest;//用一个指针变量来存储数组arr1的起始位置
	while (*dest++ = *src++)
	{
		;
	}
	return pc;
}
int main()
{
	char arr1[20] = "abcdef";
	char arr2[] = "oiuswa";
	my_strcpy(arr1, arr2);
	printf("%s\n", arr1);
	return 0;
}

1.4strcat

char *strcat(char *dest, const char *src)
  • dest – 指向目标数组,该数组包含了一个 C 字符串,且足够容纳追加后的字符串。
  • src – 指向要追加的字符串,该字符串不会覆盖目标字符串。
  • 源字符串必须以’\0’结束
  • 目标空间必须有足够的大,能容纳下源字符串的内容
  • 目标空间必须可修改
  • 字符串能否自己追加自己?
  • 头文件 #include <string.h>

演示实现strcat:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jKDrSyWA-1665664684548)(./image-20221006221428223.png)]

模拟strcat的使用:

#include <string.h>
#include <assert.h>
char* my_strcat(char* dest, const char* src)
{
	assert(dest && src);
	char* pc = dest;
	while (*dest)
	{
		dest++;
	}
	while (*dest++ = *src++)
	{
		;
	}
	return pc;
}
int main()
{
	char arr1[50] = "abcde";
	char arr2[] = "poiu";
	my_strcat(arr1, arr2);
	printf("%s", arr1);
	return 0;
}

接下来我们谈谈strcat追加自身的问题

strcat可以追加自身吗?大家可以想一想
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PltUgKZO-1665664684549)(./image-20221006223850793.png)]

通过编程我们发现,strcat在追加自身时,程序会挂掉,实际上追加自身会造成死循环。这是为什么呢?

我们画图理解一下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y1qHJ8GF-1665664684549)(./image-20221006224932359.png)]

指针dest找到\0后停止,然后src用b覆盖,导致源数据被更改,所以这里会造成死循环。

我们得出结论strcat不能追加自身,否则会造成死循环

1.5strncpy

char *strncpy(char *dest, const char *src, size_t n)
  • dest – 指向用于存储复制内容的目标数组。
  • src – 要复制的字符串。
  • n – 要从源中复制的字符数。
  • 拷贝n个字符从源字符串目标空间
  • 如果源字符串的长度小于n,则拷贝完源字符串之后,在目标的后边追加0,直到n个。
  • 头文件 #include <string.h>

演示实现strncpy:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DEfBpmIz-1665664684550)(./image-20221006225943728.png)]

模拟实现strncpy:

#include <string.h>
#include <assert.h>
char* my_strncpy(char* dest, const char* src, size_t n)
{
	//断言
	assert(dest && src);
	char* pc = dest;
	while (n--)
	{
		*dest = *src;
		src++;
		dest++;
	}
	return pc;
}
int main()
{
	char arr1[50] = "bit";
	char arr2[] = "pouces";
	my_strncpy(arr1, arr2, 3);
	printf("%s", arr1);
    //链式访问
    //printf("%s", my_strncpy(arr1, arr2, 3));
	return 0;
}

1.6strncat

char *strncat(char *dest, const char *src, size_t n)
  • dest – 指向目标数组,该数组包含了一个 C 字符串,且足够容纳追加后的字符串,包括额外的空字符。
  • src – 要追加的字符串。
  • n – 要追加的最大字符数。

演示strcat的使用:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ugwWbZco-1665664684550)(./image-20221007215936439.png)]

模拟实现strncat:

char* my_strncat(char* dest, const char* src, size_t n)
{
	char* str = dest;
	while (*dest)
	{
		dest++;
	}
	while (n--)
	{
		*dest++ = *src++;
	}
	return str;
}
int main()
{
	char arr1[50] = "bitoudes";
	char arr2[] = "yeson";
	//实现链式访问
	printf("%s\n", my_strncat(arr1, arr2, 3));
	return 0;
}

1.7strncmp

int strncmp(const char *str1, const char *str2, size_t n)
  • str1 – 要进行比较的第一个字符串。
  • str2 – 要进行比较的第二个字符串。
  • n – 要比较的最大字符数。
  • 比较到出现另个字符不一样或者一个字符串结束或者n个字符全部比较完。

演示使用:strncmp

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZsV6fygJ-1665664684551)(./image-20221007220704496.png)]

在C语言中,字符串之间的比较是需要用strcmp来进行比较的,而不能用大于符号和小于符号。strncmpstrcmp的基础上可以指定比较几个字符。通过运行我们发现,当比较2个字符时,返回0表示前俩个字符相等,当比较三个字符时,返回-1就表示arr2>arr1。

模拟实现strncmp:

#include <string.h>
int my_strncmp(const char* str1, const char* str2, size_t n)
{
	while (n--)
	{
		if (*str1 == *str2)
		{
			if (*str1 == '\0' || n == 0)
			{
				return 0;
			}
			str1++;
			str2++;
		}
		else
			return *str1 - *str2;//做差的是字符ASCII的值大小
	}
}
int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "abnoexcdf";
    int sz=my_strncmp(arr1, arr2, 2);
	printf("%d\n", sz);
	return 0;
}

1.8strstr

char *strstr(const char *haystack, const char *needle)
  • haystack – 要被检索的 C 字符串。
  • needle – 在 haystack 字符串内要搜索的小字符串。

演示strstr的使用:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-676BLiJ6-1665664684551)(./image-20221010123854807.png)]

我们可以看到这个函数找到字串后会从找到的字串一直输出直到找到’\0’为止,所以这里将WORD也打印在了屏幕上,如果在arr1中找不到arr2字串呢?那么这个函数将会返回一个空指针(null)。了解基本原理之后我们来模拟实现一下这个函数。

char* my_strstr(const char* str1, const char* str2)
{
  const char* s1 = str1;
  const char* s2 = str2;
  const char* p = str1;//用p指针记录s1每次走的位置
    while (*p)
    {
        s1 = p;
        s2 = str2;
        while (*s1 != '\0' && *s2 != '\0' &&(*s1==*s2))
        {
            s1++;
            s2++;
        }
        if (*s2 == '\0')
        {
            return (char*)p;
        }
        p++;
    }
    return NULL;
}

int main()
{
    char arr1[] = "HEELLO WORD";
    char arr2[] = "LLC";
    char* ret = my_strstr(arr1, arr2);
    printf("%s\n", ret);
    //链式访问
    printf("%s\n", my_strstr(arr1, arr2));
    return 0;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KUXx4dnL-1665664684552)(./image-20221013153642399.png)]

1.9strtok

char *strtok(char *str, const char *delim)
  • str – 要被分解成一组小字符串的字符串。
  • delim – 包含分隔符的 C 字符串。
  • 第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记。
  • strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注: strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容 并且可修改。)
  • strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串 中的位置。
  • strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标 记。
  • 如果字符串中不存在更多的标记,则返回 NULL 指针。

演示使用strtok:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dBTGcFG0-1665664684552)(./image-20221013160133492.png)]

这个strtok函数我们可以这样理解,当找到要包含的C字符串时,strtok会返回一个指向这个标记的指针保存这个位置,标记后当下次在传一个空指针时会找这个位置,从这个位置开始寻找下一个要包含的C字符串。

当寻找不到下一个特殊字符后,会返回一个空指针。

1.10strerror

char * strerror ( int errnum )
  • errnum – 错误号,通常是 errno

演示使用:strerror

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3OxXVqeF-1665664684553)(./image-20221013163305320.png)]

这里会提示,系统提示没有这样的文件或目录。

1.11memcpy

void * memcpy ( void * destination, const void * source, size_t num )
  • 函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。
  • 这个函数在遇到 ‘\0’ 的时候并不会停下来。
  • 如果source和destination有任何的重叠,复制的结果都是未定义的。

演示使用:memcpy

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-891QBhOs-1665664684554)(./image-20221013172725175.png)]

这里我们先使用memcpy将数组myname中的字符拷贝到结构体第一个元素的name下,然后就46的值给年龄,接着在拷贝一个结构体大小将person中存放的字符串拷贝到person_copy.

模拟实现:memcpy

#include <stdio.h>
#include <string.h>
#include <assert.h>
struct {
	char name[40];
	int age;
} person, person_copy;
void* my_memcpy(void* dst, const void* src, size_t count)
{
	assert(dst && src);
	void* ret = dst;
	while (count--)
	{
		*(char*)dst = *(char*)src;
		dst = (char*)dst + 1;//void*类型无法进行++操作,这里用了+1来代替
		src = (char*)src + 1;
	}
    return ret;
}

int main()
{
	char myname[] = "Pierre de Fermat";
	/* using memcpy to copy string: */
	my_memcpy(person.name, myname, strlen(myname)+1);
	person.age = 46;
	/* using memcpy to copy structure: */
	my_memcpy(&person_copy, &person, sizeof(person));
	printf("person_copy: %s, %d \n", person_copy.name, person_copy.age);
	return 0;
}
//注当两个字符串位于同一空间拷贝时可能重合问题,接下来的memmove可以解决这个问题

1.12memmove

void * memmove ( void * destination, const void * source, size_t num );
  • 和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。
  • 如果源空间和目标空间出现重叠,就得使用memmove函数处理。

演示使用:memmove

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-chct0EAq-1665664684554)(./image-20221013180116655.png)]

模拟实现:memmove

#include <assert.h>
void* my_memmove(void* dst, const void* src, size_t num)
{
	assert(dst && src);
	char* ret = dst;
	if (dst < src)//从前-->后
	{
		while (num--)
		{
			*(char*)dst = *(char*)src;
			dst = (char*)dst + 1;//void*类型无法进行++操作,这里用了+1来代替
			src = (char*)src + 1;
		}
	}
	else
	{
		while (num--)
		{
			*((char*)dst + num) = *((char*)src + num);
		}
	}
	return ret;
}
int main()
{
	char arr[] = "abcdeflong";
	my_memmove(arr + 3,arr,5);
	printf("%s\n", arr);
	return 0;
}

注意在模拟实现memmove时会出现两种清空,到底从前往后拷贝还是从后往前拷贝这里我用画图为大家分析了一下。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KpfNMk7r-1665664684555)(./image-20221013184256915.png)]

最后我们得到结论:**有时候需要向前拷贝,有时候需要向后拷贝。**根据我们src与dst的位置来决定。

1.13memcmp

const void * ptr1, const void * ptr2,  size_t num );
  • str1 – 指向内存块的指针。
  • str2 – 指向内存块的指针。
  • n – 要被比较的字节数。
  • 如果返回值 < 0,则表示 str1 小于 str2。
  • 如果返回值 > 0,则表示 str1 大于 str2。
  • 如果返回值 = 0,则表示 str1 等于 str2

演示使用:memcmp

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q2QY4h0v-1665664684555)(./image-20221013185759355.png)]

模拟实现:memcmp

#include <assert.h>
int my_memcmp(const void* ptr1, const void* ptr2, size_t num)
{
	assert(ptr1 && ptr2);
	while (num--)
	{
		if (*(char*)ptr1 != *(char*)ptr2)
		{
			
			return *(char*)ptr1 - *(char*)ptr2;
		}
		ptr1 = (char*)ptr1 + 1;
		ptr2 = (char*)ptr2 + 1;
	}
	return 0;
}
int main()
{
	char arr1[] = "ooudce";
	char arr2[] = "oouace";
	int sz=my_memcmp(arr1, arr2, 4);
	printf("%d\n", sz);
	return 0;
}

这个模拟实现就比较简单了,这里比较巧妙的一步就行直接return他们的差值实际上是字符ASCll的差值。

字符分类函数:

函数如果它的参数符合下列条件就返回真
iscntrl任何控制字符
isspace空白字符:空格‘ ’,换页‘\f’,换行’\n’,回车’\r’,制表符’\t’,或垂直制表符’\v’
isdigit十进制数字0~9
isxdigit十六进制数字,包括所有十进制数字,小写字母af,大写字母AF
islower小写字母a~z
isupper大写字母A~Z
isalpha字母az或AZ
isalnum字母或数字az,AZ或0~9
ispunct标点符号,任何不属于数字或字母的图像字符(可打印符号)
ispunct标点符号,任何不属于数字或字母的图像字符(可打印符号)
isgraph任何图像字符
isprint任何可打印字符,包括图像字符和空白字符

博客到这里就差不多结束了,也就很久没更新了。不知道大家最近有没有在学习呢?一起加油把!!!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值