C++筆記整理(參考整理自各大博客)


為什么構造函數不能是虛函數,析構函數往往是虛函數?

靜態存儲區。無論在哪里構建,其過程都是兩步:首先,分配一塊內存;其次,調用構造函數。好,問題來了,如果構造函數是虛函數,那么就需要通過vtable 

調用,但此時面對一塊 raw memeory,到哪里去找 vtable 呢?畢竟,vtable 是在構造函數中才初始化的啊,而不是在其之前。因此構造函數不能為虛函數。 

析構函數可以是虛函數,且常常如此,這個就好理解了,因為此時 vtable 已經初始化了;況且我們通常通過基類的指針來銷毀對象,如果析構函數不為虛的話,就

不能正確識別對象類型,從而不能正確銷毀對象。

 

線程和進程

概念:

進程是表示資源分配的基本單位,又是調度運行的基本單位。

線程是進程中執行運算最小單位,亦即執行處理機調度的基本單位

 

好處:

(1)易於調度。

(2)提高並發性。通過線程可方便有效地實現並發性。進程可創建多個線程來執行同一程序的不同部分。

(3)開銷少。創建線程比創建進程要快,所需開銷很少。。

(4)利於充分發揮多處理器的功能。通過創建多線程進程(即一個進程可具有兩個或更多個線程),每個線程在一個處理器上運行,從而實現應用程序的並發性,使每個處理器都得到充分運行。

 

關系

(1)一個線程只能屬於一個進程,而一個進程可以有多個線程,但至少有一個線程。線程是操作系統可識別的最小執行和調度單位。

(2)資源分配給進程,同一進程的所有線程共享該進程的所有資源。 同一進程中的多個線程共享代碼段(代碼和常量),數據段(全局變量和靜態變量),擴展段(堆存儲)。但是每個線程擁有自己的棧段,棧段又叫運行時段,用來存放所有局部變量和臨時變量。

(3)處理機分給線程,即真正在處理機上運行的是線程。

(4)線程在執行過程中,需要協作同步。不同進程的線程間要利用消息通信的辦法實現同步。

 

多態

多態分為靜態多態和動態多態。

靜態多態:函數重載,泛型編程

動態多態:虛函數

(1)靜態多態

靜態多態是編譯器在編譯期間完成的,編譯器會根據實參類型來選擇調用合適的函數,如果有合適的函數可以調用就調,沒有的話就會發出警告或者報錯。。。比較簡單,不做多介紹。 

 

(2)動態多態

顯然這和靜態多態是一組反義詞,它是在程序運行時根據基類的引用(指針)指向的對象來確定自己具體該調用哪一個類的虛函數。

動態多態的條件: 
●基類中必須包含虛函數,並且派生類中一定要對基類中的虛函數進行重寫。 
●通過基類對象的指針或者引用調用虛函數。

 

總結一道面試題:哪些函數不能定義為虛函數?

①友元函數,它不是類的成員函數 
②全局函數 
③靜態成員函數,它沒有this指針 
構造函數,拷貝構造函數,以及賦值運算符重載(可以但是一般不建議作為虛函數)

以上摘自:https://blog.csdn.net/qq_39412582/article/details/81628254

 

多態的作用:(①可重用性 ②可擴展性)

1. 應用程序不必為每一個派生類編寫功能調用,只需要對抽象基類進行處理即可。大大提高程序的可復用性。//繼承

2. 派生類的功能可以被基類的方法或引用變量所調用,這叫向后兼容,可以提高可擴充性和可維護性。 //多態的真正作用,以前需要用switch實現

有了 malloc/free 為什么還要 new/delete  ?

malloc 與 free 是 C++/C 語言的標准庫函數,new/delete 是 C++的運算符。它們都可用於申請動態內存和釋放內存。 

對於非內部數據類型的對象而言,光用 malloc/free 無法滿足動態對象的要求。對象在創建的同時要自動執行構造函數,對象在消亡之前要自動執行析構函數。

