超硬核!十萬字c++題,讓你秒殺老師和面試官(上)

我發現呀,這大家對面試題的需求還是很大的,這里總結了上千道知識點,能換您一個收藏嗎

C++

?

引用和指針的區別?

指針是一個實體,需要分配內存空間。引用只是變量的別名,不需要分配內存空間。

引用在定義的時候必須進行初始化,并且不能夠改變。指針在定義的時候不一定要初始化,并且指向的空間可變。(注:不能有引用的值不能為NULL)

有多級指針,但是沒有多級引用,只能有一級引用。

指針和引用的自增運算結果不一樣。(指針是指向下一個空間,引用時引用的變量值加1)

sizeof 引用得到的是所指向的變量(對象)的大小,而sizeof 指針得到的是指針本身的大小。

引用訪問一個變量是直接訪問,而指針訪問一個變量是間接訪問。

使用指針前最好做類型檢查,防止野指針的出現;

引用底層是通過指針實現的;

作為參數時也不同,傳指針的實質是傳值,傳遞的值是指針的地址;傳引用的實質是傳地址,傳遞的是變量的地址。

從匯編層去解釋一下引用

x的地址為ebp-4,b的地址為ebp-8,因為棧內的變量內存是從高往低進行分配的。所以b的地址比x的低。lea eax,[ebp-4]? 這條語句將x的地址ebp-4放入eax寄存器mov dword ptr [ebp-8],eax 這條語句將eax的值放入b的地址ebp-8中上面兩條匯編的作用即:將x的地址存入變量b中,這不和將某個變量的地址存入指針變量是一樣的嗎?所以從匯編層次來看,的確引用是通過指針來實現的。

C++中的指針參數傳遞和引用參數傳遞

指針參數傳遞本質上是值傳遞,它所傳遞的是一個地址值。值傳遞過程中,被調函數的形式參數作為被調函數的局部變量處理,會在棧中開辟內存空間以存放由主調函數傳遞進來的實參值,從而形成了實參的一個副本(替身)。值傳遞的特點是,被調函數對形式參數的任何操作都是作為局部變量進行的,不會影響主調函數的實參變量的值(形參指針變了,實參指針不會變)。

引用參數傳遞過程中,被調函數的形式參數也作為局部變量在棧中開辟了內存空間,但是這時存放的是由主調函數放進來的實參變量的地址。被調函數對形參(本體)的任何操作都被處理成間接尋址,即通過棧中存放的地址訪問主調函數中的實參變量(根據別名找到主調函數中的本體)。因此,被調函數對形參的任何操作都會影響主調函數中的實參變量。

引用傳遞和指針傳遞是不同的,雖然他們都是在被調函數棧空間上的一個局部變量,但是任何對于引用參數的處理都會通過一個間接尋址的方式操作到主調函數中的相關變量。而對于指針傳遞的參數,如果改變被調函數中的指針地址,它將應用不到主調函數的相關變量。如果想通過指針參數傳遞來改變主調函數中的相關變量(地址),那就得使用指向指針的指針或者指針引用。

從編譯的角度來講,程序在編譯時分別將指針和引用添加到符號表上,符號表中記錄的是變量名及變量所對應地址。指針變量在符號表上對應的地址值為指針變量的地址值,而引用在符號表上對應的地址值為引用對象的地址值(與實參名字不同,地址相同)。符號表生成之后就不會再改,因此指針可以改變其指向的對象(指針變量中的值可以改),而引用對象則不能修改。

形參與實參的區別?

形參變量只有在被調用時才分配內存單元,在調用結束時, 即刻釋放所分配的內存單元。因此,形參只有在函數內部有效。 函數調用結束返回主調函數后則不能再使用該形參變量。

實參可以是常量、變量、表達式、函數等, 無論實參是何種類型的量,在進行函數調用時,它們都必須具有確定的值, 以便把這些值傳送給形參。 因此應預先用賦值,輸入等辦法使實參獲得確定值,會產生一個臨時變量。

實參和形參在數量上,類型上,順序上應嚴格一致, 否則會發生“類型不匹配”的錯誤。

函數調用中發生的數據傳送是單向的。 即只能把實參的值傳送給形參,而不能把形參的值反向地傳送給實參。 因此在函數調用過程中,形參的值發生改變,而實參中的值不會變化。

當形參和實參不是指針類型時,在該函數運行時,形參和實參是不同的變量,他們在內存中位于不同的位置,形參將實參的內容復制一份,在該函數運行結束的時候形參被釋放,而實參內容不會改變。

值傳遞:有一個形參向函數所屬的棧拷貝數據的過程,如果值傳遞的對象是類對象 ???或是大的結構體對象,將耗費一定的時間和空間。(傳值)

指針傳遞:同樣有一個形參向函數所屬的棧拷貝數據的過程,但拷貝的數據是一個固定為4字節的地址。(傳值,傳遞的是地址值)

引用傳遞:同樣有上述的數據拷貝過程,但其是針對地址的,相當于為該數據所在的地址起了一個別名。(傳地址)

效率上講,指針傳遞和引用傳遞比值傳遞效率高。一般主張使用引用傳遞,代碼邏輯上更加緊湊、清晰。

static的用法和作用?

1.先來介紹它的第一條也是最重要的一條:隱藏。(static函數,static變量均可)

當同時編譯多個文件時,所有未加static前綴的全局變量和函數都具有全局可見性。

2.static的第二個作用是保持變量內容的持久。(static變量中的記憶功能和全局生存期)存儲在靜態數據區的變量會在程序剛開始運行時就完成初始化,也是唯一的一次初始化。共有兩種變量存儲在靜態存儲區:全局變量和static變量,只不過和全局變量比起來,static可以控制變量的可見范圍,說到底static還是用來隱藏的。

3.static的第三個作用是默認初始化為0(static變量)

其實全局變量也具備這一屬性,因為全局變量也存儲在靜態數據區。在靜態數據區,內存中所有的字節默認值都是0x00,某些時候這一特點可以減少程序員的工作量。

4.static的第四個作用:C++中的類成員聲明static

函數體內static變量的作用范圍為該函數體,不同于auto變量,該變量的內存只被分配一次,因此其值在下次調用時仍維持上次的值;??

在模塊內的static全局變量可以被模塊內所用函數訪問,但不能被模塊外其它函數訪問;???

在模塊內的static函數只可被這一模塊內的其它函數調用,這個函數的使用范圍被限制在聲明它的模塊內;???

在類中的static成員變量屬于整個類所擁有,對類的所有對象只有一份拷貝;???

在類中的static成員函數屬于整個類所擁有,這個函數不接收this指針,因而只能訪問類的static成員變量。

類內:

static類對象必須要在類外進行初始化,static修飾的變量先于對象存在,所以static修飾的變量要在類外初始化;

由于static修飾的類成員屬于類,不屬于對象,因此static類成員函數是沒有this指針的,this指針是指向本對象的指針。正因為沒有this指針,所以static類成員函數不能訪問非static的類成員,只能訪問 static修飾的類成員;

static成員函數不能被virtual修飾,static成員不屬于任何對象或實例,所以加上virtual沒有任何實際意義;靜態成員函數沒有this指針,虛函數的實現是為每一個對象分配一個vptr指針,而vptr是通過this指針調用的,所以不能為virtual;虛函數的調用關系,this->vptr->ctable->virtual function

靜態變量什么時候初始化

初始化只有一次,但是可以多次賦值,在主程序之前,編譯器已經為其分配好了內存。

靜態局部變量和全局變量一樣,數據都存放在全局區域,所以在主程序之前,編譯器已經為其分配好了內存,但在C和C++中靜態局部變量的初始化節點又有點不太一樣。在C中,初始化發生在代碼執行之前,編譯階段分配好內存之后,就會進行初始化,所以我們看到在C語言中無法使用變量對靜態局部變量進行初始化,在程序運行結束,變量所處的全局內存會被全部回收。

而在C++中,初始化時在執行相關代碼時才會進行初始化,主要是由于C++引入對象后,要進行初始化必須執行相應構造函數和析構函數,在構造函數或析構函數中經常會需要進行某些程序中需要進行的特定操作,并非簡單地分配內存。所以C++標準定為全局或靜態對象是有首次用到時才會進行構造,并通過atexit()來管理。在程序結束,按照構造順序反方向進行逐個析構。所以在C++中是可以使用變量對靜態局部變量進行初始化的。

?const?

阻止一個變量被改變,可以使用const關鍵字。在定義該const變量時,通常需要對它進行初始化,因為以后就沒有機會再去改變它了;???

對指針來說,可以指定指針本身為const,也可以指定指針所指的數據為const,或二者同時指定為const;???

在一個函數聲明中,const可以修飾形參,表明它是一個輸入參數,在函數內部不能改變其值;???

對于類的成員函數,若指定其為const類型,則表明其是一個常函數,不能修改類的成員變量,類的常對象只能訪問類的常成員函數;???

對于類的成員函數,有時候必須指定其返回值為const類型,以使得其返回值不為“左值”。

const成員函數可以訪問非const對象的非const數據成員、const數據成員,也可以訪問const對象內的所有數據成員;

非const成員函數可以訪問非const對象的非const數據成員、const數據成員,但不可以訪問const對象的任意數據成員;

一個沒有明確聲明為const的成員函數被看作是將要修改對象中數據成員的函數,而且編譯器不允許它為一個const對象所調用。因此const對象只能調用const成員函數。

const類型變量可以通過類型轉換符const_cast將const類型轉換為非const類型;

const類型變量必須定義的時候進行初始化,因此也導致如果類的成員變量有const類型的變量,那么該變量必須在類的初始化列表中進行初始化;

對于函數值傳遞的情況,因為參數傳遞是通過復制實參創建一個臨時變量傳遞進函數的,函數內只能改變臨時變量,但無法改變實參。則這個時候無論加不加const對實參不會產生任何影響。但是在引用或指針傳遞函數調用中,因為傳進去的是一個引用或指針,這樣函數內部可以改變引用或指針所指向的變量,這時const 才是實實在在地保護了實參所指向的變量。因為在編譯階段編譯器對調用函數的選擇是根據實參進行的,所以,只有引用傳遞和指針傳遞可以用是否加const來重載。一個擁有頂層const的形參無法和另一個沒有頂層const的形參區分開來。

const成員函數的理解和應用?

const Stock & Stock::topval (②const Stock & s)?③const

①處const:確保返回的Stock對象在以后的使用中不能被修改

②處const:確保此方法不修改傳遞的參數 S

③處const:保證此方法不修改調用它的對象,const對象只能調用const成員函數,不能調用非const函數

