strstr strlen strcpy函数实现

strstr strlen strcpy函数实现

分类: c++语言   59人阅读  评论(0)  收藏  举报

strcpy函数实现
strcpy 看似是标准函数库里面最简单的函数了,谁都可以实现这个函数,
  但是,并不一定谁都能实现的很好。林锐博士面试微软的时候,就做这个题目。
  他也没有把这个题目完全的做对。建议你自己先动手写一个自己的,不要先看
  答案。估计有 90%的人写出来的,达不到性能的要求。
  标准答案是这样的:
  static char * strcpy(char *dest, const char *src)
  {
  assert(dest != NULL && src != NULL);
  char *ret = dest;
  while ((*dest++ = *src++) != '\0');
  return ret;
  }
  测试了一下,这个函数,在10亿数据规模的复制,在我电脑上大概是 140 ms (release)
  下面的写法基本上可以判定是错误:
  static char * strcpy(char *dest, const char *src)
  {
  assert(dest != NULL && src != NULL);
  char *ret = dest;
  while ((*dest = *src) != '\0')
  {
  dest++;
  src++;
  }
  return ret;
  }
  可能你会很奇怪,为什么这个是错误的。这个写法大概损失 10%的性能,而且和编译器能优化
  的程度有关。原因你看反汇编的代码就知道,指令增加了不少。因为,你要给出一个最优的结果。
  但是,似乎还有提升的可能性, 在做这个题目的时候,我首先写出来的是:
  static char * strcpy(char *dest, const char *src)
  {
  assert(dest != NULL && src != NULL);
  int i = 0;
  while (*src != '\0')
  {
  dest[i++] = *src++;
  }
  dest[i] = '\0';
  return dest;
  }
  别看这算法简单,其实,和标准答案的速度完全一样。为什么,估计要看反汇编。
  但是,我总是不相信会这样简单,后来所有题目做好了,还有好多时间,我就想这个算法
  的优化,下面是我优化过的算法:
  static char * strcpy(char *dest, const char *src)
  {
  assert(dest != NULL && src != NULL);
  char *s = (char *)src;
  int delt = dest - src;
  while ((s[delt] = *s++) != '\0');
  return dest;
  }
  这个算法 很巧妙的回避了一个指针的累加,结果是 96ms 速度提升了近1倍。


//version 1, no memory overlap is considered
void *memcpy(void *dst, const void *src, size_t size)
{
    //check argument
    assert(null != dst && null != src);
    
    byte *pDst = (byte*)dst;
    const byte *pSrc = (byte*)src;
    
    while(size-- > 0)
    {
        *pDst++ = *pSrc++;
    }
    
    return dst;
}


//version 2, memory overlap is considered
void *memcpy(void *dst, const void *src, size_t size)
{
    //check argument
    assert(null != dst && null != src);
    
    if (dst < src)
    {
        byte *pDst = (byte*)dst;
        const byte *pSrc = (const byte*)src;
        
        while(size-- > 0)
        {
            *pDst++ = *pSrc++;
        }
    }
    else if (dst > src)
    {
        byte *pDst = (byte*)dst+size-1;
        const byte *pSrc = (const byte*)src+size-1;
        
        while(size-- > 0)
        {
            *pDst-- = *pSrc--;
        }
    }
    
    return dst;
}










