C语言字符串函数和内存操作函数

字符串函数

1.strlen

strlen是用来记字符串的长度的。写一下我知道的三种实现方法

方法1:计数(bf)
暴力算法,不解释

size_t my_strlen1(const char* str)
{
	int len = 0;
	while (*str++)
		len++;
	return len;
}

方法2:递归
这个方法很有趣。
有一些面试题会要求你不创建临时变量去写strlen,就只能用递归写了。

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

方法3:指针减指针
我们知道,在c语言里面,指针减指针得到的结果是它们之间的元素个数。

size_t my_strlen3(const char* str)
{
	char* pc = str;
	while (*pc)
	{
		pc++;
	}
	return pc - str;
}

关于strlen的一些易错点:

  1. strlen的返回值是无符号整数
    给道题就理解了
char* str1 = "hello";
	char* str2 = "hello world";
	if (strlen(str1) - strlen(str2)>0)
	{
		printf("str1 is shorter than str2\n");
	}
	else
	{
		printf("str1 is longer than str2\n");
	}

答案:
在这里插入图片描述
原因也很简单,由于strlen的返回值是无符号数,无符号数相减仍然是无符号数。无符号数永远大于等于0.

  1. strlen若遇不到终止符NUL就会一直越界往后找(它就是这么犟)

2.strcpy和strncpy

strcpy的作用是把一段字符串复制到另一个地方。
有几个很重要的点

  1. 源字符串必须要有终止符NUL
  2. 目标空间必须足够大,足够大的意思就是能放的下这一段字符串
  3. 目标空间必须可以被修改。(不能是char*的常量字符串,也不能是const char* 的具有常属性的字符串)

这里就不具体举例子了。

strcpy的实现也很简单,但是要写的漂亮也不是那么容易的。先贴代码

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

这段代码里面有几个要强调的点:

  1. 传入参数的时候,由于源字符串是不可修改的,可以加上const。一旦它被修改了,程序直接报错。方便debug(在长的程序中,这个好处实在是太好了)
  2. 对目标字符串和来源字符串进行断言。一旦它们两有一个为空字符串,程序直接崩溃。(也是大大方便了debug)
  3. 解引用和用于迭代的++可以放在一起。当*src等于终止符NUL时,它也将被赋值给了目标字符串。同时整个条件判断也会变成假,跳出循环。
    这里给了我们一个启示,以后我们写代码时可以想一想++可以放在条件判断中吗?

讲完了strcpy,现在讲一下strncpy。
strncpy可以操控copy的个数,不一定要把整一个字符串都拷贝过去。可以拷贝你想要的个数。

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

输出worlo。就是把world的前三个字符拷贝到了str1里面。

3.strcmp和strncmp

这是msdn里面对strcmp的描述(msdn是微软自己做的函数标准库)
在这里插入图片描述
这是函数返回值。
在这里插入图片描述
注意了:less是更小,greater是更大。这里的比较是按照每一个字符的ASCII码来比的,和长度并没有什么关系。

来个例子说明:

	char str1[20] = "hello";
	char str2[] = "worl";
	printf("%d\n", strcmp(str1, str2));

如果说是按照长度来判断,str1比str2长,输出的值应该大于0才对。实际上输出:
在这里插入图片描述
这是因为h的ASCII码是104,w的ASCII码是119.104比119小,所以输出的数是负数。实际上vs2019输出-1也是它的一种微软特色。

我们可以这样实现,返回两个字符之间ASCII码值的差值。不一定要-1或者1

代码:

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

这里要说明一点:为什么*str1等于终止符就停止了。*str2等于终止符不可以吗?
原因是:这个判断是写在条件下面这个条件之后的。此时*str1和*str2是一样的。

*str1 == *str2

讲完了strcmp,再讲一下strncmp。
同样strncmp可以选择比较的个数。
你也可以理解成比较两个字符串之间有没有相同的字串。
在这里插入图片描述

4.strcat和strncat(strcat不可以自己追加自己的原因)

