常見面試題整理+網易實習生面試
(下午要去面試有道...還是做一點功課比較好...)
簡答
1. 多型實現機制
多型性可以簡單的概括為“1個介面,多種方法”,在程式執行的過程中才決定呼叫的機制,通過父類指標呼叫子類的函式,可以讓父類指標有多種形態。
編譯器為每個類的物件提供一個虛表指標,這個指標指向物件所屬類的虛表(存放了虛擬函式的地址)。在程式執行時,根據物件的型別去初始化vptr,從而讓vptr正確的指向所屬類的虛表,從而在呼叫虛擬函式時,就能夠找到正確的函式。
在建構函式中進行虛表的建立和虛表指標的初始化。在構造子類物件時,要先呼叫父類的建構函式,此時編譯器只“看到了”父類,並不知道後面是否後還有繼承者,它初始化父類物件的虛表指標,該虛表指標指向父類的虛表。當執行子類的建構函式時,子類物件的虛表指標被初始化,指向自身的虛表。
2. 虛擬函式,純虛擬函式
為了方便使用多型特性,我們常常需要在基類中定義虛擬函式。定義他為虛擬函式是為了允許用基類的指標來呼叫子類的這個函式。它虛就虛在所謂“推遲聯編”或者“動態聯編”上,一個類函式的呼叫並不是在編譯時刻被確定的,而是在執行時刻被確定的。由於編寫程式碼的時候並不能確定被呼叫的是基類的函式還是哪個派生類的函式,所以被成為“虛”函式。
純虛擬函式是在基類中宣告的虛擬函式,它在基類中沒有定義,但要求任何派生類都要定義自己的實現方法。同時含有純虛擬函式的類稱為抽象類,它不能生成物件。
3. 引用和指標的區別
(1) 引用必須被初始化,指標不必。
(2) 引用初始化以後不能被改變,指標可以改變所指的物件。
(3) 不存在指向空值的引用,但是存在指向空值的指標。
4. 過載和重寫的區別
過載:是指允許存在多個同名函式,而這些函式的引數表不同(或許引數個數不同,或許引數型別不同,或許兩者都不同)。
重寫:是指子類重新定義父類虛擬函式的方法。(和多型相關)
5. 拷貝建構函式(深拷貝和淺拷貝)
如果一個建構函式的第一個引數是自身類型別的引用,且任何額外引數都有預設值,則此建構函式時拷貝建構函式。
淺拷貝是建立了一個物件用一個現成的物件初始化它的時候只是複製了成員(簡單賦值)而沒有拷貝分配給成員的資源(如給其指標變數成員分配了動態記憶體); 深拷貝是當一個物件建立時,如果分配了資源,就需要定義自己的拷貝建構函式,使之不但拷貝成員也拷貝分配給它的資源。
6. 記憶體分配方式及他們的區別
(1) 從靜態儲存區域分配。記憶體在程式編譯的時候就已經分配好,這塊記憶體在程式的整個執行期間都存在。例如全域性變數,static 變數。
(2) 在棧上建立。在執行函式時,函式內區域性變數的儲存單元都可以在棧上建立,函式執行結束時這些儲存單元自動被釋放。棧記憶體分配運算內置於處理器的指令集。
(3) 從堆上分配,亦稱動態記憶體分配。程式在執行的時候用malloc 或new 申請任意多少的記憶體,程式設計師自己負責在何時用free 或delete 釋放記憶體。動態記憶體的生存期由程式設計師決定,使用非常靈活,但問題也最多。
7. C++中型別轉換機制
(http://blog.csdn.net/kesalin/article/details/8119586)
(1) 隱式型別轉換
將某種型別的物件拷貝到另一種不同型別的物件中時就會發生隱式轉型,不推薦。
(2) 顯示型別轉換
cast_name<type>(expression),cast_name可以是以下四種:
Ø static_cast
任何具有明確定義的型別轉化,只要不包含底層的const,都可以使用static_cast。可以將非常量轉化為常量,反之不可以。不進行安全檢查。
Ø const_cast
可以將常量轉換為非常量。只能改變常量屬性,不能改變表示式的型別。
Ø dynamic_cast
主要用來在繼承體系中的安全向下轉型,它能安全地將指向基類的指標轉型為指向子類的指標或引用,並獲知轉型動作成功是否。具有型別檢查功能,比static_cast更安全,但是存在一定的效率損失。
Ø reinterpret_cast
只用於底層程式碼,一般我們都用不到它~依賴於機器,為運算物件的位模式提供較低層次上的重新解釋。
8. new/delete和malloc/free的區別
new/delete是C++運算子,malloc/free是C語言的標準庫函式。對於非內部資料型別的物件而言,光用maloc/free無法滿足動態物件的要求。物件在建立的同時要自動執行建構函式,物件在消亡之前要自動執行解構函式(除分配/釋放記憶體外,還可能有其他更為詳細的工作)。由於malloc/free是庫函式而不是運算子,不在編譯器控制權限之內,不能夠把執行建構函式和解構函式的任務強加於malloc/free。因此C++語言需要一個能完成動態記憶體分配和初始化工作的運算子new,以及一個能完成清理與釋放記憶體工作的運算子delete。注意new/delete不是庫函式。
9. 程序和執行緒的區別
程式設計
1. 字串裡對稱子串的最大長度
(騰訊實習生筆試題…當時居然空了……)
輸入一個字串,輸出該字串中對稱的子字串的最大長度。比如輸入字串“goooogle”, 由於該字串裡最長的對稱子字串是“goooog”, 因此輸出 6。程式碼如下:
#include<iostream>
#include<string>
usingnamespace std;
intsymLength(string str);
boolisSym(string str);
int main(){
string test;
int maxSymLength;
while(cin>>test){
maxSymLength =symLength(test);
cout << maxSymLength<< endl;
}
return 0;
}
intsymLength(string str)//字串中最長的對稱子字串
{
int maxSymLength = 0;
if(str.size()==1) return 0;
for(int i=0; i<str.size()-1; i++)
for(int j=str.size()-1;j>i; j--){
string temp =str.substr(i,j-i+1);
if(isSym(temp))maxSymLength = max(j-i+1,maxSymLength);
}
return maxSymLength;
}
boolisSym(string str)//判斷一個字串是否對稱
{
for(int i=0;i<str.size()/2;i++){
if(str[i]==str[str.size()-1-i])continue;
else return false;
}
return true;
}
自己寫的這種方法時間複雜度比較高,參考了網上的程式碼,可以從o(n^3)降到o(n^2)。
主要思路是如果我們從內向外比較字元,那麼對於aba型的字串,如果我們判斷了b是對稱的,只需要再左右各移一位就可以判斷下一個字串是否是對稱的,這樣就能避免重複;原字串中每一個字元有兩種情況,一種是子串是以單個字元為中心對稱分佈的,即子串的長度是奇數;另一種情況是子串以兩個字串為中心,即子串的長度是偶數。參考程式碼(http://www.cnblogs.com/python27/archive/2011/12/18/2291977.html):
#include<iostream>
#include<string>
usingnamespace std;
/*********************************************************************
* 計算字串最大對稱子串的長度
*********************************************************************/
intMaxSymmetricalSubstringLenth(char* pstring)
{
int maxlength = 1;
char* pchar = pstring + 1;
while(*pchar != '\0')
{
//以該字串為中心,子串長度為奇數
char *pfirst = pchar - 1;
char *psecond = pchar + 1;
while(pfirst > pstring &&psecond < &pstring[strlen(pstring) - 1] && *pfirst == *psecond)
{
pfirst--;
psecond++;
}
int templength = psecond - pfirst + 1;
if(templength > maxlength)
{
maxlength = templength;
}
//字該字元及之後的字元兩個為中心,子串為偶數
pfirst = pchar - 1;
psecond = pchar;
while(pfirst > pstring &&psecond < &pstring[strlen(pstring) - 1] && *pfirst == *psecond)
{
pfirst--;
psecond++;
}
templength = psecond - pfirst + 1;
if(templength > maxlength)
{
maxlength = templength;
}
pchar++;
}
return maxlength;
}
int main()
{
cout<<"Please Enter YourString:"<<endl;
char *yourstring = new char[1000];
cin>>yourstring;
cout<<"The Max SymmetricalSubString Length is:"<<endl;
cout<<MaxSymmetricalSubstringLenth(yourstring)<<endl;
delete[] yourstring;
return 0;
}
2. 列印回形矩陣&蛇形矩陣
(同騰訊實習生筆試題…)
輸入一個正整數N,輸出N*N的回形/蛇形矩陣。eg.N=4, 如下所示:
回形矩陣:
1 2 3 4
12 13 14 5
11 16 15 6
10 9 8 7
蛇形矩陣:
1 2 6 7
3 5 8 13
4 9 12 14
10 11 15 16
程式碼如下:
#include<iostream>
using namespace std;
void roundMatrix(int matrix[],int N);
void snakeMatrix(int matrix[],int N);
void printMatrix(int m[], int M);
int main(){
int N;
cin >> N;
int *round = new int[N*N];
int *snake = new int[N*N];
roundMatrix(round,N);
snakeMatrix(snake,N);
printMatrix(round,N*N);
printMatrix(snake,N*N);
delete[] round;
delete[] snake;
return 0;
}
void roundMatrix(int matrix[],int N)
{
int round = 0;
int num = 1;
while(1)
{
for(int col=round;col<N-round&&num<=N*N;col++) //從左到右
matrix[round*N+col] = num++;
if(num>N*N) break;
for(int row=round+1;row<N-round&&num<=N*N;row++) //從上到下
matrix[row*N+N-1-round] = num++;
if(num>N*N) break;
for(int col=N-2-round;col>=round&&num<=N*N;col--) //從右到左
matrix[(N-1-round)*N+col] = num++;
if(num>N*N) break;
for(int row=N-2-round;row>round&&num<=N*N;row--) //從下到上
matrix[row*N+round] = num++;
if(num>N*N) break;
round++;
}
}
void snakeMatrix(int matrix[], int N)
{
int diag = 0;
int num = 1;
while(1)
{
if(diag<N){
//從左下到右上
if(diag%2==0){
for(int col=0;col<=diag&&num<=N*N;col++)
matrix[(diag-col)*N+col] = num++;
}
//從右上到左下
else{
for(int col=diag;col>=0&&num<=N*N;col--)
matrix[(diag-col)*N+col] = num++;
}
}
else{
int diag1 = 2*(N-1)-diag;
//從左下到右上
if(diag%2==0){
for(int col=N-1-diag1;col<=N-1&&num<=N*N;col++)
matrix[(diag-col)*N+col] = num++;
}
//從右上到左下
else{
for(int col=N-1;col>=N-1-diag1&&num<=N*N;col--)
matrix[(diag-col)*N+col] = num++;
}
}
printMatrix(matrix, N*N);
if(num>N*N) break;
diag++;
}
}
void printMatrix(int m[], int M)
{
int N = sqrt(M*1.0);
for(int i=0;i<M;i++){
if((i+1)%N==0){
cout<<m[i]<<" \n";
}
else cout<<m[i]<<" ";
}
}
3. 二叉樹的遍歷(非遞迴)
(基礎…)
深度優先搜尋演算法(前序遍歷),可以藉助堆疊的資料結構,由於堆疊是後進先出的順序,由此可以先將右子樹壓棧,然後再對左子樹壓棧,這樣一來,左子樹結點就存在了棧頂上,因此某結點的左子樹能在它的右子樹遍歷之前被遍歷。
參考程式碼:
void depthFirstSearch(Tree* root){
stack<Tree *> nodeStack; //使用C++的STL標準模板庫
nodeStack.push(root);
Tree *node;
while(!nodeStack.empty()){
node = nodeStack.top();
printf(format, node->data); //遍歷根結點
nodeStack.pop();
if(node->rchild){
nodeStack.push(node->rchild); //先將右子樹壓棧
}
if(node->lchild){
nodeStack.push(node->lchild); //再將左子樹壓棧
}
}
}
廣度優先演算法,藉助佇列資料結構,由於佇列是先進先出的順序,因此可以先將左子樹入隊,然後再將右子樹入隊。這樣一來,左子樹結點就存在隊頭,可以先被訪問到。
參考程式碼:
void breadthFirstSearch(Tree* root){
queue<Tree *> nodeQueue; //使用C++的STL標準模板庫
nodeQueue.push(root);
Tree *node;
while(!nodeQueue.empty()){
node = nodeQueue.front();
nodeQueue.pop();
printf(format, node->data);
if(node->lchild){
nodeQueue.push(node->lchild); //先將左子樹入隊
}
if(node->rchild){
nodeQueue.push(node->rchild); //再將右子樹入隊
}
}
}
4. 各種排序演算法
選擇排序、快速排序、希爾排序、堆排序不是穩定的排序演算法,
氣泡排序、插入排序、歸併排序和基數排序是穩定的排序演算法。
寫下快速排序和歸併排序:
(1) 快速排序
通過掃描一次將要排序的資料分割成獨立的兩部分(通常用首位作為flag),其中一部分的所有資料都比另外一部分的所有資料都要小,然後再按此方法對這兩部分資料分別進行快速排序,整個排序過程可以遞迴進行,以此達到整個資料變成有序序列。
#include<iostream>
#include<vector>
usingnamespace std;
voidquicksort(vector<int> &v, int left, int right);
int main(){
vector<int> test;
int elem;
while(cin>>elem &&elem!='\n'){
test.push_back(elem);
}
quicksort(test,0,test.size()-1);
for(int i=0;i<test.size();i++)
cout << test[i]<< " ";
return 0;
}
voidquicksort(vector<int> &v, int left, int right){
if(left<right){
int flag = v[left];
int low = left;
int high = right;
while(low<high){
while(low<high&& v[high]>flag){
high--;
}
v[low] = v[high];
while(low < high&& v[low] < flag){
low++;
}
v[high] = v[low];
}
v[low] = flag;
quicksort(v,left,low-1);
quicksort(v,low+1,right);
}
}
(2) 歸併排序
原理,把原始陣列分成若干子陣列,對每一個子陣列進行排序,繼續把子陣列與子數組合並,合併後仍然有序,直到全部合併完,形成有序的陣列
#include<iostream>
#include<vector>
usingnamespace std;
voidmergesort(int a[], int first, int last, int temp[]);
voidmergearray(int a[], int first, int mid, int last, int temp[]);
int main(){
int test[6] = {2,3,1,6,8,4};
int after[6];
mergesort(test,0,5,after);
for(int i=0;i<6;i++)
cout << after[i]<< " ";
return 0;
}
voidmergearray(int a[], int first, int mid, int last, int temp[])
{
int i = first, j = mid + 1;
int m = mid, n = last;
int k = 0;
while (i <= m && j <= n)
{
if (a[i] <= a[j])
temp[k++] = a[i++];
else
temp[k++] = a[j++];
}
while (i <= m)
temp[k++] = a[i++];
while (j <= n)
temp[k++] = a[j++];
for (i = 0; i < k; ++i)
a[first + i] = temp[i];
}
voidmergesort(int a[], int first, int last, int temp[])
{
if (first < last)
{
int mid = (first + last) / 2;
mergesort(a, first, mid,temp); //左邊有序
mergesort(a, mid + 1, last,temp); //右邊有序
mergearray(a, first, mid,last, temp); //再將兩個有序數列合併
}
}
網易實習生面試
因為軟體園好多地方都在施工,甚至把路都堵住了,頂著大太陽找了好久才找到網易.......對於一個路痴來說簡直是.....地圖+指南針還經常搞不清往哪裡走==心真累。
面試官看起來蠻年輕,典型技術男的長相,先是做了自我介紹,然後針對簡歷簡單聊了幾句....然後就拿出了白紙,做了兩道程式設計題:
(1)string型陣列,將數組裡所有的無序字串保留下來,如["abc","ab","bca","cba","cd"],則輸出為["abc","bca","cba"]。
具體思路就是a.對每個字串進行排序,生成一個新的有序字串陣列;b.對有序字串陣列掃描一次,用map<string,vector<int>>記錄下有序字串及對應的位置;c.如果記錄字串位置的vector長度大於1,則保留vector中所有位置的無序字串。
#include<iostream>
#include<algorithm>
#include<string>
#include<vector>
#include<map>
using namespace std;
#define LEN 10
vector<string> findDisorder(string test[], int N);
int main(){
string test[LEN] = {"what", "abc", "hawt","ab","bca","acb","www","htaw","mmm"};
vector<string> result;
result = findDisorder(test,LEN);
for(int i=0;i<result.size();i++)
cout << result[i] << " ";
return 0;
}
vector<string> findDisorder(string test[], int N){
vector<string> sorted;
vector<string> ans;
for(int i=0;i<N;i++){
sorted.push_back(test[i]);
sort(sorted[i].begin(),sorted[i].end());//直接使用STL中的排序函式
}
map<string,vector<int>> locations;//存放所有的無序字串及其在源字串中的位置
for(int i=0;i<sorted.size();i++){
if(locations.count(sorted[i]))
locations[sorted[i]].push_back(i);
else{
vector<int> loc;
loc.push_back(i);
locations.insert(pair<string,vector<int>>(sorted[i],loc));
}
}
map< string,vector<int> >::iterator it;
for(it=locations.begin();it!=locations.end();it++){
if(it->second.size()!=1) {
for(int i=0;i<it->second.size();i++)
ans.push_back(test[it->second[i]]);
}
}
return ans;
}
(2)int型陣列,將數組裡的元素連線拼成最大的數字,如[12,132,89,15]的輸出是一個string:“891513212”。
具體思路就是使用歸併排序,而比較大小的sort準則需要自己寫。簡單來說下sort準則:[a,b]可以組合成兩個字串:a+b/b+a,比大小,前者大則a大,後者大則b大。
#include<iostream>
#include<string>
#include<sstream>
using namespace std;
#define LEN 3
string maxNum(int test[], int N);
void mergesort(string a[], int first, int last, string temp[]);
void mergearray(string a[], int first, int mid, int last, string temp[]);
bool maxThan(string a, string b);
int main(){
int test[LEN] = {98, 12, 120};
string result = maxNum(test,LEN);
cout << result << endl;
return 0;
}
string maxNum(int test[], int N)
{
string *ans = new string[N];
string *temp = new string[N];
string sum="";
for(int i=0;i<N;i++){
stringstream os;
os<<test[i];
ans[i] = os.str();
}
mergesort(ans,0,N-1,temp);
for(int i=0;i<N;i++) sum+=ans[i];
delete[] ans;
delete[] temp;
return sum;
}
void mergesort(string a[], int first, int last, string temp[])
{
if (first < last)
{
int mid = (first + last) / 2;
mergesort(a, first, mid, temp); //左邊有序
mergesort(a, mid + 1, last, temp); //右邊有序
mergearray(a, first, mid, last, temp); //再將兩個有序數列合併
}
}
void mergearray(string a[], int first, int mid, int last, string temp[])
{
int i = first, j = mid + 1;
int m = mid, n = last;
int k = 0;
while (i<=m && j <= n)
{
if (maxThan(a[i],a[j]))
temp[k++] = a[i++];
else
temp[k++] = a[j++];
}
while (i <= m)
temp[k++] = a[i++];
while (j <= n)
temp[k++] = a[j++];
for (i = 0; i < k; ++i)
a[first + i] = temp[i];
}
bool maxThan(string a, string b){
string str1 = a+b;
string str2 = b+a;
return str1>str2;
}
感覺自己通常可以理清解決問題的思路,但是幾乎只能寫出虛擬碼...好多函式記不清...基礎還是太差==另外,我之前投的崗位是研發工程實習生,但是聽面試官的意思是他們主要做有道系統開發,在這方面完全沒有優勢....好吧,等待結果....估計要掛...