備戰秋招——C++知識點


 

1.字符串的末尾'\0'也算一個字符,一個字節。

2.使用庫函數strcpy(a,b)進行拷貝b->a操作,strcpy會從源地址一直往后拷貝,直到遇到'\0'為止。所以拷貝的長度是不定的。如果一直沒有遇到'\0'導致越界訪問非法內存,程序就崩了。

3.strlen的結果未統計’\0’所占用的1個字節。  

4.寫出完整的strcpy函數

char * strcpy( char *strDest, const char *strSrc ) 
{
 assert( (strDest != NULL) && (strSrc != NULL) );
 char *address = strDest; 
 while( (*strDest++ = * strSrc++) != ‘\0’ ); 
 return address;
}

 

5.malloc和free要對應使用,防止內存泄漏

6.printf(str) 改為 printf("%s",str),否則可使用格式化 字符串攻擊。printf會把str當成一個格式化字符串(formatstring),printf在其中尋找特殊的格式字符比如"%d"。如果碰到格式字符,一個變量的參數值就從堆棧中取出。很明顯,攻擊者至少可以通過打印出堆棧中的這些值來偷看程序的內存。

7.malloc之后應該對內存分配是否成功進行判斷。free(p)之后應該把p=NULL,否則p會變成野指針。

8.浮點型變量並不精確,所以不可將float變量用“==”或“!=”與數字比較,應該設法轉化成“>=”或“<=”形式。

9.數組名的本質如下:   
(1)數組名指代一種數據結構,這種數據結構就是數組;   

char str[10];
cout << sizeof(str) << endl;

 

 

輸出結果為10,str指代數據結構char[10]。   
(2)數組名可以轉換為指向其指代實體的指針,而且是一個指針常量,不能作自增、自減等操作,不能被修改;   
char str[10];    
str++; //編譯出錯,提示str不是左值  

(3) 數組名作為函數形參時,淪為普通指針。  
Windows NT 32位平台下,指針的長度(占用內存的大小)為4字節,故sizeof( str ) 、sizeof ( p ) 都為4。  

10.static關鍵字至少有下列n個作用: 

  
(1)函數體內static變量的作用范圍為該函數體,不同於auto變量,該變量的內存只被分配一次,因此這個靜態變量的值在下次調用該函數時仍維持上次的值;   
(2)在模塊內的static全局變量可以被模塊內所用函數訪問,但不能被模塊外其它函數訪問;   
(3)在模塊內的static函數只可被這一模塊內的其它函數調用,這個函數的使用范圍被限制在聲明它的模塊內;   
(4)在類中的static成員變量屬於整個類所擁有,對類的所有對象只有一份拷貝;   
(5)在類中的static成員函數屬於整個類所擁有,這個函數不接收this指針,因而只能訪問類的static成員變量。    

11.const關鍵字至少有下列n個作用:   

(1)欲阻止一個變量被改變,可以使用const關鍵字。在定義該const變量時,通常需要對它進行初始化,因為以后就沒有機會再去改變它了;   const int* p ;p指向的變量的值不能變。int* const p; p指向的東西不能變
(2)對指針來說,可以指定指針本身為const,也可以指定指針所指的數據為const,或二者同時指定為const;   
(3)在一個函數聲明中,const可以修飾形參,表明它是一個輸入參數,在函數內部不能改變其值;   
(4)對於類的成員函數,若指定其為const類型,則表明其是一個常函數,不能修改類的 成員變量;   
(5)對於類的成員函數,有時候必須指定其 返回值為const類型,以使得其返回值不為“左值”。例如:   
const classA operator*(const classA& a1,const classA& a2);   
operator*的返回結果必須是一個const對象。如果不是,這樣的變態代碼也不會編譯出錯:   
classA a, b, c;   
(a * b) = c; // 對a*b的結果賦值   
操作(a * b) = c顯然不符合編程者的初衷,也沒有任何意義。   
warning:不要再頭文件中聲明static的全局函數,不要在cpp內聲明非static的全局函數,如果你要在多個cpp中復用該函數,就把它的聲明提到頭文件里去,否則cpp內部聲明需加上static修飾;
 
C++和C的區別
設計思想上:

C++是面向對象的語言,而C是面向過程的結構化編程語言

語法上:

C++具有重載、繼承和多態三種特性

C++相比C,增加多許多類型安全的功能,比如強制類型轉換、

