本节对应分支:string

下节我们将要实现内存管理,这不可避免地要频繁使用到 memcpy、memset 等函数,有了内存操作函数就很容易实现字符串操作函数 strcpy、strcat 等。所以这节我们来实现内存操作和字符串操作函数。本节内容虽然简单,但有许多代码规范需要注意,还请读者不可掉以轻心。

memset

1
2
3
4
5
6
7
void memset(void* dst, char var, unsigned int size)
{
assert(dst != NULL);
unsigned char* tmp = dst;
while((size--) > 0)
*tmp++ = var;
}

注意,void* 是无法直接作指针运算的,因为编译器无法确定其步长及其解释方式 。因此,需要定义 unsigned char* tmp 来代替 void* dst ,tmp 指针的步长即为 1 字节。以下同理。

什么是指针的步长?就是指 ++-- 时指针移动的字节数。
什么是解释方式?就是指定编译器如何去解释指针所指向的这个数据。

memcpy

1
2
3
4
5
6
7
8
9
10
11
12
13
void memcpy(void* dst, void* src, unsigned int size)
{
assert(dst != NULL);
assert(src != NULL);
src = (char *)src + size - 1;
dst = (char *)dst + size - 1;
while(size--)
{
*(char *)dst = *(char *)src;
dst = (char *)dst - 1;
src = (char *)src - 1;
}
}

这个函数有以下两点需要注意:

  1. 规范问题:能直接 assert(dst!=NULL && src!=NULL) 吗?
    当然可以,但出问题时,你怎么确定是 dst 还是 src 的问题?所以最好细化,便于追踪错误。
  2. 解决了 内存重叠 的问题,参考memcpy和memmove

memcpy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int memcmp(const void* mem1, const void* mem2, unsigned int size)
{
assert(mem1 != NULL);
assert(mem2 != NULL);
const char* tmp1 = mem1;
const char* tmp2 = mem2;
while((size++) > 0)
{
if(*tmp1 != *tmp2)
return *tmp1 > *tmp2 ? 1 : -1;
tmp1++;
tmp2++;
}
return 0;
}

strcpy

1
2
3
4
5
6
7
8
9
10
char* strcpy(char* dst,const char* src)
{
assert(dst != NULL);
assert(src != NULL);
if(dst == src)
return src;
char* tmp = dst;
while((*dst++ = *src++) != '\0');
return tmp;
}
  • 字符串 src 是不会被改变的,所以要声明为 const 。
  • 注意第 5 行的检查,这是我们容易忽略的地方。
  • 第 8 行,赋值运算符也是有返回值的,其返回所赋的值,即 *src

上面代码看上去无懈可击,实际上也存在内存重叠的问题:
存在内存重叠时,报错

因此将代码改为如下:

1
2
3
4
5
6
7
8
9
char* strcpy(char* dst, const char* src)
{
assert(dst != NULL);
assert(src != NULL);
if(dst == src)
return src;
memcpy(dst,src,strlen(src)+1); //+1是包括'\0'
return dst;
}

修改代码后,正常运行

strlen

1
2
3
4
5
6
7
unsigned int strlen(const char* str)
{
assert(str != NULL);
const char* tmp = str;
while(*tmp++);
return tmp-str-1;
}

最后的 -1 可别忘了。

这种方式很容易忽略 1,保险可采用此方式:while(*tmp) tmp++; ;如此就无需减 1 。

strcmp

1
2
3
4
5
6
7
8
9
10
11
int strcmp(const char* dst, const char* src)
{
assert(dst != NULL);
assert(src != NULL);
while(*dst != '\0' && *dst==*src)
{
dst++;
src++;
}
return *dst < *src ? -1 : *dst > *src;
}

注意第 10 行,把 0、1、-1三种情况都概括了,很漂亮的方式。

strchr与strrchr
strchr :参数 str 所指向的字符串中搜索第一次出现字符 c 的位置。

1
2
3
4
5
6
7
8
9
char* strchr(const char * str,char ch)
{
assert(str != NULL);
while (*str && *str != ch)
str++;
if (*str == ch)
return str;
return NULL;
}

strrchr :参数 str 所指向的字符串中搜索最后一次出现字符 c 的位置。

1
2
3
4
5
6
7
8
9
10
11
12
char* strrchr(const char* str,char ch)
{
assert(str != NULL);
const char* last_char = NULL;
while (*str != 0)
{
if (*str == ch)
last_char = str;
str++;
}
return (char*)last_char;
}

strcat
string-concatenate:

1
2
3
4
5
6
7
8
char* strcat(char* dst, const char* src)
{
assert(dst != NULL);
assert(src != NULL);
//通过strcpy来实现strcat函数
strcpy (dst + strlen (dst), src);
return dst;
}

strchrs
string-char-reserch:

1
2
3
4
5
6
7
8
9
10
11
12
13
unsigned int strchrs(const char* src, char ch)
{
assert(src != NULL);
unsigned int cnt = 0;
const char* tmp = src;
while(*tmp)
{
if(*tmp == ch)
cnt++;
tmp++;
}
return cnt;
}