如果不看glibc的代码,那么也许你永远也不知道什么叫境界,仅仅认为简单的可读性强的代码就是最好的代码的人也一定停留在应届毕业生的水平,程序很大意义上是给机器看的而不是给人看的,人看程序很大意义上是维护和经验学习,本文从简单的几个标准c的库函数来分析一下函数实现算法设计的重要性,首先谈谈strlen的实现,然后谈一下strstr的实现,最后实现一个我自己的strstr。先从strlen开始吧,一般而言,strlen的显而易见的实现就是傻傻的呆呆的一个一个字符的扫描,同时递加计数器,待到扫描到0的时候返回计数器的值就是字符串的长度,这种算法绝对简单,相同思想的别的算法可以实现的短小精悍,起码能唬过面试官,必要的时候能让女朋友更加崇拜你,但是高手并不仅仅满足于代码本身的美,而是喜于挖掘更深层次的东西,考虑每次扫描一个字符,然后判断它是否为0,时间将大大消耗在一个个判断和慢速的推进,如果字符串很长,那么时间将会消耗得很大,因此显然的想法就是将推进步伐加大,然后判断这一大步中是否包含有字符0,一次迈一大步节省了总的步数,但是判断一大步是否包含0成了瓶颈,步伐越大判断越难,难道又要在一大步中一个个字符扫描吗?如果那样最终的步进并没有变得高效,因此在判断“是否包含0”的时候必须用另一种时间复杂度为O(1)的算法来解决,这就是位运算,于是glibc的天才实现就是一次步进一个32位的word,然后用一个巧妙的位运算来解析出有没有0包含其中,实际上如果机器本身实现了更长的数据类型则完全可以使用这种更长的类型来实现更长的步进,在32位的x86机器上,32字节的长度就够长了,直接获得了底层的高效支持,来看一下代码:


size_t strlen ( const char *str )


{


const char *char_ptr;


const unsigned long int *longword_ptr;


unsigned long int longword, magic_bits, himagic, lomagic;


for (char_ptr = str; ((unsigned long int) char_ptr & (sizeof (longword) - 1)) != 0; ++char_ptr)


if (*char_ptr == '/0') //这个for主要是想看看第一个4字节对齐过程中是否会结束操作,如果结束则直接返回实际结束的位置和开始位置的差


return char_ptr - str;


longword_ptr = (unsigned long int *) char_ptr;


//himagic:10000000 10000000 10000000 10000000


//lomagic:00000001 00000001 00000001 00000001


himagic = 0x80808080L;


lomagic = 0x01010101L;


...


if (sizeof (longword) > 8)


abort ();


for (;;)


{


longword = *longword_ptr++;


//仔细看看下面的这个判断,longword - lomagic的结果,如果longword中有一个字节全0,那么在其减去lomagic之后,对应的该字节的最高位将会成为1,和himagic与操作之后结果就会不为0,然后就剩下判断了,该操作正确的前提是所有的字符都是ascii字符,也就是其最高位开始都是0,这个位运算是很显然的,另外除了这个技巧之外还有一个类似的算法如下:


unsigned long magic = 0x7efefeffL; (01111110 11111110 11111110 11111111)


unsigned long longword = XXXX;


unsigned long r1 = longword + magic; (如果有一个字符不是0,那么经过这个加法后,比此字符更高的一位的最低位的0将会成为1)


unsigned long r2 = (r1 ^ ~longword) ; (这个操作将会加法后的结果中和原来的longword相比没有改变的位全部置为1,只要magic对应每个字节的最低位被进位,那么加和和原来的数据的该位必不相同,如果原始位为1,那么由于进位加上magic的变成1的相应位后就会成为0,如果原始位为0,那么加上magic后将成为1)


unsigned long r3 = r2 & ~magic; (magic的反码的每个字节的最低位为1其余为0,和r2相与之后如果不为0,就说明有全是0的字节)


if( r3 != 0 ) ... (完毕,这个算法稍显复杂,但是更加具有技巧性,由此可见位运算是多么强大)


if (((longword - lomagic) & himagic) != 0)


{


const char *cp = (const char *) (longword_ptr - 1);


if (cp[0] == 0)


return cp - str;


if (cp[1] == 0)


return cp - str + 1;


if (cp[2] == 0)


return cp - str + 2;


if (cp[3] == 0)


return cp - str + 3;


...//不考虑的情况


}


}


}


欣赏完了strlen的实现,我不禁想赞叹其作者的聪明才智,其实不是作者很聪明,而是只要对c语言位运算有深刻理解的程序员都应该能做出来,在此基础之上,我们返璞归真的看一下ststr的实现,strstr其本质就是在既有字符串中匹配字串,完全可以应用上述算法,于是我自己实现了一个算法完全按照上述strlen的算法,在引出这个属于我自己的算法之前还是从最基本的BSD的libc实现的strstr开始吧:


