這個offer你喫定了!C++「面試真題」及基本知識點總結

自閉型小編。對於前幾篇文章,表示很抱歉。小編今後一定實事求是的做好每一篇文章!

  1. 結構體和共同體的區別。
    定義:

結構體struct:把不同類型的數據組合成一個整體,自定義類型。

共同體union:使幾個不同類型的變量共同佔用一段內存。

地址:

struct和union都有內存對齊,結構體的內存佈局依賴於CPU、操作系統、編譯器及編譯時的對齊選項。

關於內存對齊,先讓我們看四個重要的基本概念:

1.數據類型自身的對齊值: 對於char型數據,其自身對齊值爲1,對於short型爲2,對於int,float,double類型,其自身對齊值爲4,單位字節。

2.結構體或者類的自身對齊值:其成員中自身對齊值最大的那個值。

3.指定對齊值:#pragma pack(n),n=1,2,4,8,16改變系統的對齊係數

4.數據成員、結構體和類的有效對齊值:自身對齊值和指定對齊值中小的那個值。

常見數據類型及其長度:

注意long int和int一樣是4byte,long double和double一樣是8byte。(關於long double,ANSI C標準規定了double變量存儲爲 IEEE 64 位(8 個字節)浮點數值,但並未規定long double的確切精度。所以對於不同平臺可能有不同的實現。有的是8字節,有的是10字節,有的是12字節或16字節。)

在標準c++中,int的定義長度要依靠你的機器的字長,也就是說,如果你的機器是32位的,int的長度爲32位,如果你的機器是64位的,那麼int的標準長度就是64位。經測試,在64位操作系統下,int的長度還是32位的。

在這裏插入圖片描述

從上面的一段文字中,我們可以看出,首先根據結構體內部成員的自身對齊值得到結構體的自身對齊值(內部成員最大的長度),如果沒有修改系統設定的默認補齊長度4的話,取較小的進行內存補齊。

結構體struct:不同之處,stuct裏每個成員都有自己獨立的地址。sizeof(struct)是內存對齊後所有成員長度的加和。

共同體union:當共同體中存入新的數據後,原有的成員就失去了作用,新的數據被寫到union的地址中。sizeof(union)是最長的數據成員的長度。

總結:

struct和union都是由多個不同的數據類型成員組成, 但在任何同一時刻, union中只存放了一個被選中的成員, 而struct的所有成員都存在。在struct中,各成員都佔有自己的內存空間,它們是同時存在的。一個struct變量的總長度等於所有成員長度之和。在Union中,所有成員不能同時佔用它的內存空間,它們不能同時存在。Union變量的長度等於最長的成員的長度。對於union的不同成員賦值, 將會對其它成員重寫, 原來成員的值就不存在了, 而對於struct的不同成員賦值是互不影響的。

2.static 和const分別怎麼用,類裏面static和const可以同時修飾成員函數嗎。
static的作用:

· 對變量:

1.局部變量:

在局部變量之前加上關鍵字static,局部變量就被定義成爲一個局部靜態變量。

1)內存中的位置:靜態存儲區

2)初始化:局部的靜態變量只能被初始化一次,且C中不可以用變量對其初始化,而C++可以用變量對其初始化。(詳見:http://www.cnblogs.com/novice-dxx/p/7094690.html)

3)作用域:作用域仍爲局部作用域,當定義它的函數或者語句塊結束的時候,作用域隨之結束。

注:當static用來修飾局部變量的時候,它就改變了局部變量的存儲位置(從原來的棧中存放改爲靜態存儲區)及其生命週期(局部靜態變量在離開作用域之後,並沒有被銷燬,而是仍然駐留在內存當中,直到程序結束,只不過我們不能再對他進行訪問),但未改變其作用域。

2.全局變量:

在全局變量之前加上關鍵字static,全局變量就被定義成爲一個全局靜態變量。

1)內存中的位置:靜態存儲區(靜態存儲區在整個程序運行期間都存在)

2)初始化:未經初始化的全局靜態變量會被程序自動初始化爲0(自動對象的值是任意的,除非他被顯示初始化)

