2018秋招面試問題(二、C++基礎問題)
目錄
c語言的程式碼段、資料段、bss段
編譯器在編譯程式的時候,將程式中的所有的元素分成了一些組成部分,各部分構成一個段,所以說段是可執行程式的組成部分。
程式碼段:程式碼段就是程式的可執行部分,直觀理解就是函式堆疊組成的。
資料段:資料段存放一些程式中的資料,初始化了的全域性變數、靜態變數都放在資料段。
bss段:未初始化的全域性變數和靜態變數都放在bss段。
C++中為什麼空類的大小為1?為什麼成員函式不佔大小?為什麼有了成員變數之後就不用加1了?
- 被編譯器插進去的一個char ,使得這個class的不同實體(object)在記憶體中配置獨一無二的地址
- 類中的靜態成員變數也是不佔類空間的,因為靜態屬於物件共享的,不為每個物件獨有。所以能計入物件大小(類空間的)都是物件所獨有的。類的所有物件都共用記憶體中同一份成員函式。每個物件通過把this指標以及其他引數傳遞方式來呼叫成員函式。
- 在有成員變數之後,物件例項化就可以通過不同成員來得到不同的記憶體地址,就不需要那個標識地址不同的char了。
如何理解面向物件(oop)
面向物件有三大特性,封裝、繼承和多型。
封裝就是將一類事物的屬性和行為抽象成一個類,使其屬性私有化,行為公開化,提高了資料的隱祕性的同時,使程式碼模組化。這樣做使得程式碼的複用性
繼承就是一個類繼承另一個類,實現了程式碼的複用,繼承後子類自動擁有了父類的屬性和方法,子類也可以寫自己特有的屬性和方法,目的是實現功能的擴充套件。
多型就是呼叫相同的方法,引數也相同時,但表現的行為卻不同,這是由虛擬函式來實現的。
面向過程程式設計、OOP面向物件程式設計和泛型程式設計
面向過程是一種以過程為中心的程式設計思想,考慮的是實際的實現過程,比如說c語言,以函式作為基本單元,就是通過函式來體現的。OOP是面向物件程式設計,把物件作為程式的基本單元,一個物件包含了資料和操作資料的函式,通過封裝、繼承、多型來實現程式。
C++中還包括過載、多型、繼承、輸入輸出流這些。
泛型程式設計就是提供了模板,將型別引數化,用template <typename T>,
typedef的作用
第一,定義別名。可以定義一種型別的別名,也可以替複雜宣告定義一個別名。可以用來同時宣告多個指標。比如char* a,b;只是定義了一個指標和一個變數。如果有typedef char* PCHAR;就可以連續定義多個指標,PCHAR a,b,c,d; 和char*a,*b是等價的。typedef更直觀。
第二,在結構體中用來省略struct,
typedef struct ListNode
{
struct ListNode *next;
int val;
}ListNode;
比如連結串列的結構體,ListNode就代替struct ListNode
第三,可以用來定義與平臺有關的資料型別。當跨平臺的時候,只需要修改typedef就行了。
關於typedef的注意點
- typedef是定義了一種型別的新別名,不同於巨集,不是簡單的字串替換。比如:
typedef char* PSTR;
const PTSR p = “abc”;//這個地方const限制了p只讀
p++;//錯誤×
記住const和 typedef一起出現時,絕不是簡單的字元替換
- typedef是一個儲存型別的關鍵字(同auto、extern、mutable、static、register等一樣),雖然它實際上不影響物件的儲存特性。所以要注意不能和別的儲存型別同時使用。
typedef static int INT;//會報錯
斷言assert瞭解嗎?
assert是一個巨集,相當於一個if語句,在debug模式下使用的,assert中的表示式為真,程式正常執行,表示式為假,就會終止程式。頻繁地呼叫assert會影響程式的效能。禁用assert,就是在include後面加#define NDEBUG。
#include#define NDEBUG #include
靜態區域性變數、靜態全域性變數、普通全域性變數之間的區別
首先它們都存在記憶體的全域性儲存區。
一個程式由多個原始檔組成。這三種變數的生命期都是整個程式。
靜態區域性變數的作用域是定義它的那個函式內部,且只能初始化一次。
靜態全域性變數的作用域是整個原始檔,不可extern到別的原始檔中使用。
普通全域性變數的作用域是整個原始檔,但是可以extern到別的原始檔中使用。
fopen文字模式和二進位制模式的區別
FILE * fopen(const char * path,const char * mode);
首先文字檔案是基於字元編碼的檔案,如ASCII碼、Unicode等,包括xml檔案、html超文字檔案、c源程式檔案等。二進位制檔案是基於值編碼的檔案,如word檔案、影象格式檔案JPG等。
fopen中二進位制模式是原封不動的讀寫檔案的全部內容。
文字模式下,你寫進去、讀出來的,和實際儲存的資料,兩者不一定相同;
二進位制模式下,你寫進去、讀出來的,和實際儲存的資料,兩者一定相同。
以文字方式開啟檔案的話,當讀取檔案的時候,系統會將所有的"/r/n"(回車換行)轉換成"/n"(換行);當寫入檔案的時候,系統會將"/n"轉換成"/r/n"寫入。
r 開啟只讀檔案,該檔案必須存在。
r+ 開啟可讀寫的檔案,該檔案必須存在。
rb+ 讀寫開啟一個二進位制檔案,只允許讀寫資料。
rt+ 讀寫開啟一個文字檔案,允許讀和寫。
w 開啟只寫檔案,若檔案存在則檔案長度清為0,即該檔案內容會消失。若檔案不存在則建立該檔案。
w+ 開啟可讀寫檔案,若檔案存在則檔案長度清為零,即該檔案內容會消失。若檔案不存在則建立該檔案。
wb 只寫開啟或新建一個二進位制檔案;只允許寫資料。
wb+ 讀寫開啟或建立一個二進位制檔案,允許讀和寫。
wt+ 讀寫開啟或著建立一個文字檔案;允許讀寫。
有 + 表示允許讀寫。
wb+和rb+都可以進行讀寫,區別在於wb+可以開啟或者建立一個檔案。
對C++頂層const和底層const的區別
對於普通變數沒有頂層底層的區別,對於指標來說才有區別。
頂層const指的是常量指標,也就是指標本身不可改變。*const
底層const指的是指向常量的指標,也就是指標指向的值不可改變。const* 就是底層。
C++11有沒有了解過?
C++11是C++程式語言的一個標準,之前的標準有C++98以及C++03。相比於C++03,C++11標準包含核心語言的新機能,而且擴充套件C++標準程式庫。
例如:
for迴圈使用更簡單
引入了空指標nullptr解決NULL二義性
void foo(int n);
void foo(char* cArr);
上面聲明瞭兩個過載函式,當我呼叫foo(NULL),編譯器將會呼叫foo(int)函式,而實際上我是想呼叫foo(char*)函式的。為了避免這個歧義,C++11重新定義了一個新關鍵字nullptr,充當單獨空指標常量。
引入了 auto 和 decltype 這兩個關鍵字實現了型別推導,auto推導變數,decltype推導表示式。
auto會忽略掉頂層const,保留底層const。
const int i = 5;
auto a = i; //a是int型別而不是const int
const auto a = i;//a就是才是const int型
引入了智慧指標
智慧指標
智慧指標是對普通指標增加了一層封裝機制,用來方便地管理一個物件的生命週期(及物件什麼時候被刪除或被析構由智慧指標本身決定,不需要使用者管理)
智慧指標的作用:智慧指標是為了避免記憶體洩露的問題,替代了new和delete。
關鍵字auto_ptr、unique_ptr、shared_ptr和weak_ptr,標頭檔案是<memory>。其中auto_ptr是c++98中的,c++11將其拋棄,是為了避免潛在的記憶體崩潰的問題(當兩個指標指向同一個記憶體的時候,會釋放兩次),替換為unique_ptr、shared_ptr則不會崩潰。unique_ptr會在編譯時就報錯,而shared_ptr則會計算引用的次數,就不會出現多次刪除。
auto_ptr<int> m (new int(5));
如果shared_ptr的兩個指標b賦值給a,則兩個指標的計數值都會增加。shared_ptr指標的計數值指的是指向這塊記憶體的指標個數。
shared_ptr的計數機制
建構函式中計數初始化為1;
拷貝建構函式中計數值加1;
賦值運算子中左邊的引用物件計數-1,右邊的引用物件技術+1;
解構函式中引用計數-1,當減為0,就會自動delete釋放掉物件空間。
C++的記憶體分配\記憶體管理方式
C++中,記憶體區分為五個區,分別是堆、棧、全域性儲存區、常量儲存區、自由儲存區。
常量儲存區:常量字串等;
全域性儲存區:全域性變數、靜態變數,程式結束後由系統釋放。
棧區:區域性變數、函式的引數變數,由編譯器自動分配釋放(棧地址是向下增長的)。
堆區:由程式設計師分配釋放,malloc在堆上分配的記憶體塊,使用free釋放記憶體。
自由儲存區:是通過new和delete動態分配和釋放物件的抽象概念,new所申請的記憶體區域在c++中稱為自由儲存區。
C的記憶體管理方式
堆、棧、全域性區、常量區。
自由儲存區和堆是兩塊不同的記憶體區域嗎?它們有可能相同嗎?
自由儲存區和堆是兩個不同的概念,它們有可能指向同一塊記憶體區域。
基本上,所有的C++編譯器預設使用堆來實現自由儲存,也就是new和delete也許會按照malloc和free的方式來被實現,這時藉由new運算子分配的物件,說它在堆上也對,說它在自由儲存區上也正確,這時候他們就是相同的記憶體。而如果程式設計師也可以通過過載操作符,改用其他記憶體來實現自由儲存,例如全域性變數做的物件池,這時自由儲存區就區別於堆了,這時候就是不同的記憶體區域。