指針和const的用法

當const修飾指針時,由于const的位置不同,它的修飾對象會有所不同。

int *const p2中const修飾p2的值,所以理解為p2的值不可以改變,即p2只能指向固定的一個變量地址,但可以通過*p2讀寫這個變量的值。頂層指針表示指針本身是一個常量

int const *p1或者const int *p1兩種情況中const修飾*p1,所以理解為*p1的值不可以改變,即不可以給*p1賦值改變p1指向變量的值,但可以通過給p賦值不同的地址改變這個指針指向。底層指針表示指針所指向的變量是一個常量。

int const *const p;

mutable

如果需要在const成員方法中修改一個成員變量的值,那么需要將這個成員變量修飾為mutable。即用mutable修飾的成員變量不受const成員方法的限制;

可以認為mutable的變量是類的輔助狀態,但是只是起到類的一些方面表述的功能,修改他的內容我們可以認為對象的狀態本身并沒有改變的。實際上由于const_cast的存在,這個概念很多時候用處不是很到了。

extern用法?

extern修飾變量的聲明

如果文件a.c需要引用b.c中變量int v,就可以在a.c中聲明extern int v,然后就可以引用變量v。

extern修飾函數的聲明

如果文件a.c需要引用b.c中的函數,比如在b.c中原型是int fun(int mu),那么就可以在a.c中聲明extern int fun(int mu),然后就能使用fun來做任何事情。就像變量的聲明一樣,extern int fun(int mu)可以放在a.c中任何地方,而不一定非要放在a.c的文件作用域的范圍中。

extern修飾符可用于指示C或者C++函數的調用規范。

比如在C++中調用C庫函數,就需要在C++程序中用extern “C”聲明要引用的函數。這是給鏈接器用的,告訴鏈接器在鏈接的時候用C函數規范來鏈接。主要原因是C++和C程序編譯完成后在目標代碼中命名規則不同。

int轉字符串字符串轉int?strcat,strcpy,strncpy,memset,memcpy的內部實現?

c++11標準增加了全局函數std::to_string

可以使用std::stoi/stol/stoll等等函數

strcpy擁有返回值,有時候函數原本不需要返回值,但為了增加靈活性如支持鏈式表達,

深拷貝與淺拷貝?

淺復制?—-只是拷貝了基本類型的數據,而引用類型數據,復制后也是會發生引用,我們把這種拷貝叫做“(淺復制)淺拷貝”,換句話說,淺復制僅僅是指向被復制的內存地址,如果原地址中對象被改變了,那么淺復制出來的對象也會相應改變。

深復制?—-在計算機中開辟了一塊新的內存地址用于存放復制的對象。

在某些狀況下,類內成員變量需要動態開辟堆內存,如果實行位拷貝,也就是把對象里的值完全復制給另一個對象,如A=B。這時,如果B中有一個成員變量指針已經申請了內存,那A中的那個成員變量也指向同一塊內存。這就出現了問題:當B把內存釋放了(如:析構),這時A內的指針就是野指針了,出現運行錯誤。

C++模板是什么,底層怎么實現的?

編譯器并不是把函數模板處理成能夠處理任意類的函數;編譯器從函數模板通過具體類型產生不同的函數;編譯器會對函數模板進行兩次編譯:在聲明的地方對模板代碼本身進行編譯,在調用的地方對參數替換后的代碼進行編譯。

這是因為函數模板要被實例化后才能成為真正的函數,在使用函數模板的源文件中包含函數模板的頭文件,如果該頭文件中只有聲明,沒有定義,那編譯器無法實例化該模板,最終導致鏈接錯誤。

C語言struct和C++struct區別

C語言中:struct是用戶自定義數據類型(UDT);C++中struct是抽象數據類型(ADT),支持成員函數的定義,(C++中的struct能繼承,能實現多態)。

C中struct是沒有權限的設置的,且struct中只能是一些變量的集合體,可以封裝數據卻不可以隱藏數據,而且成員不可以是函數。

C++中,struct的成員默認訪問說明符為public(為了與C兼容),class中的默認訪問限定符為private,struct增加了訪問權限,且可以和類一樣有成員函數。

struct作為類的一種特例是用來自定義數據結構的。一個結構標記聲明后,在C中必須在結構標記前加上struct,才能做結構類型名

虛函數可以聲明為inline嗎?

虛函數用于實現運行時的多態,或者稱為晚綁定或動態綁定。而內聯函數用于提高效率。內聯函數的原理是,在編譯期間,對調用內聯函數的地方的代碼替換成函數代碼。內聯函數對于程序中需要頻繁使用和調用的小函數非常有用。

虛函數要求在運行時進行類型確定,而內斂函數要求在編譯期完成相關的函數替換;

類成員初始化方式?構造函數的執行順序 ?為什么用成員初始化列表會快一些?

賦值初始化,通過在函數體內進行賦值初始化;列表初始化,在冒號后使用初始化列表進行初始化。

這兩種方式的主要區別在于:

對于在函數體中初始化,是在所有的數據成員被分配內存空間后才進行的。

列表初始化是給數據成員分配內存空間時就進行初始化,就是說分配一個數據成員只要冒號后有此數據成員的賦值表達式(此表達式必須是括號賦值表達式),那么分配了內存空間后在進入函數體之前給數據成員賦值,就是說初始化這個數據成員此時函數體還未執行。?

一個派生類構造函數的執行順序如下:

虛擬基類的構造函數(多個虛擬基類則按照繼承的順序執行構造函數)。

基類的構造函數(多個普通基類也按照繼承的順序執行構造函數)。

類類型的成員對象的構造函數(按照初始化順序)

派生類自己的構造函數。

方法一是在構造函數當中做賦值的操作,而方法二是做純粹的初始化操作。我們都知道,C++的賦值操作是會產生臨時對象的。臨時對象的出現會降低程序的效率。

成員列表初始化?

必須使用成員初始化的四種情況

當初始化一個引用成員時;

當初始化一個常量成員時;

當調用一個基類的構造函數,而它擁有一組參數時;

當調用一個成員類的構造函數,而它擁有一組參數時;

成員初始化列表做了什么

編譯器會一一操作初始化列表,以適當的順序在構造函數之內安插初始化操作,并且在任何顯示用戶代碼之前;

list中的項目順序是由類中的成員聲明順序決定的,不是由初始化列表的順序決定的;

構造函數為什么不能為虛函數?析構函數為什么要虛函數?

1. 從存儲空間角度,虛函數相應一個指向vtable虛函數表的指針,這大家都知道,但是這個指向vtable的指針事實上是存儲在對象的內存空間的。問題出來了,假設構造函數是虛的,就須要通過 vtable來調用,但是對象還沒有實例化,也就是內存空間還沒有,怎么找vtable呢?所以構造函數不能是虛函數。

2. 從使用角度,虛函數主要用于在信息不全的情況下,能使重載的函數得到相應的調用。構造函數本身就是要初始化實例,那使用虛函數也沒有實際意義呀。所以構造函數沒有必要是虛函數。虛函數的作用在于通過父類的指針或者引用來調用它的時候可以變成調用子類的那個成員函數。而構造函數是在創建對象時自己主動調用的,不可能通過父類的指針或者引用去調用,因此也就規定構造函數不能是虛函數。

3. 構造函數不須要是虛函數,也不同意是虛函數,由于創建一個對象時我們總是要明白指定對象的類型,雖然我們可能通過實驗室的基類的指針或引用去訪問它但析構卻不一定,我們往往通過基類的指針來銷毀對象。這時候假設析構函數不是虛函數,就不能正確識別對象類型從而不能正確調用析構函數。

4. 從實現上看,vbtl在構造函數調用后才建立,因而構造函數不可能成為虛函數從實際含義上看,在調用構造函數時還不能確定對象的真實類型(由于子類會調父類的構造函數);并且構造函數的作用是提供初始化,在對象生命期僅僅運行一次,不是對象的動態行為,也沒有必要成為虛函數。

5. 當一個構造函數被調用時,它做的首要的事情之中的一個是初始化它的VPTR。因此,它僅僅能知道它是“當前”類的,而全然忽視這個對象后面是否還有繼承者。當編譯器為這個構造函數產生代碼時,它是為這個類的構造函數產生代碼——既不是為基類,也不是為它的派生類(由于類不知道誰繼承它)。所以它使用的VPTR必須是對于這個類的VTABLE。并且,僅僅要它是最后的構造函數調用,那么在這個對象的生命期內,VPTR將保持被初始化為指向這個VTABLE, 但假設接著另一個更晚派生的構造函數被調用,這個構造函數又將設置VPTR指向它的 VTABLE,等.直到最后的構造函數結束。VPTR的狀態是由被最后調用的構造函數確定的。這就是為什么構造函數調用是從基類到更加派生類順序的還有一個理由。可是,當這一系列構造函數調用正發生時,每一個構造函數都已經設置VPTR指向它自己的VTABLE。假設函數調用使用虛機制,它將僅僅產生通過它自己的VTABLE的調用,而不是最后的VTABLE(全部構造函數被調用后才會有最后的VTABLE)。

因為構造函數本來就是為了明確初始化對象成員才產生的,然而virtual function主要是為了再不完全了解細節的情況下也能正確處理對象。另外,virtual函數是在不同類型的對象產生不同的動作,現在對象還沒有產生,如何使用virtual函數來完成你想完成的動作。

直接的講,C++中基類采用virtual虛析構函數是為了防止內存泄漏。具體地說,如果派生類中申請了內存空間,并在其析構函數中對這些內存空間進行釋放。假設基類中采用的是非虛析構函數,當刪除基類指針指向的派生類對象時就不會觸發動態綁定,因而只會調用基類的析構函數,而不會調用派生類的析構函數。那么在這種情況下,派生類中申請的空間就得不到釋放從而產生內存泄漏。所以,為了防止這種情況的發生,C++中基類的析構函數應采用virtual虛析構函數。

析構函數的作用,如何起作用?

構造函數只是起初始化值的作用,但實例化一個對象的時候,可以通過實例去傳遞參數,從主函數傳遞到其他的函數里面,這樣就使其他的函數里面有值了。規則,只要你一實例化對象,系統自動回調用一個構造函數,就是你不寫,編譯器也自動調用一次。

析構函數與構造函數的作用相反,用于撤銷對象的一些特殊任務處理,可以是釋放對象分配的內存空間;特點:析構函數與構造函數同名,但該函數前面加~。 析構函數沒有參數,也沒有返回值,而且不能重載,在一個類中只能有一個析構函數。 當撤銷對象時,編譯器也會自動調用析構函數。 每一個類必須有一個析構函數,用戶可以自定義析構函數,也可以是編譯器自動生成默認的析構函數。一般析構函數定義為類的公有成員。

