這一週是國慶長假結束之後的一週,先後參加了宇龍酷派C++工程師的面試(一面、二面、簽約見面會)、浙江大華windows開發工程師(筆試和一、二、三面)、360的windows開發工程師的筆試、29所的面試、百度測試工程師的筆試和一面,相當的累,各種趕場子,也收集了一些筆試題和麪試中的現場筆試題,總結一下:
【宇龍酷派】
當時投宇龍酷派,完全是隨便亂投的,看到有一個C++工程師的崗位,然後覺得挺適合的就投了,完了就是它們HR打電話通知我去一面,當時覺得很蹊蹺,因爲投研發崗的好像正在筆試,也沒多想就去了,在三教一樓的一個小教室裏,裏面有兩個面試官,其中一個是面技術的,等到面我的時候,我好奇的問了一下他爲什麼不用筆試,他說他更喜歡面試的時候現場在電腦上寫程序,中間他還問了我一些C++的基礎知識,和java的一些區別,已經一些非技術類的問題,有點聊天的意思,然後就對着我的簡歷問了幾個問題,說盡量會把我的簡歷遞給上面的人篩選,然後讓我等通知,果然第二天就收到讓我二面的通知,二面的時間正好和我去浙江大華面試衝突了,最後趕在大華面試結束後,打個飛的過去,和他們HR的副總監進行了二面,問了我是否掛過科,哪裏人,“軟件設計師”是什麼水平?(哈哈)。然後就讓我等着最後發offer。第二天果然就發了,讓晚上5點半過去開個見面會,會上又是那個HR副總監,在那大談特談,各種“洗腦”,然後下面坐着的十多個科大同學都在盤算,趕緊講完,去參加360的筆試。反正也算是第一個offer了,555~
【浙江大華】
大華算是這一週走求職招聘流程最完整的一個企業了:先是10月9日下午2點去聽他家的宣講,然後10號去合工大(本部)參加下午兩點半的筆試(話說這是我第一次去合工大,感覺校園相當不錯,妹子也多,有活力),通過筆試之後,11號下午3點又跑到合工大去面試,結果(估計筆試成績太低)等到6點,他們工作人員都快要吃晚飯了,我才趕上一面,面試的老頭挺好的,問了我很多windows開發的東東,比如多線程用過哪些函數,智能指針,對話框函數,做過的項目,開發一個服務器程序最重要的幾個部分,對本專業和求職意向的差異,一面差不多快20多分鐘才結束。此時我已經飢腸轆轆了,問了一個HRmm說待會馬上給我安排二面,然後我說晚上有急事(實際上是酷派的終面),然後她說讓我先等等,還說她多訂了一份飯,讓給我吃,我當時非常尷尬,旁邊的哥們也瞎起鬨,讓我趕緊吃吧,我就只好吃着了,勉強吃完只好,面試官們也開始了二面,果然我排在了其他人的前面,第一個二面,二面的面試官就顯得比較嚴肅和雷厲風行,主要是問我筆試的兩個算法題的情況,然後又出了個和strcpy()函數相關的問題問我,再然後就又問我的編程風格吶,balabala....,最後在外面等着三面,中間酷派的HR打電話過來催了一次,我說馬上,然後終面的HR就對着我的簡歷看了又看,問了我的成績,做的項目,導師的評價,然後中間知道他居然也是湖北人,又問了我期望的薪水,我說個了個8000(要知道那可是在杭州啊!),然後HR說月底之前給通知(十天後給了通知),風風火火的浙江大華求職就這麼結束了。
大華的筆試題考了太多的結構體(混着聯合體)的字節對齊問題,至少有如下的三道:
1)在32位系統中,有以下結構體,問:sizeof(struct SA)的值是:
struct SA{
int a1:8;
int a2:8;
char a3[2];
char a4[2];
};
在32位的win7上用vs2012運行可以得到如下結果:
之所以出現結構體對齊的概念,主要是爲了方便CPU尋址,或者說是內存對齊。這樣,即使是同樣的結構體,成員換了順序,大小就不同了,內存對齊的規則大致有如下5點:
1、內存對齊與編譯器設置有關,首先要搞清編譯器的默認值是多少?
2、可以通過如下設定來改變編譯器內存對齊默認值:
#pragma pack(n)
3、每個結構體變量對齊,如果對齊參數n(編譯器默認或者通過#pragma編譯指令指定)大於該變量所佔字節數m,那麼就按照m對齊,那麼就按照m對齊,內存偏移後的地址是m的倍數,否則是按照n對齊,內存偏移後的地址是n的倍數。(最小化長度規則)
4、結構體總大小:對齊後的長度必須是成員中最大的對齊參數的整數倍。最大對齊參數是從第三步得到的。
5、補充:如果結構體A中還有結構體B,那麼B的對齊方式按照選B自己裏面最長的成員來對齊。
此外,該結構體中還出現了位域,對於情況,相當的特殊,有如下的處理原則:
C99規定int、unsigned int和bool可以作爲位域類型。但編譯器幾乎都對此作了擴展,允許其它類型類型的存在。
如果結構體中含有位域(bit-field),總結規則如下
1) 如果相鄰位域字段的類型相同,且其位寬之和小於類型的sizeof大小,則後面的字段將緊鄰前一個字段存儲,直到不能容納爲止;
2) 如果相鄰位域字段的類型相同,但其位寬之和大於類型的sizeof大小,則後面的字段將從新的存儲單元開始,其偏移量爲其類型大小的整數倍;
3) 如果相鄰的位域字段的類型不同,則各編譯器的具體實現有差異,VC6採取不壓縮方式(不同位域字段存放在不同的位域類型字節中),Dev-C++和GCC都採取壓縮方式;
4)如果位域字段之間穿插着非位域字段,則不進行壓縮;
5) 結構體的總大小爲結構體最寬基本類型成員大小的整數倍,且應儘量節省內存。
因此根據上述的幾點原則,a1和a2都要對齊到1上,即“該數據的存放地址%1==0”,偏移地址分別爲0和1;
a2和a3分別對齊到2和4上,但對與結構體的總大小,不應該爲(1+1+2+2=6),因爲要“結構體的總大小%sizeof(int)==0”,因此結構的總大小爲8!
更多和更具體的內容可以參考這篇文章:http://www.cnblogs.com/alex-tech/archive/2011/03/24/1993856.html
注意到,該結構體中即使出現了數組,對上面的規則5)也沒有影響,比如,對上面的結構體做如下修改:
struct SB{
int a1:8;
int a2:8;
char a3[5];
char a4[2];
};
則sizeof(struct SB)爲:12
2)在32位的操作系統中,設有以下說明和定義:
typedef union
{
long i;
int k[5];
char c;
}DATE;
struct data
{
int cat;
DATE cow;
double dog;
};
則,運行如下語句的結果是:
printf("sizeof(struct data)+sizeof(DATE) is %d\n",sizeof(struct data)+sizeof(DATE));
在該例子中出現了union,union的長度取決於其中的長度最大的那個成員變量的長度。即union中成員變量時重疊擺放的,其開始地址相同。
也即,union(共用體)的各個成員是以同一個地址開設存放的,每一時刻只能存儲一個成員,這樣就要求它在分配內存單元的時候要滿足兩點:
1、一般而言,共用體類型實際暫用存儲空間爲其最長的成員所佔的存儲空間;
2、若是該最長的存儲空間對其他成員的元類型(如果是數組,取其類型的數據長度,如下例 int a[5]的元類型長度爲4),則該最大空間自動延伸!
union mm{
char a;//元長度1
int b[5];//元長度4
double c;//元長度8
int d[3];
};
計算sizeof(union mm)則爲24而非20!因爲20%8!=0.
則上述題2)語句的輸出應爲:sizeof(DATE)=20 (其中最寬的類型都只佔4個字節),再計算struct data,分成兩部分,1)cat和cow (都是按4個字節對齊)佔4+20個字節,2)24%8==0,則dog偏移地址爲24。1)和2)兩部分共佔24+8個字節;則sizeof(struct data)+sizeof(DATE)=32+20=52
更多請參看:http://pppboy.blog.163.com/blog/static/30203796201082494026399/
3)定義聯合體:
typedef union
{
double a;
int b;
char c[16];
}U;
則sizeof(U)的結果爲 16 ;
運行結果如下:
4)在C語言中,要求運算數必須是整型的運算符包括:求餘運算符(%)、按位操作符(<<,>>,~,&,|和^)等
大華的兩道很普通的算法題我基本都做出來了,但是給我的分數卻不高,主要是可能筆試題注重基礎,不要過多使用庫函數,此外也要考慮算法複雜度的問題,儘量採用O(n)以下複雜度的算法,很忌諱多層嵌套的循環!
1)輸入一個字符串,請編程分析該字符串中出現頻率最高的字母及其出現的次數,這裏不考慮有多個字母同時出現次數最多的情況,比如"abcaabcad",返回結果爲'a',次數爲4。
函數聲明如下:
int ckstr(const char *msg, char *ret,int *times);
msg:輸入參數,輸入的字符串;
ret:輸出參數,出現頻率最高的字母;
times:輸出參數,出現的次數。
返回值:成功返回0,失敗返回-1。
最初我寫的算法是用的一個兩層循環,然後不停更新ret和times的值,直到字符串結束,後來和一個大牛同學討論之後,感覺是可以用映射的,這樣可以把複雜度降到O(n),具體的算法如下:
#include<cstring>
#include<climits>
#include<iostream>
int ckstr(const char *msg, char *ret,int *times){
int map[128];
memset(map,0,sizeof(map));
const char *p=msg;
if(p==NULL)
return -1;
else
{
while(*p){ //字符串未結束
if((map[*p]+=1)>INT_MAX) //超出整型範圍,失敗
return -1;
++p;
}
int index=0;
*ret=static_cast<char>(index-0);
*times=map[index];
for(index=1;index<128;index++)
if(map[index]>*times) {
*ret=static_cast<char>(index-0);
*times=map[index];
}
return 0;
}
return -1; //無異常情況,不會執行到這一步
}
int main()
{
const char str[]="abcaabcad";
char ret='\0';
int times=0;
if(0==ckstr(str,&ret,×))
std::cout<<"most use:"<<ret<<std::endl
<<"most times:"<<times<<std::endl;
else
std::cout<<"error occurs,please check!"<<std::endl;
system("pause");
return 0;
}
也就是將可取的128個ascii碼做映射,統計各出現字符的次數,找出其中的最大值即可!
看到網上一個相似的版本,用到了STL函數std::max_element<>():
#include<iostream>
#include<cstring>
#include<algorithm>
void GetMostUseChar(const char* str){
enum{BUF_SIZE=256};
int map[BUF_SIZE];
memset(map,0,sizeof(map));
const char *p=str;
while(*p){
map[*p]++;
++p;
}
int *TheMaxPos=std::max_element(map,map+BUF_SIZE);
int nMaxCount=*TheMaxPos;
char chMostUse=TheMaxPos-map;
std::cout<<"most use:"<<chMostUse<<std::endl
<<"use times:"<<nMaxCount<<std::endl;
}
int main()
{
const char str[]="abcaabcad";
GetMostUseChar(str);
system("pause");
return 0;
}
2)請把點分十進制的IPV4地址字符串,轉換爲16進制的數值,比如將192.168.1.10轉換成0xC0A8010A;
函數聲明如下:
int ip2val(const char *ip,unsigned int *val);
ip:輸入參數,格式爲點分十進制,假設IP地址合法;
val:輸出參數,16進制;
返回值:成功返回0,失敗返回-1.
這道題,我直接使用了C庫函數sscanf(),結果被判1分!實際上是考察將字符串轉換成給定進制的數字!新的思路是用一個數組表示該ipv4的四個值部分:
#include<cstdlib>
#include<cstdio>
#include<cstring>
int ip2val(const char *ip,unsigned int *val){
const char *p=ip;
unsigned int temp[4];
memset(temp,0,sizeof(temp));
if(ip==NULL) return -1;
else{
for(int i=0;i<4;i++){
while(*p!='.'&& *p!='\0'){
temp[i]=(*p-48)+10*temp[i];
++p;
}
++p;
}
*val=(temp[0]<<24) + (temp[1]<<16) + (temp[2]<<8) + temp[3];
return 0;
}
return -1; //不會運行到這,否則出現異常!
}
int main()
{
const char str[]="192.168.1.10";
unsigned int val=0;
if(0==ip2val(str,&val))
printf("0x%X\n",val);
else
printf("error exist!");
system("pause");
return 0;
}
【360】
奇虎的筆試現場真的是非常混亂,各路霸筆的通通到齊,前面的選擇題大概有10道智力和邏輯題,比如小白鼠“試毒”,檢測次品,此外還有有限狀態機,正則表達式
的題,很繁很雜,引起我興趣的是最後那道編程題:傳教士和野人的過河問題!令人驚訝的是隨便搜索一下,百度文庫居然給了兩種算法。
大致的題目如下:
傳教士和野人問題(The Missionaries and Cannibals Problem).在河的左岸有M個傳教士,1條船和C個野人。傳教士們想用這條船將所有的成員運過河取,但是受到一下條件的限制:
1)傳教士和野人都能划船,但船一次最多隻能裝運兩個;
2)在任一岸邊野人的數目都不得超過傳教士,否則傳教士就會遭遇危險,被野人吃掉;
3)此外,假定野人會服從任何一種過河安排。
試給出衣蛾確保全部成員安全過河的方案。
【百度】
由於我投的是百度的測試崗,所以試題算是相當簡單的,考察的範圍包括數據庫、網絡的基礎知識和一些簡答的算法題,大概能記得的題目有下面幾道,研發的題居然在百度文庫上找到了:
1)TCP/IP的四層結構:
TCP/ip協議棧分爲四層:物理接口層(對應OSI物理層,數據鏈路層)
Internet層(OSI網絡層),傳輸層(同OSI)應用層(OSI會話層、表示層、應用層)
更詳細的內容來自百度百科:
TCP/IP是一組用於實現網絡互連的通信協議。Internet網絡體系結構以TCP/IP爲核心。基於TCP/IP的參考模型將協議分成四個層次,它們分別是:網絡訪問層、網際互連層、傳輸層(主機到主機)、和應用層。
1.應用層
應用層對應於OSI參考模型的高層,爲用戶提供所需要的各種服務,例如:FTP、Telnet、DNS、SMTP等.
2.傳輸層
傳輸層對應於OSI參考模型的傳輸層,爲應用層實體提供端到端的通信功能。該層定義了兩個主要的協議:傳輸控制協議(TCP)和用戶數據報協議(UDP).TCP協議提供的是一種可靠的、面向連接的數據傳輸服務;而UDP協議提供的則是不可靠的、無連接的數據傳輸服務.
3.網際互聯層
網際互聯層對應於OSI參考模型的網絡層,主要解決主機到主機的通信問題。該層有四個主要協議:網際協議(IP)、地址解析協議(ARP)、互聯網組管理協議(IGMP)和互聯網控制報文協議(ICMP)。IP協議是網際互聯層最重要的協議,它提供的是一個不可靠、無連接的數據報傳遞服務。
4.網絡訪問層
網絡訪問層與OSI參考模型中的物理層和數據鏈路層相對應。事實上,TCP/IP本身並未定義該層的協議,而由參與互連的各網絡使用自己的物理層和數據鏈路層協議,然後與TCP/IP的網絡訪問層進行連接。
2)用簡單語言描述數據庫操作步驟:http://210.28.216.200/cai/shujukuxitong/kcjj/chapter01/right1_6.htm
http://www.blogjava.net/youling/archive/2008/11/28/243177.html
到這裏,還得說說百度的一面:一面問了我很多問題,總共面了大概1個小時,面試官是個挺年輕的小夥,先上來就問我昨天的一道算法設計題:
1、公司技術部接到一個任務,需要使用a-z,0-9組成3位的字符密碼,現請你設計一個算法,將可能的密碼組合全部打印出來。
我試卷上是用的遞歸,主要是時間也比較緊,具體的可以參照這個哥們的,意思意思就行:http://blog.csdn.net/leo115/article/details/8067496。面試的時候那個面試官讓我用非遞歸的方法重寫一遍,同時還提醒了我,可以考慮36進制!(0-9、A-F是16進制,0-9、A-Z就是36進制!)
這樣一想的話,這道題的複雜度瞬間從O(n^3)降到O(n),因爲我們只需把所有的36進制的3位數從小到到打印輸出即可!
#include<cstring>
#include<iostream>
void main(){
const char map[]={'0','1','2','3','4','5','6','7','8','9',
'A','B','C','D','E','F','G','H','I','J','K','L','M','N',
'O','P','Q','R','S','T','U','V','W','X','Y','Z'};
int len=sizeof(map)/sizeof(char);
int i;
for(i=0;i<len;i++)
std::cout<<"00"<<map[i]<<"\t";
for(i=len;i<len*len;i++)
std::cout<<"0"<<map[i/len]<<map[i%len]<<"\t";
for(i=len*len;i<len*len*len;i++)
std::cout<<map[i/(len*len)]<<map[(i%(len*len))/len]<<map[(i%(len*len))%len]<<"\t";
system("pause");
return;
}
2、二叉樹的非遞歸後序遍歷(當時就寫出了遞歸的算法,非遞歸愣是沒寫出像樣的來~)
算法思路:先找到最左邊的結點,並將路上遇到的結點壓棧,然後取棧頂元素(即最左邊的結點),1)檢查他是否有右子樹 2)右子樹是否被訪問過
如果沒有右子樹或者右子樹已經被訪問過,則訪問當前結點,然後將pre指向當前結點,並將當前結點從棧中刪除(當前結點出棧),否則(有右子樹且未被訪問過),把右子樹壓棧。如下是網友fly1988happy寫的一個非遞歸程序:
//二叉樹的後序非遞歸遍歷
void PostOrderTraverse(TreeNode *root){
if(NULL==root) return;
std::stack<TreeNode*> s;
TreeNode * pre=NULL;
while(root!=NULL || !s.empty()){
while(root!=NULL){
s.push(root);
root=root->pLeft;
}
if(!s.empty()){
root=s.top();
if(NULL==root->pRight || root->pRight==pre)
{
visit(root);
s.pop();
pre=root;
root=NULL;
}
else
root=root->pRight;
}
}
}
除此之外,前序和中序的非遞歸遍歷點這裏。