這次整理來自QQ羣186588041,全部資料來自譚校長、張教主、H神、Robort、fight for dream、刀刀、二貨(也就是我)
主要是總結了一些常常被問道的面試題
經驗不足,水平有限,但願讀者能提出寶貴的意見~~~~
這個和CSDN上個人blog是同步的:http://blog.csdn.net/charles_r_chiu/article/details/47858885html
答:虛函數聲明以下: virtual ReturnType FunctionName(Parameter);引入虛函數是爲了動態綁定。
純虛函數聲明以下:virtual ReturnType FunctionName()= 0;引入純虛函數是爲了派生接口。python
答:標準規定:當derived class經由一個base class指針被刪除而該base class的析構函數爲non-virtual時,將發生未定義行爲。一般將發生資源泄漏。
解決方法即爲:爲多態基類聲明一個virtual 析構函數。ios
答:理論上++i更快,實際與編譯器優化有關,一般幾乎無差異。
i++實現的代碼爲:程序員
//i++實現代碼爲: int operator++(int) { int temp = *this; ++*this; return temp; }//返回一個int型的對象自己
++i的實現代碼:面試
// ++i實現代碼爲: int& operator++() { *this += 1; return *this; }//返回一個int型的對象引用
i++和++i的考點比較多,簡單來講,就是i++返回的是i的值,而++i返回的是i+1的值。也就是++i是一個肯定的值,是一個可修改的左值,以下使用:算法
cout << ++(++(++i)) << endl; cout << ++ ++i << endl;
能夠不停的嵌套++i。
這裏有不少的經典筆試題,一塊兒來觀摩下:express
int main() { int i = 1; printf("%d,%d\n", ++i, ++i); //3,3 printf("%d,%d\n", ++i, i++); //5,3 printf("%d,%d\n", i++, i++); //6,5 printf("%d,%d\n", i++, ++i); //8,9 system("pause"); return 0; }
首先是函數的入棧順序從右向左入棧的,計算順序也是從右往左計算的,不過都是計算完之後在進行的壓棧操做:
對於第5行代碼,首先執行++i,返回值是i,這時i的值是2,再次執行++i,返回值是i,獲得i=3,將i壓入棧中,此時i爲3,也就是壓入3,3;
對於第6行代碼,首先執行i++,返回值是原來的i,也就是3,再執行++i,返回值是i,依次將3,5壓入棧中獲得輸出結果
對於第7行代碼,首先執行i++,返回值是5,再執行i++返回值是6,依次將5,6壓入棧中獲得輸出結果
對於第8行代碼,首先執行++i,返回i,此時i爲8,再執行i++,返回值是8,此時i爲9,依次將i,8也就是9,8壓入棧中,獲得輸出結果。
上面的分析也是基於vs搞的,不過準確來講函數多個參數的計算順序是未定義的(the order of evaluation of function arguments are undefined)。筆試題目的運行結果隨不一樣的編譯器而異。
參考:http://www.stroustrup.com/bs_faq2.html#evaluation-order
下面是使用其餘編譯器輸出的結果:編程
//------ C++ version ------ #include <cstdio> int main() { int i = 1; printf("%d %d\n", ++i, ++i); printf("%d %d\n", ++i, i++); printf("%d %d\n", i++, i++); printf("%d %d\n", i++, ++i); return 0; } //------ C version ------ #include <stdio.h> int main() { int i = 1; printf("%d %d\n", ++i, ++i); printf("%d %d\n", ++i, i++); printf("%d %d\n", i++, i++); printf("%d %d\n", i++, ++i); return 0; }
上面是參考的執行代碼。
gcc-5.1(C++) gcc-5.1(C++14) gcc-5.1(C) 的執行結果:數組
3 3 5 3 6 5 8 9
gcc-4.3.2(C++) clang-3.7(C++) clang-3.7(C) 的執行結果安全
2 3 4 4 5 6 7 9
gcc-5.1(C99 strict) 的編譯結果:編譯不經過。
C99 strict prog.c: In function 'main': prog.c:6:28: error: operation on 'i' may be undefined [-Werror=sequence-point] printf("%d %d\n", ++i, ++i); ^ prog.c:7:29: error: operation on 'i' may be undefined [-Werror=sequence-point] printf("%d %d\n", ++i, i++); ^ prog.c:8:29: error: operation on 'i' may be undefined [-Werror=sequence-point] printf("%d %d\n", i++, i++); ^ prog.c:9:28: error: operation on 'i' may be undefined [-Werror=sequence-point] printf("%d %d\n", i++, ++i); ^ cc1: all warnings being treated as errors
由上可見,這種比較毫無心義。
答:reserve()用於讓容器預留空間,避免再次內存分配;capacity() 返回在從新進行內存分配之前所能容納的元素數量。
答:template內出現的類型若是依賴於某個template參數,則稱之爲從屬類型;若是從屬類型在class內呈嵌套狀,則稱之爲嵌套從屬類型。
template<typename C> void doSomething(const C& container) { if(container.size() > 0) C::iterator iter(container.begin()); }
此時,根據C++的規則,編譯器先假設C::iterator不是一個類型。然而iter的聲明只有在C::iterator是一個類型時才合理。所以須要咱們本身告訴編譯器。
那麼,就須要再C::iterator以前加上typename,告訴編譯器C::iterator是一個類型。
template<typename C> void doSomething(const C& container) { if(container.size() > 0) typename C::iterator iter(container.begin()); }
如上就是。
答:不能夠。
當複製一個auto_ptr時,它所指向的對象的全部權被交到複製的auto_ptr上面,而它自身將被設置爲null。複製一個auto_ptr意味着改變它的值。
答:一般在類外初始化static數據成員,可是 static const 的整型(bool,char,int,long)能夠再類聲明中初始化,static const的其餘類型也必須在類外初始化(包括整型的數組)。
答:總的思想是RAII:設計一個class,令他的構造函數和析構函數分別獲取和釋放資源。
有兩個方法:
利用「函數的局部對象不管函數以何種方式(包括因異常)結束都會被析構」這一特性,將「必定要釋放的資源」放進局部對象的析構函數;
使用智能指針。
答:在這裏把各類繼承都說一下:
補充:不一樣繼承描述符下,基類的各類修飾符下的成員轉換爲子類成員的修飾符示意:
參考:《TC++PL》 p363
繼承描述符 | 父public成員 | 父protected成員 | 父private成員 |
---|---|---|---|
public | 子public成員 | 子protected成員 | - |
protected | 子protected成員 | 子protected成員 | - |
private | 子private成員 | 子private成員 | - |
答:
(1)、將構造函數、析構函數、複製構造函數、賦值操做符聲明爲私有,便可實現單例模式
單例模式實現代碼一般爲:
class Singleton { public: static Singleton* Instance(); protected: Singleton(); private: static Singleton* _instance; }; Singleton::Singleton(){} Singleton* Singleton::_instance = nullptr; Singleton* Singleton::Instance() { if(_instance == nullptr) _instance = new Singleton; return _instance; }
(2)、避免用戶的複製行爲,能夠將複製構造函數聲明爲private或者使用C++11中的delete語法。
(3)、實現線程安全的單例模式:上面實現中的GetInstance()不是線程安全的,由於在單例的靜態初始化中存在競爭條件。若是碰巧有多個線程在同時調用該方法,那麼就有可能被構造屢次。
比較簡單的作法是在存在競爭條件的地方加上互斥鎖。這樣作的代價是開銷比較高。由於每次方法調用時都須要加鎖。
比較經常使用的作法是使用雙重檢查鎖定模式(DCLP)。可是DCLP並不能保證在全部編譯器和處理器內存模型下都能正常工做。如,共享內存的對稱多處理器一般突發式提交內存寫操做,這會形成不一樣線程的寫操做從新排序。這種狀況一般能夠採用volatile解決,他能將讀寫操做同步到易變數據中,但這樣在多線程環境下仍舊存在問題。
答: 相等(equality)是以operator==爲基礎,若是x==y爲真,則斷定x和y相等。
等價(equavalence)是以operator<爲基礎,若是!(x < y) && !(y < x)爲真,則斷定x和y等價。
一般,關聯容器採用「等價」,而順序容器採用「相等」。
答:function object就是重載了函數調用操做符 operator()的一個struct或者class
全部內置一元仿函數均繼承自unary_function,全部內置二元仿函數均繼承自binary_function
繼承自unary_function和binary_function的仿函數能夠成爲「可配接「的仿函數。可配接的仿函數,可以與其餘STL組件更」和諧「地協同工做。
答: (1)、構造函數拋異常:不會發生資源泄漏。假設在operator new()時拋出異常,那麼將會因異常而結束這次調用,內存分配失敗,不可能存在內存泄露。假設在別處(operator new() )執行以後拋出異常,此時析構函數調用,已構造的對象將得以正確釋放,且自動調用operator delete()釋放內存
析構函數拋異常:
能夠拋出異常,但該異常必須留在析構函數;若析構函數因異常退出,狀況會很糟糕(all kinds of bad things are likely to happen)
a、可能使得已分配的對象未能正常析構,形成內存泄露;
b、例如在對像數組的析構時,若是對象的析構函數拋出異常,釋放代碼將引起未定義行爲。考慮一個對象數組的中間部分在析構時拋出異常,它沒法傳播,由於傳播的話將使得後續部分不能正常釋放;它也沒法吸取,由於這違反了」異常中立「原則(異常中立,就是指任何底層的異常都會拋出到上層,也就至關因而異常透明的)。
(2)、拋出異常時,將暫停當前函數的執行,開始查找匹配的catch子句。首先檢查throw自己是否在try塊內部若是是,檢查與該try相關的catch子句,看是否能夠處理該異常。若是不能處理,就退出當前函數,而且釋放當前函數的內存並銷燬局部對象,繼續到上層的調用函數中查找,直到找到一個能夠處理該異常的catch。
答:使用mutable去掉const的成員函數的const性質
const_cast和mutable的比較
const_cast:
1) 強制去掉對象的const屬性。
2) 缺點:對const對象,調用包含const_cast的const成員函數,屬於未定義行爲。
mutable:
1) 使用場景:對可能要發生變化的成員前,加上存儲描述符mutable。
2) 實質:對加了mutable的成員,無視全部const聲明。
爲何要有這種去除常量標誌的需求?
答:兩個概念:物理常量性和邏輯常量性
物理常量性:實際上就是常量。
邏輯常量性:對用戶而言是常量,但在用戶不能訪問的細節上不是常量。
參考:《TC++PL》 p206
答:(1)、
a、使用單參數的構造函數或N個參數中有N-1個是默認參數的構造函數,如:
class A { public: A(stirng s); A(string s,int a = 0); };
b、使用operator what_you_want_to_convert_type() const
class A { public: operator char*() const { return data;//當從其餘類型轉換到char*時自動調用 } private: char* data; };
(2)、在單參數的構造函數或N個參數中有N-1個是默認參數的構造函數聲明以前加上explicit。
解答:這個問題主要是針對連續內存容器和非連續內存容器。
a、對於連續內存容器,如vector、deque等,增減元素均會使得當前以後的全部迭代器失效。所以,以刪除元素爲例:因爲erase()老是指向被刪除元素的下一個元素的有效迭代器,所以,能夠利用該連續內存容器的成員erase()函數的返回值。常見的編程寫法爲:
for(auto iter = myvec.begin(); iter != myvec.end()) //另外注意這裏用 "!=" 而非 "<" { if(delete iter) iter = myvec.erase(iter); else ++iter; }
還有兩種極端的狀況是:
(1)、vector插入元素時位置過於靠前,致使須要後移的元素太多,所以vector增長元素建議使用push_back而非insert;
(2)、當增長元素後整個vector的大小超過了預設,這時會致使vector從新分分配內存,效率極低。所以習慣的編程方法爲:在聲明瞭一個vector後,當即調用reserve函數,令vector能夠動態擴容。一般vector是按照以前大小的2倍來增加的。
b、對於非連續內存容器,如set、map等。增減元素只會使得當前迭代器無效。仍以刪除元素爲例,因爲刪除元素後,erase()返回的迭代器將是無效的迭代器。所以,須要在調用erase()以前,就使得迭代器指向刪除元素的下一個元素。常見的編程寫法爲:
for(auto iter = myset.begin(); iter != myset.end()) //另外注意這裏用 "!=" 而非 "<" { if(delete iter) myset.erase(iter++); //使用一個後置自增就OK了 else ++iter; }
其實在C++11中erase的返回值就是下一個節點,也能夠利用函數的返回值。
解答:這個問題答案過於複雜,建議直接百度這個問題,網上的說法已經足夠完善。
不過針對網上的一些說法補充而且糾正一下。網上在回答這個問題的時候沒有對new操做符進行深刻的解釋,在這裏大體說一下。
new可分爲operator new(new 操做)、new operator(new 操做符)和placement new(定位 new)。其中operator new執行和malloc相同的任務,即分配內存,但對構造函數一無所知;而 new operator則調用operator new,分配內存後再調用對象構造函數進行對象的構造。
其中operator new是能夠重載的。placement new,就是operator new的一個重載版本,容許你在一個已經分配好的內存中構造一個新的對象。而網上對new說法,大多針對operator new而言,所以說new是帶有類型的(覺得調用了類的構造函數),不過若是直接就說new是帶有類型的話,明顯是不合適的,好比原生的operator new。能夠參考個人一個程序,這個程序是用代理模式實現一個自定義二維數組,在第二個維度拷貝構造的時候, 拷貝構造須要深拷貝(固然第一個維度也須要),執行深拷貝時代碼大體以下:
class Array2D //二維數組模板 { private: size_t length2,length1; //數組各個維的大小 Array1D<T>* data; } void* raw =::operator new[](length2*sizeof(Array1D<T>)); data = static_cast<Array1D<T>*>(raw);
可見執行operator new的時候申請的原生內存是能夠不帶有類型的。
1) malloc()分配指定字節數的未經初始化的內存空間,返回的是void指針;new操做符爲一個指定類型的對象分配空能,並調用其構造函數初始化,返回的是該對象的指針。
2) malloc()必需要作初始化,以及將void指針轉換成合適類型的指針。同時要考慮到分配的內存大小正好是你所須要的大小。當new操做符使用"(value)" notation,便可獲得值爲value的初始化。若是考慮上初始化的開銷,malloc()和new沒有性能上的差異。
解答:這其實能夠看作是一個編程風格的問題。
a、使用RAII(Resource Acquisition Is Initialization,資源獲取即初始化)技法,以構造函數獲取資源(內存),析構函數釋放。
b、相比於使用原生指針,更建議使用智能指針,尤爲是C++11標準化後的智能指針。
c、注意delete和delete[]的使用方法。
d、這是很複雜的一種狀況,是關於類的copy constructor的。首先先介紹一些概念。
同default constructor同樣,標準保證,若是類做者沒有爲class聲明一個copy constructor,那麼編譯器會在須要的時候產生出來(這也是一個常考點:問道"若是類做者未定義出default/copy constructor,編譯器會自動產生一個嗎?"答案是否認的)
不過請注意!!這裏編譯器即便產生出來,也是爲知足它的需求,而非類做者的需求!!
而何時是編譯器"須要"的時候呢?是在當這個class 【不表現出】bitwise copy semantics(位逐次拷貝,即淺拷貝)的時候。
在4中狀況下class【不表現出】bitwise copy semantics
(1)、當class內含一個member object且該member object聲明瞭一個copy constructor(不管該copy ctor是類做者本身生明的仍是編譯器合成的);
(2)、當class繼承自一個base class且該base class有一個copy constructor(不管該copy ctor是類做者本身生明的仍是編譯器合成的);
(3)、當class聲明瞭virtual function;
(4)、當class派生自一個繼承鏈,且該鏈中存在virtual base class時。
言歸正傳,若是class中僅僅是一些普通資源,那麼default memberwise copy是徹底夠用的;然而,擋在該class中存在了一塊動態分配的內存,而且在以後執行了bitwise copy semantics後,將會有一個按位拷貝的對象和原來class中的某個成員指向同一塊heap空間,當執行它們的析構函數後,該內存將被釋放兩次,這是未定義的行爲。所以,在必要的時候須要使用user-defined explicit copy constructor,來避免內存泄露。
解答:STL中的sort(),在數據量大時,採用quicksort,分段遞歸排序;一旦分段後的數量小於某個門限值,改用Insertion sort,避免quicksort深度遞歸帶來的過大的額外負擔,若是遞歸層次過深,還會改用heapsort。
本質:指針是一個變量,存儲內容是一個地址,指向內存的一個存儲單元。而引用是原變量的一個別名,實質上和原變量是一個東西,是某塊內存的別名。
指針的值能夠爲空,且非const指針能夠被從新賦值以指向另外一個不一樣的對象。而引用的值不能爲空,而且引用在定義的時候必須初始化,一旦初始化,就和原變量「綁定」,不能更改這個綁定關係。
不過以下的寫法也是同的過編譯器的:
int *iptr = NULL; int& iref = *iptr;
可是,上面的寫法是非人類的。
對指針執行sizeof()操做獲得的是指針自己的大小(32位系統爲4,64位系統爲8)。而對引用執行sizeof()操做,因爲引用自己只是一個被引用的別名,因此獲得的是所綁定的對象的所佔內存大小。
指針的自增(++)運算表示對地址的自增,自增大小要看所指向單元的類型。而引用的自增(++)運算表示對值的自增。
在做爲函數參數進行傳遞時的區別:指針做爲函數傳輸做爲傳遞時,函數內部的指針形參是指針實參的一個副本,改變指針形參並不能改變指針實參的值,經過解引用*運算符來更改指針所指向的內存單元裏的數據。而引用在做爲函數參數進行傳遞時,實質上傳遞的是實參自己,即傳遞進來的不是實參的一個拷貝,所以對形參的修改實際上是對實參的修改,因此在用引用進行參數傳遞時,不只節約時間,並且能夠節約空間。
上面是引用和指針的區別,總的來講,若是你須要一個可能會爲空,或者還會指向別的值的時候,就使用指針,若是是要一開始就要指向一個object,並不會改變的時候就能夠只用引用。
顧名思義,數組指針應該是指向數組的指針,而指針數組則是指該數組的元素均爲指針。
數組指針,是指向數組的指針,其本質爲指針,形如int (*p)[10],p即爲指向數組的指針;數組指針是指向數組首元素的地址的指針,其本質爲指針,能夠當作是二級指針
指針數組,在C語言和C++中,數組元素全爲指針的數組稱爲指針數組,其中一維指針數組的定義形式爲:
類型名 *數組標識符[數組長度]
指針數組中每個元素均爲指針,其本質爲數組,例如咱們常用的動態數組的就是基於此的使用,以下示例:
size_t row,col; //輸入row和col的數值 int **MathTable = new int*[row]; for( int i = 0 ; i < row ; i++ ) MathTable[i] = new int[col]; //code for( int i = 0 ; i < row ; i++ ) delete [] MathTable[i]; delete []MathTable;
也就是形如int p[10]這樣的聲明,就是咱們這裏的指針數組,從聲明形態上來說,是因爲[]的優先級高於,又有諸以下面的指針:
*ptr_arry[i]
指針數組中的元素能夠表示爲:
*(*(ptr_arry+i))
()的優先級較高,又因爲又結合的緣由,能夠化簡爲:
**(ptr_arry+i)
因爲數組元素均爲指針,所以prt_array[i]是指第i+1個元素的指針。
此處還有兩個須要區分的概念,就是函數指針和指針函數。
函數指針
函數指針:指向函數的指針變量,在C編譯時,每個函數都有一個入口地址,那麼指向這個函數的函數指針即是指向這個地址。函數指針主要有兩個做用:用做調用函數和作函數的參數。
int (*func)(int x);
諸如上面的代碼這是申明瞭一個函數指針,代碼(*func)中括號是必須的,這會告訴編譯器這是一個函數指針而不是聲明一個具備返回類型爲指針的函數,後面的形參要是這個函數所指向的函數形參而定。使用以下面的代碼:
#include <iostream> using namespace std; int(*func)(int a, int b); int bar(int a, int b) { return a + b; } int foo(int a, int b) { return a; } int _tmain(int argc, _TCHAR* argv[]) { func = bar; cout << func(12, 34) << endl; system("pause"); func = foo; cout << func(12, 34) << endl; system("pause"); return 0; }
這樣的聲明有些繁瑣,其實可使用typedef來進行簡化:
#include <iostream> using namespace std; typedef int(*PF)(int, int); //int(*func)(int a, int b); int bar(int a, int b) { return a + b; } int foo(int a, int b) { return a; } int _tmain(int argc, _TCHAR* argv[]) { PF func; func = bar; cout << func(12, 34) << endl; system("pause"); func = foo; cout << func(12, 34) << endl; system("pause"); return 0; }
函數指針的另外一個做用就是做爲函數的參數,能夠在一個函數的形參列表中傳入函數指針,而後邊能夠在這個函數中使用這個函數指針所指向的函數,這樣邊可使程序變得更加清晰和簡潔。
#include <iostream> using namespace std; typedef int(*PF)(int, int); //int(*func)(int a, int b); int bar(int a, int b) { return a + b; } int foo(int a, int b) { return a; } void func(int a, int b, PF ptr) { cout << ptr(a, b) << endl; return; } int _tmain(int argc, _TCHAR* argv[]) { PF ptr; ptr = bar; func(12, 34, ptr); system("pause"); ptr = foo; func(12, 34, ptr); system("pause"); return 0; }
一旦知道函數指針是如何工做的,咱們就能夠構建一些複雜的定義,例如:
void *(*(*fp1)(int))[10];
fp1是一個指向函數的指針,該函數接受一個整型參數,而且返回類型是一個指向包含了10個void指針數組的指針。是否是很繞?
float (*((*fp2)(int,int,float)))(int);
fp2是一個指向函數的指針,該函數接受三個參數(int,int,float),返回值是一個指向函數的指針,該函數接受一個整型參數並返回一個float。
typedef doubele (*(*(*fp3)())[10])();
fp3是一個函數指針,該函數無參數,且返回一個指向含有10個指向函數指針指針數組的指針,這些函數不接收參數,且返回值是double值
int (*(*fp4())[10])();
fp4是一個返回指針的函數,該指針指向含有10個函數指針的數組,這些函數的返回值是整型。
指針函數
與函數指針相區別的定義應該就是指針函數,指針函數本質上是一個函數,是指函數的返回值爲指針的函數,通常是形以下的函數:
int* func(int x,int y);
如上就是一個返回值是指針的函數,很常見。
函數對象
上面談到了函數指針以及應用,這裏涉獵下函數對象。從通常函數回調意義上來講,函數對象和函數指針是相同的,可是函數對象卻具備許多函數指針不具備的優勢,函數對象使程序設計更加靈活,並且可以實現函數的內聯(inline)調用,使整個程序實現性能加速。
首先是如何申請二維的數組,這裏咱們先申請一個指針數組,而後令指針數組中的每個元素都指向一個數組,這樣二維數組就成了:
size_t row, col; //輸入row和col的數值 int **MathTable = new int*[row]; for (int i = 0; i < row; i++) MathTable[i] = new int[col];
而後是釋放空間的過程:
//code for (int i = 0; i < row; i++) delete[] MathTable[i]; delete[]MathTable;
符合new和delete配對的原則,怎麼new出來就怎麼delete掉。
總結下來須要注意的大概有下面幾點:
1)、儘可能避免使用raw pointer構建shared_ptr,至於緣由此處不便於多講,後續還有講解
2)、shared_ptr使得依據共享生命週期而經行地資源管理進行垃圾回收更爲方便
3)、shared_ptr對象的大小一般是unique_ptr的兩倍,這個差別是因爲Control Block致使的,而且shared_ptr的引用計數的操做是原子的,這裏的分析也會在後續看到
4)、默認的資源銷燬是採用delete,可是shared_ptr也支持用戶提供deleter,與unique_ptr不一樣,不一樣類型的deleter對shared_ptr的類型沒有影響。
標準:分別隸屬於兩個不一樣的標準委員會。C以C99標準爲主流,C11已經發布;C++以C++98/03爲主流,C++11/14也日趨流行。
語言自己:
C++是面嚮對象語言,C是面向過程語言。
結構:C以結構體struct爲核心結構;C++以類class爲核心結構。
多態:C能夠以宏定義的方式「自定義」部分地支持多態;C++自身提供多態,並以模板templates支持編譯期多態,以虛函數virtual function支持運行期多態。
頭文件的調用:C++用< >代替" "表明系統頭文件;且複用C的頭文件時,去掉".h"在開頭加上"C"。
輸入輸出:鑑於C++中以對象做爲核心,輸入和輸出都是在流對象上的操做。
封裝:C中的封裝因爲struct的特性所有爲公有封裝,C++中的封裝因爲class的特性更加完善、安全。
常見風格:C中經常使用宏定義來進行文本替換,不具備類型安全性;C++中常建議採用常量定義,具備類型安全性。
效率:常見的說法是同等目的C一般比C++更富有效率(這其實有必定的誤解,主要在於C++代碼更難於優化且少有人使用編譯期求值的特性)。
經常使用語言/庫特性:
數組:C中採用內建數組,C++中建議採用vector。相比之下vector的大小能夠動態增加,且使用一些技巧後增加並不低效,且成員函數豐富。
字符串 C中採用C風格的string(實則爲字符串數組),C++中建議採用string,對比與上一條相似。
內存分配:C中使用malloc與free,它們是是C標準庫函數,C++中建議使用new/delete代替前者,他們說是C++的運算符(這是筆試面試常考點)以C++中的new爲例,new可分爲operator new(new 操做)、new operator(new 操做符)和placement new(定位 new)。其中operator new執行和malloc相同的任務,即分配內存,但對構造函數一無所知;而 new operator則調用operator new,分配內存後再調用對象構造函數進行對象的構造。其中operator new是能夠重載的。placement new,就是operator new的一個重載版本,容許你在一個已經分配好的內存中構造一個新的對象。
指針:C中一般使用的是原生指針(raw pointer),因爲常出現程序員在申請後忘記釋放形成資源泄漏的問題,在C++98中加入了「第一代」基於引用計數的智能指針auto_ptr,因爲初代的各類問題(主要是沒法解決循環指針),在03標準也就是TR1中引入了shared_ptr,weak_ptr和unique_ptr這三個功能各異的智能指針,並與11標準中正式肯定,較好的解決了上述問題。
僅有C++纔有的經常使用特性:
語言(範式)特性:
面向對象編程:C++中以關鍵字class和多態特性支持的一種編程範式;
泛型編程:C++中以關鍵字template支持的一種編程範式;
模板元編程 :C++中以模板特化和模板遞歸調用機制支持的一種編程範式。
C++中以對象和類型做爲整個程序的核心,在對象方面,時刻注意對象建立和析構的成本,例若有一個很經常使用的(具名)返回值優化((N)RVO);
在類型方面,有運行時類型信息(RTTI)等技術做爲C++類型技術的支撐。
函數重載:C++容許擁有不一樣變量但具備相同函數名的函數(函數重載的編譯器實現方式、函數重載和(主)模板特化的區別都曾考過)。
異常:以catch、throw、try等關鍵字支持的一種機制。
名字空間:namespace,能夠避免和減小命名衝突且讓代碼具備更強的可讀性。
謂詞用法:一般以bool函數或仿函數(functor)或lambda函數的形式,出如今STL的大多數算法的第三個元素。
常見關鍵字(操做符)特性:
auto:在C中,auto表明自動類型一般均可省略;而在C++11新標準中,則起到一種「動態類型」的做用——一般在自動類型推導和decltype搭配使用。
空指針:在C中常以NULL表明空指針,在C++中根據新標準用nullptr來表明空指針。
&: 在C中僅表明取某個左值(lvalue)的地址,在C++中還能夠表示引用(別名)。
&&:在C中僅能表示邏輯與,在C++中還能夠表示右值引用。
[]:在C中僅能表示下標操做符,在C++中還能夠表示lambda函數的捕捉列表。
{}:在C中僅能用於數組的初始化,在C++中因爲引入了初始化列表(initializer_list),可用於任何類型、容器等的初始化。
常量定義:C中常以define來定義常量,C++中用const來定義運行期常量,用constexpr來定義編譯器常量。
經常使用新特性:
右值引用和move語義(太多內容,建議自查)。
基於範圍的for循環(與python中的寫法相似,經常使用於容器)。
基於auto——decltype的自動類型推導。
lambda函數(一種局部、匿名函數,高效方便地出如今須要局部、匿名語義的地方)。
標準規範後的多線程庫。
爲何要內存對齊?這個網上講得更全面,就再也不多說了。簡而言之,爲了速度和正確性。詳見網頁:http://blog.csdn.net/lgouc/article/details/8235471,英文原版:(http://www.ibm.com/developerworks/library/pa-dalign/)
C/C++中的內存對齊之基礎篇
這裏主要以【不帶有】虛繼承鏈和虛函數的struct/class爲例,【注意:全部的空間均須要爲最大類型大小的整數倍】
這裏有一個C和C++不一樣的狀況:在C中,空struct/class不佔有內存,在C++中,空struct/class一般佔有1byte的空間,緣由是編譯器強行在裏面放入了一個char,這樣可使得這個class的不一樣實例化在內存中分配到獨一無二的地址。
最基本的內存對齊狀況(其中註釋表明該類型實際大小)
struct A { char c; //1byte double d; //8byte int i; //4byte };
在64位g++和vs2013下運行sizeof(A)結果均爲24。這種狀況下的計算都比較簡單,首先肯定最大類型的大小,這裏是double,所以Max = 8,所以佔據的空間就應該是8的倍數(相應的,若struct內最大的類型爲int,那麼佔據的空間就應該是4的倍數)。補齊的大小就根據最大類型的長度來肯定。一般在內存中按照變量聲明的順序來分配空間,先爲char分配,佔據1byte, 8 - 1 = 7,剩餘空間小於下一個變量double的須要空間,所以另外開闢一個8byte用於安放double,緊接着安放int,它佔據另外一個8byte空間的4個byte。而char後面的7byte、int後面的4byte都用於內存對齊。
所以總大小爲8+8+8 = 24(能夠當作1+7 + 8 + 4+4)。
struct A { double d; //8byte char c; //1byte int i; //4byte };
在64位g++和vs2013下運行sizeof(A)結果均爲16。根據上述說明,很容易獲得 8 + 1+4+3 = 16,其中3爲char、int以後的補齊。
稍複雜一點的內存對其狀況
class A { public: static double dd; char c; //1byte double d; //8byte static A a; int i; //4byte };
在64位g++和vs2013下運行sizeof(A)結果均爲24。這裏只須要注意一下,static data member會被放在class object以外。所以sizeof(A)時,不會計算他們的大小在內。其他計算同 2 中的第一個例子相同。
只有一種類型時的狀況:如一個struct中僅有一個char或int等,因爲「全部的空間均須要爲最大類型大小的整數倍」這個原則,struct的空間大小就是這些類型各自的大小,而不用再進行補齊。
C/C++中的內存對齊之深刻篇——虛繼承和虛函數
class A { public: virtual ~A(); char c; //1byte double d; //8byte int i; //4byte };
在32位g++下運行sizeof(A)結果爲24,在64位g++下運行sizeof(A)結果爲32,在vs2013下運行sizeof(A)結果爲32。
32位g++下:一般將vptr放在object的最前面,能夠肯定該內存空間與data member的內存空間不須要獨立。也就是說,該例中,不管虛析構函數被聲明在哪裏,都會在分配空間時最早給一個vptr分配4byte的空間,且該空間後是能夠緊接着分配char的1byte的空間。以此類推,結合上面的例子,能夠得出4(vptr)+1(char)+3(補齊) + 8 + 4+4 = 24
64位g++下:一般將vptr放在object的最前面,沒法肯定該內存空間與data member的內存空間是否須要獨立。也就是說,該例中,不管虛析構函數被聲明在哪裏,都會在分配空間時最早給一個vptr分配8byte的空間,且不清楚該空間後是否能夠緊接着分配char的1byte的空間(因爲該vptr佔據8byte,不管是否須要間隔,效果都同樣),以此類推,結合上面的例子,能夠得出
8(vptr)+ 1(char)+7(補齊) + 8 + 4+4 = 32
在vs2013下:一般將vptr放在object的最前面,vptr的大小與實際最大類型的大小相關。也就說說,該例中,不管虛析構函數被聲明
在哪裏,都會在分配空間時最早給一個vptr分配4byte的空間,因爲後面存在double類型,須要將vptr補齊。結合上面的例子,能夠得出
4(vptr)+4(補齊) + 1+7 + 8 +4+4 = 32
二、帶有普通繼承的class的內存對齊狀況
class A { int i; //4byte char c1;//1byte }; class B : public A { char c2;//1byte }; class C : public B { char c3;//1byte };
在64位g++下,調用sizeof(A)、sizeof(B)、sizeof(C)後的結果均爲8;在vs2013下分別爲8,12,16
g++下:普通繼承時,派生類和基類的內存空間沒有間隔。 A:4+1+3(補齊) = 8 B:4+1+1(c2)+2(補齊) = 8 C:4+1+1(c2)+1(c3)+1(補齊) = 8 注意這裏全部成員均爲私有成員,若是改爲public或protected則大小會有變化 vs2013下:普通繼承時,派生類和基類的內存空間須要獨立,即先補齊基類,再分配派生類。 A:4+1+3(補齊) = 8 B:4+1+3(補齊) + 1(c2)+3(補齊) = 12 C:4+1+3(補齊) + 1(c2)+3(補齊) + 1(c3)+3(補齊) = 16
三、帶有虛擬繼承鏈的class的內存對齊狀況
class A { int i; //4byte char c1;//1byte }; class B : virtual public A { char c2;//1byte }; class C : virtual public B { char c3;//1byte };
調用sizeof(A)、sizeof(B)、sizeof(C)後,32位g++下,分別爲8,16,24;64位g++下,分別爲:8,24,40;vs2013下分別爲8,16,24
32位g++下: A:仍然是4+1+3(補齊) = 8 B:4+1+3 + 4(vptr)+1(c2)+3(補齊) = 16 C;4+1+3 + 4(vptr)+1(c2)+3(補齊) + 4(vptr)+1(c3)+3(補齊) = 24 64位g++下: A:仍然是4+1+3(補齊) = 8 B:4+1+3 + 8(vptr)+1(c2)+7(補齊) = 24 C;4+1+3 + 8(vptr)+1(c2)+7(補齊) + 8(vptr)+1(c3)+7(補齊) = 40 vs2013下: A:仍然是4+1+3(補齊) = 8 B:4+1+3 + 4(vptr)+1(c2)+3(補齊) = 16 C;4+1+3 + 4(vptr)+1(c2)+3(補齊) + 4(vptr)+1(c3)+3(補齊) = 24 注意這裏vs2013的狀況表面看上去和32位g++相同,實則否則。例如去掉class B對於A的虛擬繼承性
class A { int i; //4byte char c1;//1byte }; class B : public A /*注意這裏跟上面相比不是虛擬繼承了*/ { char c2;//1byte }; class C : virtual public B { char c3;//1byte };
調用sizeof(A)、sizeof(B)、sizeof(C)後,32位g++下:分別爲8,8,16;vs2013下分別爲8,12,20
32位g++下: A:仍然是4+1+3(補齊) = 8 B:B:4+1+1(c2)+2(補齊) = 8(由於不是虛擬繼承) C;4+1+1(c2)+2(補齊) + 4(vptr)+1(c3)+3(補齊) = 16 vs2013下: A:仍然是4+1+3(補齊) = 8 B:4+1+3(補齊) + 1(c2)+3(補齊) = 12 C;4+1+3(補齊) + 1(c2)+3(補齊) + 4(vptr)+1(c3)+3(補齊) = 20
虛基類的繼承是C++中爲了多重繼承而產生的,可是虛基類的繼承有帶來了新的問題,如何可以實現這種動態綁定呢?
由於好多函數是不定參數個數的,好比最經常使用的printf,因此須要參數的入棧順序是從右往左。
inside C++ object
對指針,返回NULL.對引用,拋出bad_cast異常more Effective C++
C++引入了4種類型轉化操做符(cast operator):static_cast,const_cast,dynamic_cast和reinterpret_cast,使用方法與C語言中略有不一樣:
(type)expression; //這是C語言的
而後引入C++的:
static_cast<type>(expression);//這是C++的
而後看一下各自的適用範圍:
static_cast:static_cast基本上擁有與C舊式轉型相同的威力和意義,以及相同的限制。可是,該類型轉換操做符不能移除常量性,由於有一個專門的操做符用來移除常量性。
const_cast:用來改變表達式中的常量性(constness)或者易變形(volatileness),只能用於此功能。
dynamic_cast:將指向基類basic class object的pointer或者reference轉型爲指向派生類derived(或這sibling base)class object的pointer或者reference中,而且能夠獲知是否轉型成功:若是轉型失敗,當轉型對象是指針的時候會返回一個null指針;當轉型對象是reference會拋出一個異常exception。dynamic_cast沒法應用在缺少虛函數的類型上,也不能改變類型的常量性。
此外,dynamic_cast還有一個用途就是找出被對象佔用的內存的起始點。
reinterpret_cast:這個操做符的轉換結果幾乎老是和編譯器平臺相關,因此不具備移植性。reinterpret_cast的最經常使用用途是轉換「函數指針」類型,以下:
typedef void(*FuncPtr)(); int doSomething(); int main() { FuncPtr funcPtrArray[10]; funcPtrArray[0] = reinterpret_cast<FuncPtr>(&doSomething); return 0; }
經過reinterpret_cast強迫編譯器了,併成功的將不一樣的類型的函數&doSomething轉換爲須要的類型。不過這個操做符進行的轉換動做不具備移植性(C++不保證全部的函數指針都能以此方式從新呈現),某些狀況下這樣的轉型可能會致使不正確的結果,因此這種操做不到萬不得已不要使用。
答:
在C++中,有下面三種對象須要拷貝的狀況:
一個對象以值傳遞的方式傳入函數體
一個對象以值傳遞的方式從函數返回
一個對象須要經過另一個對象進行初始化
以上的狀況就須要拷貝構造函數的調用。
當類中的數據成員須要動態分配存儲空間時,不能夠依賴default copy constructor。當default copy constructor被因編譯器須要而合成時,將執行default memberwise copy語義。此時若是類中有動態分配的存儲空間時,將會發生慘重的災情。在須要時(包括這種對象要賦值、這種對象做爲函數參數要傳遞、函數返回值爲這種對象等狀況),要考慮到自定義拷貝構造函數。
----------
不能,語法上經過,語義上有問題。
derived class對象內的base class成分會在derived class自身構造以前構造完畢。所以,在base class的構造函數中執行的virtual函數將會是base class的版本,決不會是derived class的版本。
即便目前確實正在構造derived class。
答:淺拷貝:若是在類中沒有顯式地聲明一個拷貝構造函數,那麼,編譯器將會根據須要生成一個默認的拷貝構造函數,完成對象之間的位拷貝。default memberwise copy即稱爲淺拷貝。
此處須要注意,並不是像大多數人認爲的「若是class未定義出copy constructor,那麼編譯器就會爲之合成一個執行default memberwise copy語義的copy constructor」。
一般狀況下,只有在default copy constructor被視爲trivial時,纔會發生上述狀況。一個class,若是既沒有任何base/member class含有copy constructor,也沒有任何virtual base class或 virtual functions,
它就會被視爲trivial。
一般狀況下,淺拷貝是夠用的。
深拷貝:然而在某些情況下,類內成員變量須要動態開闢堆內存,若是實行位拷貝,也就是把對象裏的值徹底複製給另外一個對象,如A=B。
這時,若是B中有一個成員變量指針已經申請了內存,那A中的那個成員變量也指向同一塊內存。若是此時B中執行析構函數釋放掉指向那一塊堆的指針,這時A內的指針就將成爲懸掛指針。
所以,這種狀況下不能簡單地複製指針,而應該複製「資源」,也就是再從新開闢一塊一樣大小的內存空間。
對象的靜態類型:對象在聲明時採用的類型。是在編譯期肯定的。
對象的動態類型:目前所指對象的類型。是在運行期決定的。對象的動態類型能夠更改,可是靜態類型沒法更改。
靜態綁定:綁定的是對象的靜態類型,某特性(好比函數)依賴於對象的靜態類型,發生在編譯期。
動態綁定:綁定的是對象的動態類型,某特性(好比函數)依賴於對象的動態類型,發生在運行期。
這個題目的答案來自張教主的整理
1:
template<typename T> void f(T);/* a */ template<typename T> void f(T*);/* b */ template< > void f<int>(int*);/* c */ int* p; f(p);
2:
template<typename T> void f(T);/* a */ template< > void f<int*>(int*);/* b */ template<typename T> void f(T*);/* c */ int* p; f(p);
如今請問1和2中的f(p)分別會調用a、b、c中的哪個?
解答:
若是你認爲1中的f(p)調用的是c的話,恭喜答對了。然而若是按絕大部分人的想法再來回答2中的調用狀況時,他會回答調用b(也就是和1中同樣的模板,這裏
順序交換了一下編號也換了,請注意),那麼狠抱歉,錯了。
分析前先回顧一下模板特化的東西。
非特化的模板也被稱爲主模板;
類模板能全特化和偏特化;
函數模板只能全特化,不過因爲函數重載的緣由,能達到偏特化的效果。
如今分析以下:
對於第1個:
template<typename T> void f(T);/* a */ template<typename T> void f(T*);/* b */ template< > void f<int>(int*);/* c */ int* p; f(p);
這裏,a是第一個主模板,b是第二個主模板,且b是第一個主模板a的重載而非偏特化(函數模板沒有偏特化)。c是第二個主模板b的顯式特化(全特化)。
在f(p)調用時,發生重載決議,會無視特化存在(標準規定:重載決議無視模板特化,重載決議只會發生在主模板之間)。在主模板a和b中決議出b,即第二個主模板被決議選中,而後再調用其全特化版本c。
對於第2個:
template<typename T> void f(T);/* a */ template< > void f<int*>(int*);/* b */ template<typename T> void f(T*);/* c */ int* p; f(p);
這裏a是第一個主模板,b是第一個主模板a的全特化,c是第二個主模板。在f(p)調用時,發生重載決議,一樣會無視特化存在,在主模板a和c中決議出c,而c並沒有全特化版本,所以直接調用c。
答:一般狀況下是不能的
緣由:inline是編譯期決定,他意味着在執行前就將調用動做替換爲被調用函數的本體;
virtual是運行期決定,他意味着直道運行期才決定調用哪一個函數。
這二者之間一般是衝突的。
然而也有特例,就是當編譯階段就已經知道調用虛函數的指針爲多態指針。這裏就再也不敖述了。
答: 標準規定,凡是具備non-trivial constructor、non-trivial destructor、non-trivial copy constructor、non-trivial assignment operator的class對象都不能做爲union的成員。
便是說,這個class的以上四種成員必須均經由編譯器合成且該class無虛函數和虛基類。
有這種限制是爲了兼容C。
lambda
線程庫
智能指針
auto
如何實現一個不能在堆上分配的類,若是要在堆上分配就是會使用new,因此能夠重載new 操做符,並將其重載於class A的private內:
class A { public: A(int a):_x(a){} int Display() { return _x; } void setVal(int x) { _x = x; return; } private: // int _x; void* operator new(size_t t){ } };
如何實現一個不能被繼承的類,這裏有一個比較簡單的方法,利用C++11的新關鍵字final:
class B final { public: B(int a) { } };
如今就不能繼承該類。
下面是第二期的地址:http://blog.csdn.net/charles_r_chiu/article/details/48227281該期正在撰寫~~~~最近找工做好累啊