構造函數和析構函數可以調用虛函數嗎,為什么

在C++中,提倡不在構造函數和析構函數中調用虛函數;

構造函數和析構函數調用虛函數時都不使用動態聯編,如果在構造函數或析構函數中調用虛函數,則運行的是為構造函數或析構函數自身類型定義的版本;

因為父類對象會在子類之前進行構造,此時子類部分的數據成員還未初始化,因此調用子類的虛函數時不安全的,故而C++不會進行動態聯編;

析構函數是用來銷毀一個對象的,在銷毀一個對象時,先調用子類的析構函數,然后再調用基類的析構函數。所以在調用基類的析構函數時,派生類對象的數據成員已經銷毀,這個時候再調用子類的虛函數沒有任何意義。

構造函數的執行順序?析構函數的執行順序?構造函數內部干了啥?拷貝構造干了啥?

構造函數順序

基類構造函數。如果有多個基類,則構造函數的調用順序是某類在類派生表中出現的順序,而不是它們在成員初始化表中的順序。

成員類對象構造函數。如果有多個成員類對象則構造函數的調用順序是對象在類中被聲明的順序,而不是它們出現在成員初始化表中的順序。

派生類構造函數。

析構函數順序

調用派生類的析構函數;

調用成員類對象的析構函數;

調用基類的析構函數。

虛析構函數的作用,父類的析構函數是否要設置為虛函數?

C++中基類采用virtual虛析構函數是為了防止內存泄漏。具體地說,如果派生類中申請了內存空間,并在其析構函數中對這些內存空間進行釋放。假設基類中采用的是非虛析構函數,當刪除基類指針指向的派生類對象時就不會觸發動態綁定,因而只會調用基類的析構函數,而不會調用派生類的析構函數。那么在這種情況下,派生類中申請的空間就得不到釋放從而產生內存泄漏。所以,為了防止這種情況的發生,C++中基類的析構函數應采用virtual虛析構函數。

純虛析構函數一定得定義,因為每一個派生類析構函數會被編譯器加以擴張,以靜態調用的方式調用其每一個虛基類以及上一層基類的析構函數。因此,缺乏任何一個基類析構函數的定義,就會導致鏈接失敗。因此,最好不要把虛析構函數定義為純虛析構函數。

構造函數析構函數可以調用虛函數嗎?

在構造函數和析構函數中最好不要調用虛函數;

構造函數或者析構函數調用虛函數并不會發揮虛函數動態綁定的特性,跟普通函數沒區別;

即使構造函數或者析構函數如果能成功調用虛函數, 程序的運行結果也是不可控的。

構造函數析構函數可否拋出異常

?C++只會析構已經完成的對象,對象只有在其構造函數執行完畢才算是完全構造妥當。在構造函數中發生異常,控制權轉出構造函數之外。因此,在對象b的構造函數中發生異常,對象b的析構函數不會被調用。因此會造成內存泄漏。

用auto_ptr對象來取代指針類成員,便對構造函數做了強化,免除了拋出異常時發生資源泄漏的危機,不再需要在析構函數中手動釋放資源;

如果控制權基于異常的因素離開析構函數,而此時正有另一個異常處于作用狀態,C++會調用terminate函數讓程序結束;

如果異常從析構函數拋出,而且沒有在當地進行捕捉,那個析構函數便是執行不全的。如果析構函數執行不全,就是沒有完成他應該執行的每一件事情。

類如何實現只能靜態分配和只能動態分配

前者是把new、delete運算符重載為private屬性。后者是把構造、析構函數設為protected屬性,再用子類來動態創建

建立類的對象有兩種方式:

靜態建立,靜態建立一個類對象,就是由編譯器為對象在棧空間中分配內存;

動態建立,A *p = new A();動態建立一個類對象,就是使用new運算符為對象在堆空間中分配內存。這個過程分為兩步,第一步執行operator new()函數,在堆中搜索一塊內存并進行分配;第二步調用類構造函數構造對象;

只有使用new運算符,對象才會被建立在堆上,因此只要限制new運算符就可以實現類對象只能建立在棧上。可以將new運算符設為私有。

如果想將某個類用作基類,為什么該類必須定義而非聲明?

派生類中包含并且可以使用它從基類繼承而來的成員,為了使用這些成員,派生類必須知道他們是什么。

什么情況會自動生成默認構造函數?

帶有默認構造函數的類成員對象,如果一個類沒有任何構造函數,但它含有一個成員對象,而后者有默認構造函數,那么編譯器就為該類合成出一個默認構造函數。不過這個合成操作只有在構造函數真正被需要的時候才會發生;如果一個類A含有多個成員類對象的話,那么類A的每一個構造函數必須調用每一個成員對象的默認構造函數而且必須按照類對象在類A中的聲明順序進行;

帶有默認構造函數的基類,如果一個沒有任務構造函數的派生類派生自一個帶有默認構造函數基類,那么該派生類會合成一個構造函數調用上一層基類的默認構造函數;

帶有一個虛函數的類

帶有一個虛基類的類

合成的默認構造函數中,只有基類子對象和成員類對象會被初始化。所有其他的非靜態數據成員都不會被初始化。

什么是類的繼承?

類與類之間的關系

has-A包含關系,用以描述一個類由多個部件類構成,實現has-A關系用類的成員屬性表示,即一個類的成員屬性是另一個已經定義好的類;

use-A,一個類使用另一個類,通過類之間的成員函數相互聯系,定義友元或者通過傳遞參數的方式來實現;

is-A,繼承關系,關系具有傳遞性;

繼承的相關概念

所謂的繼承就是一個類繼承了另一個類的屬性和方法,這個新的類包含了上一個類的屬性和方法,被稱為子類或者派生類,被繼承的類稱為父類或者基類;

繼承的特點

子類擁有父類的所有屬性和方法,子類可以擁有父類沒有的屬性和方法,子類對象可以當做父類對象使用;

繼承中的訪問控制

public、protected、private

繼承中的構造和析構函數

繼承中的兼容性原則

什么是組合?

一個類里面的數據成員是另一個類的對象,即內嵌其他類的對象作為自己的成員;創建組合類的對象:首先創建各個內嵌對象,難點在于構造函數的設計。創建對象時既要對基本類型的成員進行初始化,又要對內嵌對象進行初始化。

創建組合類對象,構造函數的執行順序:先調用內嵌對象的構造函數,然后按照內嵌對象成員在組合類中的定義順序,與組合類構造函數的初始化列表順序無關。然后執行組合類構造函數的函數體,析構函數調用順序相反。

?

抽象基類為什么不能創建對象?

抽象類是一種特殊的類,它是為了抽象和設計的目的為建立的,它處于繼承層次結構的較上層。

(1)抽象類的定義:
? ?稱帶有純虛函數的類為抽象類。

(2)抽象類的作用:
? ?抽象類的主要作用是將有關的操作作為結果接口組織在一個繼承層次結構中,由它來為派生類提供一個公共的根,派生類將具體實現在其基類中作為接口的操作。所以派生類實際上刻畫了一組子類的操作接口的通用語義,這些語義也傳給子類,子類可以具體實現這些語義,也可以再將這些語義傳給自己的子類。

(3)使用抽象類時注意:
? ?抽象類只能作為基類來使用,其純虛函數的實現由派生類給出。如果派生類中沒有重新定義純虛函數,而只是繼承基類的純虛函數,則這個派生類仍然還是一個抽象類。如果派生類中給出了基類純虛函數的實現,則該派生類就不再是抽象類了,它是一個可以建立對象的具體的類。

抽象類是不能定義對象的。一個純虛函數不需要(但是可以)被定義。

純虛函數定義


 純虛函數是一種特殊的虛函數,它的一般格式如下:
  class <類名>
  {
  virtual <類型><函數名>(<參數表>)=0;
  …
  };
  在許多情況下,在基類中不能對虛函數給出有意義的實現,而把它聲明為純虛函數,它的實現留給該基類的派生類去做。這就是純虛函數的作用。
  純虛函數可以讓類先具有一個操作名稱,而沒有操作內容,讓派生類在繼承時再去具體地給出定義。凡是含有純虛函數的類叫做抽象類。這種類不能聲明對象,只是作為基類為派生類服務。除非在派生類中完全實現基類中所有的的純虛函數,否則,派生類也變成了抽象類,不能實例化對象。

純虛函數引入原因


  1、為了方便使用多態特性,我們常常需要在基類中定義虛擬函數。
  2、在很多情況下,基類本身生成對象是不合情理的。例如,動物作為一個基類可以派生出老虎、孔 雀等子類,但動物本身生成對象明顯不合常理。
  為了解決上述問題,引入了純虛函數的概念,將函數定義為純虛函數(方法:virtual ReturnType Function()= 0;)。若要使派生類為非抽象類,則編譯器要求在派生類中,必須對純虛函數予以重載以實現多態性。同時含有純虛函數的類稱為抽象類,它不能生成對象。這樣就很好地解決了上述兩個問題。
例如,繪畫程序中,shape作為一個基類可以派生出圓形、矩形、正方形、梯形等, 如果我要求面積總和的話,那么會可以使用一個 shape * 的數組,只要依次調用派生類的area()函數了。如果不用接口就沒法定義成數組,因為既可以是circle ,也可以是square ,而且以后還可能加上rectangle,等等.

相似概念


1、多態性

指相同對象收到不同消息或不同對象收到相同消息時產生不同的實現動作。C++支持兩種多態性:編譯時多態性,運行時多態性。
  a.編譯時多態性:通過重載函數實現
  b.運行時多態性:通過虛函數實現。
2、虛函數
  虛函數是在基類中被聲明為virtual,并在派生類中重新定義的成員函數,可實現成員函數的動態重載。
3、抽象類
  包含純虛函數的類稱為抽象類。由于抽象類包含了沒有定義的純虛函數,所以不能定義抽象類的對象。

?

類什么時候會析構?

  1. 對象生命周期結束,被銷毀時;
  2. delete指向對象的指針時,或delete指向對象的基類類型指針,而其基類虛構函數是虛函數時;
  3. 對象i是對象o的成員,o的析構函數被調用時,對象i的析構函數也被調用。

?

為什么友元函數必須在類內部聲明?

因為編譯器必須能夠讀取這個結構的聲明以理解這個數據類型的大、行為等方面的所有規則。有一條規則在任何關系中都很重要,那就是誰可以訪問我的私有部分。

介紹一下C++里面的多態?