C++支持范式編程,比如模板類、函數模板等

 

C/C++ 中指針和引用的區別?

1.指針有自己的一塊空間,而引用只是一個別名;

2.使用sizeof看一個指針的大小是4,而引用則是被引用對象的大小;

3.指針可以被初始化為NULL,而引用必須被初始化且必須是一個已有對象 的引用;

4.作為參數傳遞時,指針需要被解引用才可以對對象進行操作,而直接對引 用的修改都會改變引用所指向的對象;

5.可以有const指針,但是沒有const引用;

6.指針在使用中可以指向其它對象,但是引用只能是一個對象的引用,不能 被改變;

7.指針可以有多級指針(**p),而引用至於一級;

8.指針和引用使用++運算符的意義不一樣;

9.如果返回動態內存分配的對象或者內存,必須使用指針,引用可能引起內存泄露。

 
 

請你說一下你理解的c++中的smart pointer四個智能指針: shared_ptr,unique_ptr,weak_ptr,auto_ptr

·智能指針也是一個類,當退出這個類對象的作用域時會自動調用析構函數,不僅會釋放這個類的成員變量,指針的內存空間,還會釋放這個指針指向的對象的內存空間,所以不用擔心這個指針指向的對象忘記釋放從而導致的內存泄漏。 

1. auto_ptr(c++98的方案,cpp11已經拋棄)

采用所有權模式。

 

auto_ptr< string> p1 (new string ("I reigned lonely as a cloud.”));
auto_ptr<string> p2;
p2 = p1; //auto_ptr不會報錯.

此時不會報錯,p2剝奪了p1的所有權,但是當程序運行時訪問p1將會報錯。所以auto_ptr的缺點是:存在潛在的內存崩潰問題!

 

2. unique_ptr(替換auto_ptr)

unique_ptr實現獨占式擁有或嚴格擁有概念,保證同一時間內只有一個智能指針可以指向該對象。它對於避免資源泄露(例如“以new創建對象后因為發生異常而忘記調用delete”)特別有用。

采用所有權模式,還是上面那個例子

unique_ptr<string> p3 (new string ("auto"));   //#4
unique_ptr<string> p4;                       //#5
p4 = p3;//此時會報錯!!

如果確實想執行類似的操作,要安全的重用這種指針,可給它賦新值。C++有一個標准庫函數std::move(),讓你能夠將一個unique_ptr賦給另一個。例如:

unique_ptr<string> ps1, ps2;
ps1 = demo("hello");
ps2 = move(ps1);
ps1 = demo("alexia");
cout << *ps2 << *ps1 << endl;

 

 

3. shared_ptr

shared_ptr實現共享式擁有概念。多個智能指針可以指向相同對象,該對象和其相關資源會在“最后一個引用被銷毀”時候釋放。從名字share就可以看出了資源可以被多個指針共享,它使用計數機制來表明資源被幾個指針共享。可以通過成員函數use_count()來查看資源的所有者個數。除了可以通過new來構造,還可以通過傳入auto_ptr, unique_ptr,weak_ptr來構造。當我們調用release()時,當前指針會釋放資源所有權,計數減一。當計數等於0時,資源會被釋放。

shared_ptr 是為了解決 auto_ptr 在對象所有權上的局限性(auto_ptr 是獨占的), 在使用引用計數的機制上提供了可以共享所有權的智能指針。

成員函數:

use_count 返回引用計數的個數

unique 返回是否是獨占所有權( use_count 為 1)

swap 交換兩個 shared_ptr 對象(即交換所擁有的對象)

reset 放棄內部對象的所有權或擁有對象的變更, 會引起原有對象的引用計數的減少

get 返回內部對象(指針), 由於已經重載了()方法, 因此和直接使用對象是一樣的.如 shared_ptr<int> sp(new int(1)); sp 與 sp.get()是等價的

 

4. weak_ptr

weak_ptr 是一種不控制對象生命周期的智能指針, 它指向一個 shared_ptr 管理的對象. 進行該對象的內存管理的是那個強引用的 shared_ptr. weak_ptr只是提供了對管理對象的一個訪問手段。weak_ptr 設計的目的是為配合 shared_ptr 而引入的一種智能指針來協助 shared_ptr 工作, 它只可以從一個 shared_ptr 或另一個 weak_ptr 對象構造, 它的構造和析構不會引起引用記數的增加或減少。weak_ptr是用來解決shared_ptr相互引用時的死鎖問題,如果說兩個shared_ptr相互引用,那么這兩個指針的引用計數永遠不可能下降為0,資源永遠不會釋放。它是對對象的一種弱引用,不會增加對象的引用計數,和shared_ptr之間可以相互轉化,shared_ptr可以直接賦值給它,它可以通過調用lock函數來獲得shared_ptr。

注意的是我們不能通過weak_ptr直接訪問對象的方法,比如B對象中有一個方法print(),我們不能這樣訪問,pa->pb_->print(); 英文pb_是一個weak_ptr,應該先把它轉化為shared_ptr,如:shared_ptr p = pa->pb_.lock(); p->print();

 

 

請回答一下數組和指針的區別

 
指針和數組的主要區別如下:

 

指針

數組

保存數據的地址

保存數據

間接訪問數據,首先獲得指針的內容,然后將其作為地址,從該地址中提取數據

直接訪問數據,

通常用於動態的數據結構

通常用於固定數目且數據類型相同的元素

通過Malloc分配內存,free釋放內存

隱式的分配和刪除

通常指向匿名數據,操作匿名函數

自身即為數據名

 
 
 
 

C++虛函數和虛函數表原理

 虛表是一個指針數組,其元素是虛函數的指針,每個元素對應一個虛函數的函數指針。需要指出的是,普通的函數即非虛函數,其調用並不需要經過虛表,所以虛表的元素並不包括普通函數的函數指針。 
 
 

虛表是屬於類的,而不是屬於某個具體的對象,一個類只需要一個虛表即可。同一個類的所有對象都使用同一個虛表。 
為了指定對象的虛表,對象內部包含一個虛表的指針,來指向自己所使用的虛表。為了讓每個包含虛表的類的對象都擁有一個虛表指針,編譯器在類中添加了一個指針,*__vptr,用來指向虛表。這樣,當類的對象在創建時便擁有了這個指針,且這個指針的值會自動被設置為指向類的虛表。

 

多繼承情況下,派生類中有多個虛函數表,虛函數的排列方式和繼承的順序一致。派生類重寫函數將會覆蓋所有虛函數表的同名內容,派生類自定義新的虛函數將會在第一個類的虛函數表的后面進行擴充。

 
 

 為什么析構函數必須是虛函數?為什么C++默認的析構函數不是虛函數

將可能會被繼承的父類的析構函數設置為虛函數,可以保證當我們new一個子類,然后使用基類指針指向該子類對象,釋放基類指針時可以釋放掉子類的空間,防止內存泄漏。

類析構順序:1)派生類本身的析構函數;2)對象成員析構函數;3)基類析構函數。