strcat是用来拼接两个字符串的。
在这里插入图片描述
有几个重要的点:

  1. strcat的源字符串必须要有终止符
  2. strcat追加的空间必须要足够大,足够大的意思就是能放得下两段字符串
  3. 目标字符串必须可以修改。和strcpy一样。不能是char*的常量字符串,也不能是const char* 的具有常属性的字符串

问题来了,自己追加自己可以吗?
答案:不可以。

在这里插入图片描述
原因我们可以通过实现一下strcat并画图来解决这个问题。
strcat思路很简单,这里就不解释了。着重讲一下为什么strcat不可以自己追加自己。

char* my_strcat(char* dst, const char* src)
{
	char* ret = dst;
	while (*dst)
	{
		dst++;
	}
	while (*src)
	{
		*dst++ = *src++;
	}
	*dst = '\0';
	return ret;

}

初始状态:
在这里插入图片描述
追加一个字符后,现在其实已经可以看出问题了。src往后走之后并不会按预期的一样找到终止符NUL。如果没看懂我们可以继续往后看。
在这里插入图片描述
追加两次后dst和src分别的位置。
在这里插入图片描述
经过几次迭代后就会变成这样,很明显越界了。但它们并没有停下来的意思。
在这里插入图片描述
因此我们吸取了教训,src和dst不能放在一起操作。
我们只能用strncat来进行追加自己的操作。

	char str1[20] = "hello";
	printf("%s\n",strncpy(str1, str1, 5));

这样就没有任何问题了。

5.strstr在这里插入图片描述

strstr是用来找substring的。
这里用bf算法实现一下strstr。更优秀的算法请参考kmp算法。
画图来理解一下:
初始状态:(prev是用来记录比较串时的开头)
在这里插入图片描述

string和substring的第一个字符不相等,所以string往后走,prev也往后走。直到相等,如图:
在这里插入图片描述
substring和string的第一个字符相等,string和substring都向后走一步。(注意prev不要走)
在这里插入图片描述
匹配完了所有字符的样子。如图:
在这里插入图片描述
当substring走到终止符的位置时,返回prev的位置。
在这里插入图片描述

代码如下:
最关键的在于:用一个变量来记住比较串的开头,当这个变量走到终止符的时候结束循环。

char* my_strstr(const char* string, const char* strCharSet)
{
	char* cp = string;
	char* setHead = strCharSet;
	while (*cp)
	{
		while (*strCharSet && *string && *string == *strCharSet)
		{
			string++;
			strCharSet++;
		}
		if (*strCharSet == '\0')
		{
			return cp;
		}
		if (*string == '\0')
			return NULL;
		cp++;
		string = cp;
		strCharSet = setHead;
	}
}

6.strtok

strtok是一个分割函数。很奇特也很有趣。
在这里插入图片描述
strtok分割的原理是把你指定的sep字符全部变成终止符NUL
sep参数是个字符串,定义了用作分隔符的字符集合。只要字符串里面有这个字符,就会变成终止符。不分顺序。

这个函数的用法也很奇特。

strtok函数的第一个参数不为 NULL时 ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。
strtok函数的第一个参数为 NULL 时,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
如果字符串中不存在更多的标记,则返回 NULL 指针。

给个例子:

int main ()
{
  char str[] =This, a sample string.";
  char * pch;
  printf ("Splitting string \"%s\" into tokens:\n",str);
  pch = strtok (str," ,.-");
  while (pch != NULL)
 {
    printf ("%s\n",pch);
    pch = strtok (NULL, " ,.-");
 }
  return 0;
}

第一次由于它传入了str,因此重头开始找,strtok把逗号变成终止符
第二次传入了NULL,因此从逗号开始找,strtok把a后面的空格变成终止符
第三次传入NULL,strtok把sample后的空格变成终止符
第四次传入NULL,strtok把string后面的句号变成终止符
最后返回一个NULL,循环结束。

7.strerror

strerror是返回错误信息的一个函数。
在这里插入图片描述
在C语言里面,每一个错误信息都有一个对应的错误码。strerror可以根据这个错误码来打印出报错信息。