(1)靜態多態(重載,模板)

是在編譯的時候,就確定調用函數的類型。

(2)動態多態(覆蓋,虛函數實現)

在運行的時候,才確定調用的是哪個函數,動態綁定。運行基類指針指向派生類的對象,并調用派生類的函數。

虛函數實現原理:虛函數表和虛函數指針。

純虛函數: virtual int fun() = 0;

函數的運行版本由實參決定,在運行時選擇函數的版本,所以動態綁定又稱為運行時綁定。

當編譯器遇到一個模板定義時,它并不生成代碼。只有當實例化出模板的一個特定版本時,編譯器才會生成代碼。

用C語言實現C++的繼承

#include <iostream>
using namespace std;//C++中的繼承與多態
struct A
{virtual void fun()    //C++中的多態:通過虛函數實現{cout<<"A:fun()"<<endl;}int a;
};
struct B:public A         //C++中的繼承:B類公有繼承A類
{virtual void fun()    //C++中的多態:通過虛函數實現(子類的關鍵字virtual可加可不加){cout<<"B:fun()"<<endl;}int b;
};//C語言模擬C++的繼承與多態typedef void (*FUN)();      //定義一個函數指針來實現對成員函數的繼承struct _A       //父類
{FUN _fun;   //由于C語言中結構體不能包含函數,故只能用函數指針在外面實現int _a;
};struct _B         //子類
{_A _a_;     //在子類中定義一個基類的對象即可實現對父類的繼承int _b;
};void _fA()       //父類的同名函數
{printf("_A:_fun()\n");
}
void _fB()       //子類的同名函數
{printf("_B:_fun()\n");
}void Test()
{//測試C++中的繼承與多態A a;    //定義一個父類對象aB b;    //定義一個子類對象bA* p1 = &a;   //定義一個父類指針指向父類的對象p1->fun();    //調用父類的同名函數p1 = &b;      //讓父類指針指向子類的對象p1->fun();    //調用子類的同名函數//C語言模擬繼承與多態的測試_A _a;    //定義一個父類對象_a_B _b;    //定義一個子類對象_b_a._fun = _fA;        //父類的對象調用父類的同名函數_b._a_._fun = _fB;    //子類的對象調用子類的同名函數_A* p2 = &_a;   //定義一個父類指針指向父類的對象p2->_fun();     //調用父類的同名函數p2 = (_A*)&_b;  //讓父類指針指向子類的對象,由于類型不匹配所以要進行強轉p2->_fun();     //調用子類的同名函數
}

?

?

繼承機制中對象之間如何轉換?指針和引用之間如何轉換?

  • 向上類型轉換

將派生類指針或引用轉換為基類的指針或引用被稱為向上類型轉換,向上類型轉換會自動進行,而且向上類型轉換是安全的。

  • 向下類型轉換

將基類指針或引用轉換為派生類指針或引用被稱為向下類型轉換,向下類型轉換不會自動進行,因為一個基類對應幾個派生類,所以向下類型轉換時不知道對應哪個派生類,所以在向下類型轉換時必須加動態類型識別技術。RTTI技術,用dynamic_cast進行向下類型轉換。

組合與繼承優缺點?

一:繼承

繼承是Is a 的關系,比如說Student繼承Person,則說明Student is a Person。繼承的優點是子類可以重寫父類的方法來方便地實現對父類的擴展。

繼承的缺點有以下幾點:

①:父類的內部細節對子類是可見的。

②:子類從父類繼承的方法在編譯時就確定下來了,所以無法在運行期間改變從父類繼承的方法的行為。

③:如果對父類的方法做了修改的話(比如增加了一個參數),則子類的方法必須做出相應的修改。所以說子類與父類是一種高耦合,違背了面向對象思想。

二:組合

組合也就是設計類的時候把要組合的類的對象加入到該類中作為自己的成員變量。

組合的優點:

①:當前對象只能通過所包含的那個對象去調用其方法,所以所包含的對象的內部細節對當前對象時不可見的。

②:當前對象與包含的對象是一個低耦合關系,如果修改包含對象的類中代碼不需要修改當前對象類的代碼。

③:當前對象可以在運行時動態的綁定所包含的對象。可以通過set方法給所包含對象賦值。

組合的缺點:①:容易產生過多的對象。②:為了能組合多個對象,必須仔細對接口進行定義。

左值右值

  1. 在C++11中所有的值必屬于左值、右值兩者之一,右值又可以細分為純右值、將亡值。在C++11中可以取地址的、有名字的就是左值,反之,不能取地址的、沒有名字的就是右值(將亡值或純右值)。舉個例子,int a = b+c, a 就是左值,其有變量名為a,通過&a可以獲取該變量的地址;表達式b+c、函數int func()的返回值是右值,在其被賦值給某一變量前,我們不能通過變量名找到它,&(b+c)這樣的操作則不會通過編譯。
  2. C++11對C++98中的右值進行了擴充。在C++11中右值又分為純右值(prvalue,Pure Rvalue)和將亡值(xvalue,eXpiring Value)。其中純右值的概念等同于我們在C++98標準中右值的概念,指的是臨時變量和不跟對象關聯的字面量值;將亡值則是C++11新增的跟右值引用相關的表達式,這樣表達式通常是將要被移動的對象(移為他用),比如返回右值引用T&&的函數返回值、std::move的返回值,或者轉換為T&&的類型轉換函數的返回值。將亡值可以理解為通過“盜取”其他變量內存空間的方式獲取到的值。在確保其他變量不再被使用、或即將被銷毀時,通過“盜取”的方式可以避免內存空間的釋放和分配,能夠延長變量值的生命期。
  3. 左值引用就是對一個左值進行引用的類型。右值引用就是對一個右值進行引用的類型,事實上,由于右值通常不具有名字,我們也只能通過引用的方式找到它的存在。右值引用和左值引用都是屬于引用類型。無論是聲明一個左值引用還是右值引用,都必須立即進行初始化。而其原因可以理解為是引用類型本身自己并不擁有所綁定對象的內存,只是該對象的一個別名。左值引用是具名變量值的別名,而右值引用則是不具名(匿名)變量的別名。左值引用通常也不能綁定到右值,但常量左值引用是個“萬能”的引用類型。它可以接受非常量左值、常量左值、右值對其進行初始化。不過常量左值所引用的右值在它的“余生”中只能是只讀的。相對地,非常量左值只能接受非常量左值對其進行初始化。
  4. 右值值引用通常不能綁定到任何的左值,要想綁定一個左值到右值引用,通常需要std::move()將左值強制轉換為右值。

?

移動構造函數

  1. 我們用對象a初始化對象b,后對象a我們就不在使用了,但是對象a的空間還在呀(在析構之前),既然拷貝構造函數,實際上就是把a對象的內容復制一份到b中,那么為什么我們不能直接使用a的空間呢?這樣就避免了新的空間的分配,大大降低了構造的成本。這就是移動構造函數設計的初衷;
  2. 拷貝構造函數中,對于指針,我們一定要采用深層復制,而移動構造函數中,對于指針,我們采用淺層復制。淺層復制之所以危險,是因為兩個指針共同指向一片內存空間,若第一個指針將其釋放,另一個指針的指向就不合法了。所以我們只要避免第一個指針釋放空間就可以了。避免的方法就是將第一個指針(比如a->value)置為NULL,這樣在調用析構函數的時候,由于有判斷是否為NULL的語句,所以析構a的時候并不會回收a->value指向的空間;
  3. 移動構造函數的參數和拷貝構造函數不同,拷貝構造函數的參數是一個左值引用,但是移動構造函數的初值是一個右值引用。意味著,移動構造函數的參數是一個右值或者將亡值的引用。也就是說,只用用一個右值,或者將亡值初始化另一個對象的時候,才會調用移動構造函數。而那個move語句,就是將一個左值變成一個將亡值。

?

C語言的編譯鏈接過程?

源代碼-->預處理-->編譯-->優化-->匯編-->鏈接-->可執行文件

  • 預處理