C++默認的析構函數不是虛函數是因為虛函數需要額外的虛函數表和虛表指針,占用額外的內存。而對於不會被繼承的類來說,其析構函數如果是虛函數,就會浪費內存。因此C++默認的析構函數不是虛函數,而是只有當需要當作父類時,設置為虛函數。

 
 

請你來說一下函數指針

1、定義

函數指針是指向函數的指針變量。

函數指針本身首先是一個指針變量,該指針變量指向一個具體的函數。這正如用指針變量可指向整型變量、字符型、數組一樣,這里是指向函數。

C在編譯時,每一個函數都有一個入口地址,該入口地址就是函數指針所指向的地址。有了指向函數的指針變量后,可用該指針變量調用函數,就如同用指針變量可引用其他類型變量一樣,在這些概念上是大體一致的。

2、用途:

調用函數和做函數的參數,比如回調函數。

3、示例:

char * fun(char * p)  {…}       // 函數fun

char * (*pf)(char * p);             // 函數指針pf

pf = fun;                        // 函數指針pf指向函數fun

pf(p);                        // 通過函數指針pf調用函數fun

 

 

請你來說一下fork函數

Fork:創建一個和當前進程映像一樣的進程可以通過fork( )系統調用:
#include <sys/types.h>

#include <unistd.h>

pid_t fork(void);

 

成功調用fork( )會創建一個新的進程,它幾乎與調用fork( )的進程一模一樣,這兩個進程都會繼續運行。在子進程中,成功的fork( )調用會返回0。在父進程中fork( )返回子進程的pid。如果出現錯誤,fork( )返回一個負值。

 

map和set有什么區別,分別又是怎么實現的?

紅黑樹占坑
 
 

請你來介紹一下STL的allocaotr

