学习札记:字符串函数与自己实现的字符串函数

字符串是人类记述信息最重要的手段之一。

机器看得懂一串二进制序列,然而对于人类来说,却是一头雾水。
优美的人类语言,人们可以欣赏,而机器却大脑宕机。

字符串,人类可以阅读,机器把它看成ASCII码,也能处理,因此,对字符串的学习是很重要的。

在C/C++语言中,提供了不少字符串函数,在下面,我们一一测试并模拟实现。

本文中所有的代码依赖以下头文件实现:

#include<iostream>
#include<assert.h>
using namespace std;

①strlen函数
strlen函数用于计算一个字符串的长度;它计算字符串结束的依据是:只要阅读到了’\0’这一结束标志,就停止计数。它需要一个待求长度的字符串做参数,返回的是长度(unsigned int)。

模拟实现时,我写出了一个“带计数器”的strlens函数。当str指针指向的元素并不是’\0’,即字符串没结束时,就把计数器+1,然后将str右移一位重复上述工作。

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

实际上也可以写出不带计数器的版本:

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

这实际上是使用了递归的思想,每脱去一层递归,就把给my_strlen函数的初始指针向后错一位。这样理解起来也不难,但是效率会低一些。

另外,由于两个同类型指针相减所得是指针间元素个数,所以另一个思路是:共产生两个指针均指向首元素,另一个指针不动,另一个指针向后遍历字符串直到结束,计算两指针的差,就是元素个数即字符串长度。

int my_strlen(const char *s)
{
   char *p = s;
   while(*p != ‘\0)
       p++;
   return p-s;
}

②strcpy函数
strcpy函数用于把一个字符串的内容复制到另一个字符串中,因此参数列表中至少需要两字符串。

下面是我自己写的版本:

void strcpys(char* str1, char* str2) {
	assert(str1 && str2);
	while (*str1++ = *str2++)
	{
		;
	}
}

这段代码所作的工作是:首先断言str1与str2均不为空指针,否则就没有意义了。然后,把(*str2)赋给(*str1),之后,str2先自增,然后str1再自增。这样就把str2的一个元素赋给了str1,重复此过程直到str2找到了’\0’,此时,‘\0’也被赋给了str1,两字符串看到了’\0’就结束了,把str2复制(赋值?)给str1的过程完成。

注意:assert断言需要包含assert.h头文件。

③strcat函数
strcat用于把一个字符串接到另一个字符串后面去。

我自己写的版本是:

void strcats(char* str1, char* str2) {
	assert(str1 && str2);
	while (*str1 != '\0') {
		*str1++;
	}
	while (*str1++ = *str2++)
	{
		;
	}
}

断言这一工作就不说了。首先先让str1的首元素指针遍历字符串直到它指向’\0’,这就是str1的尾部。在str1的尾部接续str2,这就可以用strcpy函数了,因为这一工作实质上就是把str2复制到str1后面。因此后面代码再实现一下strcpy功能就行了。

④strcmp函数
strcmp函数用于比较两个字符串在“字典”中的次序,也就是ASCII码次序。
若两个字符串完全相同,那么返回0;
若两个字符串不相同,那么返回两个不同元素的ASCII码值的差。
实际上,strcmp返回值只有0与±1,在下面我们实现的strcmps函数中,我们返回的是两个不同元素的ASCII码值的差。

int strcmps(char* str1, char* str2) {
	assert(str1 && str2);
	while (*str2 != '\0' && (*str2 - *str1 == 0)) {
		str1++;
		str2++;
	}
	return *str1 - *str2;
}

首先断言。然后,若两字符串开头的部分相同,就先跳过这相同的部分;跳过这一部分后遇到的第一位上的字符的ASCII码值的差就是strcmps的返回值。当然也可以改写为只返回0与±1的版本。
如果跳过的过程中,有一个字符串已经结束而另一个字符串尚未结束,那二者肯定不相同,反回未结束字符串当前指针指向的元素与’\0’的ASCII码的差。

⑤strstr函数
strstr函数用于指示一个字符串是否是另一个字符串的子串。
这个函数很好玩,实现起来有点难度。希望我能解释明白。

char* strstrs(const char* str1, const char* str2) {
	assert(str1, str2);
	const char* begin1 = str1;
	const char* begin2 = str2;
	const char* pointer = str1;
	while (*pointer != '\0') {
		begin1 = pointer;
		begin2 = str2;
		while (*begin1 != '\0' && *begin2 != '\0' && *begin1 - *begin2 == 0) {
			begin1++;
			begin2++;
		}
		if (*begin2 == '\0') {
			return (char *)pointer;
		}
		pointer++;
	}
	return NULL;
}