由於malloc/free 是庫函數而不是運算符,不在編譯器控制權限之內,不能夠把執行構造函數和析構函數的任務強加於 malloc/free

因此 C++語言需要一個能完成動態內存分配和初始化工作的運算符 new,以及一個能完成清理與釋放內存工作的運算符 delete。

注意 new/delete 不是庫函數

 

重載,重寫和重定義

重載overload:是函數名相同,參數列表不同 重載 只是在類的內部存在。但是不能靠返回類型來判斷。
 
重寫override:也叫做覆蓋。 子類重新定義父類中有相同名稱和參數的虛函數。函數特征相同。但是具體實現不同,主要是在繼承關系中出現的 。
 
重寫需要注意:
1 被重寫的函數不能是static的。 必須是virtual的
2 重寫函數必須有相同的類型,名稱和參數列表
3 重寫函數的訪問修飾符可以不同。盡管virtual是private的,派生類中重寫改寫為public,protected也是可以的
 

重定義 (redefining)也叫做隱藏:

子類重新定義父類中有相同名稱的非虛函數 ( 參數列表可以不同 ) 。

 

C++內存管理

摘自:https://www.cnblogs.com/mrlsx/p/5411874.html,很全面。

在c++中內存主要分為5個存儲區:

全局/靜態存儲區:全局變量靜態變量分配到該區,到程序結束時自動釋放,包括DATA段(全局初始化區)與BBS段(全局未初始化段)。其中,初始化的全局變量和靜態變量存放在DATA段,未初始化的全局變量和靜態變量存放在BBS段。BBS段特點:在程序執行前BBS段自動清零,所以未初始化的全局變量和靜態變量在程序執行前已經成為0.

棧(Stack):局部變量函數參數等存儲在該區,由編譯器自動分配和釋放.棧屬於計算機系統的數據結構,進棧出棧有相應的計算機指令支持,而且分配專門的寄存器存儲棧的地址,效率分高,內存空間是連續的,但棧的內存空間有限。

堆(Heap):需要程序員手動分配和釋放(new,delete),屬於動態分配方式。內存空間幾乎沒有限制,內存空間不連續,因此會產生內存碎片。操作系統有一個記錄空間內存的鏈表,當收到內存申請時遍歷鏈表,找到第一個空間大於申請空間的堆節點,將該節點分配給程序,並將該節點從鏈表中刪除。一般,系統會在該內存空間的首地址處記錄本次分配的內存大小,用於delete釋放該內存空間。

 

文字常量區:存放常量,而且不允許修改。程序結束后由系統釋放。

程序代碼區:存放程序的二進制代碼

 

使用存儲區的三種方式:
1)靜態存儲區(Static Memory)

全局變量,靜態變量及靜態類成員存儲在該區,在編譯期間就進行分配,生存期到程序結束。存儲在該區的對象只初始化一次,且在程序運行期間地址固定不變。

2)自動存儲區(Autormatic Memory)

局部變量,函數參數等存儲在該區,由編譯器自動分配和釋放

3)自由存儲區(Free Store)

由程序員手動分配和釋放內存(new,delete)

 

堆和棧的區別:

1)空間大小:棧的內存空間是連續的,空間大小通常是系統預先規定好的,即棧頂地址和最大空間是確定的;而堆得內存空間是不連續的,由一個記錄空間空間的鏈表負責管理,因此內存空間幾乎沒有限制,在32位系統下,內存空間大小可達到4G

2)管理方式:棧由編譯器自動分配和釋放,而堆需要程序員來手動分配和釋放,若忘記delete,容易產生內存泄漏

3)生長方向不同:對於棧,他是向着內存地址減小的方向生長的,這也是為什么棧的內存空間是有限的;而堆是向着內存地址增大的方向生長的