char * strstr1(const char * s, const char * find)


{


char c, sc;


size_t len;


if ((c = *find++) != 0)


{


len = strlen(find);


do


{


do


{


if ((sc = *s++) == 0) //按照字节推进,到达源字符串末尾时直接返回NULL


return (NULL);


} while (sc != c);


} while (strncmp(s, find, len) != 0); //比对从此位置开始的len长度的所有字符,strncmp是一个复杂的操作,因此注定此函数效率不高,但是很清晰。


s--;


}


return ((char *)s);


}


下面看看windows的CRT的实现吧,比BSD好了那么一点,说实话这个算法是非常不错的,就我个人测试得出的结果,它的效率除了在某种情况下低于glibc的之外,其它的情况下是最高的:


char * strstr2 ( const char * str1, const char * str2)


{


char *cp = (char *) str1;


char *s1, *s2;


if ( !*str2 )


return((char *)str1);


while (*cp) //该算法以str2为基准在str1逐字节匹配


{


s1 = cp;


s2 = (char *) str2;


while ( *s1 && *s2 && !(*s1-*s2) )


s1++, s2++;


if (!*s2) //如果s2在和s1比较中提前结束,那么说明匹配成功


return(cp);


cp++;


}


return(NULL);


}


下面的strstr3是glibc的算法,我看了好长时间都没有看懂,我就不解释代码的意义了,该代码体现了一种境界,这种境界不是一般的人所能达到的,代码已经不仅仅是让一般人读的了,代码的读者是和机器合为一体的人才能读懂的:


typedef unsigned chartype;


char * strstr3( char *phaystack, char *pneedle)


{


unsigned char *haystack, *needle;


chartype b;


unsigned char *rneedle;


haystack = ( unsigned char *) phaystack;


if ((b = *(needle = ( unsigned char *) pneedle)))


{


//printf("un is:%d/n",b);


chartype c;


haystack--; /* possible ANSI violation */


{


chartype a;


do


if (!(a = *++haystack))


goto ret0;


while (a != b);


}


if (!(c = *++needle))


goto foundneedle;


++needle;


goto jin;


for (;;)


{


{


chartype a;


if (0)


jin:{


if ((a = *++haystack) == c)


goto crest;


}


else


a = *++haystack;


do


{


for (; a != b; a = *++haystack)


{


if (!a)


goto ret0;


if ((a = *++haystack) == b)


break;


if (!a)


goto ret0;


}


}while ((a = *++haystack) != c);


}


crest:


{


chartype a;


{


const unsigned char *rhaystack;


if (*(rhaystack = haystack-- + 1) == (a = *(rneedle = needle)))


do


{


if (!a)


goto foundneedle;


if (*++rhaystack != (a = *++needle))


break;


if (!a)


goto foundneedle;


}while (*++rhaystack == (a = *++needle));


needle = rneedle; /* took the register-poor aproach */


}


if (!a)


break;


}


}


}


foundneedle:


return (char *) haystack;


ret0:


return 0;


}


最后看看我的版本,我所实现的这个strstr函数有个特点,它只在一定的情况下才能体现高效性能,就是在大容量字符串匹配的情况下,因为这个算法和glibc的strlen一样,是按照4个字节的步伐向前推进的,因此一旦找到匹配的字符创,那么后面的匹配将会非常快,经过我的测试,这个实现在大量字符匹配时会很高效:


char * strstr4( const char *str, const char * find)