3)作用域:全局靜態變量在聲明他的文件之外是不可見的。準確地講從定義之處開始到文件結尾。

注:static修飾全局變量,並未改變其存儲位置及生命週期,而是改變了其作用域,使當前文件外的源文件無法訪問該變量,好處如下:(1)不會被其他文件所訪問,修改(2)其他文件中可以使用相同名字的變量,不會發生衝突。對全局函數也是有隱藏作用。而普通全局變量只要定義了,任何地方都能使用,使用前需要聲明所有的.c文件,只能定義一次普通全局變量,但是可以聲明多次(外部鏈接)。注意:全局變量的作用域是全局範圍,但是在某個文件中使用時,必須先聲明。

· 對類中的:

1.成員變量

用static修飾類的數據成員實際使其成爲類的全局變量,會被類的所有對象共享,包括派生類的對象。因此,static成員必須在內外進行初始化(初始化格式: int base::var=10;),而不能在構造函數內進行初始化,不過也可以用const修飾static數據成員在類內初始化 。

特點:

不要試圖在頭文件中定義(初始化)靜態數據成員。在大多數的情況下,這樣做會引起重複定義這樣的錯誤。即使加上#ifndef #define #endif或者#pragma once也不行。
靜態數據成員可以成爲成員函數的可選參數,而普通數據成員則不可以。
靜態數據成員的類型可以是所屬類的類型,而普通數據成員則不可以。普通數據成員的只能聲明爲 所屬類類型的指針或引用。
2.成員函數

用static修飾成員函數,使這個類只存在這一份函數,所有對象共享該函數,不含this指針。
靜態成員是可以獨立訪問的,也就是說,無須創建任何對象實例就可以訪問。base::func(5,3);當static成員函數在類外定義時不需要加static修飾符。
在靜態成員函數的實現中不能直接引用類中說明的非靜態成員,可以引用類中說明的靜態成員。因爲靜態成員函數不含this指針。
不可以同時用const和static修飾成員函數。
C++編譯器在實現const的成員函數的時候爲了確保該函數不能修改類的實例的狀態,會在函數中添加一個隱式的參數const this*。但當一個成員爲static的時候,該函數是沒有this指針的。也就是說此時const的用法和static是衝突的。

我們也可以這樣理解:兩者的語意是矛盾的。static的作用是表示該函數只作用在類型的靜態變量上,與類的實例沒有關係;而const的作用是確保函數不能修改類的實例的狀態,與類型的靜態變量沒有關係。因此不能同時用它們。

const的作用:

1.限定變量爲不可修改。

2.限定成員函數不可以修改任何數據成員。

3.const與指針:

const char *p 表示 指向的內容不能改變。

char * const p,就是將P聲明爲常指針,它的地址不能改變,是固定的,但是它的內容可以改變。

3.指針和引用的區別,引用可以用常指針實現嗎。
本質上的區別是,指針是一個新的變量,只是這個變量存儲的是另一個變量的地址,我們通過訪問這個地址來修改變量。

而引用只是一個別名,還是變量本身。對引用進行的任何操作就是對變量本身進行操作,因此以達到修改變量的目的。