4)碎片問題:由於棧的內存空間是連續的,先進后出的方式保證不會產生零碎的空間;而堆分配方式是每次在空閑鏈表中遍歷到第一個大於申請空間的節點,每次分配的空間大小一般不會正好等於申請的內存大小,頻繁的new操作勢必會產生大量的空間碎片

5)分配效率:棧屬於機器系統提供的數據結構,計算機會在底層對棧提供支持,出棧進棧由專門的指令執行,因此(棧)效率較高。而堆是c/c++函數庫提供的,當申請空間時需要按照一定的算法搜索足夠大小的內存空間,當沒有足夠的空間時,還需要額外的處理,因此效率較低。

 

使用內存時幾點注意事項:

1)用new和malloc申請內存時,在使用前要檢查內存是否分配成功

char *p=new char[10];
if(p==NULL)
return;

2)使用內存之前要進行初始化

3)在對內存進行操作時,防止越界,如數組操作要注意下標范圍

4)對於動態分配的內存,一定要手動釋放,否則程序每運行一次就會丟失一部分內存,造成內存泄漏

5)防止內存釋放后繼續使用它,主要有以下三種情況:

a.程序中的對象調用關系過於復雜,實在難以搞清楚某個對象究竟是否已經釋放了內存,此時應該重新設計數據結構,從根本上解決對象管理的混亂局面。

b.函數的return語句寫錯了,注意不要返回指向“棧內存”的“指針”或者“引用”,因為該內存在函數體結束時被自動銷毀。

c.使用free或delete釋放了內存后,沒有將指針設置為NULL。導致產生“野指針”。

野指針:“野指針”不是NULL指針,是指向“垃圾”內存的指針。人們一般不會錯用NULL指針,因為用if語句很容易判斷。但是“野指針”是很危險的,if語句對它不起作用。

“野指針”的成因主要有三種:

(a)指針變量沒有被初始化。任何指針變量剛被創建時不會自動成為NULL指針,它的缺省值是隨機的,它會亂指一氣。所以,指針變量在創建的同時應當被初始化,要么將指針設置為NULL,要么讓它指向合法的內存

char *p;   //此時p為野指針

 

(b)指針p被free或者delete之后,沒有置為NULL,讓人誤以為p是個合法的指針.

char *p=new char[10];  //指向堆中分配的內存首地址
cin>> p;
delete []p; //p重新變為野指針

 

(c)指針操作超越了變量的作用范圍。

char *p=new char[10];  //指向堆中分配的內存首地址
cin>> p;
cout<<*(p+10);  //可能輸出未知數據

 

6)指針的注意點:

a.指針指向常量存儲區對象

char *p="abc";

此時p指向的是一個字符串常量,不能對*p的內容進行寫操作,如srtcpy(p,s)是錯誤的,因為p的內容為“abc”字符串常量,該數據存儲在常量存儲區,但可以對指針p進行操作,讓其指向其他的內存空間

b.資源泄漏

char *p=new char[3];  //分配三個字符空間,p指向該內存空間

p="ab";   //此時p指向常量“ab”,而不再是new char分配的內存空間了,從而造成了資源泄漏

delete []p;         //釋放時報錯

c.內存越界

char *p=new char[3];  //分配三個字符空間,p指向該內存空間

strcpy(p,"abcd");  //將abcd存處在分配的內存空間中,由於strlen("abcd")=4>3,越界

delete []p;  //釋放時出錯

 

注:p="ab"和strcpy(p,"ab"),含義不一樣,前者指針p指向常量“ab”存儲區域的首地址,改變了p最開始指向的new申請的內存空間;而后者是將“ab”分配到new申請的內存空間中;

關於指針具體看:http://www.cnblogs.com/mrlsx/p/5419030.html

 

const和#define(宏常量),const有哪些優勢?

(1)數據類型:const常量有數據類型,而宏常量沒有。因此編譯器可以對前者進行安全檢查。后者只是進行字符替換,沒有類型安全檢查,可能會有意料之外的錯誤。

(2)調試功能:有些集成化的調試工具可以對const常量進行調試,但不能對宏進行調試。inline內聯函數也有類型檢查。

 

 

 

引用與指針的區別是什么?

指針通過某個指針變量指向一個對象后,對它所指向的變量間接操作。程序中使用指針,程序的可讀性差;

引用本身就是目標變量的別名,對引用的操作就是對目標變量的操作。

 

1) 引用必須被初始化,指針不必。

2) 引用初始化以后不能被改變,指針可以改變所指的對象。

3) 不存在指向空值的引用,但是存在指向空值的指針,引用會更安全。

 

 volatile表示什么?有什么作用?

易變的,不會被編譯器進行優化,讓程序取數據直接去內存中的

用來解決變量在“共享”環境下容易出現讀取錯誤的問題。

摘自:https://www.jianshu.com/p/2de5b739178a

 

例: volatile int i=10;

volatile 指出 i 是隨時可能發生變化的,每次使用它的時候必須從 i的地址中讀取。

摘自:https://www.cnblogs.com/yc_sunniwell/archive/2010/07/14/1777432.html

 

STL中map和set的原理(關聯式容器)

map和set的底層實現主要通過紅黑樹來實現

紅黑樹是一種特殊的二叉查找樹

1)每個節點或者是黑色,或者是紅色 

2)根節點是黑色

3) 每個葉子節點(NIL)是黑色。 [注意:這里葉子節點,是指為空(NIL或NULL)的葉子節點!]

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

5)從一個節點到該節點的子孫節點的所有路徑上包含相同數目的黑節點。

 

特性4)5)決定了沒有一條路徑會比其他路徑長出2倍,因此紅黑樹是接近平衡的二叉樹。

 

C++表達式x&(x-1)

x = x & (x - 1)
含義:這條語句執行一次,就會把x用二進制格式表示時的最右邊的一個二進制1變為二進制0,因為x-1會將該位(x用二進制表示時最右邊的一個二進制1)變為0;
應用1:把一個整數用二進制表示時,其中二進制1的個數;

int Func(int x)
{
    int count = 0;
    while (x)
    {
        count ++;
        x = x & (x - 1);
    }
    return count;
}

 

設x=9999,其二進制格式為: 10011100001111; 則count=8;
思路:將x轉化為二進制格式,統計一下含有的二進制1的個數;
應用2:判斷一個整數(x)是否是2的n次方;

int Func(int x)
{
    if ((x&(x-1))==0)
    {
        return true;
    }
    else
    {
        return false;
    }
}

 

思路:如果一個整數是2的n次方,那么,這個數用二進制表示時,其最高位為二進制1,其余位為二進制0;

來自:https://blog.csdn.net/u012260238/article/details/79246438

 

 友元和內聯函數

 友元函數:

void Test(const Test& a)//讓這個類之外的函數可以訪問類的私有變量
{
    a._a = 1;//可以在友元函數內部直接訪問類的私有變量
}
class Test
{
friend void Test(const Test& a);//友元函數的聲明;在哪個類里面聲明,這個函數就是哪個函數的友元函數
private:
    int _a;
};
int main()
{
    Test a;
    return 0;
}

 

想要通過一個類外的函數去訪問類中的私有變量在一般情況下是不可行的,友元函數可以直接訪問類的非公有成員,它的定義是在類外部的普通函數,不輸入任何類,但

是它需要在類的內部進行聲明,聲明時需要加上friend關鍵字。

友元函數可以訪問類的非公有成員但它不是類的成員

友元函數不能用const進行修飾。

友元函數可以在類中的任何地方進行友元聲明,不受類訪問限定符的限制。

一個函數可以是多個類的友元函數。

友元函數的調用和普通函數的調用原理相同。

友元函數的聲明

友元函數的聲明僅僅指定了訪問的權限,而非一個通常意義上的函數聲明。如果我們希望類的用戶能夠調用某個友元函數,那么我們必須在友元聲明之外再專門對函數進行一次聲明。

 

內聯函數:

將一個函數定義為內聯函數,編譯時C++編譯器會在調用內聯函數的地方展開,沒有函數壓棧的開銷,提高了函數運行的效率,但是增大了函數的體積。

作用:內聯函數的作用類似於宏,再調用內聯函數的地方用內聯函數的內容進行替換,消去了函數調用時所需要的時間,提高了函數運行的效率,在Debug模式下不會替換,

Release模式才會進行替換。 

inline是一種以空間換時間的做法,省去調用函數額外開銷。所以代碼很長或者有循環/遞歸的函數不適宜使用內聯函數

inline對於編譯器而言,只是一個建議,編譯器自動優化,如果定義為inline的函數體內部有循環/遞歸時,編譯器優化時會忽略掉內聯函數

inline必須與函數定義放在一起,才能成為內聯函數,僅將inline放在聲明前是不會起到作用的

定義在類內的成員函數默認定義為內聯函數

 

 STL

string  vector  set  list  map

一般是.size()   .empty()

set跟vector差不多,它跟vector的唯一區別就是,set里面的元素是有序的且唯一的,只要你往set里添加元素,它就會自動排序,而且,如果你添加的元素set里面本來就存在,那么這次添加操作就不執行。要想用set先加個頭文件set。

map運用了哈希表地址映射的思想,也就是key-value的思想,來實現的。

參考:

https://www.cnblogs.com/skyfsm/p/6934246.html

簡明易懂,有例子。

 

TCP為什么需要3次握手與4次揮手

為什么需要“三次握手”

            在謝希仁著《計算機網絡》第四版中講“三次握手”的目的是“為了防止已失效的連接請求報文段突然又傳送到了服務端,因而產生錯誤”。在另一部經典的《計算機網絡》一書中講“三次握手”的目的是為了解決“網絡中存在延遲的重復分組”的問題。這兩種不用的表述其實闡明的是同一個問題。
            謝希仁版《計算機網絡》中的例子是這樣的,“已失效的連接請求報文段”的產生在這樣一種情況下:client發出的第一個連接請求報文段並沒有丟失,而是在某個網絡結點長時間的滯留了,以致延誤到連接釋放以后的某個時間才到達server。本來這是一個早已失效的報文段。但server收到此失效的連接請求報文段后,就誤認為是client再次發出的一個新的連接請求。於是就向client發出確認報文段,同意建立連接。假設不采用“三次握手”,那么只要server發出確認,新的連接就建立了。由於現在client並沒有發出建立連接的請求,因此不會理睬server的確認,也不會向server發送數據。但server卻以為新的運輸連接已經建立,並一直等待client發來數據。這樣,server的很多資源就白白浪費掉了。采用“三次握手”的辦法可以防止上述現象發生。例如剛才那種情況,client不會向server的確認發出確認。server由於收不到確認,就知道client並沒有要求建立連接。”。主要目的防止server端一直等待,浪費資源。

 

為什么需要“四次揮手”
      那可能有人會有疑問,在tcp連接握手時為何ACK是和SYN一起發送,這里ACK卻沒有和FIN一起發送呢。原因是因為tcp是全雙工模式,接收到FIN時意味將沒有數據再發來,但是還是可以繼續發送數據。1
---------------------
作者:席飛劍
來源:CSDN
原文:https://blog.csdn.net/xifeijian/article/details/12777187
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!

 

十大經典排序算法最強總結

摘自:https://blog.csdn.net/hellozhxy/article/details/79911867

原出處里面非常詳細,這里只摘一張圖供臨時抱佛腳

 

還在不斷整理。。。。。

有不少是整理自《經典C++面試題目100例》,不一一列舉,原出處:https://blog.csdn.net/weixin_41168353/article/details/80083861

 


免責聲明!

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



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