字符串函数和内存函数详解(带有部分函数的模拟实现)

用c语言编程时,我们可以自定义函数,还可以使用c数据库中提供的各式各样的函数,本期介绍c语言中的字符函数和内存函数


字符串函数:

字符串函数和内存函数都包含在 <string.h>头文件当中

#include <string.h>

strlen

我们常常为了完成某项特定任务或解决某项特定问题时,自定义函数来解决问题,此时,函数名就代表了一切,

含义:

strlen前部分是string的缩写后部分是lenth的缩写,所以此函数就是用来求字符串长度的;

用法:

通过查阅cplusplus.icon-default.png?t=N176https://legacy.cplusplus.com/reference/cstring/strlen/?kw=strlen就可以看到关于strlen函数的详解;

我们看到该函数的返回值为size_t,函数参数为const char* ,并且求得的是'\0'之前字符串的长度

知道了函数的参数和返回值,我们就可以调用此函数

	char str[] = "abcdef";
	ret = strlen(str);

 str中存放的是:a b c d e f \0

\0之前有6个字符所以打印出的结果也是6;

strlen模拟实现

计数器方式:

int my_strlen(char* str)
{
	int len = 0;
	while (*str != '\0')
	{
		str++;
		len++;
	}
	return len;
}//计数器

计数器的方式非常简单,遍历字符串的每一个字符,直到遇到\0时停止;

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

int my_strlen2(char* str)
{
	if (*str != '\0')
	{
		return my_strlen2(str + 1) + 1;
	}
	else
	return 0;
}//递归(不创建临时变量)

和计数器的解题思路差不多,就是遍历字符串的内容,不是\0就+1继续遍历下一个字符

指针-指针:

int my_strlen3(char* str)
{
	char* p = str;
	while (*p != '\0')
	{
		p++;
	}
	return p - str;

}//指针-指针

指针±整数 = 指针;那么指针±指针= 整数;

让指针一直++到\0之前,然后再用当前指向\0之前的指针减去指向首元素的指针,就可以求出两指针之间的元素个数;

strcpy

字符串拷贝函数;

用法:

通过查阅https://legacy.cplusplus.com/reference/cstring/strcpy/?kw=strcpyicon-default.png?t=N176https://legacy.cplusplus.com/reference/cstring/strcpy/?kw=strcpy

 我们了解到strcpy函数的返回参数是char*,函数参数是char* const char*;

返回的是目的地函数的首地址,既然是拷贝,那么只改变目的地所指向的内存空间的内容就可以了,拷贝源头的字符串内容不会改变,

strcpy函数会将源头字符串的所有内容都拷贝到目的地中,包括\0;

注:目标字符串必须拥有足够大的空间来存放拷贝内容

	char str1[] = "abcdef";
	char str2[20] = { 0 };
	printf("%s\n", strcpy(str2, str1));

strcpy模拟实现

char* my_strcpy(char* dest, const char* src)
{
	assert(dest && src);
	char* ret = dest;
	while (*dest++ = *src++)
	{
		;
	}
	return ret;
}

可能while循环中的内容有点不好理解;就是每次先对指针解引用,赋值,然后++;并且判断条件就是括号内表达式的内容;当遇到\0时,也是会先赋值,再停下并且++,

strcat

字符串拼接

用法:

查阅https://legacy.cplusplus.com/reference/cstring/strcat/?kw=strcaticon-default.png?t=N176https://legacy.cplusplus.com/reference/cstring/strcat/?kw=strcat

 我们了解到,该函数的返回参数是char*,函数参数是char* , const char*;

函数同样返回目标字符串的首地址,strcat函数会先找出目标函数的\0,再从\0开始把源头的内容拼接在目标字符串后面包括\0,

注意:1.目标字符串必须含有\0,没有\0的话拼接动作不知道从哪里开始;

           2. 源字符串中也必须含有\0,不然拼接动作不知道什么时候结束;

           3. 目标字符串必须拥有足够大的空间;

	char str1[] = "world!";
	char str2[20] = "hello ";
	printf("%s\n", strcat(str2, str1));

strcat模拟实现:

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

strcmp

字符串比较函数

用法:

 

查阅 https://legacy.cplusplus.com/reference/cstring/strcmp/?kw=strcmpicon-default.png?t=N176https://legacy.cplusplus.com/reference/cstring/strcmp/?kw=strcmp

了解到函数的返回参数是int,函数参数是const char*,const char*;

strcpy函数会比较两个字符串相同位置字符的ASCII码值,