(1)指針:指針是一個變量,只不過這個變量存儲的是一個地址,指向內存的一個存儲單元;而引用跟原來的變量實質上是同一個東西,只不過是原變量的一個別名而已。如:
int a=1;int *p=&a;
int a=1;int &b=a;
上面定義了一個整形變量和一個指針變量p,該指針變量指向a的存儲單元,即p的值是a存儲單元的地址。 而下面2句定義了一個整形變量a和這個整形a的引用b,事實上a和b是同一個東西,在內存佔有同一個存儲單元。
(2)可以有const指針,但是沒有const引用(const引用可讀不可改,與綁定對象是否爲const無關)
(3)指針可以有多級,但是引用只能是一級(int **p;合法 而 int &&a是不合法的)
(4)指針的值可以爲空,但是引用的值不能爲NULL,並且引用在定義的時候必須初始化;
(5)指針的值在初始化後可以改變,即指向其它的存儲單元,而引用在進行初始化後就不會再改變了。
(6)"sizeof引用"得到的是所指向的變量(對象)的大小,而"sizeof指針"得到的是指針本身的大小;
(7)指針和引用的自增(++)運算意義不一樣;
指針傳參的時候,還是值傳遞,試圖修改傳進來的指針的值是不可以的。只能修改地址所保存變量的值。 引用傳參的時候,傳進來的就是變量本身,因此可以被修改。
4.什麼是多態,多態有什麼用途。
定義:“一個接口,多種方法”,程序在運行時才決定調用的函數。
實現:C++多態性主要是通過虛函數實現的,虛函數允許的類重寫override(注意和overload的區別,overload是重載,是允許同名函數的表現,這些函數參數列表/類型不同)。
多態與非多態的實質區別就是函數地址是早綁定還是晚綁定。如果函數的調用,在編譯器編譯期間就可以確定函數的調用地址,並生產代碼,是靜態的,就是說地址是早綁定的。而如果函數調用的地址不能在編譯器期間確定,需要在運行時才確定,這就屬於晚綁定。
3.目的:接口重用。封裝可以使得代碼模塊化,繼承可以擴展已存在的代碼,他們的目的都是爲了代碼重用。而多態的目的則是爲了接口重用。

4.用法:聲明基類的指針,利用該指針指向任意一個子類對象,調用相應的虛函數,可以根據指向的子類的不同而實現不同的方法。

補充一下關於重載、重寫、隱藏(總是不記得)的區別:

Overload(重載):在C++程序中,可以將語義、功能相似的幾個函數用同一個名字表示,但參數或返回值不同(包括類型、順序不同),即函數重載。

(1)相同的範圍(在同一個類中);

(2)函數名字相同;

(3)參數不同;

(4)virtual 關鍵字可有可無。

Override(重寫):是指派生類函數覆蓋基類函數,特徵是:

(1)不同的範圍(分別位於派生類與基類);

(2)函數名字相同;

(3)參數相同;

(4)基類函數必須有virtual 關鍵字。

注:重寫基類虛函數的時候,會自動轉換這個函數爲virtual函數,不管有沒有加virtual,因此重寫的時候不加virtual也是可以的,不過爲了易讀性,還是加上比較好。

Overwrite(隱藏):隱藏,是指派生類的函數屏蔽了與其同名的基類函數,規則如下: (1)如果派生類的函數與基類的函數同名,但是參數不同。此時,不論有無virtual關鍵字,基類的函數將被隱藏(注意別與重載混淆)。

(2)如果派生類的函數與基類的函數同名,並且參數也相同,但是基類函數沒有virtual關鍵字。此時,基類的函數被隱藏(注意別與覆蓋混淆)。

補充一下虛函數表:
多態是由虛函數實現的,而虛函數主要是通過虛函數表(V-Table)來實現的。

如果一個類中包含虛函數(virtual修飾的函數),那麼這個類就會包含一張虛函數表,虛函數表存儲的每一項是一個虛函數的地址。如下圖:

在這裏插入圖片描述

這個類的每一個對象都會包含一個虛指針(虛指針存在於對象實例地址的最前面,保證虛函數表有最高的性能),這個虛指針指向虛函數表。

注:對象不包含虛函數表,只有虛指針,類才包含虛函數表,派生類會生成一個兼容基類的虛函數表。

原始基類的虛函數表
下圖是原始基類的對象,可以看到虛指針在地址的最前面,指向基類的虛函數表(假設基類定義了3個虛函數)

在這裏插入圖片描述

單繼承時的虛函數(無重寫基類虛函數)
假設現在派生類繼承基類,並且重新定義了3個虛函數,派生類會自己產生一個兼容基類虛函數表的屬於自己的虛函數表。

在這裏插入圖片描述

Derive class 繼承了 Base class 中的三個虛函數,準確的說,是該函數實體的地址被拷貝到 Derive類的虛函數表,派生類新增的虛函數置於虛函數表的後面,並按聲明順序存放。

