第四章、現場編寫類似strstr/strcpy/strpbrk的函數
作者:July。
說明:注意關鍵字,“十分鐘”,“現場編寫”。 本文諸多函數的編寫都存在問題,如\0寫成 /0,待日後統一修正。July、2011.10.11。
微博:http://weibo.com/julyweibo 。
出處:http://blog.csdn.net/v_JULY_v 。
wiki:http://tctop.wikispaces.com/。
----------------------------------------------
前奏
有網友向我反應,之前三章(http://t.cn/hgVPmH)的面試題目,是否有點太難了。誠如他所說,絕大部分公司的面試題不會像微軟等公司的面試題目出的那麼變態,或複雜。
面試考察的是你對基礎知識的掌握程度,及編程能力是否過硬的一種檢測,所以,紮實基礎知識,提高編程能力,比去看什麼所謂的面經,或去背面試題目的答案強多了。
很多中、小型公司自己的創造能力,包括人力,物力資源都有限,所以,他們的面試題目除了copy一些大公司的題庫之外(當然,考察你對基礎知識的掌握情況,是肯定不會放過的),還有一個途徑就是讓你在限定時間內(如十分鐘),當場實現一些類似strcpy/strcat/strpbrk等庫函數,這個主要看你對細節的把握,以及編程能力是否之紮實了。
同時,本章裏出現的代碼(除了第4節的c標準庫部分源碼)都是個人限定在短時間內(正好,突出現場感)編寫的,很多問題,難免有所考慮不周。所以,如果你發現本章任何一段代碼有任何問題,懇請不吝指正。
第一節、字符串查找
1.1題目描述:
給定一個字符串A,要求在A中查找一個子串B。
如A="ABCDF",要你在A中查找子串B=“CD”。
分析:比較簡單,相當於實現strstr庫函數,主體代碼如下:
- //在字符串中查找指定字符串的第一次出現,不能找到則返回-1
- int strstr(char *string, char *substring)
- {
- if (string == NULL || substring == NULL)
- return -1;
- int lenstr = strlen(string);
- int lensub = strlen(substring);
- if (lenstr < lensub)
- return -1;
- int len = lenstr - lensub;
- for (int i = 0; i <= len; i++) //複雜度爲O(m*n)
- {
- for (int j = 0; j < lensub; j++)
- {
- if (string[i+j] != substring[j])
- break;
- }
- if (j == lensub)
- return i + 1;
- }
- return -1;
- }
上述程序已經實現了在字符串中查找第一個子串的功能,時間複雜度爲O(n*m),也可以用KMP算法,複雜度爲O(m+n)。具體的,在此不再贅述。
希望此狂想曲系列能給各位帶來的是一種方法,一種創造力,一種舉一反三的能力,而不是機械的只是爲大家提供答案。那樣的話,一切永遠都只是邯鄲學步,你我都無從進步(而這同時卻是許多所謂的面經或面試寶典之類的書很樂意做的事,有點不解)。
爲人打通思路,提高他人創造力,我想,這是狂想曲與其它的面試解答所不同的地方,也是我們寫狂想曲系列文章的意義與價值之所在。
1.2、題目描述
在一個字符串中找到第一個只出現一次的字符。如輸入abaccdeff,則輸出b。
代碼則可以如下編寫:
- //查找第一個只出現一次的字符,
- //copyright@ yansha
- //July、updated,2011.04.24.
- char FirstNotRepeatChar(char* pString)
- {
- if(!pString)
- return '/0';
- const int tableSize = 256;
- //有點要提醒各位注意,一般常數的空間消耗,如這裏的256,我們也認爲此空間複雜度爲O(1)。
- int hashTable[tableSize] = {0}; //存入數組,並初始化爲0
- char* pHashKey = pString;
- while(*(pHashKey) != '/0')
- hashTable[*(pHashKey++)]++;
- while(*pString != '/0')
- {
- if(hashTable[*pString] == 1)
- return *pString;
- pString++;
- }
- return '/0'; //沒有找到滿足條件的字符,退出
- }
代碼二,bitmap:
- # include<stdio.h>
- # include<string.h>
- const int N = 26;
- int bit_map[N];
- void findNoRepeat(char *src)
- {
- int pos;
- char *str = src;
- int i ,len = strlen(src);
- //統計
- for(i = 0 ; i < len ;i ++)
- bit_map[str[i]-'a'] ++;
- //從字符串開始遍歷 其bit_map==1 那麼就是結果
- for(i = 0 ; i < len ; i ++)
- {
- if(bit_map[str[i]-'a'] == 1)
- {
- printf("%c",str[i]);
- return ;
- }
- }
- }
- int main()
- {
- char *src = "abaccdeff";
- findNoRepeat(src);
- printf("/n");
- return 0;
- }
第二節、字符串拷貝
題目描述:
要求實現庫函數strcpy,
原型聲明:extern char *strcpy(char *dest,char *src);
功能:把src所指由NULL結束的字符串複製到dest所指的數組中。
說明:src和dest所指內存區域不可以重疊且dest必須有足夠的空間來容納src的字符串。
返回指向dest的指針。
分析:如果編寫一個標準strcpy函數的總分值爲10,下面給出幾個不同得分的答案:
- //得2分
- void strcpy( char *strDest, char *strSrc )
- {
- while( (*strDest++ = * strSrc++) != '/0' );
- }
- //得4分
- void strcpy( char *strDest, const char *strSrc )
- {
- //將源字符串加const,表明其爲輸入參數,加2分
- while( (*strDest++ = * strSrc++) != '/0' );
- }
- //得7分
- void strcpy(char *strDest, const char *strSrc)
- {
- //對源地址和目的地址加非0斷言,加3分
- assert( (strDest != NULL) && (strSrc != NULL) );
- while( (*strDest++ = * strSrc++) != '/0' );
- }
- //得9分
- //爲了實現鏈式操作,將目的地址返回,加2分!
- char * strcpy( char *strDest, const char *strSrc )
- {
- assert( (strDest != NULL) && (strSrc != NULL) );
- char *address = strDest;
- while( (*strDest++ = * strSrc++) != '/0' );
- return address;
- }
- //得10分,基本上所有的情況,都考慮到了
- //如果有考慮到源目所指區域有重疊的情況,加1分!
- char * strcpy( char *strDest, const char *strSrc )
- {
- if(strDest == strSrc) { return strDest; }
- assert( (strDest != NULL) && (strSrc != NULL) );
- char *address = strDest;
- while( (*strDest++ = * strSrc++) != '/0' );
- return address;
- }
第三節、小部分庫函數的實現
考察此類編寫同庫函數一樣功能的函數經常見於大大小小的IT公司的面試題目中,以下是常見的字符串庫函數的實現,希望,對你有所幫助,有任何問題,歡迎不吝指正:
- //@yansha:字串末尾要加結束符'/0',不然輸出錯位結果
- char *strncpy(char *strDes, const char *strSrc, unsigned int count)
- {
- assert(strDes != NULL && strSrc != NULL);
- char *address = strDes;
- while (count-- && *strSrc != '/0')
- *strDes++ = *strSrc++;
- *strDes = '/0';
- return address;
- }
- //查找字符串s中首次出現字符c的位置
- char *strchr(const char *str, int c)
- {
- assert(str != NULL);
- for (; *str != (char)c; ++ str)
- if (*str == '/0')
- return NULL;
- return str;
- }
- int strcmp(const char *s, const char *t)
- {
- assert(s != NULL && t != NULL);
- while (*s && *t && *s == *t)
- {
- ++ s;
- ++ t;
- }
- return (*s - *t);
- }
- char *strcat(char *strDes, const char *strSrc)
- {
- assert((strDes != NULL) && (strSrc != NULL));
- char *address = strDes;
- while (*strDes != '/0')
- ++ strDes;
- while ((*strDes ++ = *strSrc ++) != '/0')
- NULL;
- return address;
- }
- int strlen(const char *str)
- {
- assert(str != NULL);
- int len = 0;
- while (*str ++ != '/0')
- ++ len;
- return len;
- }
- //此函數,夢修改如下
- char *strdup_(char *strSrc)
- //將字符串拷貝到新的位置
- {
- if(strSrc!=NULL)
- {
- char *start=strSrc;
- int len=0;
- while(*strSrc++!='/0')
- len++;
- char *address=(char *)malloc(len+1);
- assert(address != NULL);
- while((*address++=*start++)!='/0');
- return address-(len+1);
- }
- return NULL;
- }
- //多謝laoyi19861011指正
- char *strstr(const char *strSrc, const char *str)
- {
- assert(strSrc != NULL && str != NULL);
- const char *s = strSrc;
- const char *t = str;
- for (; *strSrc != '/0'; ++ strSrc)
- {
- for (s = strSrc, t = str; *t != '/0' && *s == *t; ++s, ++t)
- NULL;
- if (*t == '/0')
- return (char *) strSrc;
- }
- return NULL;
- }
- char *strncat(char *strDes, const char *strSrc, unsigned int count)
- {
- assert((strDes != NULL) && (strSrc != NULL));
- char *address = strDes;
- while (*strDes != '/0')
- ++ strDes;
- while (count -- && *strSrc != '/0' )
- *strDes ++ = *strSrc ++;
- *strDes = '/0';
- return address;
- }
- int strncmp(const char *s, const char *t, unsigned int count)
- {
- assert((s != NULL) && (t != NULL));
- while (*s && *t && *s == *t && count --)
- {
- ++ s;
- ++ t;
- }
- return (*s - *t);
- }
- char *strpbrk(const char *strSrc, const char *str)
- {
- assert((strSrc != NULL) && (str != NULL));
- const char *s;
- while (*strSrc != '/0')
- {
- s = str;
- while (*s != '/0')
- {
- if (*strSrc == *s)
- return (char *) strSrc;
- ++ s;
- }
- ++ strSrc;
- }
- return NULL;
- }
- int strcspn(const char *strSrc, const char *str)
- {
- assert((strSrc != NULL) && (str != NULL));
- const char *s;
- const char *t = strSrc;
- while (*t != '/0')
- {
- s = str;
- while (*s != '/0')
- {
- if (*t == *s)
- return t - strSrc;
- ++ s;
- }
- ++ t;
- }
- return 0;
- }
- int strspn(const char *strSrc, const char *str)
- {
- assert((strSrc != NULL) && (str != NULL));
- const char *s;
- const char *t = strSrc;
- while (*t != '/0')
- {
- s = str;
- while (*s != '/0')
- {
- if (*t == *s)
- break;
- ++ s;
- }
- if (*s == '/0')
- return t - strSrc;
- ++ t;
- }
- return 0;
- }
- char *strrchr(const char *str, int c)
- {
- assert(str != NULL);
- const char *s = str;
- while (*s != '/0')
- ++ s;
- for (-- s; *s != (char) c; -- s)
- if (s == str)
- return NULL;
- return (char *) s;
- }
- char* strrev(char *str)
- {
- assert(str != NULL);
- char *s = str, *t = str, c;
- while (*t != '/0')
- ++ t;
- for (-- t; s < t; ++ s, -- t)
- {
- c = *s;
- *s = *t;
- *t = c;
- }
- return str;
- }
- char *strnset(char *str, int c, unsigned int count)
- {
- assert(str != NULL);
- char *s = str;
- for (; *s != '/0' && s - str < count; ++ s)
- *s = (char) c;
- return str;
- }
- char *strset(char *str, int c)
- {
- assert(str != NULL);
- char *s = str;
- for (; *s != '/0'; ++ s)
- *s = (char) c;
- return str;
- }
- //@heyaming
- //對原 strtok 的修改,根據MSDN,strToken可以爲NULL.實際上第一次call strtok給定一字串,
- //再call strtok時可以輸入NULL代表要接着處理給定字串。
- //所以需要用一 static 保存沒有處理完的字串。同時也需要處理多個分隔符在一起的情況。
- char *strtok(char *strToken, const char *str)
- {
- assert(str != NULL);
- static char *last;
- if (strToken == NULL && (strToken = last) == NULL)
- return (NULL);
- char *s = strToken;
- const char *t = str;
- while (*s != '/0')
- {
- t = str;
- while (*t != '/0')
- {
- if (*s == *t)
- {
- last = s + 1;
- if (s - strToken == 0) {
- strToken = last;
- break;
- }
- *(strToken + (s - strToken)) = '/0';
- return strToken;
- }
- ++ t;
- }
- ++ s;
- }
- return NULL;
- }
- char *strupr(char *str)
- {
- assert(str != NULL);
- char *s = str;
- while (*s != '/0')
- {
- if (*s >= 'a' && *s <= 'z')
- *s -= 0x20;
- s ++;
- }
- return str;
- }
- char *strlwr(char *str)
- {
- assert(str != NULL);
- char *s = str;
- while (*s != '/0')
- {
- if (*s >= 'A' && *s <= 'Z')
- *s += 0x20;
- s ++;
- }
- return str;
- }
- void *memcpy(void *dest, const void *src, unsigned int count)
- {
- assert((dest != NULL) && (src != NULL));
- void *address = dest;
- while (count --)
- {
- *(char *) dest = *(char *) src;
- dest = (char *) dest + 1;
- src = (char *) src + 1;
- }
- return address;
- }
- void *memccpy(void *dest, const void *src, int c, unsigned int count)
- {
- assert((dest != NULL) && (src != NULL));
- while (count --)
- {
- *(char *) dest = *(char *) src;
- if (* (char *) src == (char) c)
- return ((char *)dest + 1);
- dest = (char *) dest + 1;
- src = (char *) src + 1;
- }
- return NULL;
- }
- void *memchr(const void *buf, int c, unsigned int count)
- {
- assert(buf != NULL);
- while (count --)
- {
- if (*(char *) buf == c)
- return (void *) buf;
- buf = (char *) buf + 1;
- }
- return NULL;
- }
- int memcmp(const void *s, const void *t, unsigned int count)
- {
- assert((s != NULL) && (t != NULL));
- while (*(char *) s && *(char *) t && *(char *) s == *(char *) t && count --)
- {
- s = (char *) s + 1;
- t = (char *) t + 1;
- }
- return (*(char *) s - *(char *) t);
- }
- //@big:
- //要處理src和dest有重疊的情況,不是從尾巴開始移動就沒問題了。
- //一種情況是dest小於src有重疊,這個時候要從頭開始移動,
- //另一種是dest大於src有重疊,這個時候要從尾開始移動。
- void *memmove(void *dest, const void *src, unsigned int count)
- {
- assert(dest != NULL && src != NULL);
- char* pdest = (char*) dest;
- char* psrc = (char*) src;
- //pdest在psrc後面,且兩者距離小於count時,從尾部開始移動. 其他情況從頭部開始移動
- if (pdest > psrc && pdest - psrc < count)
- {
- while (count--)
- {
- *(pdest + count) = *(psrc + count);
- }
- } else
- {
- while (count--)
- {
- *pdest++ = *psrc++;
- }
- }
- return dest;
- }
- void *memset(void *str, int c, unsigned int count)
- {
- assert(str != NULL);
- void *s = str;
- while (count --)
- {
- *(char *) s = (char) c;
- s = (char *) s + 1;
- }
- return str;
- }
測試:以上所有的函數,都待進一步測試,有任何問題,歡迎任何人隨時不吝指出。
第四節、c標準庫部分源代碼
爲了給各位一個可靠的參考,以下,我摘取一些c標準框裏的源代碼,以饗各位:
- char * __cdecl strcat (char * dst,const char * src)
- {
- char * cp = dst;
- while( *cp )
- cp++; /* find end of dst */
- while( *cp++ = *src++ ) ; /* Copy src to end of dst */
- return( dst ); /* return dst */
- }
- int __cdecl strcmp (const char * src,const char * dst)
- {
- int ret = 0 ;
- while( ! (ret = *(unsigned char *)src - *(unsigned char *)dst) && *dst)
- ++src, ++dst;
- if ( ret < 0 )
- ret = -1 ;
- else if ( ret > 0 )
- ret = 1 ;
- return( ret );
- }
- size_t __cdecl strlen (const char * str)
- {
- const char *eos = str;
- while( *eos++ ) ;
- return( (int)(eos - str - 1) );
- }
- char * __cdecl strncat (char * front,const char * back,size_t count)
- {
- char *start = front;
- while (*front++)
- ;
- front--;
- while (count--)
- if (!(*front++ = *back++))
- return(start);
- *front = '/0';
- return(start);
- }
- int __cdecl strncmp (const char * first,const char * last,size_t count)
- {
- if (!count)
- return(0);
- while (--count && *first && *first == *last)
- {
- first++;
- last++;
- }
- return( *(unsigned char *)first - *(unsigned char *)last );
- }
- /* Copy SRC to DEST. */
- char *
- strcpy (dest, src)
- char *dest;
- const char *src;
- {
- reg_char c;
- char *__unbounded s = (char *__unbounded) CHECK_BOUNDS_LOW (src);
- const ptrdiff_t off = CHECK_BOUNDS_LOW (dest) - s - 1;
- size_t n;
- do
- {
- c = *s++;
- s[off] = c;
- }
- while (c != '/0');
- n = s - src;
- (void) CHECK_BOUNDS_HIGH (src + n);
- (void) CHECK_BOUNDS_HIGH (dest + n);
- return dest;
- }
- char * __cdecl strncpy (char * dest,const char * source,size_t count)
- {
- char *start = dest;
- while (count && (*dest++ = *source++)) /* copy string */
- count--;
- if (count) /* pad out with zeroes */
- while (--count)
- *dest++ = '/0';
- return(start);
- }
有關狂想曲的修訂
程序員面試題狂想曲-tctop(the crazy thinking of programers)的修訂wiki(http://tctop.wikispaces.com/)已於今天建立,我們急切的想得到讀者的反饋,意見,建議,以及更好的思路,算法,和代碼優化的建議。所以,
- 如果你發現了狂想曲系列中的任何一題,任何一章(http://t.cn/hgVPmH)中的錯誤,問題,與漏洞,歡迎告知給我們,我們將感激不盡,同時,免費贈送本blog內的全部博文集錦的CHM文件1期;
- 如果你能對狂想曲系列的創作提供任何建設性意見,或指導,歡迎反饋給我們,並真誠邀請您加入到狂想曲的wiki修訂工作中;
- 如果你是編程高手,對狂想曲的任何一章有自己更好的思路,或算法,歡迎加入狂想曲的創作組,以爲千千萬萬的讀者創造更多的價值,更好的服務。
Ps:狂想曲tctop的wiki修訂地址爲:http://tctop.wikispaces.com/。歡迎圍觀,更歡迎您加入到狂想曲的創作或wiki修訂中。