讀取c源程序,對其中的偽指令(以#開頭的指令)和特殊符號進行處理。包括宏定義替換、條件編譯指令、頭文件包含指令、特殊符號。?預編譯程序所完成的基本上是對源程序的“替代”工作。經過此種替代,生成一個沒有宏定義、沒有條件編譯指令、沒有特殊符號的輸出文件。.i預處理后的c文件,.ii預處理后的C++文件。

  • 編譯階段

編譯程序所要作得工作就是通過詞法分析和語法分析,在確認所有的指令都符合語法規則之后,將其翻譯成等價的中間代碼表示或匯編代碼。.s文件

  • 匯編過程

匯編過程實際上指把匯編語言代碼翻譯成目標機器指令的過程。對于被翻譯系統處理的每一個C語言源程序,都將最終經過這一處理而得到相應的目標文件。目標文件中所存放的也就是與源程序等效的目標的機器語言代碼。.o目標文件

  • 鏈接階段

鏈接程序的主要工作就是將有關的目標文件彼此相連接,也即將在一個文件中引用的符號同該符號在另外一個文件中的定義連接起來,使得所有的這些目標文件成為一個能夠誒操作系統裝入執行的統一整體。

vector與list的區別與應用?怎么找某vector或者list的倒數第二個元素

  1. vector數據結構
    vector和數組類似,擁有一段連續的內存空間,并且起始地址不變。因此能高效的進行隨機存取,時間復雜度為o(1);但因為內存空間是連續的,所以在進行插入和刪除操作時,會造成內存塊的拷貝,時間復雜度為o(n)。另外,當數組中內存空間不夠時,會重新申請一塊內存空間并進行內存拷貝。連續存儲結構:vector是可以實現動態增長的對象數組,支持對數組高效率的訪問和在數組尾端的刪除和插入操作,在中間和頭部刪除和插入相對不易,需要挪動大量的數據。它與數組最大的區別就是vector不需程序員自己去考慮容量問題,庫里面本身已經實現了容量的動態增長,而數組需要程序員手動寫入擴容函數進形擴容。
  2. list數據結構
    list是由雙向鏈表實現的,因此內存空間是不連續的。只能通過指針訪問數據,所以list的隨機存取非常沒有效率,時間復雜度為o(n);但由于鏈表的特點,能高效地進行插入和刪除。非連續存儲結構:list是一個雙鏈表結構,支持對鏈表的雙向遍歷。每個節點包括三個信息:元素本身,指向前一個元素的節點(prev)和指向下一個元素的節點(next)。因此list可以高效率的對數據元素任意位置進行訪問和插入刪除等操作。由于涉及對額外指針的維護,所以開銷比較大。

區別:

vector的隨機訪問效率高,但在插入和刪除時(不包括尾部)需要挪動數據,不易操作。list的訪問要遍歷整個鏈表,它的隨機訪問效率低。但對數據的插入和刪除操作等都比較方便,改變指針的指向即可。list是單向的,vector是雙向的。vector中的迭代器在使用后就失效了,而list的迭代器在使用之后還可以繼續使用。?

int?mySize = vec.size();vec.at(mySize -2);

list不提供隨機訪問,所以不能用下標直接訪問到某個位置的元素,要訪問list里的元素只能遍歷,不過你要是只需要訪問list的最后N個元素的話,可以用反向迭代器來遍歷:

???????STL vector的實現,刪除其中的元素,迭代器如何變化?為什么是兩倍擴容?釋放空間?

size()函數返回的是已用空間大小,capacity()返回的是總空間大小,capacity()-size()則是剩余的可用空間大小。當size()和capacity()相等,說明vector目前的空間已被用完,如果再添加新元素,則會引起vector空間的動態增長。

由于動態增長會引起重新分配內存空間、拷貝原空間、釋放原空間,這些過程會降低程序效率。因此,可以使用reserve(n)預先分配一塊較大的指定大小的內存空間,這樣當指定大小的內存空間未使用完時,是不會重新分配內存空間的,這樣便提升了效率。只有當n>capacity()時,調用reserve(n)才會改變vector容量。

?resize()成員函數只改變元素的數目,不改變vector的容量。

1. 空的vector對象,size()和capacity()都為0

2. 當空間大小不足時,新分配的空間大小為原空間大小的2倍。

3. 使用reserve()預先分配一塊內存后,在空間未滿的情況下,不會引起重新分配,從而提升了效率。

4. 當reserve()分配的空間比原空間小時,是不會引起重新分配的。

5. resize()函數只改變容器的元素數目,未改變容器大小。

6. 用reserve(size_type)只是擴大capacity值,這些內存空間可能還是“野”的,如果此時使用“[ ]”來訪問,則可能會越界。而resize(size_type new_size)會真正使容器具有new_size個對象。

?

?

  1. 不同的編譯器,vector有不同的擴容大小。在vs下是1.5倍,在GCC下是2倍;
  2. 空間和時間的權衡。簡單來說, 空間分配的多,平攤時間復雜度低,但浪費空間也多。
  3. 使用k=2增長因子的問題在于,每次擴展的新尺寸必然剛好大于之前分配的總和,也就是說,之前分配的內存空間不可能被使用。這樣對內存不友好。最好把增長因子設為(1,2)
  4. 對比可以發現采用采用成倍方式擴容,可以保證常數的時間復雜度,而增加指定大小的容量只能達到O(n)的時間復雜度,因此,使用成倍的方式擴容。

如何釋放空間:

由于vector的內存占用空間只增不減,比如你首先分配了10,000個字節,然后erase掉后面9,999個,留下一個有效元素,但是內存占用仍為10,000個。所有內存空間是在vector析構時候才能被系統回收。empty()用來檢測容器是否為空的,clear()可以清空所有元素。但是即使clear(),vector所占用的內存空間依然如故,無法保證內存的回收。

如果需要空間動態縮小,可以考慮使用deque。如果vector,可以用swap()來幫助你釋放內存。

vector(Vec).swap(Vec);
將Vec的內存空洞清除;
vector().swap(Vec);
清空Vec的內存;

?

容器內部刪除一個元素???????

順序容器???????

erase迭代器不僅使所指向被刪除的迭代器失效,而且使被刪元素之后的所有迭代器失效(list除外),所以不能使用erase(it++)的方式,但是erase的返回值是下一個有效迭代器;

It = c.erase(it);

關聯容器

erase迭代器只是被刪除元素的迭代器失效,但是返回值是void,所以要采用erase(it++)的方式刪除迭代器;

c.erase(it++)

???????STL迭代器如何實現

  1. 迭代器是一種抽象的設計理念,通過迭代器可以在不了解容器內部原理的情況下遍歷容器,除此之外,STL中迭代器一個最重要的作用就是作為容器與STL算法的粘合劑。
  2. 迭代器的作用就是提供一個遍歷容器內部所有元素的接口,因此迭代器內部必須保存一個與容器相關聯的指針,然后重載各種運算操作來遍歷,其中最重要的是*運算符與->運算符,以及++、--等可能需要重載的運算符重載。這和C++中的智能指針很像,智能指針也是將一個指針封裝,然后通過引用計數或是其他方法完成自動釋放內存的功能。
  3. 最常用的迭代器的相應型別有五種:value type、difference type、pointer、reference、iterator catagoly;

???????set與hash_set的區別

  1. set底層是以RB-Tree實現,hash_set底層是以hash_table實現的;
  2. RB-Tree有自動排序功能,而hash_table不具有自動排序功能;
  3. set和hash_set元素的鍵值就是實值;
  4. hash_table有一些無法處理的型別;

???????hashmap與map的區別

  1. 底層實現不同;
  2. map具有自動排序的功能,hash_map不具有自動排序的功能;
  3. hashtable有一些無法處理的型別;

map、set是怎么實現的,紅黑樹是怎么能夠同時實現這兩種容器? 為什么使用紅黑樹?

  1. 他們的底層都是以紅黑樹的結構實現,因此插入刪除等操作都在O(logn)時間內完成,因此可以完成高效的插入刪除;
  2. 在這里我們定義了一個模版參數,如果它是key那么它就是set,如果它是map,那么它就是map;底層是紅黑樹,實現map的紅黑樹的節點數據類型是key+value,而實現set的節點數據類型是value
  3. 因為map和set要求是自動排序的,紅黑樹能夠實現這一功能,而且時間復雜度比較低。
  4. ?

如何在共享內存上使用stl標準庫?

  1. 想像一下把STL容器,例如map, vector, list等等,放入共享內存中,IPC一旦有了這些強大的通用數據結構做輔助,無疑進程間通信的能力一下子強大了很多。我們沒必要再為共享內存設計其他額外的數據結構,另外,STL的高度可擴展性將為IPC所驅使。STL容器被良好的封裝,默認情況下有它們自己的內存管理方案。當一個元素被插入到一個STL列表(list)中時,列表容器自動為其分配內存,保存數據。考慮到要將STL容器放到共享內存中,而容器卻自己在堆上分配內存。一個最笨拙的辦法是在堆上構造STL容器,然后把容器復制到共享內存,并且確保所有容器的內部分配的內存指向共享內存中的相應區域,這基本是個不可能完成的任務。

?

  1. 假設進程A在共享內存中放入了數個容器,進程B如何找到這些容器呢?一個方法就是進程A把容器放在共享內存中的確定地址上(fixed offsets),則進程B可以從該已知地址上獲取容器。另外一個改進點的辦法是,進程A先在共享內存某塊確定地址上放置一個map容器,然后進程A再創建其他容器,然后給其取個名字和地址一并保存到這個map容器里。進程B知道如何獲取該保存了地址映射的map容器,然后同樣再根據名字取得其他容器的地址。

map插入方式有幾種?

  1. 用insert函數插入pair數據,

mapStudent.insert(pair<int,?string>(1,?"student_one"));??

  1. 用insert函數插入value_type數據

mapStudent.insert(map<int,?string>::value_type?(1,?"student_one"));

  1. 在insert函數中使用make_pair()函數

mapStudent.insert(make_pair(1,?"student_one"));??

  1. 用數組方式插入數據

mapStudent[1]?=?"student_one";??

???????STL中unordered_map(hash_map)和map的區別,hash_map如何解決沖突以及擴容

  1. unordered_map和map類似,都是存儲的key-value的值,可以通過key快速索引到value。不同的是unordered_map不會根據key的大小進行排序,
  2. 存儲時是根據key的hash值判斷元素是否相同,即unordered_map內部元素是無序的,而map中的元素是按照二叉搜索樹存儲,進行中序遍歷會得到有序遍歷。
  3. 所以使用時map的key需要定義operator<。而unordered_map需要定義hash_value函數并且重載operator==。但是很多系統內置的數據類型都自帶這些,
  4. 那么如果是自定義類型,那么就需要自己重載operator<或者hash_value()了。
  5. 如果需要內部元素自動排序,使用map,不需要排序使用unordered_map
  6. unordered_map的底層實現是hash_table;
  7. hash_map底層使用的是hash_table,而hash_table使用的開鏈法進行沖突避免,所有hash_map采用開鏈法進行沖突解決。
  8. 什么時候擴容:當向容器添加元素的時候,會判斷當前容器的元素個數,如果大于等于閾值---即當前數組的長度乘以加載因子的值的時候,就要自動擴容啦。
  9. 擴容(resize)就是重新計算容量,向HashMap對象里不停的添加元素,而HashMap對象內部的數組無法裝載更多的元素時,對象就需要擴大數組的長度,以便能裝入更多的元素。

vector越界訪問下標,map越界訪問下標?vector刪除元素時會不會釋放空間?

  1. 通過下標訪問vector中的元素時不會做邊界檢查,即便下標越界。也就是說,下標與first迭代器相加的結果超過了finish迭代器的位置,程序也不會報錯,而是返回這個地址中存儲的值。如果想在訪問vector中的元素時首先進行邊界檢查,可以使用vector中的at函數。通過使用at函數不但可以通過下標訪問vector中的元素,而且在at函數內部會對下標進行邊界檢查。
  2. map的下標運算符[]的作用是:將key作為下標去執行查找,并返回相應的值;如果不存在這個key,就將一個具有該key和value的某人值插入這個map。
  3. erase()函數,只能刪除內容,不能改變容量大小; erase成員函數,它刪除了itVect迭代器指向的元素,并且返回要被刪除的itVect之后的迭代器,迭代器相當于一個智能指針;clear()函數,只能清空內容,不能改變容量大小;如果要想在刪除內容的同時釋放內存,那么你可以選擇deque容器。

map[]與find的區別?

  1. map的下標運算符[]的作用是:將關鍵碼作為下標去執行查找,并返回對應的值;如果不存在這個關鍵碼,就將一個具有該關鍵碼和值類型的默認值的項插入這個map。
  2. map的find函數:用關鍵碼執行查找,找到了返回該位置的迭代器;如果不存在這個關鍵碼,就返回尾迭代器。

STL中list與queue之間的區別

  1. list不再能夠像vector一樣以普通指針作為迭代器,因為其節點不保證在存儲空間中連續存在;
  2. list插入操作和結合才做都不會造成原有的list迭代器失效;
  3. list不僅是一個雙向鏈表,而且還是一個環狀雙向鏈表,所以它只需要一個指針;
  4. list不像vector那樣有可能在空間不足時做重新配置、數據移動的操作,所以插入前的所有迭代器在插入操作之后都仍然有效;
  5. deque是一種雙向開口的連續線性空間,所謂雙向開口,意思是可以在頭尾兩端分別做元素的插入和刪除操作;可以在頭尾兩端分別做元素的插入和刪除操作;
  6. deque和vector最大的差異,一在于deque允許常數時間內對起頭端進行元素的插入或移除操作,二在于deque沒有所謂容量概念,因為它是動態地以分段連續空間組合而成,隨時可以增加一段新的空間并鏈接起來,deque沒有所謂的空間保留功能。

STL中的allocator,deallocator

  1. 第一級配置器直接使用malloc()、free()和relloc(),第二級配置器視情況采用不同的策略:當配置區塊超過128bytes時,視之為足夠大,便調用第一級配置器;當配置器區塊小于128bytes時,為了降低額外負擔,使用復雜的內存池整理方式,而不再用一級配置器;
  2. 第二級配置器主動將任何小額區塊的內存需求量上調至8的倍數,并維護16個free-list,各自管理大小為8~128bytes的小額區塊;
  3. 空間配置函數allocate(),首先判斷區塊大小,大于128就直接調用第一級配置器,小于128時就檢查對應的free-list。如果free-list之內有可用區塊,就直接拿來用,如果沒有可用區塊,就將區塊大小調整至8的倍數,然后調用refill(),為free-list重新分配空間;
  4. 空間釋放函數deallocate(),該函數首先判斷區塊大小,大于128bytes時,直接調用一級配置器,小于128bytes就找到對應的free-list然后釋放內存。
  5. ?

???????STL中hash_map擴容發生什么??

  1. hash table表格內的元素稱為桶(bucket),而由桶所鏈接的元素稱為節點(node),其中存入桶元素的容器為stl本身很重要的一種序列式容器——vector容器。之所以選擇vector為存放桶元素的基礎容器,主要是因為vector容器本身具有動態擴容能力,無需人工干預。
  2. 向前操作:首先嘗試從目前所指的節點出發,前進一個位置(節點),由于節點被安置于list內,所以利用節點的next指針即可輕易完成前進操作,如果目前正巧是list的尾端,就跳至下一個bucket身上,那正是指向下一個list的頭部節點。

?

???????map如何創建?

1.vector ?? ??底層數據結構為數組 ,支持快速隨機訪問

2.list ??? ??? ???底層數據結構為雙向鏈表,支持快速增刪

3.deque ??? ??底層數據結構為一個中央控制器和多個緩沖區,詳細見STL源碼剖析P146,支持首尾(中間不能)快速增刪,也支持隨機訪問

deque是一個雙端隊列(double-ended queue),也是在堆中保存內容的.它的保存形式如下:

[堆1] -->?[堆2] -->[堆3] --> ...

每個堆保存好幾個元素,然后堆和堆之間有指針指向,看起來像是list和vector的結合品.

4.stack? ??? ??底層一般用list或deque實現,封閉頭部即可,不用vector的原因應該是容量大小有限制,擴容耗時

5.queue?? ??底層一般用list或deque實現,封閉頭部即可,不用vector的原因應該是容量大小有限制,擴容耗時(stack和queue其實是適配器,而不叫容器,因為是對容器的再封裝)

6.priority_queue?? ??的底層數據結構一般為vector為底層容器,堆heap為處理規則來管理底層容器實現

7.set ? ? ??? ??? ? ? ??底層數據結構為紅黑樹,有序,不重復

8.multiset ?? ? ? ?底層數據結構為紅黑樹,有序,可重復?

9.map ? ? ??? ??? ? ?底層數據結構為紅黑樹,有序,不重復

10.multimap? ??底層數據結構為紅黑樹,有序,可重復

11.hash_set?? ??底層數據結構為hash表,無序,不重復

12.hash_multiset 底層數據結構為hash表,無序,可重復?

13.hash_map ? ?底層數據結構為hash表,無序,不重復

14.hash_multimap 底層數據結構為hash表,無序,可重復?

?

???????vector的增加刪除都是怎么做的?為什么是1.5倍?

  1. 新增元素:vector通過一個連續的數組存放元素,如果集合已滿,在新增數據的時候,就要分配一塊更大的內存,將原來的數據復制過來,釋放之前的內存,在插入新增的元素;
  2. 對vector的任何操作,一旦引起空間重新配置,指向原vector的所有迭代器就都失效了 ;
  3. 初始時刻vector的capacity為0,塞入第一個元素后capacity增加為1;
  4. 不同的編譯器實現的擴容方式不一樣,VS2015中以1.5倍擴容,GCC以2倍擴容。

?

對比可以發現采用采用成倍方式擴容,可以保證常數的時間復雜度,而增加指定大小的容量只能達到O(n)的時間復雜度,因此,使用成倍的方式擴容。

  1. 考慮可能產生的堆空間浪費,成倍增長倍數不能太大,使用較為廣泛的擴容方式有兩種,以2二倍的方式擴容,或者以1.5倍的方式擴容。
  2. 以2倍的方式擴容,導致下一次申請的內存必然大于之前分配內存的總和,導致之前分配的內存不能再被使用,所以最好倍增長因子設置為(1,2)之間:?
  3. 向量容器vector的成員函數pop_back()可以刪除最后一個元素.
  4. 而函數erase()可以刪除由一個iterator指出的元素,也可以刪除一個指定范圍的元素。
  5. 還可以采用通用算法remove()來刪除vector容器中的元素.
  6. 不同的是:采用remove一般情況下不會改變容器的大小,而pop_back()與erase()等成員函數會改變容器的大小。

什么是函數指針?

函數指針指向的是特殊的數據類型,函數的類型是由其返回的數據類型和其參數列表共同決定的,而函數的名稱則不是其類型的一部分。

一個具體函數的名字,如果后面不跟調用符號(即括號),則該名字就是該函數的指針(注意:大部分情況下,可以這么認為,但這種說法并不很嚴格)。

函數指針的聲明方法

int?(*pf)(const?int&,?const?int&); (1)

上面的pf就是一個函數指針,指向所有返回類型為int,并帶有兩個const int&參數的函數。注意*pf兩邊的括號是必須的,否則上面的定義就變成了:

int?*pf(const?int&,?const?int&); (2)

而這聲明了一個函數pf,其返回類型為int *, 帶有兩個const int&參數。

為什么有函數指針

函數與數據項相似,函數也有地址。我們希望在同一個函數中通過使用相同的形參在不同的時間使用產生不同的效果。

  1. 一個函數名就是一個指針,它指向函數的代碼。一個函數地址是該函數的進入點,也就是調用函數的地址。函數的調用可以通過函數名,也可以通過指向函數的指針來調用。函數指針還允許將函數作為變元傳遞給其他函數;
  2. 兩種方法賦值:

指針名 = 函數名; ?指針名 = &函數名

???????說說你對c和c++的看法,c和c++的區別?

  1. 第一點就應該想到C是面向過程的語言,而C++是面向對象的語言,一般簡歷上第一條都是熟悉C/C++基本語法,了解C++面向對象思想,那么,請問什么是面向對象?
  2. C和C++動態管理內存的方法不一樣,C是使用malloc/free函數,而C++除此之外還有new/delete關鍵字;(關于malooc/free與new/delete的不同又可以說一大堆,最后的擴展_1部分列出十大區別);
  3. 接下來就不得不談到C中的struct和C++的類,C++的類是C所沒有的,但是C中的struct是可以在C++中正常使用的,并且C++對struct進行了進一步的擴展,使struct在C++中可以和class一樣當做類使用,而唯一和class不同的地方在于struct的成員默認訪問修飾符是public,而class默認的是private;
  4. C++支持函數重載,而C不支持函數重載,而C++支持重載的依仗就在于C++的名字修飾與C不同,例如在C++中函數int fun(int ,int)經過名字修飾之后變為 _fun_int_int ,而C是?
    _fun,一般是這樣的,所以C++才會支持不同的參數調用不同的函數;
  5. C++中有引用,而C沒有;這樣就不得不提一下引用和指針的區別(文后擴展_2);
  6. 當然還有C++全部變量的默認鏈接屬性是外鏈接,而C是內連接;
  7. C 中用const修飾的變量不可以用在定義數組時的大小,但是C++用const修飾的變量可以(如果不進行&,解引用的操作的話,是存放在符號表的,不開辟內存);
  8. 當然還有局部變量的聲明規則不同,多態,C++特有輸入輸出流之類的,很多,下面就不再列出來了; “`

c/c++的內存分配,詳細說一下棧、堆、靜態存儲區?

1、棧區(stack)—??由編譯器自動分配釋放,存放函數的參數值,局部變量的值等

其操作方式類似于數據結構中的棧。??
2、堆區(heap)?—??一般由程序員分配釋放,若程序員不釋放,程序結束時可能由OS(操作系統)回收。注意它與數據結構中的堆是兩回事,分配方式倒是類似于鏈表。??
3、全局區(靜態區)(static)—,全局變量和靜態變量的存儲是放在一塊的,初始化的全局變量和靜態變量在一塊區域,未初始化的全局變量和未初始化的靜態變量在相鄰的另一塊區域。程序結束后由系統釋放。??
4、文字常量區??—常量字符串就是放在這里的。程序結束后由系統釋放。
5、程序代碼區??? —存放函數體的二進制代碼。??

???????堆與棧的區別?

  1. 管理方式對于棧來講,是由編譯器自動管理,無需我們手工控制;對于堆來說,釋放工作由程序員控制,容易產生memory leak。?
  2. 空間大小一般來講在32位系統下,堆內存可以達到4G的空間,從這個角度來看堆內存幾乎是沒有什么限制的。但是對于棧來講,一般都是有一定的空間大小的,例如,在VC6下面,默認的棧空間大小是1M(好像是,記不清楚了)。當然,我們可以修改:?打開工程,依次操作菜單如下:Project->Setting->Link,在Category 中選中Output,然后在Reserve中設定堆棧的最大值和commit。 注意:reserve最小值為4Byte;commit是保留在虛擬內存的頁文件里面,它設置的較大會使棧開辟較大的值,可能增加內存的開銷和啟動時間。?
  3. 碎片問題對于堆來講,頻繁的new/delete勢必會造成內存空間的不連續,從而造成大量的碎片,使程序效率降低。對于棧來講,則不會存在這個問題,因為棧是先進后出的隊列,他們是如此的一一對應,以至于永遠都不可能有一個內存塊從棧中間彈出,在他彈出之前,在他上面的后進的棧內容已經被彈出,詳細的可以參考數據結構,這里我們就不再一一討論了。?
  4. 生長方向對于堆來講,生長方向是向上的,也就是向著內存地址增加的方向;對于棧來講,它的生長方向是向下的,是向著內存地址減小的方向增長。?
  5. 分配方式堆都是動態分配的,沒有靜態分配的堆。棧有2種分配方式:靜態分配和動態分配。靜態分配是編譯器完成的,比如局部變量的分配。動態分配由alloca函數進行分配,但是棧的動態分配和堆是不同的,它的動態分配是由編譯器進行釋放,無需我們手工實現。?
  6. 分配效率棧是機器系統提供的數據結構,計算機會在底層對棧提供支持:分配專門的寄存器存放棧的地址,壓棧出棧都有專門的指令執行,這就決定了棧的效率比較高。堆則是C/C++函數庫提供的,它的機制是很復雜的,例如為了分配一塊內存,庫函數會按照一定的算法(具體的算法可以參考數據結構/操作系統)在堆內存中搜索可用的足夠大小的空間,如果沒有足夠大小的空間(可能是由于內存碎片太多),就有可能調用系統功能去增加程序數據段的內存空間,這樣就有機會分到足夠大小的內存,然后進行返回。顯然,堆的效率比棧要低得多。

???????野指針是什么?如何檢測內存泄漏?

  1. 野指針:指向內存被釋放的內存或者沒有訪問權限的內存的指針。
  2. “野指針”的成因主要有3種:
  • 指針變量沒有被初始化。任何指針變量剛被創建時不會自動成為NULL指針,它的缺省值是隨機的,它會亂指一氣。所以,指針變量在創建的同時應當被初始化,要么將指針設置為NULL,要么讓它指向合法的內存。例如
    char *p = NULL;
    char *str = new char(100);
  • 指針p被free或者delete之后,沒有置為NULL;
  • 指針操作超越了變量的作用范圍。

如何避免野指針:

  • 對指針進行初始化

①將指針初始化為NULL。

char * ? p ?= NULL;

②用malloc分配內存

char * p = (char * )malloc(sizeof(char));

③用已有合法的可訪問的內存地址對指針初始化

char num[ 30] = {0};

char *p = num;

  • 指針用完后釋放內存,將指針賦NULL。

delete(p);

p = NULL;

???????懸空指針和野指針有什么區別?

  1. 野指針:野指針指,訪問一個已刪除或訪問受限的內存區域的指針,野指針不能判斷是否為NULL來避免。指針沒有初始化,釋放后沒有置空,越界
  2. 懸空指針:一個指針的指向對象已被刪除,那么就成了懸空指針。野指針是那些未初始化的指針。
      1. 內存泄漏
  3. 內存泄漏

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

  • 后果

只發生一次小的內存泄漏可能不被注意,但泄漏大量內存的程序將會出現各種證照:性能下降到內存逐漸用完,導致另一個程序失敗;

  • 如何排除

使用工具軟件BoundsChecker,BoundsChecker是一個運行時錯誤檢測工具,它主要定位程序運行時期發生的各種錯誤;

調試運行DEBUG版程序,運用以下技術:CRT(C run-time libraries)、運行時函數調用堆棧、內存泄漏時提示的內存分配序號(集成開發環境OUTPUT窗口),綜合分析內存泄漏的原因,排除內存泄漏。

  • 解決方法

智能指針。

檢查、定位內存泄漏

檢查方法:在main函數最后面一行,加上一句_CrtDumpMemoryLeaks()。調試程序,自然關閉程序讓其退出,查看輸出:

輸出這樣的格式{453}normal block at 0x02432CA8,868 bytes long

被{}包圍的453就是我們需要的內存泄漏定位值,868 bytes long就是說這個地方有868比特內存沒有釋放。

定位代碼位置

在main函數第一行加上_CrtSetBreakAlloc(453);意思就是在申請453這塊內存的位置中斷。然后調試程序,程序中斷了,查看調用堆棧。加上頭文件#include <crtdbg.h>

???????new和malloc的區別?

  1. new/delete是C++關鍵字,需要編譯器支持。malloc/free是庫函數,需要頭文件支持;
  2. 使用new操作符申請內存分配時無須指定內存塊的大小,編譯器會根據類型信息自行計算。而malloc則需要顯式地指出所需內存的尺寸。
  3. new操作符內存分配成功時,返回的是對象類型的指針,類型嚴格與對象匹配,無須進行類型轉換,故new是符合類型安全性的操作符。而malloc內存分配成功則是返回void * ,需要通過強制類型轉換將void*指針轉換成我們需要的類型。
  4. new內存分配失敗時,會拋出bac_alloc異常。malloc分配內存失敗時返回NULL。
  5. new會先調用operator new函數,申請足夠的內存(通常底層使用malloc實現)。然后調用類型的構造函數,初始化成員變量,最后返回自定義類型指針。delete先調用析構函數,然后調用operator delete函數釋放內存(通常底層使用free實現)。malloc/free是庫函數,只能動態的申請和釋放內存,無法強制要求其做自定義類型對象構造和析構工作。

???????delete p;與delete[]p,allocator

  1. 動態數組管理new一個數組時,[]中必須是一個整數,但是不一定是常量整數,普通數組必須是一個常量整數;
  2. new動態數組返回的并不是數組類型,而是一個元素類型的指針;
  3. delete[]時,數組中的元素按逆序的順序進行銷毀;
  4. new在內存分配上面有一些局限性,new的機制是將內存分配和對象構造組合在一起,同樣的,delete也是將對象析構和內存釋放組合在一起的。allocator將這兩部分分開進行,allocator申請一部分內存,不進行初始化對象,只有當需要的時候才進行初始化操作。

???????new和delete的實現原理, delete是如何知道釋放內存的大小的額?

  • new簡單類型直接調用operator new分配內存;而對于復雜結構,先調用operator new分配內存,然后在分配的內存上調用構造函數;對于簡單類型,new[]計算好大小后調用operator new;對于復雜數據結構,new[]先調用operator new[]分配內存,然后在p的前四個字節寫入數組大小n,然后調用n次構造函數,針對復雜類型,new[]會額外存儲數組大小;
  1. new表達式調用一個名為operator? new(operator new[])函數,分配一塊足夠大的、原始的、未命名的內存空間;
  2. 編譯器運行相應的構造函數以構造這些對象,并為其傳入初始值;
  3. 對象被分配了空間并構造完成,返回一個指向該對象的指針。
  • delete簡單數據類型默認只是調用free函數;復雜數據類型先調用析構函數再調用operator delete;針對簡單類型,delete和delete[]等同。假設指針p指向new[]分配的內存。因為要4字節存儲數組大小,實際分配的內存地址為[p-4],系統記錄的也是這個地址。delete[]實際釋放的就是p-4指向的內存。而delete會直接釋放p指向的內存,這個內存根本沒有被系統記錄,所以會崩潰。
  • 需要在 new [] 一個對象數組時,需要保存數組的維度,C++ 的做法是在分配數組空間時多分配了 4 個字節的大小,專門保存數組的大小,在 delete [] 時就可以取出這個保存的數,就知道了需要調用析構函數多少次了。

???????malloc申請的存儲空間能用delete釋放嗎

不能,malloc /free主要為了兼容C,new和delete 完全可以取代malloc /free的。malloc /free的操作對象都是必須明確大小的。而且不能用在動態類上。new 和delete會自動進行類型檢查和大小,malloc/free不能執行構造函數與析構函數,所以動態對象它是不行的。當然從理論上說使用malloc申請的內存是可以通過delete釋放的。不過一般不這樣寫的。而且也不能保證每個C++的運行時都能正常。

???????malloc與free的實現原理?

  1. 在標準C庫中,提供了malloc/free函數分配釋放內存,這兩個函數底層是由brk、mmap、,munmap這些系統調用實現的;
  2. brk是將數據段(.data)的最高地址指針_edata往高地址推,mmap是在進程的虛擬地址空間中(堆和棧中間,稱為文件映射區域的地方)找一塊空閑的虛擬內存。這兩種方式分配的都是虛擬內存,沒有分配物理內存。在第一次訪問已分配的虛擬地址空間的時候,發生缺頁中斷,操作系統負責分配物理內存,然后建立虛擬內存和物理內存之間的映射關系;
  3. malloc小于128k的內存,使用brk分配內存,將_edata往高地址推;malloc大于128k的內存,使用mmap分配內存,在堆和棧之間找一塊空閑內存分配;brk分配的內存需要等到高地址內存釋放以后才能釋放,而mmap分配的內存可以單獨釋放。當最高地址空間的空閑內存超過128K(可由M_TRIM_THRESHOLD選項調節)時,執行內存緊縮操作(trim)。在上一個步驟free的時候,發現最高地址空閑內存超過128K,于是內存緊縮。
  4. malloc是從堆里面申請內存,也就是說函數返回的指針是指向堆里面的一塊內存。操作系統中有一個記錄空閑內存地址的鏈表。當操作系統收到程序的申請時,就會遍歷該鏈表,然后就尋找第一個空間大于所申請空間的堆結點,然后就將該結點從空閑結點鏈表中刪除,并將該結點的空間分配給程序。

???????malloc、realloc、calloc的區別

  • malloc函數

void* malloc(unsigned int num_size);

int *p = malloc(20*sizeof(int));申請20個int類型的空間;

  • calloc函數

void* calloc(size_t n,size_t size);

int *p = calloc(20, sizeof(int));

省去了人為空間計算;malloc申請的空間的值是隨機初始化的,calloc申請的空間的值是初始化為0的;

  • realloc函數

void realloc(void *p, size_t new_size);

給動態分配的空間分配額外的空間,用于擴充容量。

???????__stdcall和__cdecl的區別?

  • __stdcall

__stdcall是函數恢復堆棧,只有在函數代碼的結尾出現一次恢復堆棧的代碼;在編譯時就規定了參數個數,無法實現不定個數的參數調用;

  • __cdecl

__cdecl是調用者恢復堆棧,假設有100個函數調用函數a,那么內存中就有100端恢復堆棧的代碼;可以不定參數個數;每一個調用它的函數都包含清空堆棧的代碼,所以產生的可執行文件大小會比調用__stacall函數大。

???????使用智能指針管理內存資源,RAII

  1. RAII全稱是“Resource Acquisition is Initialization”,直譯過來是“資源獲取即初始化”,也就是說在構造函數中申請分配資源,在析構函數中釋放資源。因為C++的語言機制保證了,當一個對象創建的時候,自動調用構造函數,當對象超出作用域的時候會自動調用析構函數。所以,在RAII的指導下,我們應該使用類來管理資源,將資源和對象的生命周期綁定。
  2. 智能指針(std::shared_ptr和std::unique_ptr)即RAII最具代表的實現,使用智能指針,可以實現自動的內存管理,再也不需要擔心忘記delete造成的內存泄漏。毫不夸張的來講,有了智能指針,代碼中幾乎不需要再出現delete了。

手寫實現智能指針類

  1. 智能指針是一個數據類型,一般用模板實現,模擬指針行為的同時還提供自動垃圾回收機制。它會自動記錄SmartPointer<T*>對象的引用計數,一旦T類型對象的引用計數為0,就釋放該對象。除了指針對象外,我們還需要一個引用計數的指針設定對象的值,并將引用計數計為1,需要一個構造函數。新增對象還需要一個構造函數,析構函數負責引用計數減少和釋放內存。通過覆寫賦值運算符,才能將一個舊的智能指針賦值給另一個指針,同時舊的引用計數減1,新的引用計數加1
  2. 一個構造函數、拷貝構造函數、復制構造函數、析構函數、移走函數;

???????內存對齊?位域?

1、 分配內存的順序是按照聲明的順序。

2、 每個變量相對于起始位置的偏移量必須是該變量類型大小的整數倍,不是整數倍空出內存,直到偏移量是整數倍為止。

3、 最后整個結構體的大小必須是里面變量類型最大值的整數倍。

?

添加了#pragma pack(n)后規則就變成了下面這樣:

1、 偏移量要是n和當前變量大小中較小值的整數倍

2、 整體大小要是n和最大變量大小中較小值的整數倍

3、 n值必須為1,2,4,8…,為其他值時就按照默認的分配規則

???????結構體變量比較是否相等

  1. 重載了 “==” 操作符
struct foo {int a;int b;bool operator==(const foo& rhs) // 操作運算符重載{return( a == rhs.a) && (b == rhs.b);}};
  1. 元素的話,一個個比;
  2. 指針直接比較,如果保存的是同一個實例地址,則(p1==p2)為真;

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/443958.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/443958.shtml
英文地址,請注明出處:http://en.pswp.cn/news/443958.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

當年,學姐把這份Java總結給我,讓我在22k的校招王者局亂殺

可以說&#xff0c;學姐給我的這份文檔真的把我的知識查漏補缺&#xff0c;面試問到了好多&#xff0c;值得收藏。 并發編程 一.Executor 為什么使用線程池&#xff1a;手動創建線程耗費性能&#xff0c;不利于管理。 首先創建線程池有兩種方式&#xff1a;使用Executors工廠…

十萬字cpp成神總結-看完月薪25k

最近會放出cpp成神之路的所有總結&#xff0c;大家感興趣的可以收藏一波。 歷史文章&#xff1a; 超硬核&#xff01;十萬字c題&#xff0c;讓你秒殺老師和面試官 位運算 若一個數m滿足 m 2^n;那么k%mk&(m-1) 為什么內存對齊 平臺原因(移植原因)不是所有的硬件平臺都能…

測試必經之路(探索性測試)

接下來&#xff0c;百萬年薪測試方面也會有專題哦。 測試計劃&#xff1a; 測試范圍、方法、資源、進度、風險 測試報告&#xff1a; 就是把測試的過程和結果寫成文檔&#xff0c;對發現的問題和缺陷進行分析。 一、探索性測試 評估測試用例的標準 1 測試用例對被測對象的…

超硬核萬字!web前端學霸筆記,學完就去找工作吧

近期應粉絲要求&#xff0c;出多個前端大總結&#xff0c;適合小白復習查閱 #第一章 Web基礎知識 Web開發基本概念 1、萬維網是一個由許多相互鏈接的超文本組成的系統&#xff0c;通過互聯網訪問。 2、web&#xff1a;worldwideweb&#xff0c;萬維網&#xff0c;簡稱web&…

金額轉換,阿拉伯數字的金額轉換成中國傳統的形式如:(¥1011)-(一千零一拾一元整)輸出。...

程序代碼如下&#xff1a; package cn.itcast.framework.interview;import java.text.NumberFormat; import java.util.HashMap;//金額轉換&#xff0c;阿拉伯數字的金額轉換成中國傳統的形式如&#xff1a;&#xff08;&#xffe5;1011&#xff09;&#xff0d;>&#xff…

大學四年自學進BAT,私下存的資源/工具/網站我全貢獻出來了

這些工具/網站是我橫掃BAT的重要一步&#xff0c;甚至是決定性的一步。以后會更簡歷書寫、面試筆試、大學學習、工具等文章。 大學四年&#xff0c;上課是不可能一直上課的&#xff0c;看課本也是不可能一直看課本的。 不是說老師教的不好&#xff0c;教材寫的不好&#xff0c…

我是CSDN最硬核作者,誰贊成,誰反對?

也許是現在&#xff0c;也許是未來&#xff0c;我是全網最硬核的作者&#xff0c;最值得愛學習愛編程的崽崽們關注的作者。 一、介紹自己 哈嘍大家好&#xff0c;我是兔老大&#xff0c;之前叫過兔兔兔兔兔兔、兔兔RabbitMQ等&#xff0c;反正都是兔子啦&#xff0c;自從大學…

當年,學姐總結奇安信18k常問面試題

她確實拿了18k&#xff0c;真人真事&#xff0c;也不是很高&#xff0c;我沒必要編。 黑色字為問題&#xff0c;紅色字為答案&#xff0c;空行為一個面試過程 自我介紹 家在哪&#xff0c;工作地 測試需要掌握啥 V模型W模型 最典型的V模型版本一般會在其開始部分對軟件開發…

最強阿里巴巴歷年經典面試題匯總:C++研發崗

這個系列計劃收集幾百份朋友和讀者的面經&#xff0c;作者合集方便查看&#xff0c;各位有面經屯著可以聯系我哦 本系列歷史文章&#xff1a; 關于我的那些面經——百度后端&#xff08;附答案&#xff09; 《關于我的那些面經》滴滴Java崗&#xff08;附答案&#xff09; 朋…

當年,兔子學姐靠這個面試小抄拿了個22k

本文順序是操作系統&#xff08;jvm&#xff09;、網絡、數據庫&#xff08;mysql/redis&#xff09;&#xff0c;都是當時兔子的學姐準備面試的時候總結的&#xff0c;學生面試基本不會跑出這個范圍&#xff0c;懂行的應該能看出來。 學姐原話&#xff1a;因為我本身的知識是A…

用JAVA SOCKET編程,讀服務器幾個字符,再寫入本地顯示

Server: package cn.itcast.framework.socket;import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket;//用JAVA SOCKET編程&#xff0c;讀服務器…

學姐,來挑戰字節最牛部門

字節&#xff08;分布式圖數據庫研發工程師&#xff09;真實面經&#xff0c;其實是個學長&#xff0c;但是同學們都叫他學姐&#xff0c;可能是因為帥到把女生都比下去了。 本系列歷史文章&#xff1a; 最強阿里巴巴歷年經典面試題匯總&#xff1a;C研發崗 關于我的那些面經…

學姐百度實習面經(輕松拿offer)

本系列歷史文章&#xff1a; 學姐&#xff0c;來挑戰字節最牛部門 最強阿里巴巴歷年經典面試題匯總&#xff1a;C研發崗 關于我的那些面經——百度后端&#xff08;附答案&#xff09; 《關于我的那些面經》滴滴Java崗&#xff08;附答案&#xff09; 朋友面神策數據庫&am…

阿里巴巴歷年經典面試題匯總:Java崗

這個系列計劃收集幾百份朋友和讀者的面經&#xff0c;作者合集方便查看&#xff0c;各位有面經屯著可以聯系我哦 本系列歷史文章&#xff1a; 學姐百度實習面經 學姐&#xff0c;來挑戰字節最牛部門 最強阿里巴巴歷年經典面試題匯總&#xff1a;C研發崗 關于我的那些面經—…

超經典,阿里巴巴歷年高頻面試題匯總:前端崗

這個系列計劃收集幾百份朋友和讀者的面經&#xff0c;作者合集方便查看&#xff0c;各位有面經屯著可以聯系我哦 本系列歷史文章&#xff1a; 阿里巴巴歷年經典面試題匯總&#xff1a;Java崗 學姐百度實習面經 學姐&#xff0c;來挑戰字節最牛部門 最強阿里巴巴歷年經典面試…

超經典,百度最愛考的安卓Android百題

這個系列計劃收集幾百份朋友和讀者的面經&#xff0c;作者合集方便查看&#xff0c;各位有面經屯著可以聯系我哦 本系列歷史文章&#xff1a; 超經典&#xff0c;阿里巴巴歷年高頻面試題匯總&#xff1a;前端崗 阿里巴巴歷年經典面試題匯總&#xff1a;Java崗 學姐百度實習面…

org.hibernate.LazyInitializationException: could not initialize proxy - no Session

今天在寫jbpm獲取流程變量的時候出現了這個異常&#xff1a;org.hibernate.LazyInitializationException: could not initialize proxy - no Session 原因就是jbpm的底層采用了懶加載的方式&#xff0c;解決這個異常的方法就是在對象的映射文件中去掉默認的懶加載&#xff0c;例…

最容易進的大廠工作,百度經典百題

最容易進大廠的機會就是百度的測試&#xff0c;不服來辯。 這個系列計劃收集幾百份朋友和讀者的面經&#xff0c;作者合集方便查看&#xff0c;各位有面經屯著可以聯系我哦 本系列歷史文章&#xff1a; 超經典&#xff0c;百度最愛考的安卓Android百題 超經典&#xff0c;阿…

超硬核!兔兔阿里p7學長給的面試知識庫

一個阿里p7學長給的nosql面試知識庫&#xff0c;絕對真實&#xff0c;學會了去面呀。 最近整理了一下超硬核系列的文章和面經系列的文章&#xff0c;可以持續關注下&#xff1a; 超硬核系列歷史文章&#xff1a;&#xff08;我保證每篇文章都有值得學習的地方&#xff0c;并…

百度校園招聘歷年經典面試題匯總:C++研發崗

這個系列計劃收集幾百份朋友和讀者的面經&#xff0c;作者合集方便查看&#xff0c;各位有面經屯著可以聯系我哦 這個系列離結束差的還特別多&#xff0c;會更新涵蓋所有一線大廠的所有崗位&#xff0c;也可以關注一下。 最容易進的大廠工作&#xff0c;百度經典百題 超經典&…