單繼承時的虛函數(重寫基類虛函數)
現在派生類重寫基類的x函數,可以看到這個派生類構建自己的虛函數表的時候,修改了base::x()這一項,指向了自己的虛函數。

在這裏插入圖片描述

多重繼承時的虛函數(Derived ::public Base1,public Base2)
這個派生類多重繼承了兩個基類base1,base2,因此它有兩個虛函數表。

在這裏插入圖片描述

它的對象會有多個虛指針(據說和編譯器相關),指向不同的虛函數表。

多重繼承時指針的調整:

Derive b;
Base1* ptr1 = &b; // 指向 b 的初始地址
Base2* ptr2 = &b; // 指向 b 的第二個子對象
因爲 Base1 是第一個基類,所以 ptr1 指向的是 Derive 對象的起始地址,不需要調整指針(偏移)。

因爲 Base2 是第二個基類,所以必須對指針進行調整,即加上一個 offset,讓 ptr2 指向 Base2 的對象。

當然,上述過程是由編譯器完成的。

Base1* b1 = (Base1*)ptr2;
b1->y(); // 輸出 Base2::y()
Base2* b2 = (Base2*)ptr1;
b2->y(); // 輸出 Base1::y()
其實,通過某個類型的指針訪問某個成員時,編譯器只是根據類型的定義查找這個成員所在偏移量,用這個偏移量獲取成員。由於 ptr2 本來就指向 Base2 子對象的起始地址,所以b1->y()調用到的是

Base2::y(),而 ptr1 本來就指向 Base1 子對象的起始地址(即 Derive對象的起始地址),所以b2->y()調用到的是Base1::y()。

虛繼承時的虛函數表
虛繼承的引入把對象的模型變得十分複雜,除了每個基類(MyClassA和MyClassB)和公共基類(MyClass)的虛函數表指針需要記錄外,每個虛擬繼承了MyClass的父類還需要記錄一個虛基類表vbtable的指針vbptr。MyClassC的對象模型如圖所示。

在這裏插入圖片描述

虛基類表每項記錄了被繼承的虛基類子對象相對於虛基類表指針的偏移量。比如MyClassA的虛基類表第二項記錄值爲24,正是MyClass::vfptr相對於MyClassA::vbptr的偏移量,同理MyClassB的虛基類表第二項記錄值12也正是MyClass::vfptr相對於MyClassA::vbptr的偏移量。(虛函數與虛繼承深入探討)

對象模型探討:
1.沒有繼承情況,vptr存放在對象的開始位置,以下是Base1的內存佈局

在這裏插入圖片描述

2.單繼承的情況下,對象只有一個vptr,它存放在對象的開始位置,派生類子對象在父類子對象的最後面,以下是D1的內存佈局

在這裏插入圖片描述

  1. 虛擬繼承情況下,虛父類子對象會放在派生類子對象之後,派生類子對象的第一個位置存放着一個vptr,虛擬子類的對象也會保存一個vptr,以下是VD1的內存佈局

在這裏插入圖片描述

  1. 棱形繼承的情況下,非虛基類子對象在派生類子對象前面,並按照聲明順序排列,虛基類子對象在派生類子對象後面

在這裏插入圖片描述

補充一下純虛函數:
定義: 在很多情況下,基類本身生成對象是不合情理的。爲了解決這個問題,方便使用類的多態性,引入了純虛函數的概念,將函數定義爲純虛函數(方法:virtual ReturnType Function()= 0;)純虛函數不能再在基類中實現,編譯器要求在派生類中必須予以重寫以實現多態性。同時含有純虛擬函數的類稱爲抽象類,它不能生成對象。
特點:
1,當想在基類中抽象出一個方法,且該基類只做能被繼承,而不能被實例化;(避免類被實例化且在編譯時候被發現,可以採用此方法)

2,這個方法必須在派生類(derived class)中被實現;

