程式設計師程式設計藝術-----第四章-----現場編寫類似strstr/strcpy/strpbrk的函式
第四章、現場編寫類似strstr/strcpy/strpbrk的函式
作者:July。
說明: 如果在部落格中程式碼使用了\n,csdn blog系統將會自動回給我變成/n。據後續驗證,可能是原來舊blog版本的bug,新版已不存在此問題。至於,本文程式碼,日後統一修正。July、2012.05.02。
微博: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;
- }
讀者反饋@xiaohui5319:樓主啊,對於你那個strstr的函式,我覺得有點小問題。我查了一下C標準庫的原始碼,它給的宣告是這樣的,兩個引數都有const。
char *
STRSTR (const char *haystack_start, const char *needle_start)
而且標準庫中沒有呼叫strlen函式,因為假如你是標準庫的設計者,strlen()函式還沒設計出來,你怎麼去計算兩個字串的長度?是不是隻能通過指標移動來實現,我覺得這些都是微軟要考察的地方。
此外:還有int lenstr=strlen(string);這是不安全的?
strlen函式的返回型別是size_t型,也就是無符號整型,假如我的陣列長度很長(假如是用堆分配的,可以很大很大),長過2的31次方減1的話,會發生一處,你這lenstr就會變成負值了
用size_t型別最保險。
以後,本程式設計藝術系列中有任何問題,暫未來得及及時修正,請讀者多加思考,多加辨明。
上述程式已經實現了在字串中查詢第一個子串的功能,時間複雜度為O(n*m),也可以用KMP演算法,複雜度為O(m+n)。為人打通思路,提高他人創造力,我想,這是狂想曲與其它的面試解答所不同的地方,也是我們寫狂想曲系列文章的意義與價值之所在。
1.2、題目描述
在一個字串中找到第一個只出現一次的字元。如輸入abaccdeff,則輸出b。
程式碼則可以如下編寫:
- //查詢第一個只出現一次的字元,
- //[email protected] 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;
- }