騰訊2017秋招筆試題難點記錄
1.在vs編譯環境下,以下程式碼的執行情況:
1234567 | int f( int a, int b, int c) { return 0; } int main(){ return f( printf ( "a" ), printf ( "b" ), printf ( "c" )); } |
2.具有3個節點的二叉樹有幾種形態?
3.在Linux上,對於多程序,子程序繼承了父程序的下列哪些?
解析:
- 子程序繼承父程序
- 使用者號UIDs和使用者組號GIDs
- 環境Environment
- 堆疊
- 共享記憶體
- 開啟檔案的描述符
- 執行時關閉(Close-on-exec)標誌
- 訊號(Signal)控制設定
- 程序組號
- 當前工作目錄
- 根目錄
- 檔案方式建立遮蔽字
- 資源限制
- 控制終端
-
子程序獨有
- 程序號PID
- 不同的父程序號
- 自己的檔案描述符和目錄流的拷貝
- 子程序不繼承父程序的程序正文(text),資料和其他鎖定記憶體(memory locks)
- 不繼承非同步輸入和輸出
-
父程序和子程序擁有獨立的地址空間和PID引數。子程序從父程序繼承了使用者號和使用者組號,使用者資訊,目錄資訊,環境(表),開啟的檔案描述符,堆疊,(共享)記憶體等。經過fork()以後,父程序和子程序擁有相同內容的程式碼段、資料段和使用者堆疊,就像父程序把自己克隆了一遍。事實上,父程序只複製了自己的PCB塊。而程式碼段,資料段和使用者堆疊記憶體空間並沒有複製一份,而是與子程序共享。只有當子程序在執行中出現寫操作時,才會產生中斷,併為子程序分配記憶體空間。由於父程序的PCB和子程序的一樣,所以在PCB中斷中所記錄的父程序佔有的資源,也是與子程序共享使用的。這裡的“共享”一詞意味著“競爭“。
- 4.設有一個遞迴演算法如下
-
1234 int
f(
int
n) {
if
(n<=3)
return
1;
else
return
f(n-2)+f(n-6)+1;
}
試問計算f(f(9))時需要計算()次f函式。
解析: 一、先算內層f(9) [1] 計算 f(9) = f(7) + f(3) + 1; [2] 計算[1]中 f(7) = f(5) + f(1) + 1; [3] 計算[2]中 f(5) = f(3) + f(-1) + 1; [4] 計算[3]中 f(3) = 1; [5] 計算[3]中 f(-1) = 1; {至此f(5)可計算得: f(5) = 1 + 1 + 1 = 3} [6] 計算(1)中f(1) = 1; {至此f(7)可計算得 :f(7) = 3 + 1 + 1 = 5} [7] 計算[1]中f(3) = 1; {至此f(9)可計算得:f(9) = 5 + 1 + 1 = 7} 計算f(9)一共呼叫了7次函式 二、計算外層f(7) 由上面步驟可知,計算f(7)呼叫了5次函式 所以一共呼叫了函式7+5=12次 5.寢室有6個同學打dota,分為對立的兩方,一方是天災軍團,一方是近衛軍團。現請你設定賽程以及每場的對陣(每方最少1人、最多5人),請問至少得進行多少場比賽,才能使得賽程結束後每位同學都和其他同學做過對手() - 解析:用三位二進位制來表示某個人三場比賽各場比賽所在的一方,比如我們用0代表在天災,1代表在近衛,那麼000就代表這個人三場比賽都在天災,而001表示這個人前兩場比賽在天災,第三場比賽在近衛。那麼三位二進位制可以有8種表示,而每一種表示都與其他7種的表示至少在一個位置上的數字是不一樣的,所以最多8人至少三場可以每個人都做過對手。
- 6.
以下程式碼列印的結果是(假設執行在 64 位計算機上):
解析: 根據位元組對齊,在64位系統下structst_t 結構體佔用的位元組為48個。 struct st_t { int status; //佔用8個(後面的4個為對齊位) short *pdata;//佔用8個 char errstr[32];//佔用32個 }; char*p=(char*)(st[2].esstr+32),p實際指向了st[3] 則p-(char*)(st)),即為&st[3]-&st[0],佔用空間為3個結構體的大小,即3*48=14412345678 struct
st_t {
int
status;
short
*pdata;
char
errstr[32];
};
st_t st[16];
char
*p=(
char
*)(st[2].esstr+32);
printf
(“%d”,(p-(
char
*)(st)));
- 7. 請選擇下列程式的輸出結果是()
解析:int a[] = {2,1,4,3,6,5,8,7,10,9}; //一維陣列a int (*b)[N/M]=(int (*)[N/M])a; //將一維陣列a強制轉化為陣列指標並賦值給陣列指標b; //上面兩句可以拆分為以下幾句理解; int a[2][N/M] = {2,1,4,3,6,5,8,7,10,9}; int (*b)[N/M]; //b是陣列指標,指向具有N/M個元素的一維陣列; b = a; 8.1234567891011121314 #include <stdio.h>
int
main()
{
const
int
N=10;
const
int
M=2;
int
* a=
new
int
[N];
for
(
int
i=0;i<N;++i)
a[i]=(0==i%2)?(i+2):(i+0);
int
(*b)[N/M]=(
int
(*)[N/M])a;
for
(
int
i=0;i<M;++i)
for
(
int
j=0;j<N/M;++j)
printf
(“%d”,b[i][j]);
return
0;
}
- 關於c++中的虛擬函式和解構函式還有建構函式的關係?
- C++中 的虛擬函式的作用主要是實現了多型的機制。而虛擬函式是通過虛擬函式表(V-Table)實現的。 建構函式不能宣告為虛擬函式,解構函式可以宣告為虛擬函式,而且有時是必須宣告為虛擬函式。 建構函式為什麼不能宣告為虛擬函式? 1 構造一個物件的時候,必須知道物件的實際型別,而虛擬函式行為是在執行期間確定實際型別的。而在構造一個物件時,由於物件還未構造成功。編譯器無法知道物件的實際型別,是該類本身,還是該類的一個派生類,或是更深層次的派生類。無法確定。 2 虛擬函式的執行依賴於虛擬函式表。而虛擬函式表在建構函式中進行初始化工作,即初始化vptr,讓他指向正確的虛擬函式表。而在構造物件期間,虛擬函式表還沒有被初 始化,將無法進行。 解構函式執行時先呼叫派生類的解構函式,其次才呼叫基類的解構函式。 解構函式為什麼宣告為虛擬函式? 如果解構函式不是虛擬函式,而程式執行時又要通過基類的指標去銷燬派生類的動態物件,那麼用delete銷燬物件時,只調用了基類的解構函式,未呼叫派生類的解構函式。這樣會造成銷燬物件不完全。 包含至少一個純虛擬函式的類視為抽象類
- 9.值型別與引用型別區別:
10.值型別
引用型別
儲存方式
直接儲存資料本身
儲存的是資料的引用,資料儲存在資料堆中
記憶體分配
分配在棧中的
分配在堆中
效率
效率高,不需要地址轉換
效率較低,需要進行地址轉換
記憶體回收
使用完後立即回收
使用完後不立即回收,而是交給GC處理回收
賦值操作
建立一個新物件
建立一個引用
型別擴充套件
不易擴充套件,所有值型別都是密封(seal)的,所以無法派生出新的值型別
具有多型的特性方便擴充套件
例項分配
通常是線上程棧上分配的(靜態分配),但是在某些情形下可以儲存在堆中
總是在程序堆中分配(動態分配)
- 下面程式碼的執行結果是()
解析:123456789 int
main(
void
)
{
char
*p[]={“TENCENT”,”CAMPUS”,”RECRUITING”};
char
**pp[]={p+2,p+1,p};
char
***ppp=pp;
printf
(“%s”,**++ppp);
printf
(“%s”,*++*++ppp);
return
0;
}
- 從題幹當中,我們可以畫出這樣的一個圖,這樣就比較直觀的看出了p,pp,ppp都指向哪裡了,關鍵是最後兩個printf語句。
(1)printf(“%s”,**++ppp);即,ppp當前所指向的位置,再往下移一個位置,即pp的位置2,而pp的位置2指向的是p的位置2,p的位置2指向的是CAMPUS,所以先輸出CAMPUS (2)printf(“%s”,*++*++ppp);這個語句等價於 printf(“%s”,*++(*++ppp));所以我們首先看,++ppp,第一個printf語句中ppp已經指向了pp的位置2,所以再往下移一個,指向了pp的位置3,而(*++ppp)則代表pp位置3所指向的內容,即p的位置1(pp的位置3指向的是p的位置1),在此基礎上前面再加上一個++,則代表指標p在位置1的基礎上再往下移動,即指標p的位置2,而p的位置2所指向的內容是CAMPUS,所以第二行輸出的也是CAMPUS。
所以正確答案是:CAMPUS CAMPUS