例如0,1,2,3在c语言里面代表的错误码是:

	printf("%s\n", strerror(0));
	printf("%s\n", strerror(1));
	printf("%s\n", strerror(2));
	printf("%s\n", strerror(3));

在这里插入图片描述
但我们也不能记住每一个错误码。c语言有一个变量叫errno(是一个全局变量),当你的程序出现错误时,errno就会记录此次错误码。

现在没有这个文件但要强行打开,会返回一个NULL指针给pf,即打开失败。errno记录了这个错误码并通过strerror打印了出来。

	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		printf("%s\n", strerror(errno));
	}

在这里插入图片描述
还有一个类似的函数perror(print error)

它会自动打印错误。
在这里插入图片描述

	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		//printf("%s\n", strerror(errno));
		perror("test");//可以在报错信息前面加上一些你想打印的信息。
	}
	return 0;

在这里插入图片描述

内存操作函数

前面的那些函数都只能对字符串操作,但下面这些函数可以对任意类型操作。

1.memcpy

memcpy和strcpy效果是一样的,都是拷贝。只不过memcpy可以拷更多类型的内容而已。
在这里插入图片描述
需要注意的是:memcpy需要传三个参数。第三个参数是字节数。
(不是你要拷贝的元素个数)

现在我们实现一下memcpy。

我们可以看到传入的参数是void*,很明显就是要求我们对其强制转换成char*类型,然后再对每一个字节进行拷贝了。

代码:

void* my_memcpy(void* dest, const void* src, size_t count)
{
	assert(dest && src);
	void* ret = src;

	while (count--)
	{
		*(char*)dest = *(char*)src;
		((char*)dest)++;
		((char*)src)++;

	}
	return ret;
}

这里多说几句。
如果想拷贝自己的部分元素到自己身上,memcpy正常来说是做不到的。但是在vs2019上,微软实现它时让它有了这个功能。(这其实是memmove的功能)

2.memmove

memmove就具有了拷贝自己的元素到自己身上的能力。
要想实现memmove就要知道为什么memcpy的写法无法自己拷贝自己。

假设有这么一种情况:要把src的内容拷贝到dst里面。
在这里插入图片描述
在拷贝了两个元素之后,我们发现会变成下面这个样子。原先的3和4找不到了。
在这里插入图片描述
最后会变成这个样子。很明显就是错误的结果。
在这里插入图片描述
因此用memcpy的方法是无法解决这个情况的。这时候我们就应该从后往前面拷贝。
在这里插入图片描述
那是不是永远从前往后拷贝就可以了呢?
我们再来看一下这个情况
在这里插入图片描述
假如现在还从后面往前面拷贝。
3和4就会被5和6覆盖掉。拷贝仍然是错的。

从上面两个情况我们可以看出来,我们知道了,要实现memmove是要分情况的。

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
从上面四种情况,
我们可以简化成两种。
当dst小于src时用前->后。
当dst大于src时用后->前。

具体代码实现:

void* my_memmove(void* dest, const void* src, size_t count)
{
	assert(dest && src);
	void* ret = src;
	if (dest >= src)
	{
		(char*)dest += (count - 1);
		(char*)src += (count - 1);

		while (count--)
		{
			*(char*)dest = *(char*)src;
			((char*)dest)--;
			((char*)src)--;

		}
	}
	else
	{
		while (count--)
		{
			*(char*)dest = *(char*)src;
			((char*)dest)++;
			((char*)src)++;

		}
	}
	return ret;
}

3.memset

可以让你指定的空间变成你指定的内容。
在这里插入图片描述

	int arr[] = { 1,2,3 };
	memset(arr, 0, 12);

前:
在这里插入图片描述
后:
在这里插入图片描述

4.memcmp

很简单,不多说。
用于比较两段空间。(可以指定比较的个数)
在这里插入图片描述
在这里插入图片描述

	int arr1[] = { 1,2,3 };
	int arr2[] = { 1,2,4 };
	printf("%d", memcmp(arr1, arr2, 8));//返回0
	printf("%d",memcmp(arr1, arr2, 12));//返回-1
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值