1.使用struct和class定義類以及類外的繼承有什麼區別?html
1)使用struct定義結構體它的默認數據成員類型是public,而class關鍵字定義類時默認成員時private.react
2) 在使用繼承的時候,struct定義類時它的派生類的默認具備public繼承,而class的派生類默認是private繼承。linux
2.派生類和虛函數概述?c++
1)基類中定義虛函數的目的是想在派生類中從新定義函數的新功能,若是派生類中沒有從新對虛函數定義,那麼使用派生類對象調用這個虛函數的時候,默認使用基類的虛函數。程序員
2)派生類中定義的函數必須和基類中的函數定義徹底相同。算法
3)基類中定義了虛函數,那麼在派生類中一樣也是。shell
3.虛函數和純虛函數的區別?數據庫
1)虛函數和純虛函數都使用關鍵字virtual。若是在基類中定義一個純虛函數,那麼這個基類是一個抽象基類,也稱爲虛基類,不能定義對象,只能被繼承。當被繼承時,必須在派生類中從新定義純虛函數。編程
class Animal { public: virtual void getColol() = 0; // 純虛函數 }; class Animal { public: virtual void getColor(); // 虛函數 };
4.深拷貝和淺拷貝的區別網頁爬蟲
舉個栗子~:
淺拷貝:
char p[] = "hello"; char *p1; p1 = p;
深拷貝:
char p[] = "hello"; char *p1 = new char[]; p1 = p;
解釋:
淺拷貝就是拷貝以後兩個指針指向同一個地址,只是對指針的拷貝。
深拷貝是通過拷貝後,兩個指針指向兩個地址,且拷貝其內容。
2)淺拷貝可能出現的問題
a. 由於兩個指針指向同一個地址,因此在一個指針改變其內容後,另外一個也可能會發生改變。
b.淺拷貝只是拷貝了指針而已,使得兩個指針指向同一個地址,因此當對象結束其生命期調用析構函數時,可能會對同一個資源釋放兩次,形成程序奔潰。
4.STL中vector實現原理,是1.5倍仍是兩倍?各有什麼優缺點?
vector是順序容器,他是一個動態數組。
1.5倍優點:能夠重用以前分配但已經釋放的內存。
2倍優點: 每次申請的內存都不能夠重用
5.STL中map,set的實現原理。
map是一個關聯容器,它是以鍵值對的形式存儲的。它的底層是用紅黑樹實現。平均查找複雜度是O(logn)
set也是一個關聯容器,它也是以鍵值對的形式存儲,但鍵值對都相同。底層也是以紅黑樹實現。平均查找複雜度O(logn)。
6. C++特色是什麼,多態實現機制?多態做用?兩個必須條件?
C++中多態機制主要體如今兩個方面,一個是函數的重載,一個是接口的重寫。接口多態指的是「一個接口的多種形態」。每個對象內部都有一個虛表指針,該虛表指針被初始化爲本類的虛表。因此在程序中,無論對象類型如何轉換,但該對象內部的虛表指針始終不變,才能實現動態的對象函數調用,這是c++多態的實現原理。
多態的基礎是繼承,須要虛函數的支持。子類繼承父類的大部分資源,不能繼承的有構造函數,析構函數,拷貝構造函數,operator = ,友元函數等。
做用:
1.隱藏實現細節,代碼可以模塊化。 2.實現接口重用:爲了類在繼承和派生過程當中正確調用。
兩個必要條件:
1.虛函數 2.一個基類的指針或者引用指向子類對象。
7.多重繼承有什麼問題?怎樣消除多重繼承中的二義性?
1)增長程序的複雜性,使程序的編寫和維護都比較困難,容易出錯;
2)繼承類和基類的同名函數產生了二義性,同名函數不知道調用基類仍是繼承類,c++ 中用虛函數解決這個問題。
3)繼承過程當中可能會繼承一些沒必要要的數據,可能會產生數據很長。
可使用成員限定符和虛函數解決多重繼承中的函數的二義性問題。
8.什麼叫動態關聯,什麼叫靜態關聯?
多態中,程序在編譯時肯定程序的執行動做叫作靜態關聯,在運行時才能肯定叫作動態關聯。
9.那些庫函數屬於高危函數?爲何?
strcpy賦值到目標區間可能會形成緩衝區溢出!
10.求兩個數的乘積和商數,用宏定義來實現
#define Chen(a,b) ((a) * (b)) #define Divide(a,b) ((a) / (b))
11.枚舉和#define宏的區別
1)用#define定義的宏在預編譯的時候進行簡單替換。枚舉在編譯的時候肯定其值。
2)能夠調試枚舉常量,但不能夠調試宏常量。
3)枚舉能夠一次定義大量的常量,而宏只能定義一個。
12.介紹下函數的重載:
重載是對不一樣類型的函數進行調用而使用相同的函數名。重載至少要在參數類型,參數個數,參數順序中有一個不一樣。
13.派生新類要通過三個步驟
1)繼承基類成員 2)改造基類成員 3)添加新成員
14.面向對象的三個基本性質?並簡單描述
1)封裝 : 將客觀事物抽象成類,每一個類對自身的數據和方法進行封裝。
2)繼承
3)多態 容許一個基類的指針或引用指向一個派生類的對象。
15.多態性體現都有那些?動態綁定怎麼實現?
多態性是一個接口,多種實現,是面向對象的核心。
動態綁定是指在編譯器在編譯階段不知道調用那個方法。
編譯時多態性:經過重載函數實現。
運行時多態性:經過虛函數實現,結合動態綁定。
16.動態聯編和靜態聯編
靜態聯編是指程序在編譯連接時就知道程序的執行順序
動態聯編是指在程序運行時才知道程序調用函數的順序。
17.爲何析構函數要定義成虛函數?
若是不將析構函數定義成虛函數,那麼在釋放內存的時候,編譯器會使用靜態聯編,認爲p就是一個基類指針,調用基類析構函數,這樣子類對象的內存沒有釋放,形成內存泄露。定義成虛函數後,使用動態聯編,先調用子類析構函數,再調用基類析構函數。
18.那些函數不能被定義成虛函數?
1)構造函數不能被定義成虛函數,由於構造函數須要知道全部信息才能構造對象,而虛函數只容許知道部分信息。
2)內聯函數在編譯時被展開,而虛函數在運行時才能動態綁定。
3)友元函數 由於不能夠被繼承。
4)靜態成員函數 只有一個實體,不能被繼承,父子都有。
19.類型轉換有那些?各適用什麼環境?dynamic_cast轉換失敗時,會出現什麼狀況?(對指針,返回NULL,對引用拋出bad_cast異常)
1) static_cast靜態類型轉換,適用於基本類型之間和具備繼承關係的類型。
i.e. 將double類型轉換爲int 類型。將子類對象轉換爲基類對象。
2)常量類型轉換 const_cast, 適用於去除指針變量的常量屬性。沒法將非指針的常量轉換爲普一般量。
3)動態類型轉換 dynamic_cast。運行時進行轉換分析的,並不是是在編譯時。dynamic_cast轉換符只能用於含有虛函數的類。
dynamic_cast用於類層次間的向上轉換和向下轉換,還能夠用於類間的交叉轉換。在類層次間進行向上轉換,即子類轉換爲父類,此時完成的功能和static_cast相同,由於編譯器默認向上轉換老是安全的。向下轉換時,由於dynamic_cast具備類型檢查的功能,因此更加安全。類間的交叉轉換指的是子類的多個父類之間指針或引用的轉換。
20.爲何要用static_cast而不用c語言中的類型轉換?
static_cast轉換,它會檢查看類型可否轉換,有類型安全檢查。
21.如何判斷一段程序是由c編譯的仍是由c++編譯的?
#ifdef _cplusplus cout << "c++" << endl; #else cout << "c" << endl; #endif
22.操做符重載,具體如何定義?
除了類屬關係運算符 "." ,成員指針運算符 "*",做用域運算符 「'::」,sizeof運算符和三目運算符"?:"之外,C++中全部運算符均可以重載。
<返回類型說明符>operator<運算符符號>(<參數表>){}
重載爲類的成員函數或類的非成員函數時,參數個數會不一樣,應爲this指針。
23.內聯函數和宏定義的區別
內聯函數是用來消除函數調用時的時間開銷的。頻繁被調用的短小函數很是受益。
A.宏定義不檢查函數參數,返回值之類的,只是展開,相對來講,內聯函數會檢查參數類型,因此更安全。
B.宏是由預處理器替換實現的,內聯函數是經過編譯器控制來實現的。
24.動態分配內存和靜態分配內存的區別?
動態分配內存是使用運算符new來分配的,在堆上分配內存。
靜態分配就是像A a;這樣的由編輯器來建立一個對象,在棧上分配內存。
25.explicit 是幹什麼用的?
構造器,阻止不該該容許的由轉換構造函數進行的隱式類型轉換。explicit是用來阻止外部非正規的拷貝構造。
26.既然new/delete的功能徹底覆蓋了malloc()和free()那麼爲何還要保留malloc/free呢?
由於C++程序要常常調用C程序,而C程序只能使用malloc/free來進行動態內存管理。
27.頭文件中#idfef/define/endif幹什麼用?
預處理,防止頭文件被重複使用,包括program once都這樣。
28.預處理器標識#error的做用是什麼?
拋出錯誤提示,標識預處理宏是否被定義。
29.怎樣用C編寫死循環?
while(1) { } for(;;) { }
30.用變量a給出以下定義:
一個有10個指針的數組,該指針指向一個函數,該函數有一個整型參數並返回一個整型數 int (*a[10])(int);
31.volatile是幹啥用的?(必須將cpu的寄存器緩存機制回答的很透徹)使用實例有哪些?(重點)
1)訪問寄存器比訪問內存要快,編譯器會優化減小內存的讀取,可能會讀髒數據。聲明變量爲volatile,編譯器不在對訪問該變量的代碼進行優化,仍然從內存讀取,使訪問穩定。
總結:volatile聲明的變量會影響編譯器編譯的結果,用volatile聲明的變量表示這個變量隨時均可能發生改變,與該變量有關的運算,不在編譯優化,以避免出錯。
2)使用實例:(區分c程序員和嵌入式系統程序員的基本問題)
並行設備的硬件寄存器 (如:狀態寄存器)
一箇中斷服務子程序會訪問到的非自動變量
多線程應用中被幾個任務共享的變量
3)一個參數既能夠是volatile又能夠是const嗎?
能夠。好比狀態寄存器,聲明爲volatile表示它的值可能隨時改變,聲明爲const 表示它的值不該該被程序試圖修改。
4) 一個指針能夠被聲明爲volatile嗎?解釋爲何?
能夠。儘管這並非很常見。一個例子是當中斷服務子程序修改一個指向一個buffer的指針時。
下面的函數有什麼錯誤:
int square(volatile int *ptr) { return *ptr * *ptr; }
下面是答案:
這段代碼有點變態。這段代碼的目的是用來返指針*ptr指向值的平方,可是,因爲*ptr指向一個volatile型參數,編譯器將產生相似下面的代碼:
int square(volatile int *ptr){ int a,b; a = *ptr; b = *ptr; return a * b; }
因爲*ptr的值可能被意想不到地該變,所以a和b多是不一樣的。結果,這段代碼可能返不是你所指望的平方值!正確的代碼以下
int square(volatile int *ptr){ int a; a = *ptr; return a * a; }
32.引用和指針的區別
1)引用是直接訪問,指針是間接訪問
2)引用是變量的別名,自己不單獨分陪本身的內存空間。而指針有本身的內存空間。
3)引用綁定內存空間必須賦初值。是一個變量別名,不能更改綁定,能夠改變對象的值
33.關於動態內存分配和靜態內存分配的區別:
1)靜態內存分配是在編譯時完成的,不佔cpu資源;動態內存分配是在運行時完成的,分配和釋放佔用cpu資源。
2)靜態內存分配是在棧上分配的,動態內存分配是在堆上分配的。
3)動態內存分配須要指針或引用數據類型的支持,而靜態內存分配不須要。
4)靜態內存分配是按計劃分配,在編譯前肯定內存塊的大小,而動態內存分配是按需分配
5)靜態內存分配是把控制權交給編譯器,而動態內存分配是把控制權交給程序員
6)靜態內存分配運行效率要比動態的高,由於動態內存分配在分配和釋放時都須要cpu資源
34.分別設置和清除一個整數的第三位?
#define BIT3 (0x1 << 3) static int a; void set_3() { a |= BIT3; } void clear_3() { a &= ~BIT3; }
35.memcpy函數的實現
void* my_memcpy(void *dest , const void * src, size_t size) { if(dest == NULL && src == NULL) { return NULL; } char* dest1 = dest; char* src1 = src; while(size-- >0) { *dest1 = *src1; dest1++; src1++; } return dest; }
36.strcpy函數的實現
char* my_strcpy(char* dest, const char* src) { assert(dest != NULL && src != NULL); char* dest1 = dest; while((*dest++ = *src++) != '\0'); return dest1; }
37.strcat函數的實現
char* strcat(char* dest, const char* src) { assert(dest != NULL && src != NULL); char* dest1 = dest; while(*dest != '\0' ) dest++; while((*dest++ = *src++) != '\0'); return dest1; }
38.strncat函數的實現
char* my_strncat(char* dest, const char * src, int n) { assert(dest != NULL && src != NULL); char* dest1 = dest; while(*dest != '\0') dest++; while(n--) { *dest = *src; dest++; src++; } dest++; *dest = '\0'; return dest1; }
39.strcmp函數的實現
int my_strcmp(const char* str1,const char* str2) { int ret = 0; while(!(ret =*(unsigned char*) str1 -*(unsigned char*) str2) && *str1) { str1++; str2++; } if(ret < 0) return -1; else if(ret >0) return 1; else return ret; }
40.strncmp函數的實現
int my_strncmp(const char* str1, const char* str2,int n) { assert(str1!= NULL && str2 != NULL); while(*str1 && *str2 && (*str1 == *str2) && n--) { str1++; str2++; } int ret = *str1 - *str2; if(ret >0) return 1; else if(ret < 0) return -1; else return ret; }
41.strlen函數的實現
int my_strlen(const char *str) { assert(str != NULL); int len = 0; while(*str++ != '\0') { len++; } return len; }
42.strstr函數的實現
char* my_strstr(const char* str1, const char* str2) { int len2; if(!(len2 = strlen(str2))) return (char *)str1; for(;*str1; str1++) { if(*str1 == *str2 && strncmp(str1,str2,len)) return (char *) str1; } return NULL; }
43.String類的實現
class String { public: String(const char *str = NULL); String(const String &other); ~String(); String & operator = (const String &other); String & operator + (const String &other); bool operator == (const String &other); int getlength(); private: char *m_data; }; String ::String(const char* str) { if(str == NULL) { m_data = new char[1]; m_data = '\0'; } else { m_data = new char[strlen(str)+1]; strcpy(m_data,str); } } String :: String(const String &other) { if(!other.m_data) { m_data = 0; } else { m_data = new char[strlen(other.m_data)+1]; strcpy(m_data,other.m_data); } } String :: ~String() { if(m_data) { delete[] m_data; m_data = 0; } } String & String :: operator = (const String & other) { if(this != &other) { delete[] m_data; if(!other.m_data) { m_data = 0; } else { m_data = new char[strlen(other.m_data)+ 1]; strcpy(m_data, other.m_data); } } return *this; } String & String :: operator + (const String & other) { String newString; if(!other) { newString = *this; } else if(!m_data) { newString = other; } else { m_data = new char[strlen(m_data) + strlen(other.m_data) + 1]; strcpy(newString.m_data,m_data); strcat(newString.m_data,other.m_data); } return newString; } bool String::operator == (const String &other) { if(strlen(m_data) != strlen(other.m_data)) { return false; } else { return strcmp(m_data,other.m_data) ? false : true; } } int String :: getlengrh() { return strlen(m_data); }
44.sizeof()的大小
http://www.myexception.cn/program/1666528.html
45.do{}while(0);的用法有那些?
1)能夠將語句做爲一個獨立的域 2)對於多語句能夠正常訪問 3)能夠有效的消除goto語句,達到跳轉語句的效果。
46.請用c/c++實現字符串反轉(不調用庫函數)「abc」類型的
char* reverse_str(char * str) { if(str == NULL) { return NULL; } char* begin; char* end; begin = end = str; while(*end != '\0') { end++; } --end; while(begin < end) { char tmp = *begin; *begin = *end; *end = tmp; begin++; end--; } return str; }
二.服務器編程
1.多線程和多進程的區別(重點必須從cpu調度,上下文切換,數據共享,多核cpu利用率,資源佔用等方面來回答。有一個問題必會問到,哪些東西是線程私有的,答案中必須包含寄存器!!)
1)進程數據是分開的:共享複雜,須要用IPC,同步簡單; 多線程共享進程數據:共享簡單,同步複雜。
2)進程建立銷燬,切換複雜。速度慢;線程建立銷燬,切換簡單,速度快。
3)進程佔用內存多,cpu利用率低;線程佔用內存少,cpu利用率高;
4)進程編程簡單,調試簡單;線程編程複雜,調試複雜;
5)進程間不會相互影響,但當一個線程掛掉將致使整個進程死掉
6)進程適用於多核,多機分佈;線程適用於多核
線程所私有的: 線程id,寄存器的值,線程的優先級和調度策略,棧,線程的私有數據,error變量,信號屏蔽字。
2.多線程鎖的種類有那些?
1)互斥鎖(mutex) 2)遞歸鎖 3)自旋鎖 4)讀寫鎖
自旋鎖(spinlock):是指當一個線程在獲取鎖的時候,若是鎖已經被其它線程獲取,那麼該線程將循環等待,而後不斷的判斷鎖是否可以被成功獲取,直到獲取到鎖纔會退出循環。
3.自旋鎖和互斥鎖的區別?
自旋鎖:當鎖被其餘線程佔用時,其它線程並非睡眠狀態,而是不停地消耗cpu,獲取鎖;互斥鎖則否則,保持睡眠,直到互斥鎖被釋放喚醒。
自旋鎖遞歸調用容易形成死鎖,對長時間才能得到鎖的狀況,容易形成cpu利用率低,只有內核可搶佔式或SMP狀況下才真正須要自旋鎖。
4.進程間通訊:
1)管道 2)命名管道 3)消息隊列 4)共享內存 5)信號量 6)套接字
5.多線程程序架構,線程數量應該如何設置?
應儘可能和cpu核數相等,或者爲cpu核數+1的個數。
6.網絡編程設計模式,reactor/proactor/半同步半異步模式?
reactor模式:同步阻塞I/O模式,註冊對應讀寫事件處理器,等待事件發生,進而調用事件處理器處理事件。
proactor模式:異步I/O模式。
reactor模式和proactor模式的主要區別是讀取和寫入是由誰來完成的。reactor模式須要應用程序本身讀取或者寫入數據,proactor模式中,應用程序不須要實際讀寫的過程。
半同步半異步模式:
上層的任務(如數據庫查詢,文件傳輸)經過同步I/O模型,簡化了編寫並行程序的難度。
底層的任務(如網絡控制器的中斷處理)經過異步I/O模型,提供了更好的效率。
7.有一個計數器,多個線程都須要更新,會遇到什麼問題,緣由是什麼應該如何作,怎樣優化?
有可能一個線程更新的數據已經被另外一個線程更新了,讀髒數據/丟失修改,更新的數據出現異常。能夠經過加鎖來解決,保證數據更新只會被一個線程更新。
8.若是select返回可讀,結果只讀到0字節,什麼狀況?
某個套接字集合中沒有準備好,可能會select內存用FD_CLR清爲0。
9.CONNECT可能會長時間阻塞,怎麼解決?
1)使用定時器,最經常使用也是最有效的一種方法。
2)使用非阻塞;設置非阻塞,返回以後使用select檢測狀態。
10.keepalive是什麼東西?如何使用?
keepalive,是在tcp中能夠檢測死鏈接的機制。
1)若是主機可達,對方就會響應ACK應答,就認爲鏈接是存活的。
2)若是可達,但應用程序退出,對方就發RST應答,發送TCP撤銷鏈接。
3)若是可達,但應用程序崩潰,對方就發FIN應答。
4)若是對方主機不響應ACK,RST,那麼繼續發送直到超時,就撤銷鏈接。默認2小時。
11.udp調用connect有什麼做用?
1)由於udp能夠是一對一,一對多,多對一,多對多的通訊,因此每次調用sendto/recvfrom都須要指定目標ip地址和端口號。經過調用connect函數創建一個端到端鏈接,就能夠和tcp同樣使用send()/recv()傳遞數據,而不須要每次都指定目標IP地址和端口號。可是它和TCP不一樣的是他不須要三次握手的過程。
2)能夠經過在已經創建UDP鏈接的基礎上,調用connect()來指定新的目標IP地址和端口號以及斷開鏈接。
12.socket編程,若是client斷電了,服務器如何快速知道?
1)使用定時器(適合有數據流動的狀況)
2)使用socket選項SO_KEEPALIVE(適合沒有數據流動的狀況)
a. 本身編寫心跳包程序,簡單說就是給本身的程序加入一個線程,定時向對端發送數據包,查看是否有ACK,根據ACK的返回狀況來管理鏈接。這個方法比較通用,但改變了現有的協議;
b.使用TCP的KEEPALIVE機制,UNIX網絡編程不推薦使用SO_KEEPALIVE來作心跳測試。
keepalive原理:TCP內嵌心跳包,以服務端爲例,當server檢測到超過必定時間(2小時)沒有數據傳輸,就會向client傳送一個keepalive pocket
三.LINUX操做系統
1.熟練netstat,tcpdump,ipcs,ipcrm
netstat:檢查網絡狀態 tcpdump : 截取數據包 ipcs:檢查共享內存 ipcrm:解除共享內存
2.共享內存段被映射進進程空間以後,存在於進程空間的什麼位置?共享內存段最大限制是多少?
將一塊內存空間映射到兩個或者多個進程地址空間。經過指針訪問該共享內存區。通常經過mmap將文件映射到進程地址共享區。
存在於進程數據段,最大限制是0x2000000Byte。
3.寫一個C程序判斷是大端仍是小端字節序
union { short value; char a[sizeof(short)]; }test; int main() { test.value = 0x0102; if(test.a[0] == 1 && test.a[1] == 2)
cout << "big" << endl; else cout << "little" << endl; return 0; }
4.i++操做是不是原子操做?並解釋爲何?
確定不是,i++主要看三個步驟:
首先把數據從內存放到寄存器,在寄存器上進行自增,而後返回到內存中,這三個操做中均可以被斷開。
5.標準庫函數和系統調用?
系統調用:是操做系統爲用戶態運行的進程和硬件設備(如cpu,磁盤,打印機等)進行交互提供的一組接口,即就是設置在應用程序和硬件設備之間的一個接口層。Linux內核是單內核,結構緊湊,執行速度快,各個模塊之間是直接調用的關係。Linux系統上到下依次是用戶進程->linux內核->硬件。其中系統調用接口是位於Linux內核中的,整個linux系統從上到下能夠是:用戶進程->系統調用接口->linux內核子系統->硬件,也就是說Linux內核包括了系統調用接口和內核子系統兩部分;或者從下到上能夠是:物理硬件->OS內核->OS服務->應用程序,操做系統起到「承上啓下」做用,向下管理物理硬件,向上爲操做系服務和應用程序提供接口,這裏的接口就是系統調用了。
庫函數:把函數放到庫裏。把一些常用的函數編完放到一個一個lib文件裏,供別人用。別人用的時候把它所在的文件名用#include<>加到裏面就能夠了。一類是c語言標準規定的庫函數,一類是編譯器特定的庫函數。
系統調用是爲了方便使用操做系統的接口。庫函數是爲了人們方便編程而使用。
6.fork()一個子進程後,父進程的全局變量可否被子進程使用?
fork()一個子進程後,子進程將會擁有父進程的幾乎一切資源,可是他們各自擁有各自的全局變量,不能通用,不像線程。對於線程,各個線程共享全局變量。
7.線程同步幾種方式?
互斥鎖,信號量,臨界區。
8.爲何要字節對其?
1)有些特殊的cpu只能處理4倍開始的內存地址。
2)若是不是整倍數讀取會致使讀取屢次。
3)數據總線爲讀取數據作了基礎。
9.對一個數組而言,delete a和delete[] a有什麼區別?
對於基礎數據類型沒有什麼區別,可是對於對象而言,delete a只調用一次析構函數,delete[] a纔會析構全部。
10.什麼是協程?
用戶態的輕量級線程,有本身的寄存器和棧。
11.線程安全的幾種方式?
1)原子操做 2)同步與鎖 3)可重入 4)阻止過分優化的volatile。
12.int (*f(int,void(*)()))(int,int)是什麼意思?
一個名爲f的參數爲int 和指向void的指針函數的指針函數,返回值爲int,參數是int和int
(一個函數,參數爲int和指向返回值爲void的無參數的函數指針,返回值爲一個指向返回值爲int,參數爲int和int的函數指針)
13.什麼是冪等性?舉個栗子?
其任意屢次執行所產生的影響與一次執行的影響相同。
14.當接收方的接收窗口爲0時還能接收數據嗎?爲何?還能接收什麼數據?那怎麼處理這些數據呢?
能夠接收。
數據:零窗口探測報文,確認報文段;攜帶緊急數據的報文段。
可能會被拋棄。
15.當接收方返回的接收窗口爲0時?發送方怎麼作?
開啓計時器,發送零窗口探測報文。
16.下面兩個函數早執行時有什麼區別?
int f(string &str); f("abc"); //報錯
int f(const string &str); f("abc"); // 正常
17.char *const* (*next)()是什麼?
next是一個指針,指向一個函數,這個函數返回一個指針,這個指針指向char類型的常量指針
18.如何判斷一個數是2的次方?
思想: 直接用這個數和這個數減一的數進行按位與操做就能夠。若是等於0則TRUE,不然false。
19. 布隆過濾器 (Bloom Filter)
Q:一個網站有 100 億 url 存在一個黑名單中,每條 url 平均 64 字節。這個黑名單要怎麼存?若此時隨便輸入一個 url,你如何快速判斷該 url 是否在這個黑名單中?
布隆過濾器:
它其實是一個很長的二進制矢量和一系列隨機映射函數。
它能夠用來判斷一個元素是否在一個集合中。它的優點是隻須要佔用很小的內存空間以及有着高效的查詢效率。
對於布隆過濾器而言,它的本質是一個位數組:位數組就是數組的每一個元素都只佔用 1 bit ,而且每一個元素只能是 0 或者 1。
一開始,布隆過濾器的位數組全部位都初始化爲 0。好比,數組長度爲 m ,那麼將長度爲 m 個位數組的全部的位都初始化爲 0。
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
0 | 0 | 1 | 。 | 。 | 。 | 。 | 。 | m-2 | m-1 |
在數組中的每一位都是二進制位。
布隆過濾器除了一個位數組,還有 K 個哈希函數。當一個元素加入布隆過濾器中的時候,會進行以下操做:
•使用 K 個哈希函數對元素值進行 K 次計算,獲得 K 個哈希值。•根據獲得的哈希值,在位數組中把對應下標的值置爲 1。
舉個例子,假設布隆過濾器有 3 個哈希函數:f1, f2, f3 和一個位數組 arr
。如今要把 2333
插入布隆過濾器中:
•對值進行三次哈希計算,獲得三個值 n1, n2, n3。•把位數組中三個元素 arr[n1], arr[n2], arr[3] 都置爲 1。
當要判斷一個值是否在布隆過濾器中,對元素進行三次哈希計算,獲得值以後判斷位數組中的每一個元素是否都爲 1,若是值都爲 1,那麼說明這個值在布隆過濾器中,若是存在一個值不爲 1,說明該元素不在布隆過濾器中。
很明顯,數組的容量即便再大,也是有限的。那麼隨着元素的增長,插入的元素就會越多,位數組中被置爲 1 的位置所以也越多,這就會形成一種狀況:當一個不在布隆過濾器中的元素,通過一樣規則的哈希計算以後,獲得的值在位數組中查詢,有可能這些位置由於以前其它元素的操做先被置爲 1 了。
如圖 1 所示,假設某個元素經過映射對應下標爲4,5,6這3個點。雖然這 3 個點都爲 1 ,可是很明顯這 3 個點是不一樣元素通過哈希獲得的位置,所以這種狀況說明這個元素雖然不在集合中,也可能對應的都是 1,這是誤判率存在的緣由。
因此,有可能一個不存在布隆過濾器中的會被誤判成在布隆過濾器中。
這就是布隆過濾器的一個缺陷:存在誤判。
可是,若是布隆過濾器判斷某個元素不在布隆過濾器中,那麼這個值就必定不在布隆過濾器中。總結就是:
•布隆過濾器說某個元素在,可能會被誤判•布隆過濾器說某個元素不在,那麼必定不在
用英文說就是:False is always false. True is maybe true。
誤判率:
布隆過濾器能夠插入元素,但不能夠刪除已有元素。其中的元素越多,false positive rate(誤報率)越大,可是false negative (漏報)是不可能的。
假設布隆過濾器有m比特,裏面有n個元素,每一個元素對應k個指紋的Hash函數
布隆過濾器存在必定的誤識別率。常見的補救辦法是在創建白名單,存儲那些可能被誤判的元素。
布隆過濾器的最大的用處就是,可以迅速判斷一個元素是否在一個集合中。所以它有以下三個使用場景:
•網頁爬蟲對 URL 的去重,避免爬取相同的 URL 地址•進行垃圾郵件過濾:反垃圾郵件,從數十億個垃圾郵件列表中判斷某郵箱是否垃圾郵箱(同理,垃圾短信)•有的黑客爲了讓服務宕機,他們會構建大量不存在於緩存中的 key 向服務器發起請求,在數據量足夠大的狀況下,頻繁的數據庫查詢可能致使 DB 掛掉。布隆過濾器很好的解決了緩存擊穿的問題。
以上來自公衆號:5分鐘學算法。
20. 排序算法
1)冒泡 平均O(n^2),最好O(n)。穩定的排序算法。
void m_sort(vector<int> &a) { int len = a.size(); bool flag = true; for(int i = 0; i <len; i++) { for(int j = i+1; j < len - i - 1; j++) { if(a[j-1] > a[j]) { swap(a[j-1],a[j]); flag = false; } } if(flag) break; } }
2)選擇排序 最好O(n^2) 平均O(n^2)
void select_sort(vector<int> &a) { int len = a.size(); for(int i = 0; i <len; i++) { int min = i; for(int j = i +1; j < len; j++) { if(a[j] < a[min]) { min = j; } } if(i != min) { swap(a[i],a[min]); } } }
3)插入排序 平均O(n^2) , 最好O(n)
void insert_sort(vector<int>& a) { int len = a.size(); for(int i = 1; i < len; i++) { int tmp = a[i]; int j = i; while(j > 0 && tmp < a[j-1]) { a[j] = a[j-1]; j--; } if(j != i) { a[j] = tmp; } } }
4)希爾排序 平均O(nlogn) 最壞O(nlong^2 n)
void shell_select(vector<int>& a) { int len = a.size(); int gap = 1; while(gap < len) { gap = gap * 3 +1; } while(gap > 0) { for(int i = gap; i< len; i++) { int tmp = a[i]; int j = i - gap; while(j >= 0 && tmp < a[j]) { a[j+gap] = a[j]; j -= gap; } a[j + gap] = tmp; } gap = floor(gap/3); } }
5) 歸併排序 平均O(nlogn) 最壞O(nlogn)
vector<int> merge_sort(vector<int> &a,int left,int right) { if(left < right) { int mid = (left + right) / 2; a = merge_sort(a,left,mid); a = merge_sort(a,mid+1,right); merge(a,left,mid,right); } return arr; } void merge(int[] &arr,int left,int mid,int right) { int[] a = new int[right - left +1]; int i = left; int j = mid+1; int k = 0; while(i <= mid && j <= right) { if(arr[i] < arr[j]) { a[k++] = arr[i++]; } else { a[k++] = arr[j++]; } } while(i <= mid) a[k++] = arr[i++]; while(j <= mid) a[k++] = arr[j++]; for(i = 0; i < k; i++) { arr[left++] = a[i]; } }
6)快速排序 平均O(nlogn) 最壞O(n^2)
void quick_sort(vector<int> &a,int left,int right) { if(left < right) { int pre = site(a, 0,right); quick_sort(a,left,pre-1); quick_sort(a,pre+1,right); } } void my_swap(vector<int>& a,int i,int j) { int tmp = a[i]; a[i] = a[j]; a[j] = tmp; } int site(vector<int>& a,int left,int right) { int pivot = left; int index = pivot + 1; for(int i = index; i<= right; i++) { if(a[i] < a[pivot]) { my_swap(a, i,index); index++; } } my_swap(a, pivot, index-1); return index-1; }
7) 堆排序 平均O(nlogn) 最好O(nlogn)
int heap[maxn],sz = 0; void push(int x) { int i = sz++; while(i > 0) { int p = (i-1)/2; while(heap[p] <= x) break; heap[i] = heap[p]; i = p; } heap[i] = x; } int pop() { int ret = heap[0]; int x = heap[--sz]; int i = 0; while(i * 2 +1 < sz) { int a = i * 2+1, b = i * 2+ 2; if(b < sz && heap[b] < heap[a]) a = b; if(heap[a] >= x) break; heap[i] = heap[a]; i = a; } heap[i] = x; return ret; }