大于返回大于0的值;

等于返回0;

小于返回小于0的值;

注:strcmp的比较机制并不是比较字符串的长度,而是比较相同位置字符的ASCII码值;

	char str1[] = "abcdef";
	char str2[] = "abc";
	int ret = strcmp(str1, str2);

strcmp模拟实现:

int my_strcmp(const char* str1,const char* str2)
{
	while(*str1 == *str2)
	{
		if (*str1 == '\0')
			return 0;
		str1++;
		str2++;
	}
	return *str1 - *str2;
}

while循环中的判断条件并不取决于指针解引用的值,而是判断两指针解引用所指向的内容相不相等;

strstr

字符串查找函数

用法:

查阅strstr - C++ Reference (cplusplus.com)icon-default.png?t=N176https://legacy.cplusplus.com/reference/cstring/strstr/?kw=strstr

了解到:函数的返回参数是char*,函数参数是const char*,const char*;

strstr函数是在一个字符串中,判断另一个字符串是否是它的子串,并且返回的是它俩相同字符串的首字符地址;若没有找到,就返回NULL;

	char str1[] = "abbbcdef";
	char str2[] = "bcd";
	printf("%s\n", strstr(str1, str2));

 strstr模拟实现:

char* my_strstr(const char* str1, const char* str2)
{
	assert(str1 && str2);
	char* p1 = NULL;
	char* p2 = NULL;
	char* cp = (char*)str1;
	while (*cp)
	{
		p1 = cp;
		p2 = (char*)str2;

		while (*p1 && *p2 && *p1 == *p2)
		{
			p1++;
			p2++;
		}
		if (*p2 == '\0')
		{
			return cp;
		}
		cp++;
	}
	return NULL;
}

此函数比较有意思,要考虑的细节还是蛮多的;

比如:在遍历长串时,发现比到一半,不相同了,指向长串的指针要移到开始比较的下一个位置;

子串怎么返回起始位置;

在什么条件下算找到子串了;

不妨一开始就创建两个指针p1、p2,分别用来指向两个字符串;然后再利用cp指针代替str1指针,代替它完成移动的操作,

当遍历发现两指针的内容相同时,p1++;p2++;直到一方出现\0或者两指针所指向内容不相同,再判断此时的p2指针是否是\0;若是\0,则证明子串已经遍历完毕,返回cp;不是\0。,此时cp++,再一次循环,重置p1和p2的内容;

strtok

字符串截取(个人理解)

用法:

 查阅strtok - C++ Reference (cplusplus.com)icon-default.png?t=N176https://legacy.cplusplus.com/reference/cstring/strtok/?kw=strtok

了解到,函数的返回参数char*,函数参数为char*,const char*

strtok函数比较奇特,

delimiters指针所指向的字符串中定义了用作分隔符的字符串集合;
strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注:
strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容
并且可修改。)
strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串
中的位置。
strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标
记。
如果字符串中不存在更多的标记,则返回 NULL 指针。

怎么完美的使用它呢?

比如:

	char *p = "123.234.345@567";
	const char* sep = ".@";
	char arr[30];
	char *str = NULL;
	strcpy(arr, p);
	for (str = strtok(arr, sep); str != NULL; str = strtok(NULL, sep))
	{
		printf("%s\n", str);
	}

结果为:

将字符串中每一个带有分隔符的小串分开 ;

strerror

strerror是一个将错误码转为错误信息的函数

用法:

查阅strerror - C++ Reference (cplusplus.com)icon-default.png?t=N176https://legacy.cplusplus.com/reference/cstring/strerror/?kw=strerror了解到;函数的返回参数为char*,函数参数为int;

这个函数就是将数值转为信息;

使用此函数时,必须包含<errno.h>头文件

 怎么用呢?

比如;处理文件时


	FILE* pFile;
	pFile = fopen("test.txt", "r");
	if (pFile == NULL)
		printf("无法打开此文件: %s\n", strerror(errno));

就会打印出相对应的错误信息:

 切记,在使用时,如果想要查看此处的代码有没有错误,要及时的使用此函数进行查看,因为新一段代码运行时都会覆盖上一段代码的错误码,导致不能及时的检查错误;

内存函数:

为什么设计内存函数呢?

因为在处理字符串相关内容时,我们可以使用字符串函数,那别的类型就不能处理了吗?

所以引进了内存函数,处理不同类型,

memcpy

内存拷贝函数

