C字符串函数的典型实现

学习字符串一段时间,整理下字符串函数的实现,并加入一些注释。

strlen -- 计算字符串的长度
/*返回值:目标串的长度
*参数:str为欲计算长度的字符串
*/
size_t strlen(const char* str)
{
     size_t length = 0;
     while(*str++)          //注:*p++相当于(*p)++
          ++length;
     return length;
}
稍加改进如下:
size_t strlen(const char* str)
{
     const char* cp = str;
     while(*cp++) ;
     return (cp - str -1);
}

不借助中间变量:
(标准写法)
int strlen(char* str)
{
     if(str[0] == '\0')
          return 0;
     else
          return ( strlen( (char*) (&str[0] + 1) ) + 1 );     //运用递归
}
(简略写法)
int strlen(char* str)
{
     if(*str == '\0')
          return 0;
     else
          return ( (strlen(str + 1) ) + 1);
}

但是如果给此函数传入一个NULL的话,程序会崩溃。所以,用以下的做法加强函数的容错性和健壮性:
int strlen(const char* str)
{
     int len = 0;
     assert(str != NULL);     //使用断言要include头文件assert.h, assert表示理论可能但实际不可能发生的事
     while(*str++)
          len++;
     return len;
}

strcpy -- 字符串拷贝
/*返回值:目标串的地址
*参数:dst为目标字符串,src为原字符串
*/
char* strcpy(char* dst, const char* src)
{
     char* r = dst;
     assert( (dst != NULL) && (src != NULL) );
     while( (*dst++ = *src++) != '\0' );          // 赋值表达式返回左操作数,所以在赋值NULL后,循环停止
     return r;
}
1. 为什么要返回char*?
-- 为了实现链式表达式,例如 int length = strlen( strcpy(dst, "hello world" ) );
2. 假如考虑dst和src内存重叠的情况,strcpy该怎么实现?
     解决内存重叠,用memmove,原型是void* memmove(void* dst, const void* src, size_t count)。
3. 这个函数不会自己判断原字符串是否比目标空间大,必须要程序员自己检查,否则很容易造成拷贝越界,如:
     char* a = "hello world";
     char c[5];
     strcpy(c, a);      //输出为c = "hello world",数组c只有5个字节的空间,但是经过strcpy后a的剩余字符也拷贝过去了,如果c后面是系统程
                              序空间,那就要出问题了。

strncpy -- strcpy 的改进版本,拷贝规定长度的字符串 
/*返回值:目标串的地址
*参数:dst为目标字符串,src为原字符串, len为复制字符串的长度,即使未遇到原串的'\0',如果已经复制了len个字符,复制同样会终止。
*/
char* strcpy(char* dst, const char* src, int len)
{
     assert(dst != NULL && src != NULL);
     char* r = dst;
     int i = 0;
     while(i++ < len && *dst++ = *src++);
     if(*dst != '\0')
          *dst = '\0';
     return r;
}
多了一个拷贝长度的参数。需要注意的是长度参数应该为目的空间的大小,并且这个函数不会自己附加字符串结束符'\0',要自己加。看下面的例子:
     char* a = "hello world";
     char* b = "123456789";
     char c[5];
    strncpy(c, b, strlen(b) ) = 123456789     //拷贝长度不对,还是越界
                                                                 出,直到遇到一个结束符'\0'。
所以正确的做法应该是: strncpy(c, a, sizeof(c)-1); c[4] = '\0';      //输出为c=hell

memcpy -- 类似strncpy 
区别:

(1)strncpy只能复制字符串,但memcpy对类型没有要求。

(2)strncpy有两个终止条件,memcpy只有一个终止条件,那就是复制n个字节。(n是memcpy的第三个参数)

(3)要特别注意目的地址和源地址重合的问题,拷贝前要加以判断。

(4)实现这个函数时一般要把原来的指针类型转换成char*,这样每次移动都是一个字节。

      strncpy是把Num个字符从src复制到dest,但是如果遇到src字符结尾,那么复制提前结束,后面没有复制完的字符,不予以处理,当然dest,src地址不能重叠。

    memcpy也是把Num个字符从src复制到dest,但是它是内存复制,完整的复制Num个字节,不会因为遇到'\0'而结束。

函数实现:
void* memcpy(void* dst, void* src, unsigned int count)
{
    assert( dst != NULL && src != NULL);
     if(dst == src)
          return src;
     char* d = (char*)dst;     //在函数里面生成临时指针,这样不会改变原始指针
     char* s = (char*)s;
     while(count-- > 0)
          *d++ = *s++;
     return dst;
}

strcat -- 字符串拼接
/*功能:把str2所指字符串添加到str1结尾处(覆盖str1结尾处的'\0')并添加'\0'
*返回值:拼接后的字符串首地址
*参数:str1为原字符串,str2为要拼接的字符串
*/
char* strcat(char* str1, char* str2)
{
   assert(str1 != NULL || str2 != NULL);
   char* pt = str1;
   //while(*str1++ != '\0');      //不能这样遍历str1!str1移到了的下一位,导致后面拼接后再打印的字符串也只能读出str1,读到'\0'处就结
                                             //束了。 *和++的优先级是一样的,当优先级一样时,程序按自左至右的顺序执行,所以当*str1='\0'时,str仍
                                             //然要往下移一位,即 str1指向满足条件后的下一个字节。 
     while(*str1 != '\0') str1++;      //正确做法!str1移到'\0'处就停止了。
     while((*str1++ = *str2++) != '\0');      //本句的计算顺序: 1. *str1=*str2;   2. 判断整个表达式的值是否为真,即*str1!='\0';  3. 满足,
                                                               // 则继续循环,否则终止,不论循环继续与否,接下来要执行str1++;str2++; str1和str2谁先自增
                                                               //在这里是无关紧要的。 
     //while(*str2 != '\0') *str1++ = *str2++;
     //*str1 = '\0';                                         //这两句与上一句等效
     return pt;
}
注:这里返回char*,同样是为了实现链式表达式。

strcmp -- 比较两个字符串
/*返回值:0:s1 == s2; 1:s1 > s2;-1:s1 < s2.
*参数:s1, s2为要比较的字符串
*/
int strcmp(const char* s1, const char* s2)
{
     int ret = 0;
     while( !( ret = *(unsigned char*)s1 - *(unsigned char*)s2 ) && *s2 )      //直到s1和s2当前数值不相等且s2不为\0时退出while
     {
          ++s1;
          ++s2;
     }
          if( ret < 0 )
               ret = -1;
          else if( ret > 0 )
               ret = 1;
     
     return ret;
}

strchr -- 查找字符串中首次出现字符ch的位置
/*返回值:如果字符串中存在字符ch,返回首次出现ch的位置指针,否则返回NULL。
*参数:s1, s2为要比较的字符串
*/
char* strchr(const char* str, int ch)
{
     while(*str && *str != (char)ch)
          str++;
     if(*str == (char)ch)
          return (char*)str;
     return NULL;
}

strstr -- 查找字符串中第一次包含另一个字符串的位置
char* strstr(const char* s1, const char* s2)
{
     if(*s1 == 0)
     {
          if(*s2)
               return NULL;
          return (char*)s1;
     }
     while(*s1)
     {
          int i = 0;
          while(1)
          {
               if(s2[i] == 0)
                    return (char*)s1;
               if(s1[i] != s2[i])
                    break;
               i++;
          }
          s1++;
     }
     return NULL;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值