字符串是人类记述信息最重要的手段之一。
机器看得懂一串二进制序列,然而对于人类来说,却是一头雾水。
优美的人类语言,人们可以欣赏,而机器却大脑宕机。
字符串,人类可以阅读,机器把它看成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!";
}