用法:

 查阅:memcpy - C++ Reference (cplusplus.com)icon-default.png?t=N176https://legacy.cplusplus.com/reference/cstring/memcpy/?kw=memcpy

了解到;函数的返回参数是void*,函数参数是void*,void*,size_t

那么为什么内存函数会接受void*这样一个没有具体类型的指针类型呢?

因为在设计此函数的相关人员,不知道将来要处理哪种类型的数据;就以void*来接受,

我们也看到,函数的第三个参数是要拷贝的字节个数,那么再结合char*指针,一个字节一个字节的拷贝,不就能够达到要求了。

注意:拷贝的目标空间要足够大

	int arr1[20] = { 0 };
	int arr2[] = { 1,2,3,4,5,6,7,8,9,10 };
	memcpy(arr1, arr2, 20);

memcpy模拟实现:

void* my_memcpy(void* dest, const void* src, size_t num)
{
	assert(dest && src);
	void* ret = dest;
	while (num--)
	{
		*(char*)dest = *(char*)src;
		dest = (char*)dest + 1;
		src = (char*)src + 1;
	}
	return ret;
}

实现就将(void*)强制类型转换为(char*),一个字节一个字节的拷贝,

而我们知道强制类型转换是临时的,但是将他强转后+1再赋给void*的时候就能达到想要的效果;

memmove

内存挪动函数

用法:

查阅

memmove - C++ Reference (cplusplus.com)icon-default.png?t=N176https://legacy.cplusplus.com/reference/cstring/memmove/?kw=memmove

了解到,函数的返回类型是void*,函数参数是void*,const void*,size_t;

和memcpy有着同样的返回类型和参数;

	int arr1[20] = { 0 };
	int arr2[] = { 1,2,3,4,5,6,7,8,9,10 };
	memmove(arr1 , arr2, 20);

模拟实现memmove:

void* my_memmove(void* dest,const void* src,size_t num)
{
	void* ret = dest;
	if (dest < src)
	{
		while (num--)
		{
			*(char*)dest = *(char*)src;
			dest = (char*)dest + 1;
			src = (char*)src + 1;
		}
	}
	else
	{
		while (num--)
		{
			*((char*)dest + num) = *((char*)src + num);
		}
	}
	return ret;
}

memmove函数模拟实现时有一些小细节;

当我们对同一块内存空间进行操作时,会覆盖原有的数据,比如

	int arr1[20] = { 0 };
	int arr2[] = { 1,2,3,4,5,6,7,8,9,10 };
	my_memmove(arr2 + 2, arr2, 20);

在执行这一段操作时,很有可能出现原有数据被覆盖的情况

                                     导致最后结果为:1 2 1 2 1 2 1 8 9 10

所以在实现时,我们分三种情况讨论

一、目标空间出现在源空间的左侧

 如图,当目标空间出现在源空间的左侧时,为了保证原有的数据不被覆盖,该怎样去挪动呢

采用 前——>后的方式来进行操作,

 

二、目标空间出现在源空间的右侧

当目标空间出现在源空间的右侧时,采用 后——>前的方式

 

三、目标空间和源空间不相干

 当目标空间和源空间不相干时,我们发现,不管是 前——>后 还是 后——>前,都是可以的;

那么合并以上三种,寻找最优解,

当目标空间出现在源空间的右侧及以后的位置时,我们采用 后——>前的方式

当目标空间出现在源空间的左侧时,采用 前——>后的方式

 

 在引入了以上两种内存函数时,我们不妨发现,其实memcpy和memmove函数的作用差不多一样,都是挪动或者拷贝内容,

其实memmove的作用是大于memcpy的;

memmove的功能包含了memcpy,因为memcpy不能处理同一个内存空间的拷贝操作;

memset

内存设置函数

用法:

 函数的返回类型void*,函数参数void*,int ,size_t

使用memset函数时要注意,此函数是以字节为单位设置内存的,并不是将一个数值存入一个类型中那么简单,

	char str[10] = { 0 };
	memset(str, '1', 9);

所以,这个函数用来处理字符串还是不错的;

memset模拟实现:

void* my_memset(void* ptr, int value, size_t num)
{
	assert(ptr);
	void* ret = ptr;
	while (num--)
	{
		*(char*)ptr = value;
		ptr = (char*)ptr + 1;
	}
	return ptr;
}

结尾:

所以,本期就是要向各位说明,在进行编程时要有效的使用我们的工具,查阅查阅各个函数的具体功能、怎么使用;会对效率有所提升的;

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值