STL的分配器用於封裝STL容器在內存管理上的底層細節。在C++中,其內存配置和釋放如下:

new運算分兩個階段:(1)調用::operator new配置內存;(2)調用對象構造函數構造對象內容

delete運算分兩個階段:(1)調用對象希構函數;(2)掉員工::operator delete釋放內存

為了精密分工,STL allocator將兩個階段操作區分開來:內存配置有alloc::allocate()負責,內存釋放由alloc::deallocate()負責;對象構造由::construct()負責,對象析構由::destroy()負責。

同時為了提升內存管理的效率,減少申請小內存造成的內存碎片問題,SGI STL采用了兩級配置器,當分配的空間大小超過128B時,會使用第一級空間配置器;當分配的空間大小小於128B時,將使用第二級空間配置器。第一級空間配置器直接使用malloc()、realloc()、free()函數進行內存空間的分配和釋放,而第二級空間配置器采用了內存池技術,通過空閑鏈表來管理內存。

 
 

C++中類成員的訪問權限

C++通過 public、protected、private 三個關鍵字來控制成員變量和成員函數的訪問權限,它們分別表示公有的、受保護的、私有的,被稱為成員訪問限定符。在類的內部(定義類的代碼內部),無論成員被聲明為 public、protected 還是 private,都是可以互相訪問的,沒有訪問權限的限制。在類的外部(定義類的代碼之外),只能通過對象訪問成員,並且通過對象只能訪問 public 屬性的成員,不能訪問 private、protected 屬性的成員
 
 

C++中struct和class的區別

在C++中,可以用struct和class定義類,都可以繼承。區別在於:structural的默認繼承權限和默認訪問權限是public,而class的默認繼承權限和默認訪問權限是private。

另外,class還可以定義模板類形參,比如template <class T, int i>。

 
 

C++中繼承權限的區別

 
公有繼承時基類中各成員屬性保持不變,基類中private成員被隱藏。派生類的成員只能訪問基類中的public/protected成員,而不能訪問private成員;派生類的對象只能訪問基類中的public成員。
私有繼承時基類中各成員屬性均變為private,並且基類中private成員被隱藏。派生類的成員也只能訪問基類中的public/protected成員,而不能訪問private成員;派生類的對象不能訪問基類中的任何的成員。 
保護繼承時基類中各成員屬性均變為protected,並且基類中private成員被隱藏。派生類的成員只能訪問基類中的public/protected成員,而不能訪問private成員;派生類的對象不能訪問基類中的任何的成員。
 
//公有繼承                      對象訪問    成員訪問
public    -->  public              Y         Y
protected -->  protected           N         Y
private   -->  private             N         N
 
//保護繼承                      對象訪問    成員訪問
public    -->  protected           N         Y
protected -->  protected           N         Y
private   -->  protected           N         N
 
//私有繼承                      對象訪問    成員訪問
public    -->  private             N         Y
protected -->  private             N         Y
private   -->  private             N         N

 

 

 C++源文件從文本到可執行文件經歷的過程?

 對於C++源文件,從文本到可執行文件一般需要四個過程:

預處理階段:對源代碼文件中文件包含關系(頭文件)、預編譯語句(宏定義)進行分析和替換,生成預編譯文件。

編譯階段:將經過預處理后的預編譯文件轉換成特定匯編代碼,生成匯編文件

匯編階段:將編譯階段生成的匯編文件轉化成機器碼,生成可重定位目標文件

鏈接階段:將多個目標文件及所需要的庫連接成最終的可執行目標文件

 

 

include頭文件的順序以及雙引號””和尖括號<>的區別?

Include頭文件的順序:對於include的頭文件來說,如果在文件a.h中聲明一個在文件b.h中定義的變量,而不引用b.h。那么要在a.c文件中引用b.h文件,並且要先引用b.h,后引用a.h,否則匯報變量類型未聲明錯誤。

雙引號和尖括號的區別:編譯器預處理階段查找頭文件的路徑不一樣。

對於使用雙引號包含的頭文件,查找頭文件路徑的順序為:

當前頭文件目錄

編譯器設置的頭文件路徑(編譯器可使用-I顯式指定搜索路徑)

系統變量CPLUS_INCLUDE_PATH/C_INCLUDE_PATH指定的頭文件路徑

對於使用尖括號包含的頭文件,查找頭文件的路徑順序為:

編譯器設置的頭文件路徑(編譯器可使用-I顯式指定搜索路徑)

