在程序員面試的時候,面試官通常會讓你實現一個或幾個C語言里的庫函數,以此來檢查你的編程功底。類似的函數實現有atoi.itoa.atof.strcmp.strcpy.memset.memcpy等等。
在平時的編程中,我們可能極少有機會要自己實現這些函數。但是面試官卻很喜歡用這些東西考察你。實際上所謂的"考察",永遠是個偽命題。面試者其實不是用這個小函數的實現來考察"你行",而大部分寄希望于用這個小函數來"你不行"。隨便在網上搜搜,就會發現很多討論這個的帖子,其中大部分都有一些解答(用來證明你不行的理由),仔細琢磨就會發現其中的有一些還蠻有道理,但有一些解釋實在是有點牽強。
說實話,這些函數實現的考察,作用還是有的,但是就我自己感覺,作用還不在單純的編程能力上,在本文的最后,我會發表我自己的一點看法。還是先說說這些函數的一些實現,都是我自己比較認可的版本,肯定也不一定完全符合面試官的要求,但無論如何,如果我碰到面試官這樣考我,我肯定拿下面的答案應付了:)。
首先來看看xtox系列的函數,這類函數涉及數類型和字符類型之間的轉換,考察的一個知識點是如何將一個數字轉化為對應的字符或者如何將對應的字符轉化為數字(以下的代碼演示了這些轉換方法),在我知道這個方法以前,我都是寫個函數,建立字符和數字的對應查詢關系(相當于建立了一個數字和字符的查詢快表)來匹配兩者的對應關系。代碼應該來說比較簡單,就不加注釋了,后文會提到需要額外說明的幾點。(特別說明:為篇幅計,下面的代碼出現了兩行代碼出現在一行的情況,真實的代碼中可能不希望這樣)
-------------------------------------------------------atoi----------------------------------------------------------
int atoi(const char* str)
{
int sign = 0,num = 0;
assert(NULL != str);
while (*str == ' ')
{
str++;
}
if ('-' == *str)
{
sign = 1; str++;
}
while ((*str >= '0') && (*str <= '9'))
{
num = num*10 + (*str - '0'); //就是這一行,將對應字符轉化為數字
str++;
}
if(sign == 1)
return -num;
else
return num;
}
-------------------------------------------------atof------------------------------------------------------------
double atof(const char* str)
{
double val = 0.0,power = 0.0;
int sign = 0;
assert(NULL != str);
while (*str == ' ')
{
str++;
}
sign = (*str == '-')? -1 : 1;
if ('-' == *str || '+' == *str)
{
str++;
}
while ((*str >= '0')&&(*str <= '9'))
{
val = val* 10.0 + (*str - '0'); str++;
}
if ('.' == *str)
{
str++;
}
power = 1.0;
while ((*str >= '0')&&(*str <= '9'))
{
val = val* 10.0 + (*str - '0');
power *= 10; str++;
}
return sign*val/power;
}
---------------------------------------------itoa------------------------------------------------------------------------
char* itoa(int val,char* buf,unsigned int radix)
{
char *bufptr;
char *firstdig;
char temp;
unsigned int digval;
assert(buf != NULL);
bufptr = buf;
if (val < 0)
{
*bufptr++ = '-'; val = (unsigned int)(-(int)val);
}
firstdig = bufptr;
do
{
digval =(unsigned int) val % radix; val /= radix;
if (digval > 9)
{
*bufptr++ = (char)(digval - 10 + 'a');
}
else
{
*bufptr++ = (char)(digval + '0');
}
} while(val > 0);
*bufptr-- = '\0';//設置字符串末尾,并將指針指向最后一個字符
do //反轉字符
{
temp = *bufptr; *bufptr = *firstdig; *firstdig = temp;
--bufptr; ++firstdig;
} while(firstdig < bufptr);
return buf;
}
----------------------------------------------itoa.end-------------------------------------------------------------------
下面是strxxx和memxxx系列,下面的一些實現,有些我到現在還抱有一些疑問,比如說strcmp函數,為什么要強制轉換成unisigned以及為什么要用*dst來判斷循環終止而不用*src,還沒有找到更好的答案或者徹底弄清楚這些問題,以后弄清楚了再補上。
-------------------------------------------------------strcmp------------------------------------------------------------
int strcmp(const char* src,const char* dst)
{
int ret = 0;
if (src == dst)
{
return 0;
}
assert(NULL != src);//期待源字符串不為空
if (dst == NULL)
{
return -1;
}
while (!(ret = *(unsigned char*)src - *(unsigned char*)dst)&& *dst)
{
++src,++dst;
}
if (ret < 0)
{
ret = -1;
}
else if (ret > 0)
{
ret = 1;
}
return ret;
}
-------------------------------------------strcpy------------------------------------------------------------------------
char* strcpy(char* dst,const char* src)
{
char* strDst = dst;
assert(src != NULL && dst != NULL);//拷貝空串被認為是沒有意義的,使用assert檢查
while ((*dst++ = *src++) != '\0')
{
NULL;
}
return strDst;
}
--------------------------------------memcpy-------------------------------------------------------------------------
void* memcpy(void* dst,const void* src,size_t count)
{
char* pbTo = (char*)dst;
char* pbFrom = (char*)src;
assert(dst!= NULL && src != NULL);
assert(pbTo >= pbFrom+count || pbFrom >= pbTo + count);//防止內存重疊(overlap)
while (count-- > 0)
{
*pbTo++ = *pbFrom++;
}
return dst;
}
--------------------------------------memmove---------------------------------------------------------------------
void* memmove(void* dst,const void* src,size_t count)
{
char* pbTo = (char*)dst;
char* pbFrom = (char*)src;
assert(dst != NULL && src != NULL);
if (dst <= src || pbTo >= pbFrom + count)//沒有overlap的情況,直接拷貝
{
while (count-- > 0)
{
*pbTo++ = *pbFrom++;
}
}
else
{
pbTo = pbTo + count -1;//overlap的情況,從高位地址向低位拷貝
pbFrom = pbFrom + count -1;
while (count-- > 0)
{
*pbTo-- = *pbFrom--;
}
}
return dst;
}
--------------------------------------memset-------------------------------------------------------------------------
void* memset(void* buf,int c,size_t count)
{
char* pvTo = (char*)buf;
assert(buf != NULL);
while (count-- >0)
{
*pvTo++ = (char)c;
}
return buf;
}
--------------------------------------memset.end---------------------------------------------------------------------
這些函數的代碼都很短小,但是面試官對你這幾行短小的代碼抱有很高的期望。
首先,正確性!實現得都不正確,那還搞毛啊,其他的小問題肯定談都不用談了,直接out!正確性要注意的地方,每個函數的功能起碼要了解(memmove等),邊界的檢查不能出錯;返回值也是要注意的地方。
其次,assert不能少!對指針有效性的檢查是非常必要的,特別是在memcpy中,存在兩個assert,分別檢查指針的有效性以及內存是否交疊。針對第二個assert還要加上必要注釋(因為代碼的維護者并不是一眼就能看出這個assert的涵義,搞不好可能直接在維護代碼中刪掉這么重要的一個assert檢查)。
然后要注意的有:指針的有效性檢驗,最好是與NULL進行比較。最后,空語句(strcpy)、大括號一個都不能少。
我自己的想法,其實這些函數(包含其他小的函數),難度不算太大,當然要寫得完全正確也非常不易。但是,在正確性的基礎上,面試官期望從你的代碼中發現你身上作為程序員的素質和態度!當我們編程的時候,我們的腦海里真的想的是手里敲的代碼嗎?當我們正在實現一個函數或者一個類的時候,我們真的認真考慮了它應該怎樣被實現嗎?針對這些小函數(當然,大函數更一樣了),參數的有效性檢查是always必要的(不要相信任何輸入!),邊界的有效性檢查是很容易出錯的!每一行代碼都要想清楚為什么要這么實現,我想,只要你腦子所想是你手上所要做的,我手"寫"我心,自然是不會出錯了,起碼也應該知道自己錯在哪里,下次改正就好。《微軟C編程精粹》最后一句話話告訴我們:成功地書寫無錯代碼的關鍵可以總結為一個總的原則,即絕不允許同樣的錯誤出現兩次!
最后,推薦一些參考書,就我自己的閱讀經驗來看,這些參考書對如何寫好這些小函數還是很有借鑒意義的,按重要性排序:
1.《編程精粹—微軟編寫優質無錯C程序秘訣》,Writing Clean Code——Microsoft Techniques for Developing Bug-free C Programs;
2.《高質量C\C++編程指南》,一本書,也是林銳編的,網上廣為流傳的同名小冊子算是精簡版;
3.《程序員面試寶典》;
本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/winglet/archive/2008/08/26/2831605.aspx