我们就指示str2是否是str1的子串吧;首先先准备三个指针,其中两个指向母串,一个指向可能的“子串”。为什么要准备这么多指针呢?请看一个例子:
母串:bbbc
子串:bbc
若两个串都只有一个指针指示,那么遍历完子串后,子串得到的结果是bbc,而母串得到的结果是bbb,它俩“不是”子母串关系。然而这里两个字符串真的是子母串的关系。解决这一问题,就再加一个指针,好让母串的每一部分都可以被访问。具体操作就是,母串每与子串一同访问一次后就把下次指针访问的开始后移一位,再重复上述工作,直到母串完全被遍历。如果下一次母串指针从第二位b开始,那么访问出来的部分母串和子串就相同了,子母串关系成立。

上述代码就做了上述工作。pointer指针实际上是每一次推着begin1指针一位一位地向后错,保证了str1串完整地被访问。

⑥strncpy函数
与strcpy功能相仿而更进一步。n代表了你可以按照自己需求,选择复制字符串中的n位到另一个字符串中。把上面的代码改造一下就行:

void strncpys(char* str1, char* str2,int n) {
	assert(str1 && str2);
	size_t i = 0;
	for (i = 0; i < n; i++)
	{
		*str1++ = *str2++;
	}
}

⑦strncat函数
同样,这个函数可以把第二个字符串的n位接到第一个字符串后。改造strcat函数如下:

void strncats(char* str1, char* str2, int n) {
	assert(str1&&str2);
	while (*str1 != '\0') {
		str1++;
	}
	while ((*str1++ = *str2++) && n--!=0) {
		;
	}
	*str1 = '\0';
}

⑧memcpy函数
这个函数功能更加强大,可以拷贝任意数据类型的信息,并且也可以自定义n位。

由于数据类型是任意的,因此参数列表的指针是通用类型指针。改造一下strncpy函数就可以实现。

在实现中,仍然离不开char*这一数据类型,因为只有字符指针可以做到一次只移动一个字节的功能,这样保证不管什么数据类型,构成这数据的每个字节都被遍历,保证信息的正确性。关键在于不要忘记写强制类型转换。

void memcpys(void* str1, void* str2, size_t n) {
	assert(str1 && str2);
	size_t i = 0;
	for (i = 0; i < n; i++)
	{
		*((char*)str1) = *((char*)str2);
		str1 = (char*)str1 + 1;
		str2 = (char*)str2 + 1;
	}
}

另外还有很多关于字符串的强大函数,在此不做介绍。

容易发现,这里人工实现的几个函数,与标准的定义有些区别 。它们中的很多标准定义的返回值是一个指向第一个字符串的指针,同时又能防止数据被修改。

解决第一个问题,只要在函数体内部定义一个指针指向str1,然后操作后返回这个指针就好;第二个问题,只要在第二个参数前用const修饰就行。

上面这几个函数,写在一起放在下面:(测试用例夹带了私货,hh)

#include<iostream>
#include<assert.h>
using namespace std;
//模拟实现strlen函数
int strlens(const char* str) {
	int count = 0;
	while (*str != '\0') {
		str++;
		count++;
	}
	return count;
}

//模拟实现strcpy函数
void strcpys(char* str1, char* str2) {
	assert(str1 && str2);
	while (*str1++ = *str2++)
	{
		;
	}
}

//模拟实现strcat函数
void strcats(char* str1, char* str2) {
	assert(str1 && str2);
	while (*str1 != '\0') {
		*str1++;
	}
	while (*str1++ = *str2++)
	{
		;
	}
}
//模拟实现strcmp函数
int strcmps(char* str1, char* str2) {
	assert(str1 && str2);
	while (*str2 != '\0' && (*str2 - *str1 == 0)) {
		str1++;
		str2++;
	}
	return *str1 - *str2;
}

//模拟实现strstr函数
char* strstrs(const char* str1, const char* str2) {
	assert(str1, str2);
	const char* begin1 = str1;
	const char* begin2 = str2;
	const char* pointer = str1;
	while (*pointer != '\0') {
		begin1 = pointer;
		begin2 = str2;
		while (*begin1 != '\0' && *begin2 != '\0' && *begin1 - *begin2 == 0) {
			begin1++;
			begin2++;
		}
		if (*begin2 == '\0') {
			return (char *)pointer;
		}
		pointer++;
	}
	return NULL;
}
//模拟实现strncpy函数
void strncpys(char* str1, char* str2,int n) {
	assert(str1 && str2);
	size_t i = 0;
	for (i = 0; i < n; i++)
	{
		*str1++ = *str2++;
	}
}

//模拟实现strncat函数
void strncats(char* str1, char* str2, int n) {
	assert(str1&&str2);
	while (*str1 != '\0') {
		str1++;
	}
	while ((*str1++ = *str2++) && n--!=0) {
		;
	}
	*str1 = '\0';
}

//模拟实现memcpy函数
void memcpys(void* str1, void* str2, size_t n) {
	assert(str1 && str2);
	size_t i = 0;
	for (i = 0; i < n; i++)
	{
		*((char*)str1) = *((char*)str2);
		str1 = (char*)str1 + 1;
		str2 = (char*)str2 + 1;
	}
}
int main() {
	char str1[120] = "Long live the People's Republic of China!";
	char str2[120] = "Long live the whole unification of people from all of the world!";
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值