{


unsigned long *long_ptr_src,*long_ptr_find;


unsigned long magic1 = 0x80808080L, magic2 = 0x01010101L;


long_ptr_src = (unsigned long*)str;


long_ptr_find = (unsigned long*)find;


while (1)


{


unsigned long tmp = *long_ptr_src^*long_ptr_find;


if(tmp == 0)


{


long_ptr_src++;


long_ptr_find++;


continue;


}


else


{


if ((((*long_ptr_src - magic2) & magic1) == 0)&&(((*long_ptr_find - magic2) & magic1) != 0))


{


char *cp = (char*)(long_ptr_find);


char * tt = (char *)(long_ptr_src);


if (cp[0] == 0 ||(cp[1] == 0 &&tt[0] == cp[0])||


(cp[2] == 0 && tt[0] == cp[0]&& tt[1] == cp[1])||


(cp[3] == 0 &&tt[0] == cp[0]&& tt[1] == cp[1]&& tt[2]==cp[2]))


return tt - (cp-find);


return NULL;


}


if (((*long_ptr_src - magic2) & magic1) != 0)


{


return NULL;


}//差一种两个字符串均到尾部的情况需要讨论,比较复杂


int mp = 1;


while (!(tmp & 0xFF000000))


{


mp++;


tmp <<= 8;


}


char * tmpchar = (char *)long_ptr_src;


tmpchar += mp;


long_ptr_src = (unsigned long*)tmpchar;


long_ptr_find = (unsigned long*)find;


}


}


}


以下是测试程序:


char *a1 = (char *)malloc(512);


memset(a1,0,512);


strcpy(a1,"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#$%^&*()_+~QWERTYUIOPasdfghjklzxcvbnmzaqwsxcderfvbgtyhnujmkiol,./;'p[]");


char *a2 = (char *)malloc(512);


memset(a2,0,512);


strcpy(a2,"defghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#$%^&*()_+~QWERTYUIO");//jklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#$%^&*()_+~QWERTYUIOPasdfghjklzxcvbnmzaqwsxcderfvbgtyhnujmkiol,");


unsigned long c1 = GetCycleCount();


char *a3 = strstrX(a1,a2);


unsigned long c2 = GetCycleCount();


unsigned long delta = c2 - c1;


最后,有一个问题值得思考,然后看看这个strstr4算法中存在的问题,就是这个strstr实现的过程和rsync算法很相似,其实也是一种权衡,在时间和空间之间的权衡。strstr4的实现思想其实和CRT的实现strstr2是一致的,只是加入了glibc的strlen实现中所用到的算法,strstr4算法的好处在于如果需要匹配的字符串很长,那么将会很高效,但是如果在源字符串很长的索引处才匹配成功,那么时间将大量花费在:


int mp = 1;


while (!(tmp & 0xFF000000))


{


mp++;


tmp <<= 8;


}


char * tmpchar = (char *)long_ptr_src;


tmpchar += mp;


long_ptr_src = (unsigned long*)tmpchar;


其实看看这个计算的结果,看似很有技巧性,实际上很多情况下的结果都是执行tmpchar+=1的操作,这个和strstr2中的cp++是一致的,不幸的是耗费了更多的时间在移位和判断上,再一个就是这个算法是根本错误的,比如一个字符串是aaaaooooo,匹配字符串是aaaooo,如果按照strstr4的理解那么就是将源字符串的匹配指针一下子移动到o处重新开始匹配,这是错误的,因此修改为strstr2的方式,那样的话效率将会大大提高:


char * tmpchar = (char *)long_ptr_src;