系統變量CPLUS_INCLUDE_PATH/C_INCLUDE_PATH指定的頭文件路徑

 

malloc的原理,另外brk系統調用和mmap系統調用的作用分別是什么?

Malloc函數用於動態分配內存。為了減少內存碎片和系統調用的開銷,malloc其采用內存池的方式,先申請大塊內存作為堆區,然后將堆區分為多個內存塊,以塊作為內存管理的基本單位。當用戶申請內存時,直接從堆區分配一塊合適的空閑塊。Malloc采用隱式鏈表結構將堆區分成連續的、大小不一的塊,包含已分配塊和未分配塊;同時malloc采用顯示鏈表結構來管理所有的空閑塊,即使用一個雙向鏈表將空閑塊連接起來,每一個空閑塊記錄了一個連續的、未分配的地址。

當進行內存分配時,Malloc會通過隱式鏈表遍歷所有的空閑塊,選擇滿足要求的塊進行分配;當進行內存合並時,malloc采用邊界標記法,根據每個塊的前后塊是否已經分配來決定是否進行塊合並。

Malloc在申請內存時,一般會通過brk或者mmap系統調用進行申請。其中當申請內存小於128K時,會使用系統函數brk在堆區中分配;而當申請內存大於128K時,會使用系統函數mmap在映射區分配。

 

C++的內存管理是怎樣的?

在C++中,虛擬內存分為代碼段、數據段、BSS段、堆區、文件映射區以及棧區六部分。

代碼段:包括只讀存儲區和文本區,其中只讀存儲區存儲字符串常量,文本區存儲程序的機器代碼。

數據段:存儲程序中已初始化的全局變量和靜態變量

bss 段:存儲未初始化的全局變量和靜態變量(局部+全局),以及所有被初始化為0的全局變量和靜態變量。

堆區:調用new/malloc函數時在堆區動態分配內存,同時需要調用delete/free來手動釋放申請的內存。

映射區:存儲動態鏈接庫以及調用mmap函數進行的文件映射

棧:使用棧空間存儲函數的返回地址、參數、局部變量、返回值

 

 

C++/C的內存分配 

 
 
 

 什么時候會發生段錯誤

 段錯誤通常發生在訪問非法內存地址的時候,具體來說分為以下幾種情況:

使用野指針

試圖修改字符串常量的內容

 

 

什么是memory leak,也就是內存泄漏

內存泄漏(memory leak)是指由於疏忽或錯誤造成了程序未能釋放掉不再使用的內存的情況。內存泄漏並非指內存在物理上的消失,而是應用程序分配某段內存后,由於設計錯誤,失去了對該段內存的控制,因而造成了內存的浪費。

內存泄漏的分類:

1. 堆內存泄漏 (Heap leak)。對內存指的是程序運行中根據需要分配通過malloc,realloc new等從堆中分配的一塊內存,再是完成后必須通過調用對應的 free或者delete 刪掉。如果程序的設計的錯誤導致這部分內存沒有被釋放,那么此后這塊內存將不會被使用,就會產生Heap Leak.

2. 系統資源泄露(Resource Leak)。主要指程序使用系統分配的資源比如 Bitmap,handle ,SOCKET等沒有使用相應的函數釋放掉,導致系統資源的浪費,嚴重可導致系統效能降低,系統運行不穩定。

3. 沒有將基類的析構函數定義為虛函數。當基類指針指向子類對象時,如果基類的析構函數不是virtual,那么子類的析構函數將不會被調用,子類的資源沒有正確是釋放,因此造成內存泄露。

 

 

new和malloc的區別

1、new分配內存按照 數據類型進行分配,malloc分配內存按照指定的 大小分配;

2、new返回的是指定對象的指針,而malloc返回的是void*,因此malloc的返回值一般都需要進行類型轉化。

3、new不僅分配一段內存,而且會調用構造函數,malloc不會。

4、new分配的內存要用delete銷毀,malloc要用free來銷毀;delete銷毀的時候會調用對象的析構函數,而free則不會。

5、new是一個操作符可以重載,malloc是一個庫函數。

6、malloc分配的內存不夠的時候,可以用realloc擴容。擴容的原理?new沒用這樣操作。

7、new如果分配失敗了會拋出bad_malloc的異常,而malloc失敗了會返回NULL。

