C++面試總結(五)雜記
1.malloc、free與new、delete的區別?
1)malloc是函式,而new是操作符
2)malloc申請記憶體時,需要我們指定申請的空間大小,且返回的型別為void*,需要將其強制轉換為所需型別指標;new申請記憶體時,會根據所申請的型別自動計算申請空間的大小,且可直接返回指定型別的指標
3)malloc釋放記憶體時,用free函式,而new刪除物件時,用的是delete操作符
4)malloc/free申請釋放記憶體時,不需要需要呼叫解構函式,而new/delete申請釋放記憶體時需要呼叫解構函式
2.sizeof的使用
對引用型別執行sizeof運算得到被引用物件所佔空間的大小;對陣列執行sizeof得到整個陣列所佔空間的大小。sizeof運算不會把陣列轉換成指標處理;對string物件或者vector物件執行sizeof運算只返回該型別固定部分的大小,不會計算物件中的元素佔用多少空間。
(1)sizeof運算子在編譯階段處理的特性?
sizeof操作符在其作用範圍內,內容不會被編譯,而只是簡單替換成其型別。
(2)sizeof(字串序列)和sizeof(字串陣列)的區別?
sizeof(字串序列)=字串長度+1;(算上最後面的’\0’)
sizeof(字串陣列)=字串長度;(用{}表示時)
(3)sizeof(動態陣列)?
動態陣列實質上是一個指標,其佔用空間為4/8(32位/64位系統)。
int *d = new int[10]; cout<<sizeof(d)<<endl; // 4 void main() { vector<int> vec(10,0); cout<<sizeof(vec)<<endl;//輸出16 } //vector物件執行sizeof運算只返回該型別固定部分的大小,不會計算物件中的元素佔用多少空間
(4)用strlen得到字串的長度?
char p[]="abcdefg";
char a[]={'a','b','c','\0'};
cout<<strlen(p)<<endl;//輸出7
cout<<strlen(a)<<endl;//輸出3
(5)sizeof(聯合)需要注意的佔用空間最大的成員及對齊方式問題?
聯合與結構的區別:結構中各成員有各自的記憶體空間,結構變數所佔用記憶體空間是各成員所佔用記憶體空間之和;聯合中,各成員共享一段記憶體空間,一個聯合變數所佔用記憶體空間等於各成員中所佔用記憶體空間最大的變數所佔用記憶體空間(需考慮記憶體對齊問題),此時共享意味著每次只能賦一種值,賦入新值則衝去舊值。
union u //8對齊
{
double a;
int b;
};
union u2 //4對齊
{
char a[13];
int b;
};
union u3 //1對齊
{
char a[13];
char b;
};
cout<<sizeof(u)<<endl; // 8
cout<<sizeof(u2)<<endl; // 16
cout<<sizeof(u3)<<endl; // 13
知道union的大小取決於它所有的成員中,佔用空間最大的一個成員的大小。所以對於u來說,大小就是最大的double型別成員a了,所以sizeof(u)=sizeof(double)=8。但是對於u2和u3,最大的空間都是char[13]型別的陣列,為什麼u3的大小是13,而u2是16呢?關鍵在於u2中的成員int b。由於int型別成員的存在,使u2的對齊方式變成4(4位元組對齊),也就是說,u2的大小必須在4的對界上,所以佔用的空間變成了16(最接近13的對界)。
結論:複合資料型別,如union,struct,class的對齊方式為成員中對齊方式最大的成員的對齊方式。
(6)sizeof(class)需注意情況?
sizeof只計算資料成員的大小;不計算static資料成員的大小;繼承時需要考慮基類資料成員問題,考慮虛擬函式問題,無論有多少個虛擬函式,計算空間時虛擬函式表只計算一次。
補充:當派生類存在多重繼承時,sizeof運算結果?
class A
{
int a;
int b;
virtual void fun()
{
}
};
class B
{
int c;
virtual void fun1()
{
}
virtual void fun2()
{
}
};
class C:public A,public B
{
int d;
};
int main( )
{
cout<<sizeof(A)<<endl;//輸出12
cout<<sizeof(B)<<endl;//輸出8
cout<<sizeof(C)<<endl;//輸出24
return 0;
}
對於同一個類的不同虛擬函式,只需考慮一次即可,而對於不同類的虛擬函式,派生類需要都考慮(每一個類虛擬函式只需考慮一次)。可參考虛擬函式表裡的記憶體分配原則。
(7)對struct進行sizeof操作
對於一個struct取sizeof,要考慮對界的問題。對界是取struct中最大的資料作為對界值。對於以下三個struct,有以下解答:
struct s1
{
char a;
double b;
int c;
char d;
};
//sizeof(struct s1)=1+7+8+4+1+3=24;對界值取8(7,3是為滿足對界填充的空間)
struct s2
{
char a;
char b;
int c;
double d;
};
//sizeof(struct s2)=1+1+2+4+8=16;對界值取8(2是為滿足對界填充的空間)
struct X
{
short s;
int i;
char c;
};
//sizeof(struct X)=2+2+4+1+3=12;對界值取4(第二個2和3是為滿足對界填充的空間)
(8)sizeof多維陣列
double* (*a)[3][6];//型別為double*;a是一個指標,*a表示一個數組指標,指向一個2維陣列
cout<<sizeof(a)<<endl; //4;
cout<<sizeof(*a)<<endl; // 72=3*6*sizeof(double *)
cout<<sizeof(**a)<<endl; // 24=6*sizeof(double *)
cout<<sizeof(***a)<<endl; // 4=sizeof(double *)
cout<<sizeof(****a)<<endl; // 8=sizeof(double)
a是一個很奇怪的定義,他表示一個指向 double*[3][6]型別陣列的指標,此3×6陣列中儲存的是指向double的指標。既然是指標,所以sizeof(a)就是4
既然a是指向double*[3][6]型別的指標:
*a就表示一個double*[3][6]的多維陣列型別,因此sizeof(*a)=3*6*sizeof(double)=72。
**a表示一個double*[6]型別的陣列,所以sizeof(**a)=6*sizeof(double*)=24。
***a就表示其中的第一個元素,也就是double*了,所以sizeof(***a)=4。
****a,就是一個double了,所以sizeof(****a)=sizeof(double)=8
3.靜態分配與動態分配的區別?
靜態分配是指在編譯期間就能確定記憶體的大小,由編譯器分配記憶體。動態分配是指在程式執行期間,由程式設計師申請的記憶體空間。堆和棧都可以動態分配,但靜態分配只能是棧。
記憶體的靜態分配和動態分配的區別主要是兩個:
一是時間不同。靜態分配發生在程式編譯和連線的時候。動態分配則發生在程式調入和執行的時候。
二是空間不同。堆都是動態分配的,沒有靜態分配的堆。棧有2種分配方式:靜態分配和動態分配。靜態分配是編譯器完成的,比如區域性變數的分配。動態分配由函式malloc進行分配。不過棧的動態分配和堆不同,他的動態分配是由編譯器進行釋放,無需我們手工實現。
對於一個程序的記憶體空間而言,可以在邏輯上分成3個部份:程式碼區,靜態資料區和動態資料區。
動態資料區一般就是“堆疊”。“棧(stack)”和“堆(heap)”是兩種不同的動態資料區,棧是一種線性結構,堆是一種鏈式結構。程序的每個執行緒都有私有的“棧”,所以每個執行緒雖然程式碼一樣,但本地變數的資料都是互不干擾。一個堆疊可以通過“基地址”和“棧頂”地址來描述。全域性變數和靜態變數分配在靜態資料區,本地變數分配在動態資料區,即堆疊中。程式通過堆疊的基地址和偏移量來訪問本地變數。
4.深拷貝與淺拷貝的區別?
深拷貝是指源物件與拷貝物件互相獨立,其中任何一個物件的改動都不會對另外一個物件造成影響。淺拷貝是指源物件與拷貝物件共用一份實體,僅僅是引用的變數不同(名稱不同),對其中任何一個物件的改動都會影響另外一個物件。
換種解釋:淺拷貝,只是對指標的拷貝,拷貝後兩個指標指向同一個記憶體空間,深拷貝不但對指標進行拷貝,而且對指標指向的內容進行拷貝,經深拷貝後的指標是指向兩個不同地址的指標。
淺拷貝會出現什麼問題呢?
其一,淺拷貝只是拷貝了指標,使得兩個指標指向同一個地址,這樣在物件塊結束,呼叫函式析構時,會造成同一份資源析構2次,即delete同一塊記憶體2次,造成程式崩潰。
其二,淺拷貝使得源物件和拷貝物件指向同一塊記憶體,任何一方的變動都會影響到另一方。
其三,在釋放記憶體的時候,會造成拷貝物件原有的記憶體沒有被釋放造成記憶體洩露。(源物件記憶體被釋放後,由於源物件和拷貝物件指向同一個記憶體空間,拷貝物件的空間不能再被利用了,刪除拷貝物件不會成功,無法操作該空間,所以導致記憶體洩露)
類的靜態成員是所有類的例項共有的,儲存在全域性(靜態)區,只此一份,不管繼承、例項化還是拷貝都是一份。因此類的靜態成員不允許深拷貝。
5.memcpy的用法與strcpy之間的區別?
memcpy函式的功能是從源指標所指的記憶體地址的起始位置開始拷貝n個位元組到目標指標所指的記憶體地址的起始位置中。
void *memcpy(void *dest, const void *src, size_t n); //函式返回指向dest的指標
strcpy和memcpy主要有以下3方面的區別。
1)複製的內容不同。strcpy只能複製字串,而memcpy可以複製任意內容,例如字元陣列、整型、結構體、類等。
2)複製的方法不同。strcpy不需要指定長度,它遇到被複制字元的串結束符”\0”才結束,所以容易溢位。memcpy則是根據其第3個引數決定複製的長度。
3)用途不同。通常在複製字串時用strcpy,而需要複製其他型別資料時則一般用memcpy。
6.malloc、alloc、calloc、realloc的區別?
alloc:唯一在棧上申請記憶體的,無需釋放;
malloc:在堆上申請記憶體,最常用;
calloc:malloc+初始化為0;
realloc:將原本申請的記憶體區域擴容,引數size大小即為擴容後大小,因此此函式要求size大小必須大於ptr記憶體大小。
7.智慧指標
由於 C++ 語言沒有自動記憶體回收機制,程式設計師每次 new 出來的記憶體都要手動 delete。程式設計師忘記 delete,流程太複雜,最終導致沒有 delete,異常導致程式過早退出,沒有執行 delete 的情況並不罕見。用智慧指標便可以有效緩解這類問題。auto_ptr、unique_ptr和shared_ptr這幾個智慧指標背後的設計思想。我簡單的總結下就是:將基本型別指標封裝為類物件指標(這個類肯定是個模板,以適應不同基本型別的需求),並在解構函式裡編寫delete語句刪除指標指向的記憶體空間。
shared_ptr允許多個指標指向同一個物件;unique_ptr則“獨佔”所指向的物件。標準庫還定義了一個名為weak_ptr的伴隨類,它是一種弱引用,指向shared_ptr所管理的物件。對於shared_ptr:賦值,拷貝,向函式傳遞一個智慧指標,或函式返回一個智慧指標都會增加當前智慧指標的計數;向一個shared_ptr賦予一個新值或者一個shared_ptr被銷燬時,計數器就會遞減。
8.型別轉換有哪些?
static_cast:任何具有明確定義的型別轉換,只要不包含const,都可以使用。如:
static_cast<double>(int);//將一個int型資料轉換為double
const_cast:用於去掉指標或引用的const屬性如:
const int i=10;
int *k=const_cast<int*>(&i);
*k=5;//去除指標的const屬性後,可以從新對其賦值
dynamic_cast:支援父類指標(或引用)到子類指標(或引用)之間的多型型別轉換,並根據父類指標是否真的轉換到子類做相應的處理。對於指標,若cast成功,返回指向子類物件的指標,否則返回NULL;對於引用,若cast成功,返回指向子類物件的引用,否則丟擲一個異常。
注意:dynamic_cast在將父類cast到子類時,父類必須要有虛擬函式,否則會報錯。
dynamic_cast主要用於類層次間的上行轉換和下行轉換,還可以用於類之間的交叉轉換。
在類層次間進行上行轉換時,dynamic_cast和static_cast的效果是一樣的;
在進行下行轉換時,dynamic_cast具有型別檢查的功能,比static_cast更安全。
reinterpret_cast:一個型別的指標轉換為其他型別的指標。
允許將任何指標型別轉換為其它的指標型別;聽起來很強大,但是也很不靠譜。它主要用於將一種資料型別從一種型別轉換為另一種型別。它可以將一個指標轉換成一個整數,也可以將一個整數轉換成一個指標,在實際開發中,先把一個指標轉換成一個整數,再把該整數轉換成原型別的指標,還可以得到原來的指標值。
9.行內函數的優點以及和巨集定義的區別?
行內函數是指用inline關鍵字修飾的簡單短小的函式,它在編譯階段會在函式呼叫處替換成函式語句,從而減少函式呼叫的開銷。在類內定義的函式被預設成行內函數。
1.巨集定義是在預處理階段進行簡單替換,而行內函數是在編譯階段進行替換
2.編譯器會對行內函數的引數型別做安全檢查或自動型別轉換,而巨集定義則不會;
3.行內函數在執行時可除錯,而巨集定義不可以
4.在類中宣告同時定義的成員函式,自動轉化為行內函數。
5.巨集定義會出現二義性,而行內函數不會
6.行內函數可以訪問類的成員變數,巨集定義則不能
10.字串和字元陣列
char str1[] = "abc";
char str2[] = "abc";
const char str3[] = "abc";
const char str4[] = "abc";
const char *str5 = "abc";
const char *str6 = "abc";
char *str7 = "abc";
char *str8 = "abc";
cout << ( str1 == str2 ) << endl;//0 分別指向各自的棧記憶體
cout << ( str3 == str4 ) << endl;//0 分別指向各自的棧記憶體
cout << ( str5 == str6 ) << endl;//1指向文字常量區地址相同
cout << ( str7 == str8 ) << endl;//1指向文字常量區地址相同