目的:使派生類僅僅只是繼承函數的接口。
補充一下純虛函數:
定義:稱帶有純虛函數的類爲抽象類。
作用:爲一個繼承體系提供一個公共的根,爲派生類提供操作接口的通用語義。
特點:1.抽象類只能作爲基類來使用,而繼承了抽象類的派生類如果沒有實現純虛函數,而只是繼承純虛函數,那麼該類仍舊是一個抽象類,如果實現了純虛函數,就不再是抽象類。
2.抽象類不可以定義對象。

補充一下多重繼承和虛繼承:
多重繼承:

定義:派生類繼承多個基類,派生類爲每個基類(顯式或隱式地)指定了訪問級別——public、protected 或 private。

class Panda : public Bear, public Endangered {
}
構造:

派生類的對象包含每個基類的基類子對象。
派生類構造函數初始化所有基類(多重繼承中若沒有顯式調用某個基類的構造函數,則編譯器會調用該基類默認構造函數),派生類只能初始化自己的基類,並不需要考慮基類的基類怎麼初始化。
多重繼承時,基類構造函數按照基類構造函數在類派生列表中的出現次序調用。
析構:總是按構造函數運行的逆序調用析構函數。(基類的析構函數最好寫成virtual,否則在子類對象銷燬的時候,無法銷燬子類對象部分資源。)假定所有根基類都將它們的析構函數適當定義爲虛函數,那麼,無論通過哪種指針類型刪除對象,虛析構函數的處理都是一致的。

拷貝構造/賦值:如果要爲派生類編寫拷貝構造函數,則需要爲調用基類相應拷貝構造函數併爲其傳遞參數,否則只會拷貝派生類部分。

深拷貝與淺拷貝:

淺拷貝:默認的複製構造函數只是完成了對象之間的位拷貝,也就是把對象裏的值完全複製給另一個對象,如A=B。這時,如果B中有一個成員變量指針已經申請了內存,那A中的那個成員變量也指向同一塊內存。

這就出現了問題:當B把內存釋放了(如:析構),這時A內的指針就是野指針了,出現運行錯誤。

深拷貝:自定義複製構造函數需要注意,對象之間發生複製,資源重新分配,即A有5個空間,B也應該有5個空間,而不是指向A的5個空間。
虛繼承與虛基類:

定義:在多重繼承下,一個基類可以在派生層次中出現多次。(派生類對象中可能出現多個基類對象)在 C++ 中,通過使用虛繼承解決這類問題。虛繼承是一種機制,類通過虛繼承指出它希望共享其虛基類的狀態。在虛繼承下,對給定虛基類,無論該類在派生層次中作爲虛基類出現多少次,只繼承一個共享的基類子對象。共享的基類子對象稱爲虛基類。

用法:istream 和 ostream 類對它們的基類進行虛繼承。通過使基類成爲虛基類,istream 和 ostream 指定,如果其他類(如 iostream 同時繼承它們兩個,則派生類中只出現它們的公共基類ios的一個副本。通過在派生列表中包含關鍵字 virtual 設置虛基類:

class istream : public virtual ios { … };

class ostream : virtual public ios { … };

// iostream inherits only one copy of its ios base class

class iostream: public istream, public ostream { … };

關注小編,後臺私信【資料】

關注,私信【資料】獲取!

☆小編根據視頻資料寫的文章,已經整理好PDF文檔!

![在這裏插入圖片描述](https://img-blog.csdnimg.cn/20200516215921165.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0FuZHJvaWR5dWV4aWE=,size_16,color_FFFFFF,t_70

關注,私信【資料】獲取!

在這裏插入圖片描述

關注,私信【資料】獲取!

在這裏插入圖片描述

關注,私信【資料】獲取!

BAT面試真題+架構技術資料,提升軟硬實力,收穫高薪好offer

感謝你的朗讀! 說了這麼多,到底該如何學習呢,有沒有資料或視頻呀?

在這裏插入圖片描述

在這裏插入圖片描述

關注,私信【資料】免費獲取!

大廠C++面試題庫、BAT架構技術資料庫(關注小編,後臺私信【資料】)

資料內容涵蓋BAT架構技術與面試真題,減少你到處搜索資料的時間,成體系的實戰技術知識,讓你面試、進階都更加容易!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章