8、申請數組時: new[]一次分配所有內存,多次調用構造函數,搭配使用delete[],delete[]多次調用析構函數,銷毀數組中的每個對象。而malloc則只能sizeof(int) * n。

 

共享內存相關api

Linux允許不同進程訪問同一個邏輯內存,提供了一組API,頭文件在sys/shm.h中。

1)新建共享內存shmget

int shmget(key_t key,size_t size,int shmflg);

key:共享內存鍵值,可以理解為共享內存的唯一性標記。

size:共享內存大小

shmflag:創建進程和其他進程的讀寫權限標識。

返回值:相應的共享內存標識符,失敗返回-1

2)連接共享內存到當前進程的地址空間shmat

void *shmat(int shm_id,const void *shm_addr,int shmflg);

shm_id:共享內存標識符

shm_addr:指定共享內存連接到當前進程的地址,通常為0,表示由系統來選擇。

shmflg:標志位

返回值:指向共享內存第一個字節的指針,失敗返回-1

3)當前進程分離共享內存shmdt

int shmdt(const void *shmaddr);

4)控制共享內存shmctl

和信號量的semctl函數類似,控制共享內存

int shmctl(int shm_id,int command,struct shmid_ds *buf);

shm_id:共享內存標識符

command: 有三個值

IPC_STAT:獲取共享內存的狀態,把共享內存的shmid_ds結構復制到buf中。

IPC_SET:設置共享內存的狀態,把buf復制到共享內存的shmid_ds結構。

IPC_RMID:刪除共享內存

buf:共享內存管理結構體。

 
 

請自己設計一下如何采用單線程的方式處理高並發

在單線程模型中,可以采用I/O復用來提高單線程處理多個請求的能力,然后再采用事件驅動模型,基於異步回調來處理事件來
 
 
請你詳細介紹一下C++11中的可變參數模板、右值引用和lambda這幾個新特性。
 
 

 C++重載,重寫與虛函數:

重載:一個類里有多個同名函數,但是它們的參數列表不同,就構成了重載
覆蓋:① 父類里的函數是虛函數 ②子類中有這個函數的同名參數列表也相同的函數就構成了重寫。
隱藏:當子類有父類的同名函數時,會隱藏父類的函數,不能構成重載。
 
 

紅黑樹和AVL樹的定義,特點,以及二者區別

平衡二叉樹(AVL樹):

平衡二叉樹又稱為AVL樹,是一種特殊的二叉排序樹。其左右子樹都是平衡二叉樹,且左右子樹高度之差的絕對值不超過1。一句話表述為:以樹中所有結點為根的樹的左右子樹高度之差的絕對值不超過1。將二叉樹上結點的左子樹深度減去右子樹深度的值稱為平衡因子BF,那么平衡二叉樹上的所有結點的平衡因子只可能是-1、0和1。只要二叉樹上有一個結點的平衡因子的絕對值大於1,則該二叉樹就是不平衡的。

 

紅黑樹:

紅黑樹是一種二叉查找樹,但在每個節點增加一個存儲位表示節點的顏色,可以是紅或黑(非紅即黑)。通過對任何一條從根到葉子的路徑上各個節點着色的方式的限制,紅黑樹確保沒有一條路徑會比其它路徑長出兩倍,因此,紅黑樹是一種弱平衡二叉樹,相對於要求嚴格的AVL樹來說,它的旋轉次數少,所以對於搜索,插入,刪除操作較多的情況下,通常使用紅黑樹。

性質:

1. 每個節點非紅即黑

2. 根節點是黑的;

3. 每個葉節點(葉節點即樹尾端NULL指針或NULL節點)都是黑的;

4. 如果一個節點是紅色的,則它的子節點必須是黑色的。

5. 對於任意節點而言,其到葉子點樹NULL指針的每條路徑都包含相同數目的黑節點;

 

區別:

AVL 樹是高度平衡的,頻繁的插入和刪除,會引起頻繁的rebalance,導致效率下降;紅黑樹不是高度平衡的,算是一種折中,插入最多兩次旋轉,刪除最多三次旋轉。

 

紅黑樹較AVL樹的優點:

AVL 樹是高度平衡的,頻繁的插入和刪除,會引起頻繁的rebalance,導致效率下降;紅黑樹不是高度平衡的,算是一種折中,插入最多兩次旋轉,刪除最多三次旋轉。

所以紅黑樹在查找,插入刪除的性能都是O(logn),且性能穩定,所以STL里面很多結構包括map底層實現都是使用的紅黑樹。

 
 