tmpchar ++;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: C语言中常用的字符串替换函数是`str_replace`,它可以在一个字符串中查找并替换指定的子字符串。下面我将简单地模拟实现一个`str_replace`函数。 ```c #include <stdio.h> #include <stdlib.h> #include <string.h> char* str_replace(const char* str, const char* search, const char* replace) { char* result; int search_len = strlen(search); int replace_len = strlen(replace); int count = 0; // 统计需要替换的次数 const char* s = str; while ((s = strstr(s, search)) != NULL) { count++; s += search_len; } // 计算替换后的字符串长度 int result_len = strlen(str) + count * (replace_len - search_len); result = (char*)malloc(result_len + 1); // 开始替换 char* p = result; s = str; while ((s = strstr(s, search)) != NULL) { int n = s - str; strncpy(p, str, n); p += n; strncpy(p, replace, replace_len); p += replace_len; str = s + search_len; } strcpy(p, str); return result; } int main(void) { const char* str = "Hello, world!"; const char* search = "world"; const char* replace = "everyone"; char* result = str_replace(str, search, replace); printf("替换前: %s\n", str); printf("替换后: %s\n", result); free(result); return 0; } ``` 上述代码实现了一个简单的`str_replace`函数。它首先统计了需要替换的次数,并根据替换的次数计算了替换后的字符串长度。然后,它分配了足够的内存空间来存储替换后的字符串,并进行了替换操作。最后,它返回了替换后的字符串。在`main`函数中,我们可以看到如何使用这个函数来替换一个字符串中的子字符串。 需要注意的是,本示例只是对`str_replace`函数的简单模拟实现,实际使用中还需要处理更多的边界情况和错误处理。 ### 回答2: C语言中没有内置的字符串替换函数,但我们可以通过模拟实现一个字符串替换函数。 首先,我们可以定义一个函数,该函数接受三个参数:源字符串、待替换的子字符串、替换后的子字符串。函数的返回值是替换完成后的字符串。 接下来,我们可以使用循环来遍历源字符串。在每次循环中,比较源字符串中是否存在待替换的子字符串。如果存在,我们就将替换后的子字符串复制到新的字符串中。如果不存在,我们将源字符串中的当前字符复制到新的字符串中。 最后,返回新的字符串即可。 下面是一个简单的模拟实现示例: ```c #include <stdio.h> #include <string.h> char* stringReplace(char* source, const char* search, const char* replace) { char* result; int i, j, sourceLen, searchLen, replaceLen, count; sourceLen = strlen(source); searchLen = strlen(search); replaceLen = strlen(replace); count = 0; for (i = 0; i < sourceLen; i++) { if (strstr(&source[i], search) == &source[i]) { count++; i += searchLen - 1; } } result = (char*)malloc(sourceLen + count * (replaceLen - searchLen) + 1); i = 0; j = 0; while (source[i]) { if (strstr(&source[i], search) == &source[i]) { strcpy(&result[j], replace); j += replaceLen; i += searchLen; } else result[j++] = source[i++]; } result[j] = '\0'; return result; } int main() { char source[] = "Hello, World!"; const char search[] = "World"; const char replace[] = "C Language"; char* result = stringReplace(source, search, replace); printf("替换后的字符串: %s\n", result); free(result); return 0; } ``` 这是一个简单的模拟实现,实际上字符串替换还涉及到更多复杂的情况和细节,比如大小写敏感、替换次数限制等等。需要根据实际需求进行更进一步的完善。 ### 回答3: C语言字符串替换函数模拟实现的方法有很多,以下是一种可能的实现方式: ```c #include <stdio.h> #include <string.h> void str_replace(char *str, const char *find, const char *replace) { int find_len = strlen(find); int replace_len = strlen(replace); int str_len = strlen(str); char result[100]; int result_len = 0; int i = 0; while (i < str_len) { if (strncmp(&str[i], find, find_len) == 0) { // 找到需要替换的字符串 strncpy(&result[result_len], replace, replace_len); result_len += replace_len; // 跳过被替换的部分 i += find_len; } else { // 将原字符串的字符复制到结果字符串中 result[result_len] = str[i]; result_len++; i++; } } // 将新的结果字符串复制回原字符串中 strncpy(str, result, result_len); str[result_len] = '\0'; } int main() { char str[100] = "Hello, World!"; char find[10] = "World"; char replace[10] = "Alice"; printf("Before replace: %s\n", str); str_replace(str, find, replace); printf("After replace: %s\n", str); } ``` 这个函数的思路是首先计算出原字符串、需要查找的字符串以及替换的字符串的长度。然后以原字符串为基础,通过遍历每个字符的方式,查找需要替换的字符串,然后将替换的字符串复制到结果字符串中,同时跳过原字符串中已经被替换的部分。最后将结果字符串复制回原字符串中,完成字符串的替换。在主函数中,我们可以看到使用这个函数对原始字符串中的特定字符串进行了替换操作。运行程序后,可以输出替换后的字符串。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值