面試題有難有易,不能因為容易,我們就輕視,更不能因為難,我們就放棄。我們面對高薪就業的態度永遠不變,那就是堅持、堅持、再堅持。出現問題,找原因;遇到困難,想辦法。我們一直堅信只有在堅持中才能看到希望,而不是看到希望才去堅持。
人生沒有如果,只有結果和后果。既然選擇了,就不后悔。年輕就是資本,年輕就要吃苦,就要歷練。就要學會在堅持中成長。如此感慨,至深的心得體會,絕對的經驗之談。
1、 Static有什么用途?
(1)函數體內static變量的作用范圍是該函數體,該變量的內存只被分配一次,因此它的值在下次調用時不變;如果不存在其他的初始化語句,在創建第一個對象時,所有的靜態數據都會被初始化為零。我們不能把靜態成員的初始化放置在類的定義中,但是可以在類的外部通過使用范圍解析運算符 :: 來重新聲明靜態變量從而對它進行初始化。
(2)模塊內的static全局變量同樣只能在該模塊內的函數訪問和調用,不能被模塊外的其他函數訪問;
(3)在類中的static成員變量屬於整個類所有,對類的所有對象只有一份拷貝;靜態成員函數內部不能調用非靜態成員函數,原因是,非靜態成員函數需要傳入一個this指針,這讓靜態成員函數很為難,它並不知道與之相關的信息,也就無法提供this指針。
普通成員變量每個對象都有各自的一份,但是靜態成員變量一共只有一份,被所有的本類對象共享。如果使用sizeof運算符計算對象的大小,得到的結果是不包含靜態成員變量在內的。
靜態成員函數與普通成員函數的區別:
- 靜態成員函數沒有 this 指針,只能訪問靜態成員(包括靜態成員變量和靜態成員函數)。
- 普通成員函數有 this 指針,可以訪問類中的任意成員;而靜態成員函數沒有 this 指針。
2、 const
(1)不管在函數聲明修飾形參、還是修飾類的成員變量,表示該成員變量不能被改變,而且通常需要進行初始化,因為之后不能再改變;
(2)對於指針來說,可以修飾指針所指向的變量(在*左邊,即指針指向內容為常量),也可以指定指針本身為const(在*右邊,指針本身是常量),或者兩者同時指定為const(都是常量)。
3、 this指針
(1)this指針本質是一個函數參數,只是編譯期隱藏起形式的,語法層面上的參數,且this指針只能在成員函數中使用,全局函數、靜態函數都不能使用;
(2)this在成員函數開始前構造,在成員結束后清除;
(3)this指針不占用對象的空間。
4、 ifndef/define/endif的作用
防止頭文件被重復引用和定義;
5、 C和C++的區別
(1)C主要面向過程,C++面向對象;
(2)C是一種結構化語言,重點在於算法和數據結構。C主要考慮通過一個過程將輸入進行各種運算后得到輸出,C++主要考慮的是如何構造一個對象模型,契合與之相對應的問題域,這樣就可以通過獲得對象的狀態信息得到輸出。
(3)什么是面向對象:面向對象是一種對現實世界理解和抽象的方法、思想,通過將需求要素轉化為對象進行問題處理的一種思想。
(4)請用簡單的語言告訴我C++ 是什么?
C++是在C語言的基礎上開發的一種面向對象編程語言,應用廣泛。C++支持多種編程范式-------面向對象編程、泛型編程和過程化編程。 其編程領域眾廣,常用於系統開發,引擎開發等應用領域,是最受廣大程序員受用的最強大編程語言之一,支持類、封裝、重載等特性!
6、 C++函數值傳遞的方式
值傳遞、指針傳遞和引用傳遞
7、 extern “C”的作用
實現C和C++的混合編程;因為函數被C++編譯后的名字會變長,與C生成的不一致,造成C++不能直接調用C函數。
extern關鍵字的作用
extern置於變量或函數前,用於標示變量或函數的定義在別的文件中,提示編譯器遇到此變量和函數時在其他模塊中尋找其定義。它只要有兩個作用:
當它與“C”一起連用的時候,如:extern "C" void fun(int a,int b);則告訴編譯器在編譯fun這個函數時候按着C的規矩去翻譯,而不是C++的(這與C++的重載有關,C++語言支持函數重載,C語言不支持函數重載,函數被C++編譯器編譯后在庫中的名字與C語言的不同)
當extern不與“C”在一起修飾變量或函數時,如:extern int g_Int;它的作用就是聲明函數或全局變量的作用范圍的關鍵字,其聲明的函數和變量可以在本模塊或其他模塊中使用。記住它是一個聲明不是定義!也就是說B模塊(編譯單元)要是引用模塊(編譯單元)A中定義的全局變量或函數時,它只要包含A模塊的頭文件即可,在編譯階段,模塊B雖然找不到該函數或變量,但它不會報錯,它會在連接時從模塊A生成的目標代碼中找到此函數。
如何引用一個已經定義過的全局變量?
引用頭文件和extern關鍵字。 如果采用引用頭文件, 若變量寫錯了,則在編譯期間便會出錯。 如果用extern則在鏈接階段報錯。
8、 struct 和class 的區別
(1)struct的成員默認是公有的,而類的成員默認是私有的;
(2)C中的struct不能包含成員函數,C++中的class可以包含成員函數。
結構與聯合有和區別?
(1)結構和聯合都是由多個不同的數據類型成員組成, 但在任何同一時刻, 聯合中只存放了一個被選中的成員(所有成員共用一塊地址空間), 而結構的所有成員都存在(不同成員的存放地址不同)。
(2)對於聯合的不同成員賦值, 將會對其它成員重寫, 原來成員的值就不存在了, 而對於結構的不同成員賦值是互不影響的。
9、 new和malloc
(1)都可用來申請動態內存和釋放內存,都是在堆(heap)上進行動態的內存操作。
(2)malloc和free是C語言的標准庫函數,new和delete是C++的運算符。
(3)new會自動調用對象的構造函數,delete 會調用對象的析構函數, 而malloc返回的都是void指針。
10、 heap與stack(堆與棧)的差別
(1)heap是堆,stack是棧;
(2)stack的空間由操作系統自動分配和釋放,存放函數的參數值、 局部變量的值等。heap上的空間一般由程序員分配和釋放,並要指明大小;
(3)棧空間有限而且是一塊連續的內存區域,堆是很大的自由存儲區;
(4)C中的malloc函數分配的內存空間就是在堆上,C++是new;
(5)程序在編譯期對變量和函數分配內存都在棧上進行,且程序運行過程中函數調用時的參數傳遞也在棧上進行
堆棧溢出原因:數組越界, 沒有回收內存, 深層次遞歸調用
11、 Vector、List和Deque的區別
Vector:表示一段連續的內存區域,每個元素被順序存儲在這段內存中,對vector的隨機訪問效率很高,但對非末尾元素的插入和刪除則效率非常低。
List:表示非連續的內存區域並通過一對指向首尾元素的指針雙向鏈接起來,插入刪除效率高,隨機訪問效率低。
Deque:也表示一段連續的內存區域,但與vector不同的是它支持高效地在其首部插入和刪除元素,它通過兩級數組結構來實現,一級表示實際的容器,第二級指向容器的首和尾。
vector擁有一段連續的內存空間,能很好的支持隨機存取,因此vector::iterator支持“+”,“+=”,“<”等操作符。
list的內存空間可以是不連續,它不支持隨機訪問,因此list::iterator則不支持“+”、“+=”、“<”等
vector::iterator和list::iterator都重載了“++”運算符。
總之,如果需要高效的隨機存取,而不在乎插入和刪除的效率,使用vector;如果需要大量的插入和刪除,而不關心隨機存取,則應使用list。
13、 內聯函數和宏的差別
內聯函數和普通函數相比可以加快程序運行的速度,因為不需要中斷調用,在編譯的時候內聯函數可以直接被鑲嵌到目標代碼中。而宏只是一個簡單的替換。
內聯函數要做參數類型檢查,這是內聯函數的優勢;
inline是指嵌入代碼,就是在調用函數的地方不是跳轉,而是把代碼直接寫到那里去。
對於短小的代碼來說inline增加空間消耗換來的是效率提高,這方面和宏是一模一樣的,但是inline在和宏相比沒有付出任何額外代價的情況下更安全。 至於是否需要inline函數,就需要根據實際情況來取舍了。
inline一般只用於如下情況:
(1)一個函數不斷被重復調用。
(2)函數只有簡單的幾行,且函數內不包含for、 while、 switch語句。
宏是在代碼處不加任何驗證的簡單替代,而內聯函數是將代碼直接插入調用處,而減少了普通函數調用時的資源消耗。
宏不是函數,只是在編譯前(編譯預處理階段)將程序中有關字符串替換成宏體。
關鍵字inline必須與函數定義體放在一起才能使函數成為內聯,僅將inline放在函數聲明前面不起任何作用。
inline是一種“用於實現的關鍵字”,而不是一種“用於聲明的關鍵字”。 內聯能提高函數的執行效率,至於為什么不把所有的函數都定義成內聯函數?如果所有的函數都是內聯函數,還用得着“內聯”這個關鍵字嗎?內聯是以代碼膨脹(復制)為代價,僅僅省去了函數調用的開銷,從而提高函數的執行效率。 如果執行函數體內代碼的時間,相比於函數調用的開銷較大,那么效率的收獲會很少。另一方面,每一處內聯函數的調用都要復制代碼,將使程序的總代碼量增大,消耗更多的內存空間。
以下情況不宜使用內聯:
(1)如果函數體內的代碼比較長,使用內聯將導致內存消耗代價較高。 (2)如果函數體內出現循環,那么執行函數體內代碼的時間要比函數調用的開銷大。 類的構造函數和析構函數容易讓人誤解成使用內聯更有效。 要當心構造函數和析構函數可能會隱藏一些行為,如“偷偷地”執行了基類或成員對象的構造函數和析構函數。 所以不要隨便地將構造函數和析構函數的定義體放在類聲明中。 一個好的編譯器將會根據函數的定義體,自動地取消不值得的內聯(這進一步說明了inline不應該出現在函數的聲明中)。
請說出const與#define 相比,有何優點?
const 常量有數據類型,而宏常量沒有數據類型。編譯器可以對前者進行類型安全檢查。而對后者只進行字符替換,沒有類型安全檢查,並且在字符替換可能會產生意料不到的錯誤。
14、 引用和指針的區別
(1)指針是一個變量,用於存放地址的變量,指向內存的一個存儲單元,引用僅是別名;
(2)引用必須初始化,指針不必;
(3)不存在指向空值的引用,但是存在指向空值的指針;
(4)sizeof() 引用對象得到的是所指對象變量的大小,sizeof() 指針得到是指針本身的大小;
(5)內存分配上,程序為指針分配內存,不用為引用分配內存
15、 數組和鏈表的區別
C++語言中可以用數組處理一組數據類型相同的數據,但在使用數組之前必須確定數組的大小。而在實際應用中,用戶使用數組之前有時無法准確確定數組的大小,只能將數組定義成足夠大小,這樣數組中有些空間可能不被使用,從而造成內存空間的浪費。
鏈表是一種常見的數據組織形式,它采用動態分配內存的形式實現。需要時可以用new分配內存空間,不需要時用delete將已分配的空間釋放,不會造成內存空間的浪費。
(1)從邏輯結構來看:
數組必須事先定義固定的長度(元素個數),不能適應數據動態地增減的情況,即數組的大小一旦定義就不能改變。當數據增加時,可能超出原先定義的元素個數;當數據減少時,造成內存浪費;
鏈表動態地進行存儲分配,可以適應數據動態地增減的情況,且可以方便地插入、刪除數據項。(數組中插入、刪除數據項時,需要移動其它數據項)。
(2)從內存存儲來看:
靜態數組從棧中分配空間(用NEW創建的在堆中),對於程序員方便快速,但是自由度小;
鏈表從堆中分配空間, 自由度大,但是申請管理比較麻煩。
(3)從訪問方式來看:
數組在內存中是連續存儲的,因此,可以利用下標索引進行隨機訪問;
鏈表是鏈式存儲結構,在訪問元素的時候只能通過線性的方式由前到后順序訪問,所以訪問效率比數組要低。
16、 鏈表
(1)鏈表是否有環
算法的思想是使用追趕的方法設定兩個指針p, q,其中p每次向前移動一步,q每次向前移動兩步。那么如果單鏈表存在環,則p和q相遇;否則q將首先遇到null退出。
(2)如何知道環的長度
記錄下上個問題的碰撞點p,slow、fast從該點開始,再次碰撞所走過的操作數就是環的長度s。
(3)如何找出環的連接點
有定理:碰撞點p到連接點的距離 = 頭指針到連接點的距離,因此,分別從碰撞點、頭指針開始走,相遇的那個點就是連接點。
(4)帶環鏈表的長度
根據已經求出連接點距離頭指針的長度,加上求出的環的長度,二者之和就是帶環單鏈表的長度
(5)單鏈表的逆置
輸入一個鏈表,反轉鏈表后,輸出鏈表的所有元素。
方法一:
思路:從原鏈表的頭部一個一個取節點並插入到新鏈表的頭部
1)
1 struct ListNode{ 2 int val; 3 struct ListNode *next; 4 ListNode(int x):val(x),next(NULL){} 5 }; 6
7 class Solution{ 8 public: 9 ListNode* ReverseList(ListNode *pHead){ 10 ListNode* newh = NULL; 11 for(ListNode *p = pHead; p; ) 12 { 13 ListNode *tmp = p->next; 14 p->next=newh; 15 newh=p; 16 p=tmp; 17 } 18 return newh; 19 } 20 };
2)
1 class Solution { 2 public: 3 ListNode* ReverseList(ListNode* pHead) { 4 if(pHead == NULL) 5 return pHead; 6 ListNode *res,*cur,*next; 7 res = new ListNode(-1); 8 cur = pHead; 9 next = cur->next; 10 while(cur != NULL) 11 { 12 ListNode *first = res->next; 13 cur->next = first; 14 res->next = cur; 15
16 cur = next; 17 next = next->next; 18 } 19 return res->next; 20 } 21 };
方法二:
思路:每次都將原第一個結點之后的那個結點放在新的表頭后面。
比如1,2,3,4,5
第一次:把第一個結點1后邊的結點2放到新表頭后面,變成2,1,3,4,5
第二次:把第一個結點1后邊的結點3放到新表頭后面,變成3,2,1,4,5
……
直到: 第一個結點1,后邊沒有結點為止。
代碼如下:
1 class Solution { 2 public: 3 ListNode* ReverseList(ListNode* pHead) { 4 if(pHead == NULL) 5 return pHead; 6
7 ListNode *res,*first,*temp; 8 res = new ListNode(-1); 9 res->next = pHead; 10
11 first = res->next; //first 始終為第一個結點,不斷后移
12 while(first->next!=NULL) //temp為待前插的
13 { 14 temp = first->next; 15 first->next = temp->next; 16 temp->next = res->next; 17 res->next = temp; 18 } 19
20 return res->next; 21 } 22 };
方法三
第三種方法跟第二種方法差不多,第二種方法是將后面的結點向前移動到頭結點的后面,第三種方法是將前面的結點移動到原來的最后一個結點的后面,思路跟第二種方法差不多,就不貼代碼了。
17、 重載和重寫(覆蓋)的區別
(1)從定義來說:
重載:是指存在多個重名函數,而這些函數的參數表不同(參數個數,類型不同);
重寫:是指子類重新定義父類虛函數的方法。
(2)從實現原理上來說:
重載:編譯器根據函數不同的參數表,對同名函數的名稱做修飾,然后這些同名函數就成了不同的函數。而函數的調用,在編譯時就已經確定是靜態的,也就是說它們的地址在編譯期就綁定(早綁定),因此重載與多態無關。
重寫:和多態有關。當子類重新定義了父類的虛函數后,父類指針根據賦給它的不同的子類指針,動態地調用屬於子類的該函數,這樣的函數調用在編譯期間是無法確定的(調用的子類的虛函數的地址無法給出)。因此,它們的地址是在運行期綁定的(晚綁定)。
18、 封裝、繼承、多態、虛函數
封裝
封裝是實現面向對象程序設計的第一步,封裝就是將數據或函數等集合在一個個的單元(類)中。其意義在於保護或者防止代碼被無意中破壞
繼承
繼承主要實現重用代碼,擴展已存在的代碼,節省開發時間。子類可以繼承父類的一些東西。
多態
定義:“一個接口,多種方法”,程序運行時才決定調用的函數
實現:C++多態主要是通過虛函數實現。虛函數允許子類重寫。
目的:封裝可以使代碼模塊化,繼承可以擴展已存在的代碼,它們的目的都是為了代碼重用。而多態的目的是為了接口重用,將接口與實現分離。
虛函數
定義:被virtual關鍵字修飾的成員函數,就是虛函數。其作用就是實現多態性。
為什么虛函數效率低?
因為虛函數需要一次間接的尋址,而一般的函數可以在編譯時定位到函數的地址,虛函數(動態類型調用)要根據某個指針定位到函數的地址。多增加了一個過程,效率雖然會低一些,但帶來了運行時的多態。
純虛函數
為什么要用純虛函數?
在很多情況下,基類本身生成對象是不合情理的。例如,動物作為一個基類可以派生出老虎、孔雀等子類,但動物本身生成對象明顯不合常理。為了解決這個問題,方便使用類的多態性,引入了純虛函數的概念,將函數定義為純虛函數。則編譯器要求在派生類中必須予以重寫以實現多態性。同時含有純虛擬函數的類稱為抽象類,它不能生成對象。
使用純虛數的情況:
(1)當想在基類中抽象出一個方法,且該基類只做能被繼承,而不能被實例化;
(2)這個方法必須在派生類(derived class)中被實現;
虛函數與純虛函數的區別:
虛函數是為了重載和多態。在基類中是有定義的,即便定義為空。在子類中可以重寫。
純虛函數在基類中沒有定義,必須在子類中加以實現。
多態的基礎是繼承,需要虛函數的支持。子類繼承父類大部分的資源,不能繼承的有構造函數,析構函數,拷貝構造函數, operator=函數,友元函數等等。
19、 內存
內存類別
棧 ——由編譯器自動分配釋放, 局部遍歷存放位置
堆 ——由程序員分配和釋放.
全局區(靜態區) ——全局變量和靜態變量的存儲是放在一起的, 初始化的全局變量和static靜態變量在一塊區域.
程序代碼區 ——存放二進制代碼.
內存分配方式
靜態存儲區,程序編譯時便分好, 整個運行期間都存在,比如全局變量,常量;
棧上分配;
堆上分配。
內存泄漏
原因:動態分配的內存沒有手動釋放完全。
避免:使用的時候應記得指針的長度; 分配多少內存應記得釋放多少, 保證一一對應的關系; 動態分配內存的指針最好不要再次賦值。
內存溢出
內存溢出是指程序在申請內存時,沒有足夠的內存空間供其使用。原因可能如下:
內存中加載的數據量過於龐大,如一次從數據庫取出過多數據
代碼中存在死循環或循環產生過多重復的對象實體
遞歸調用太深,導致堆棧溢出等
內存泄漏最終導致內存溢出
緩沖區溢出(棧溢出)
20、 進程和線程的差別
進程是最小的分配資源單位,線程是最小的執行單位。
進程是程序的一次執行。線程可以理解為進程中執行的一段程序片段。 在一個多任務環境中下面的概念可以幫助我們理解兩者間的差別。
進程間是獨立的,這表現在內存空間、 上下文環境上;線程運行在進程空間內。 一般來講(不使用特殊技術),進程無法突破進程邊界存取其他進程內的存儲空間;而線程由於處於進程空間內,所以同一進程所產生的線程共享同一內存空間。
同一進程中的兩段代碼不能夠同時執行,除非引入線程。
線程是屬於進程的,當進程退出時該進程所產生的線程都會被強制退出並清除。 線程占用的資源要少於進程所占用的資源。 進程和線程都可以有優先級。
21、 TCP和UDP
TCP是傳輸控制協議,提供的是面向連接、 可靠的字節流服務。 當客戶和服務器彼此交換數據前,必須先在雙方之間建立一個TCP連接,之后才能傳輸數據。 TCP提供超時重發、 丟棄重復數據、 檢驗數據、 流量控制等功能,保證數據能從一端傳到另一端。
UDP是用戶數據報協議,是一個簡單的面向數據報的運輸層協議。UDP不提供可靠性,它只是把應用程序傳給IP層的數據報發送出去,但是並不保證它們能到達目的地。 由於UDP在傳輸數據報前不用在客戶和服務器之間建立一個連接,且沒有超時重發等機制,故而傳輸速度很快。
TCP和UDP通信的差別?
(1)TCP面向連接, UDP面向無連接的
(2)TCP有保障的,UDP傳輸無保障的
(3)TCP是效率低的,UDP效率高的
(4)TCP是基於流的,UDP基於數據報文
(5)TCP傳輸重要數據,UDP傳輸不重要的數據
22、 編寫Socket套接字
Socket相當於進行網絡通信兩端的插座,只要對方的Socket和自己的Socket有通信聯接,雙方就可以發送和接收數據了。 如果你要編寫的是一個服務程序,那么先調用socket()創建一個套接字,調用bind()綁定IP地址和端口,然后啟動一個死循環,循環中調用accept()接受連接。對於每個接受的連接,可以啟動多線程方式進行處理,在線程中調用send()、 recv()發送和接收數據。
如果你要編寫的是一個客戶端程序,那么就簡單多了。 先調用socket()創建一個套接字,然后調用connect()連接服務器,之后就是調用send()、 recv()發送和接收數據了。
服務器端程序編寫:
(1)調用ServerSocket(int port)創建一個服務器端套接字,並綁定到指定端口上。
(2)調用accept(),監聽連接請求,則接收連接,返回通信套接字。
(3)調用Socket類的getOutStream()和getInputStream獲取輸出流和輸入流,開始網絡數據的發送和接收。
(4)關閉通信套接字.Socket.close()。
客戶端程序編寫:
(1)調用Socket()創建一個流套接字,並連接到服務器端。
(2)調用Socket類的getOutputStream()和fetInputStream獲取輸出流和輸入流,開始網絡數據的發送和接收。
(3)關閉通信套接字.Socket.close()。
23、 三次握手和四次揮手
1、建立連接協議(三次握手)
(1)客戶端發送一個帶SYN標志的TCP報文到服務器。這是三次握手過程中的報文1。
(2)服務器端回應客戶端的,這是三次握手中的第2個報文,這個報文同時帶ACK標志和SYN標志。因此它表示對剛才客戶端SYN報文的回應;同時又標志SYN給客戶端,詢問客戶端是否准備好進行數據通訊。
(3)客戶必須再次回應服務段一個ACK報文,這是報文段3。
2、連接終止協議(四次握手)
由於TCP連接是全雙工的,因此每個方向都必須單獨進行關閉。這原則是當一方完成它的數據發送任務后就能發送一個FIN來終止這個方向的連接。收到一個 FIN只意味着這一方向上沒有數據流動,一個TCP連接在收到一個FIN后仍能發送數據。首先進行關閉的一方將執行主動關閉,而另一方執行被動關閉。
(1) TCP客戶端發送一個FIN,用來關閉客戶到服務器的數據傳送(報文段1)。
(2) 服務器收到這個FIN,它發回一個ACK,確認序號為收到的序號加1(報文段2)。和SYN一樣,一個FIN將占用一個序號。
(3) 服務器關閉客戶端的連接,發送一個FIN給客戶端(報文段3)。
(4) 客戶段發回ACK報文確認,並將確認序號設置為收到序號加1(報文段4)。
24、 排序
http://www.cnblogs.com/eniac12/p/5329396.html#s3
http://www.cnblogs.com/eniac12/p/5332117.html
25、 linux基本命令
http://www.cnblogs.com/laov/p/3541414.html#grep
26、 gdb調試
http://www.jb51.net/article/36393.htm
參考:http://www.cnblogs.com/bozhicheng/p/6259784.html 程序員面試寶典
27、設計模式懂嘛,簡單舉個例子?
設計模式(Design pattern)是一套被反復使用、多數人知曉的、經過分類編目的、代碼設計經驗的總結。
(1)比如單例模式,保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。
適用於:當類只能有一個實例而且客戶可以從一個眾所周知的訪問點訪問它時;當這個唯一實例應該是通過子類化可擴展的,並且客戶應該無需更改代碼就能使用一個擴展的實例時。
(2)比如工廠模式,定義一個用於創建對象的接口,讓子類決定實例化哪一個類。Factory Method 使一個類的實例化延遲到其子類。
適用於:當一個類不知道它所必須創建的對象的類的時候;當一個類希望由它的子類來指定它所創建的對象的時候;當類將創建對象的職責委托給多個幫助子類中的某一個,並且你希望將哪一個幫助子類是代理者這一信息局部化的時候。
C++中常用的設計模式有哪些?
共有23種設計模式,但真正在開發中常用的模式有:
(1)Factory Method(工廠模式);
(2)Strategy(策略模式);
(3)Singleton(單例模式);
(4)Iterator(迭代器模式);
(5)Abstract Factory(抽象工廠模式);
(6)Builder(建造者模式);
(7)Adapter(適配器模式);
(8)Bridge(橋接模式);
(9)Composite(組合模式);
(10)Interpreter(解釋器模式);
(11)Command(命令模式);
(12)Mediator(中介者模式);
(13)Observer(觀察者模式);
(14)State(狀態模式);
(15)Proxy(代理模式)。
編寫一個單例模式的例子:
單例模式:保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。該實例被所有程序模塊共享。
1 #include<iostream>
2 using namespacestd; 3
4 classCSingleton 5 { 6 private: 7 CSingleton(){}//構造函數是私有的
8 static CSingleton *m_pInstance; // static
9
10 public: 11 static CSingleton *GetInstance() // static
12 { 13 if(m_pInstance==NULL) // 判斷是否第一次調用
14 m_pInstance=newCSingleton(); 15
16 return m_pInstance; 17 } 18 }; 19
20 CSingleton * CSingleton::m_pInstance=NULL; // static屬性類外初始化
21
22 void main() 23 { 24 CSingleton *p1= CSingleton::GetInstance(); 25 CSingleton *p2= CSingleton::GetInstance(); 26 cout<<(p1==p2)<<endl;//結果為true表示單例
27 }
28、STL庫用過嗎?常見的STL容器有哪些?算法用過哪幾個?
STL包括兩部分內容:容器和算法。(重要的還有融合這二者的迭代器)
(1)容器,即存放數據的地方。比如array等。
在STL中,容器分為兩類:序列式容器和關聯式容器。
序列式容器,其中的元素不一定有序,但都可以被排序。如:vector、list、deque、stack、queue、heap、priority_queue;
關聯式容器,內部結構基本上是一顆平衡二叉樹。所謂關聯,指每個元素都有一個鍵值和一個實值,元素按照一定的規則存放。如:RB-tree、set、map、multiset、multimap、hashtable、ash_set、hash_map、hash_multiset、hash_multimap。
下面各選取一個作為說明。
vector:它是一個動態分配存儲空間的容器。區別於c++中的array,array分配的空間是靜態的,分配之后不能被改變,而vector會自動重分配(擴展)空間。
set:其內部元素會根據元素的鍵值自動被排序。區別於map,它的鍵值就是實值,而map可以同時擁有不同的鍵值和實值。
(2)算法,如排序,復制……以及個容器特定的算法。這點不用過多介紹,主要看下面迭代器的內容。
(3)迭代器是STL的精髓,我們這樣描述它:迭代器提供了一種方法,使它能夠按照順序訪問某個容器所含的各個元素,但無需暴露該容器的內部結構。它將容器和算法分開,好讓這二者獨立設計。
STL中unordered_map和map的區別?
map是STL中的一個關聯容器,提供鍵值對的數據管理。底層通過紅黑樹來實現,實際上是二叉排序樹和非嚴格意義上的二叉平衡樹。所以在map內部所有的數據都是有序的,且map的查詢、插入、刪除操作的時間復雜度都是O(logN)。
unordered_map和map類似,都是存儲key-value對,可以通過key快速索引到value,不同的是unordered_map不會根據key進行排序。unordered_map底層是一個防冗余的哈希表,存儲時根據key的hash值判斷元素是否相同,即unoredered_map內部是無序的。
28、請編寫能直接實現strlen()函數功能的代碼
1 int strlen(char*str) 2 { 3 int i= 0; 4 for(;str[i]!=’\0’;i++); 5 return i; 6 }
請編寫能直接實現strstr()函數功能的代碼
1 char *strstr(char *str, const char *sub) 2 { 3 4 for(int i=0;i<strlen(str);i++) 5 { 6 char *p = &str[i]; 7 char *q=sub; 8 while(*p == *q) 9 { 10 p++; 11 q++; 12 if(*q==’\0’) 13 return &str[i]; 14 } 15
16 } 17 return null; 18 }
請編寫能直接實現strcmp()函數功能的代碼
1 int strcmp(const char *str1, const char *str2) 2 { 3 assert(str1 != NULL && str2 != NULL); 4 while (*str1 && *str1 == *str2) 5 { 6 str1++; 7 str2++; 8 } 9 if (*(unsigned char*)str1 < *(unsigned char*)str2) 10 { 11 return -1; 12 } 13 else if (*(unsigned char*)str1 > *(unsigned char*)str2) 14 { 15 return 1; 16 } 17 else
18 { 19 return 0; 20 } 21 } 注意: 1.參數是 const
2.異常輸入處理 assert(str1 != NULL&&str2 != NULL); 3.字符之間大小比較時一定要先將 char* 型指針先轉換為 unsigned char*
因為有符號字符值的范圍是-128~127,無符號字符值的范圍是0~255,而字符串的ASCII沒有負值。 例如 *str1的值為1,*str2的值為255。 本來 *str1 < *str2,但是有符號數中255是-1.
請編寫能直接實現strcat()函數功能的代碼
1 char *strcat(char *strDest, const char *strSrc) 2 { 3 assert(strDest != NULL && strSrc != NULL); 4 char* address = strDest; 5 while (*strDest != '\0') strDest++; 6 while (*strSrc != '\0') 7 { 8 *strDest = *strSrc; 9 strDest++; strSrc++; 10 } 11 *strDest = '\0'; // return address;
12 } 1.注意參數和返回值 2.要覆蓋原字符串’\0’,結尾添加’\0
請編寫能直接實現strcpy()函數功能的代碼
1 char *strcpy(char* strDest, const char*strSrc) 2 { 3 assert(strDest != NULL&&strSrc != NULL); 4
5 char* address = strDest; 6 while (*strSrc != '\0') 7 { 8 *strDest = *strSrc; 9 strDest++; 10 strSrc++; 11 } 12 *strDest = '\0'; 13
14 return address; 15 } 如果有必要也應該考慮地址重疊問題
請編寫能直接實現memcpy()函數功能的代碼
1 // 自己動手實現memcpy()時就需要考慮地址重疊的情況。
2 void *memcpy(void *dest, const void *src, size_t count) 3 { 4 char *d; 5 const char *s; 6
7 if (dest > (src + count)) || (dest < src)) 8 { 9 d = dest; 10 s = src; 11 while (count--) 12 *d++ = *s++; 13 } 14 else /* overlap */
15 { 16 d = (char *)(dest + count - 1); /* offset of pointer is from 0 */
17 s = (char *)(src + count -1); 18 while (count --) 19 *d-- = *s--; 20 } 21
22 return dest; 23 }
如何重載前++和后++運算符?
1 // 前++不帶參數,后++帶一個int型參數以示區分。 2
3 iCount &operator ++() // 前綴++
4 { 5 cout<<”前綴++”<<endl; 6 m_data++; 7 return *this; 8 } 9
10 iCount &operator ++(int) // 后置++
11 { 12 cout<<”后綴++”<<endl; 13 iCount temp=*this; 14 m_data++; 15 return temp; 16 }
C++中哪些運算符不可以重載?
不能重載的5個運算符:
(1) . (2) ?: (3) sizeof (4) :: (5) *
memset、memcpy和strcpy的根本區別
(1) memset用來對一段內存空間內全部設置為某個字符,一般用在對定義的字符串進行初始化為指定值;
(2) memcpy用來做內存拷貝,可以用來拷貝任何數據類型的對象,可以指定拷貝的數據長度;
(3) strcpy只能拷貝字符串,遇到’\0’就結束拷貝。
分別給出bool、int、float、指針變量與零值比較的if語句
(1)bool型變量:if(!var)
(2)int型變量:if(var==0)
(3)float型變量:
const float EPSINON=0.000001;
if((x>=- EPSINON)&&(x<= EPSINON))
(4)指針變量:if(var==NULL)
深度遍歷二叉樹
(1)深度優先搜索算法:沿着樹的深度遍歷樹的節點,盡可能深地搜索樹的分支;
(2)廣度優先搜索算法:又叫寬度優先搜索,或橫向優先搜索,是從根節點開始,沿着樹的寬度遍歷樹的節點,如果所有節點均被訪問則算法停止。
1 struct Node 2 { 3 Node *Parent; 4 Node *Left,*Right; 5 }; 6
7 void Through(Node *Root) 8 { 9 if(Root) printf(Root->data); 10 if(Root->Left != null) Through(Root->Left); // 左
11 if(Root->Right != null) Through(Root-> Right); // 右
12 }
29、一個數據成員是否可以既是const又是static,如果不行,為什么?
(1) 一個數據成員可以既是const又是static,表示為靜態常量;
(2) 常量一般在構造函數后初始化; 參考:https://blog.csdn.net/fengguangle/article/details/78019905
(3) 靜態成員一般在類外初始化;
(4) 靜態常量在類外初始化,但要在類外初始化的同時聲明為const。
30、構造函數與析構函數的異同點
1.構造函數有如下特點:
(1) 構造函數的名字必須與類名相同;
(2) 構造函數可以有任意類型的參數,但不能有返回類型;
(3) 定義對象時,編譯系統會自動調用構造函數;
(4) 構造函數是特殊的成員函數,函數體可以在類體內也可以在類體外;
(5) 構造函數被聲明為公有函數,但它不能像其他成員函數那樣被顯式調用,它是在定義對象的同時被調用的。
2.析構函數有如下特點:
(1)析構函數的名字必須與類名相同,但它前面必須加一個波浪號;
(2)析構函數沒有參數,也沒有返回值,而且不能被重載,因此在一個類中只能有一個析構函數;
(3)當撤銷對象時,編譯系統會自動調用析構函數;
(4)析構函數可以是virtual,而構造函數不能是虛函數。
構造函數為什么一般不定義為虛函數?而析構函數一般寫成虛函數的原因 ?
1、構造函數不能聲明為虛函數
1)因為創建一個對象時需要確定對象的類型,而虛函數是在運行時確定其類型的。而在構造一個對象時,由於對象還未創建成功,編譯器無法知道對象的實際類型,是類本身還是類的派生類等等。
2)虛函數的調用需要虛函數表指針,而該指針存放在對象的內存空間中;若構造函數聲明為虛函數,那么由於對象還未創建,還沒有內存空間,更沒有虛函數表地址用來調用虛函數即構造函數了。
2、析構函數最好聲明為虛函數
首先析構函數可以為虛函數,當析構一個指向派生類的基類指針時,最好將基類的析構函數聲明為虛函數,否則可以存在內存泄露的問題。
如果析構函數不被聲明成虛函數,則編譯器實施靜態綁定,在刪除指向派生類的基類指針時,只會調用基類的析構函數而不調用派生類析構函數,這樣就會造成派生類對象析構不完全。
31、自動調用復制(拷貝)構造函數的幾種情形
1. 拷貝構造函數的功能是用一個已知對象來初始化另一個同類的對象。拷貝構造函數其實也是類的構造函數,與類名相同,有且只有一個參數,是該類對象的引用;每個類必須有一個拷貝構造函數。如果定義類的時候沒有編寫,編譯器編譯時會自動生成一個拷貝構造函數。
2. 拷貝構造函數在三種情況下會自動被調用:
(1)當類的一個對象去初始化該類的另一個對象時;
(2)如果函數的形參是類的對象,調用函數進行形參和實參結合時;
(3)如果函數的返回值是類對象,函數調用完成返回時。
拷貝構造函數和賦值函數重載 (operator=):
定義會調用拷貝構造函數, 賦值會調用賦值函數(operator=);
拷貝構造函數是一種特殊的構造函數, 參數是一個變量實例而已。
32、成員函數和友元函數的區別
(1)成員函數是類定義的一部分,通過特定的對象來調用。成員函數既可以隱式訪問調用對象的成員,而無須使用成員操作符;
(2)友元函數不是類的組成部分,因此被稱為直接函數調用。友元函數不能隱式訪問類成員,而必須將成員操作符用於作為參數傳遞的對象。類的友元函數是定義在類外部,但有權訪問類的所有私有(private)成員和保護(protected)成員。
33、函數模板與函數重載的異同?
(1)函數的重載是指定義了幾個名字相同,但參數的類型或參數的個數不同的函數;
(2)模板函數是指的幾個函數的具體算法相同,而參數類型不同的函數;
(3)模板函數可以減少重載函數,但也可能引發錯誤。
34、靜態綁定和動態綁定的介紹
靜態綁定和動態綁定是C++多態性的一種特性
1)對象的靜態類型和動態類型
靜態類型:對象在聲明時采用的類型,在編譯時確定;
動態類型:當前對象所指的類型,在運行期決定,對象的動態類型可變,靜態類型無法更改;
2)靜態綁定和動態綁定
靜態綁定:綁定的是對象的靜態類型,函數依賴於對象的靜態類型,在編譯期確定;
動態綁定:綁定的是對象的動態類型,函數依賴於對象的動態類型,在運行期確定;
只有虛函數才使用的是動態綁定,其他的全部是靜態綁定
35、引用是否能實現動態綁定,為什么引用可以實現
可以。因為引用(或指針)既可以指向基類對象也可以指向派生類對象,這一事實是動態綁定的關鍵。用引用(或指針)調用的虛函數在運行時確定,被調用的函數是引用(或指針)所指的對象的實際類型所定義的。
36、深拷貝和淺拷貝的區別
深拷貝和淺拷貝可以簡單的理解為:如果一個類擁有資源,當這個類的對象發生復制過程的時候,如果資源重新分配了就是深拷貝;反之沒有重新分配資源,就是淺拷貝。
在對含有指針成員的對象進行拷貝時,必須要自己定義拷貝構造函數,使拷貝后的對象指針成員有自己的內存空間,即進行深拷貝,這樣就避免了內存泄漏發生。
總結:淺拷貝只是對指針的拷貝,拷貝后兩個指針指向同一個內存空間,深拷貝不但對指針進行拷貝,而且對指針指向的內容進行拷貝,經深拷貝后的指針是指向兩個不同地址的指針。
37、C++的四種強制轉換
類型轉化機制可以分為隱式類型轉換和顯示類型轉化(強制類型轉換)
(new-type) expression
new-type (expression)
隱式類型轉換比較常見,在混合類型表達式中經常發生;
四種強制類型轉換操作符:
static_cast、dynamic_cast、const_cast、reinterpret_cast
1)static_cast :編譯時期的靜態類型檢查
static_cast < type-id > ( expression )
該運算符把expression轉換成type-id類型,在編譯時使用類型信息執行轉換,在轉換時執行必要的檢測(指針越界、類型檢查),其操作數相對是安全的;
2)dynamic_cast:運行時的檢查
用於在集成體系中進行安全的向下轉換downcast,即基類指針/引用->派生類指針/引用;
dynamic_cast是4個轉換中唯一的RTTI操作符,提供運行時類型檢查;
dynamic_cast如果不能轉換返回NULL;
源類中必須要有虛函數,保證多態,才能使用dynamic_cast<source>(expression);
3)const_cast
去除const常量屬性,使其可以修改 ; volatile屬性的轉換;
4)reinterpret_cast
通常為了將一種數據類型轉換成另一種數據類型;
38、調試程序的方法
windows下直接使用vs的debug功能;
linux下直接使用gdb,我們可以在其過程中給程序添加斷點,監視等輔助手段,監控其行為是否與我們設計相符。(上文有gdb的指令集合鏈接);
39、volatile關鍵字在程序設計中有什么作用
volatile是“易變的”、“不穩定”的意思。volatile是C的一個較為少用的關鍵字,它用來解決變量在“共享”環境下容易出現讀取錯誤的問題;
40、引用作為函數參數以及返回值的好處
對比值傳遞,引用傳參的好處:
1)在函數內部可以對此參數進行修改;
2)提高函數調用和運行的效率(所以沒有了傳值和生成副本的時間和空間消耗);
如果函數的參數實質就是形參,不過這個形參的作用域只是在函數體內部,也就是說實參和形參是兩個不同的東西,要想形參代替實參,肯定有一個值的傳遞。函數調用時,值的傳遞機制是通過“形參=實參”來對形參賦值達到傳值目的,產生了一個實參的副本。即使函數內部有對參數的修改,也只是針對形參,也就是那個副本,實參不會有任何更改。函數一旦結束,形參生命也宣告終結,做出的修改一樣沒對任何變量產生影響。
用引用作為返回值最大的好處就是在內存中不產生被返回值的副本。但是有以下的限制:
1)不能返回局部變量的引用,因為函數返回以后局部變量就會被銷毀;
2)不能返回函數內部new分配的內存的引用。雖然不存在局部變量的被動銷毀問題,可對於這種情況(返回函數內部new分配內存的引用),又面臨其它尷尬局面。例如,被函數返回的引用只是作為一 個臨時變量出現,而沒有被賦予一個實際的變量,那么這個引用所指向的空間(由new分配)就無法釋放,造成memory leak;
3)可以返回類成員的引用,但是最好是const。因為如果其他對象可以獲得該屬性的非常量的引用,那么對該屬性的單純賦值就會破壞業務規則的完整性。
41、線程安全和線程不安全
線程安全就是多線程訪問時,采用了加鎖機制,當一個線程訪問該類的某個數據時,進行保護,其他線程不能進行訪問直到該線程讀取完,其他線程才可以使用,不會出現數據不一致或者數據污染。
線程不安全就是不提供數據訪問保護,有可能多個線程先后更改數據所得到的數據就是臟數據。
42、友元函數和友元類
友元提供了不同類的成員函數之間、類的成員函數和一般函數之間進行數據共享的機制。
通過友元,一個不同函數或者另一個類中的成員函數可以訪問類中的私有成員和保護成員。
友元的正確使用能提高程序的運行效率,但同時也破壞了類的封裝性和數據的隱藏性,導致程序可維護性變差。
1)友元函數
友元函數是可以訪問類的私有成員的非成員函數。它是定義在類外的普通函數,不屬於任何類,但是需要在類的定義中加以聲明:
friend 類型 函數名(形式參數);
一個函數可以是多個類的友元函數,只需要在各個類中分別聲明。
2)友元類
友元類的所有成員函數都是另一個類的友元函數,都可以訪問另一個類中的隱藏信息(包括私有成員和保護成員)。
friend class 類名;
使用友元類時注意:
(1) 友元關系不能被繼承。
(2) 友元關系是單向的,不具有交換性。若類B是類A的友元,類A不一定是類B的友元,要看在類中是否有相應的聲明。
(3) 友元關系不具有傳遞性。若類B是類A的友元,類C是B的友元,類C不一定是類A的友元,同樣要看類中是否有相應的申明
43、C++線程中的幾種鎖機制
線程之間的鎖有:互斥鎖、條件鎖、自旋鎖、讀寫鎖、遞歸鎖。一般而言,鎖的功能越強大,性能就會越低
1)互斥鎖
互斥鎖用於控制多個線程對他們之間共享資源互斥訪問的一個信號量。也就是說是為了避免多個線程在某一時刻同時操作一個共享資源。例如線程池中的有多個空閑線程和一個任務隊列。任何是一個線程都要使用互斥鎖互斥訪問任務隊列,以避免多個線程同時訪問任務隊列以發生錯亂。
在某一時刻,只有一個線程可以獲取互斥鎖,在釋放互斥鎖之前其他線程都不能獲取該互斥鎖。如果其他線程想要獲取這個互斥鎖,那么這個線程只能以阻塞方式進行等待。
2)條件鎖
條件鎖就是所謂的條件變量,某一個線程因為某個條件未滿足時可以使用條件變量使該程序處於阻塞狀態。一旦條件滿足以“信號量”的方式喚醒一個因為該條件而被阻塞的線程。最為常見就是在線程池中,起初沒有任務時任務隊列為空,此時線程池中的線程因為“任務隊列為空”這個條件處於阻塞狀態。一旦有任務進來,就會以信號量的方式喚醒一個線程來處理這個任務。這個過程中就使用到了條件變量pthread_cond_t。
3)自旋鎖
前面的兩種鎖是比較常見的鎖,也比較容易理解。下面通過比較互斥鎖和自旋鎖原理的不同,這對於真正理解自旋鎖有很大幫助。
假設我們有一個兩個處理器core1和core2計算機,現在在這台計算機上運行的程序中有兩個線程:T1和T2分別在處理器core1和core2上運行,兩個線程之間共享着一個資源。
首先我們說明互斥鎖的工作原理,互斥鎖是是一種sleep-waiting的鎖。假設線程T1獲取互斥鎖並且正在core1上運行時,此時線程T2也想要獲取互斥鎖(pthread_mutex_lock),但是由於T1正在使用互斥鎖使得T2被阻塞。當T2處於阻塞狀態時,T2被放入到等待隊列中去,處理器core2會去處理其他任務而不必一直等待(忙等)。也就是說處理器不會因為線程阻塞而空閑着,它去處理其他事務去了。而自旋鎖就不同了,自旋鎖是一種busy-waiting的鎖。也就是說,如果T1正在使用自旋鎖,而T2也去申請這個自旋鎖,此時T2肯定得不到這個自旋鎖。與互斥鎖相反的是,此時運行T2的處理器core2會一直不斷地循環檢查鎖是否可用(自旋鎖請求),直到獲取到這個自旋鎖為止。
從“自旋鎖”的名字也可以看出來,如果一個線程想要獲取一個被使用的自旋鎖,那么它會一致占用CPU請求這個自旋鎖使得CPU不能去做其他的事情,直到獲取這個鎖為止,這就是“自旋”的含義。
注意:自旋鎖適合於短時間的的輕量級的加鎖機制。
4)讀寫鎖
說到讀寫鎖我們可以借助於“讀者-寫者”問題進行理解。首先我們簡單說下“讀者-寫者”問題。
計算機中某些數據被多個進程共享,對數據庫的操作有兩種:一種是讀操作,就是從數據庫中讀取數據不會修改數據庫中內容;另一種就是寫操作,寫操作會修改數據庫中存放的數據。因此可以得到我們允許在數據庫上同時執行多個“讀”操作,但是某一時刻只能在數據庫上有一個“寫”操作來更新數據。這就是一個簡單的讀者-寫者模型。
44、i++是否為原子操作?
不是。操作系統原子操作是不可分割的,在執行完畢不會被任何其它任務或事件中斷,分為兩種情況(兩種都應該滿足):
(1) 在單線程中, 能夠在單條指令中完成的操作都可以認為是" 原子操作",因為中斷只能發生於指令之間。
(2) 在多線程中,不能被其它進程(線程)打斷的操作就叫原子操作。
i++分為三個階段:
內存到寄存器
寄存器自增
寫回內存
這三個階段中間都可以被中斷分離開.
45、指針數組 數組指針 指針函數 函數指針
指針數組:指針數組可以說成是”指針的數組”,首先這個變量是一個數組,其次,”指針”修飾這個數組,意思是說這個數組的所有元素都是指針類型,在32位系統中,指針占四個字節。
數組指針:數組指針可以說成是”數組的指針”,首先這個變量是一個指針,其次,”數組”修飾這個指針,意思是說這個指針存放着一個數組的首地址,或者說這個指針指向一個數組的首地址。
根據上面的解釋,可以了解到指針數組和數組指針的區別,因為二者根本就是種類型的變量。
int *p[4]; //指針數組。 是個有4個元素的數組, 每個元素的是指向整型的指針。(數組的每個元素都是指針)
int (*p)[4]; //數組指針。 它是一個指針,指向有4個整型元素的數組。(一個指針指向有4個整型元素的數組)
int *func(void); //指針函數。 無參函數, 返回整型指針。(函數的返回值為 int*)
int (*func)(void); //表示函數指針,可以指向無參, 且返回值為整型指針的函數。(函數的返回值為int)
右左規則:
因為C語言所有復雜的指針聲明,都是由各種聲明嵌套構成的。如何解讀復雜指針聲明呢?右左法則是一個既着名又常用的方法。不過,右左法則其實並不是C標准里面的內容,它是從C標准的聲明規定中歸納出來的方法。C標准的聲明規則,是用來解決如何創建聲明的,而右左法則是用來解決如何辯識一個聲明的,兩者可以說是相反的;
右左法則:首先從最里面的圓括號(未定義的標識符)看起,然后往右看,再往左看。每當遇到圓括號時,就應該掉轉閱讀方向。一旦解析完圓括號里面所有的東西,就跳出圓括號。重復這個過程直到整個聲明;
筆者要對這個法則進行一個小小的修正,應該是從未定義的標識符開始閱讀,而不是從括號讀起,之所以是未定義的標識符,是因為一個聲明里面可能有多個標識符,但未定義的標識符只會有一個。現在通過一些例子來討論右左法則的應用,先從最簡單的開始,逐步加深:
int (*func)(int *p);
首先找到那個未定義的標識符,就是func,它的外面有一對圓括號,而且左邊是一個*號,這說明func是一個指針,然后跳出這個圓括號,先看右邊,也是一個圓括號,這說明(*func)是一個函數,而func是一個指向這類函數的指針,就是一個函數指針,這類函數具有int*類型的形參,返回值類型是 int。
int (*func)(int *p, int (*f)(int*));
func被一對括號包含,且左邊有一個*號,說明func是一個指針,跳出括號,右邊也有個括號,那么func是一個指向函數的指針,這類函數具有int *和int (*)(int*)這樣的形參,返回值為int類型。再來看一看func的形參int (*f)(int*),類似前面的解釋,f也是一個函數指針,指向的函數具有int*類型的形參,返回值為int。
int (*func[5])(int *p);
func右邊是一個[]運算符,說明func是一個具有5個元素的數組,func的左邊有一個*,說明func的元素是指針,要注意這里的*不是修飾 func的,而是修飾func[5]的,原因是[]運算符優先級比*高,func先跟[]結合,因此*修飾的是func[5]。跳出這個括號,看右邊,也是一對圓括號,說明func數組的元素是函數類型的指針,它所指向的函數具有int*類型的形參,返回值類型為int。
46、C++四種類型轉換的關鍵字及其特點
C++的四種強制類型轉換,所以C++不是類型安全的。關鍵字分別為:static_cast , dynamic_cast , const_cast , reinterpret_cast
四種轉換的區別:
static_cast:可以實現C++中內置基本數據類型之間的相互轉換。如果涉及到類的話,static_cast只能在有相互聯系的類型中進行相互轉換,不一定包含虛函數。
const_cast: const_cast操作不能在不同的種類間轉換。相反,它僅僅把一個它作用的表達式轉換成常量。它可以使一個本來不是const類型的數據轉換成const類型的,或者把const屬性去掉。
reinterpret_cast: 有着和C風格的強制轉換同樣的能力。它可以轉化任何內置的數據類型為其他任何的數據類型,也可以轉化任何指針類型為其他的類型。它甚至可以轉化內置的數據類型為指針,無須考慮類型安全或者常量的情形。不到萬不得已絕對不用。
dynamic_cast:
(1)其他三種都是編譯時完成的,dynamic_cast是運行時處理的,運行時要進行類型檢查。
(2)不能用於內置的基本數據類型的強制轉換。
(3)dynamic_cast轉換如果成功的話返回的是指向類的指針或引用,轉換失敗的話則會返回NULL。
(4)使用dynamic_cast進行轉換的,基類中一定要有虛函數,否則編譯不通過。
(5)在類的轉換時,在類層次間進行上行轉換時,dynamic_cast和static_cast的效果是一樣的。在進行下行轉換時,dynamic_cast具有類型檢查的功能,比static_cast更安全。向上轉換即為指向子類對象的向下轉換,即將父類指針轉化子類指針。向下轉換的成功與否還與將要轉換的類型有關,即要轉換的指針指向的對象的實際類型與轉換以后的對象類型一定要相同,否則轉換失敗。
47、內存對齊的原則以及作用?
- 結構體內的成員按自身長度自對齊(32位機器上,如char=1,short=2,int=4,double=8),所謂自對齊是指該成員的起始地址必須是它自身長度的整數倍。如int只能以0,4,8這類地址開始。
- 結構體的總大小為結構體的有效對齊值的整數倍(默認以結構體中最長的成員長度為有效值的整數倍,當用#pragrma pack(n)指定時,以n和結構體中最長的成員的長度中較小者為其值)。即sizeof的值,必須是其內部最大成員的整數倍,不足的要補齊。
內存對齊的作用:
1、平台原因(移植原因):不是所有的硬件平台都能訪問任意地址上的任意數據的;某些硬件平台只能在某些地址處取某些特定類型的數據,否則拋出硬件異常。
2、性能原因:經過內存對齊后,CPU的內存訪問速度大大提升。
48、auto_ptr類與shared_ptr類
從c++11開始, auto_ptr已經被標記為棄用, 常見的替代品為shared_ptr。shared_ptr的不同之處在於引用計數, 在復制(或賦值)時不會像auto_ptr那樣直接轉移所有權。 兩者都是模板類,卻可以像指針一樣去使用。只是在指針上面的一層封裝。
auto_ptr實際也是一種類, 擁有自己的析構函數, 生命周期結束時能自動釋放資源,正因為能自動釋放資源, 特別適合在單個函數內代替new/delete的調用, 不用自己調用delete,也不用擔心意外退出造成內存的泄漏。
atuo_ptr的缺陷:
- auto_ptr不能共享所有權,即不要讓兩個auto_ptr指向同一個對象(因為它采用的是轉移語義的拷貝,原指針會變為NULL)。
- auto_ptr不能管理對象數組(因為它內部的析構函數調用的是delete而不是delete[])。
- auto_ptr不能作為容器對象,STL容器中的元素經常要支持拷貝,賦值等操作,在這過程中auto_ptr會傳遞所有權。
shared_ptr 使用引用計數的方式來實現對指針資源的管理。同一個指針資源,可以被多個 shared_ptr 對象所擁有,直到最后一個 shared_ptr 對象析構時才釋放所管理的對象資源。
可以說,shared_ptr 是最智能的智能指針,因為其特點最接近原始的指針。不僅能夠自由的賦值和拷貝,而且可以安全的用在標准容器中。
參考STL智能指針:https://blog.csdn.net/k346k346/article/details/81478223