map和unordered_map的底層實現

map底層是基於紅黑樹實現的,因此map內部元素排列是有序的。而unordered_map底層則是基於哈希表實現的,因此其元素的排列順序是雜亂無序的。
 
 

需要無序容器,快速查找刪除,不擔心略高的內存時用unordered_map;有序容器穩定查找刪除效率,內存很在意時候用map。

對於map,其底層是基於紅黑樹實現的,優點如下:

1)有序性,這是map結構最大的優點,其元素的有序性在很多應用中都會簡化很多的操作

2)map的查找、刪除、增加等一系列操作時間復雜度穩定,都為logn

缺點如下:

1)查找、刪除、增加等操作平均時間復雜度較慢,與n相關

對於unordered_map來說,其底層是一個哈希表,優點如下:

查找、刪除、添加的速度快,時間復雜度為常數級O(c)

缺點如下:

因為unordered_map內部基於哈希表,以(key,value)對的形式存儲,因此空間占用率高

Unordered_map的查找、刪除、添加的時間復雜度不穩定,平均為O(c),取決於哈希函數。極端情況下可能為O(n)

 

 

棧和堆的區別,以及為什么棧要快 

 堆和棧的區別:

堆是由低地址向高地址擴展;棧是由高地址向低地址擴展

 

堆中的內存需要手動申請和手動釋放;棧中內存是由OS自動申請和自動釋放,存放着參數、局部變量等內存

 

堆中頻繁調用malloc和free,會產生內存碎片,降低程序效率;而棧由於其先進后出的特性,不會產生內存碎片

 

堆的分配效率較低,而棧的分配效率較高

 

棧的效率高的原因:

 

棧是操作系統提供的數據結構,計算機底層對棧提供了一系列支持:分配專門的寄存器存儲棧的地址,壓棧和入棧有專門的指令執行;而堆是由C/C++函數庫提供的,機制復雜,需要一些列分配內存、合並內存和釋放內存的算法,因此效率較低。

 

 

 堆和棧的區別

1)申請方式:

棧由系統自動分配和管理,堆由程序員手動分配和管理。

2)效率:

棧由系統分配,速度快,不會有內存碎片。

堆由程序員分配,速度較慢,可能由於操作不當產生內存碎片。

3)擴展方向

棧從高地址向低地址進行擴展,堆由低地址向高地址進行擴展。

4)程序局部變量是使用的棧空間,new/malloc動態申請的內存是堆空間,函數調用時會進行形參和返回值的壓棧出棧,也是用的棧空間。

 
 

 Array&List, 數組和鏈表的區別

數組的優點:

1. 隨機訪問性強

2. 查找速度快

數組的缺點:

1. 插入和刪除效率低

2. 可能浪費內存

3. 內存空間要求高,必須有足夠的連續內存空間。

4. 數組大小固定,不能動態拓展

 

鏈表的優點:

1. 插入刪除速度快

2. 內存利用率高,不會浪費內存

3. 大小沒有固定,拓展很靈活。

鏈表的缺點:

不能隨機查找,必須從第一個開始遍歷,查找效率低

 

 

快排代碼

 
 

 第K大

 
 

 各種排序

 
 

解決hash沖突的方法

當哈希表關鍵字集合很大時,關鍵字值不同的元素可能會映象到哈希表的同一地址上,這樣的現象稱為哈希沖突。目前常用的解決哈希沖突的方法如下:

開放定址法: 當發生地址沖突時,按照某種方法繼續探測哈希表中的其他存儲單元,直到找到空位置為止。

再哈希法:當發生哈希沖突時使用另一個哈希函數計算地址值,直到沖突不再發生。這種方法不易產生聚集,但是增加計算時間,同時需要准備許多哈希函數。

鏈地址法:將所有哈希值相同的Key通過鏈表存儲。key按順序插入到鏈表中

建立公共溢出區:采用一個溢出表存儲產生沖突的關鍵字。如果公共溢出區還產生沖突,再采用處理沖突方法處理。

 
 

最長公共子串 and 最長公共連續子序列

最長公共子串長度就為max{c[i][j]}了

 

 

 

最長回文子串 

 
 

反轉鏈表 

 
 

設計模式

 
 

git中Merge和rebase區別

 
 
 
 
 

 實現一個LRU cache

 
 
 
 
 
 
 

 動態鏈接 靜態鏈接


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM