一. 內聯函數和宏定義的區別
1.內聯函數在運行時可調試,而宏定義不可以;
2.編譯器會對內聯函數的參數類型做安全檢查或自動類型轉換(同普通類型),而宏定義不會;
3.內聯函數可以訪問類的成員變量,而宏定義則不能;
4.在類中聲明同時定義的成員函數,自動轉化為內聯函數;
5.在預編譯時,宏定義在調用處執行字符串的原樣替換。在編譯期間,內聯函數在調用處展開,同時進行參數檢查;
6.兩者都可節省函數調用是所帶來的時間和空間開銷,采用空間換時間的方式,在其調用處進行展開;
7.內聯函數可作為某個類的成員函數,這樣就可以使用類的保護成員和私有成員。當一個表達式涉及到類的保護成員和私有成員時,宏就不能實現了。
宏定義的缺點:會產生二義性(括號的使用等),不會檢查參數是否合法,存在安全隱患,不能訪問類的成員也不能成為類的成員函數
二. C++多態
1.多態分為靜態多態(函數重載和泛型編程)和動態多態(虛函數);
2.靜態多態和動態多態的實際區別就是函數地址是早綁定還是晚綁定。如果函數調用時在編譯期間就可以確定函數的調用地址並產生代碼,就是靜態的,也就是說地址是早綁定的(函數重載和泛型編程)。而如果函數調用地址不能在編譯期間確定,需要在運行時才能確定,這就屬於晚綁定(通過虛函數實現);
3. C++的多態性用一句話概括就是:在基類的函數前加上virtual關鍵字,在派生類中重寫該函數,運行時基類指針或引用將會根據對象的實際類型來調用相應的函數。如果對象類型是派生類,就調用派生類的函數;如果對象類型是基類,就調用基類的函數
每當創建一個包含有虛函數的類或從包含有虛函數的類派生一個類時,編譯器就為這個類創建一個vtable,如上圖所示。在這個表中,編譯器放置了在這個類中或在它的基類中所有已聲明為virtual的函數的地址。如果在這個派生類中沒有對在基類中聲明為virtual的函數進行重新定義,編譯器就使用基類 的這個虛函數地址。然后編譯器在這個類中放置vptr。當使用簡單繼承時,對於每個對象只有一個vptr。vptr必須被初始化為指向相應的vtable,這在構造函數中發生。
一旦vptr被初始化為指向相應的vtable,對象就"知道"它自己是什么類型。但只有當虛函數被調用時這種自我認知才有用。
沒有虛函數類對象的大小正好是數據成員的大小,包含有一個或者多個虛函數的類對象編譯器向里面插入了一個vptr指針(void *),指向一個存放函數地址的表就是我們上面說的VTABLE,這些都是編譯器為我們做的我們完全可以不關心這些。所以有虛函數的類對象的大小是數據成員的大小加上一個vptr指針(void *)的大小。
總結vtable和vptr和類對象的關系:
每一個具有虛函數的類都有一個虛函數表vtable,里面按在類中聲明的虛函數的順序存放着虛函數的地址,這個虛函數表vtable是這個類的所有對象所共有的,也就是說無論用戶聲明了多少個類對象,但是這個vtable虛函數表只有一個。
在每個具有虛函數的類的對象里面都有一個vptr虛函數指針,這個指針指向vtable的首地址,每個類的對象都有這么一種指針。
以下擴展:
在定義一個派生類對象時,派生類中新增加的數據成員當然用派生類的構造函數初始化,但是對於從基類繼承來的數據成員的初始化工作就必須由基類的構造函數完成,這就需要在派生類的構造函數中完成對基類構造函數的調用。同樣,派生類的析構函數能完成派生類中新增加數據成員的掃尾、清理工作,而從基類繼承來的數據成員的掃尾工作也應有基類的析構函數完成。由於析構函數不能帶參數,因此派生類的析構函數默認直接調用了基類的析構函數。
定義派生類對象時構造函數的調用順序(析構函數的調用順序相反):
a. 先調用用基類的構造函數
b. 然后調用派生類對象成員所屬的構造函數(如果有對象成員)
c. 最后調用派生類的構造函數
參考博客:https://www.cnblogs.com/cxq0017/p/6074247.html
四. 為什么析構函數要定義成虛函數
首先明確:1. 每個析構函數(不加virtual)只負責清除自己的的成員。2. 基類指針可指向派生類成員(這很正常)。
那么當析構一個指向派生類成員的基類指針時,程序就不知道該怎么辦了,所以要保證運行適當的析構函數,基類的析構函數必須為虛函數。
五. 中struct和class有什么區別?C語言中的struct和C++中的struct一樣嗎?有什么區別?
C++中的struct和class區別:
1. 默認訪問權限不同,struct是C中的升級版本,默認是public,class默認是private
2. class可以有默認的構造和析構函數,而struct沒有
C中的struct和C++中struct區別:C中struct只是一個自定義的數據類型(結構體),struct是抽象的數據類型,支持一些類的操作和定義
六. 說說什么是野指針?野指針什么情況下出現?
野指針:指向一個已經刪除的對象或者未申請的訪問受限地址的指針。
出現情況:
1. 指針未初始化(指針定義不會自動初始化成空指針,而是隨機的一個值,可能指向任意空間)
2. 指針所指變量釋放后沒有置為NULL
3. 指針所指變量已超過生存周期(如返回棧內存的指針)
七. C++四種類型轉換機制
1. static_cast(靜態類型轉換)主要用在繼承關系的對象類型或內置類型的轉換(不用於指針)。如:A a;B b; a=static_cast<A>b;(強制轉換)
2. dynamic_cast(動態類型轉換)用於有繼承關系的對象類型轉換(指針或引用)或者有虛函數的對象。如:A a;B b;B*p=&b;A *pp=dynamic_cast<A*>p;
3. const_cast:用於將指針常量轉換為普通的常量。如:const int * p="2"; int * pp= const_cast<int *> p;
4. reinterpret_cast:將一個類型的指針轉換為另一個類型的指針。如:double * b=2.0;int *a=reinterpret_cast<double*>b;
八. const的作用
1. const定義的常量必須賦值初始化。不能另外的初始化
2. const修飾函數的輸入參數:當傳入的參數是用戶自定義的類型,最好是用const引用修飾,可以提高效率。
3. const修飾函數的返回值
4. const修飾類的成員函數
九. 內存管理你懂多少?(包括內存泄漏,野指針知識,非法調用,內存溢出等)
1. 內存泄漏:
十. 深拷貝和淺拷貝
https://blog.csdn.net/weixin_41143631/article/details/81486817
十一. #include<file.h> 與 #include "file.h"的區別?
前者先從標准庫中尋找和引用file.h,后者先從當前路徑尋找和引用file.h
十二. 全局變量和局部變量有什么區別?是怎么實現的?操作系統和編譯器是怎么知道的?
兩者的主要區別是作用域和生命周期不同。全局變量有效范圍是從變量定義的位置開始到本源文件結束,而局部變量只能在自己的作用域有效。全局變量生命周期和整個程序生命周期一樣,而局部變量的生命周期和函數的生命周期一樣。全局變量的內存分配是靜態的,在main函數前初始化,如果沒有初始化,會被初始化為0。局部變量的內存分配是動態的,位於線程堆棧中,如果沒有初始化的,初值視當前內存的值而定。
操作系統和編譯器從變量的定義和存儲區域來區分局部變量和全局變量。
十三. 什么函數不能聲明為虛函數
一個類中將所有成員函數都盡可能設置為虛函數總是有益的。
但設置虛函數須注意:
1. 只有類的成員函數才能聲明為虛函數
2. 靜態成員函數不能是虛函數
3. 內聯函數不能是虛函數
4. 構造函數不能是虛函數
5. 析構函數可以是虛函數,而且通常聲明為虛函數
十四. 面向對象
面向對象的三大特性:封裝、繼承、多態。
類和對象:類由數據成員和成員函數構成,代表抽象派,玩的就是概念,某種意義上來說是一種行為藝術;而對象是具體的,比如說過年回家和老爹下中國象棋,發現棋盤上少了一對‘象’,那是你爸在告訴你該找“對象”了(單身狗表示選擇GoDie)。
封裝:把客觀事物封裝成抽象的類,並且類可以把自己的數據和方法只讓可信的類或者對象操作,對不可信的類進行信息隱藏。(C++最大的優點:可以隱藏代碼的實現細節,使得代碼更模塊化)
繼承:可以使用現有類的所有功能,並在無需重新編寫原來的類的情況下對這些功能進行擴展,但是基類的構造函數、復制構造函數、析構函數、賦值運算符不能被派生類繼承。(優點是實可以擴展已存在的代碼模塊類)
多態:一個類實例的相同方法在不同情形有不同表現形式。多態實現的兩種方式:將子類對象的指針賦給父類類型的指針或將一個基類的引用指向它的派生類實例。(其中比較重要的是虛函數的使用以及指針或引用)
this指針:一個對象的this指針並不是對象本身的一部分,不會影響sizeof(對象)的結果。this作用域是在類的內部,當在類的非靜態(前面沒加Static)成員函數中訪問類的非靜態成員的時候,編譯器會自動將對象本身的地址作為一個隱含參數傳遞給函數。也就是說,各成員的訪問均通過this指針進行。(靜態成員是沒有this指針的)
十五. 指針和數組定義常見筆試題
鏈接:https://blog.csdn.net/sinat_38972110/article/details/82372140
int *a[10]; 指向int類型的指針數組a[10] int (*a)[10]; 指向有10個int類型的數組的指針a int (*a)(int);函數指針,指向有一個參數並且返回類型均為int的函數 int* a(int); 定義一個int參數並且返回類型為int*的函數 int (*a[10])(int); 函數指針的數組,指向有一個參數並且返回類型均為int的函數的數組
十六. 簡述sizeof和strlen的區別
最常考察的題目之一,主要區別如下:
1. sizeof是一個操作符,strlen是庫函數。
2. sizeof的參數可以是數據的類型,也可以是變量,而strlen只能以結尾為'\0'的字符串作參數。
3. 編譯器在編譯時就計算出了sizeof的結果。而strlen函數只能在運行時才能計算出來。並且sizeof計算出來的是數據類型占內存的大小,而strlen計算的是字符串的實際長度。
4. 數組做sizeof的參數不退化,傳遞strlen就退化為指針了。
十七. vector的實現原理以及實現機制
vector簡單來說就是一個動態增長的數組,里面有一個指針指向一片連續的內存空間,當空間裝不下要容納的數據時會自動申請一塊更大的空間(空間適配器)將原來的數據拷貝到新的空間,然后釋放舊的空間。當刪除時空間不釋放只清空里面的數據。
在vector動態增加大小時,並不是在原有的空間持續增加新的空間(無法保證原空間的后面還有可供配置的空間),而是以原來大小的兩倍另外開辟一塊較大的空間,然后將原來的內容拷貝過來,並釋放原來的空間。因此,對vector的任何操作一旦引起空間的重新配置,指向原vector的所有迭代器都會失效,這是比較容易犯的錯誤。
十八. list和vector有什么區別
vector和數組類似,它擁有一段連續的內存空間,並且起始地址不變,因此非常好的支持隨機存取(即使用[]操作符訪問其中的元素),但在中間進行插入和刪除會造成內存塊的拷貝(復雜度是O(n)),另外,當數組的內存空間不夠時,需要重新申請一塊足夠大的內存並進行內存的拷貝。這些都大大影響了vector的效率。
list是由數據機構中的雙向鏈表實現的,因此它的內存空間可以是不連續的。因此只能通過指針來進行數據的訪問,這個特點使得它的隨機存取非常沒有效率,需要遍歷中間的元素,搜索復雜度O(n),因此它沒有提供[]操作符的重載。但由於鏈表的特點,它在任意位置的刪除和插入的效率都非常高。
十九. 指針和引用的區別
1. 指針是一個變量,指向一個地址,引用是一個原變量的別名
2. 有const指針,沒有const應用
3. 指針可以有多級,而引用只能有一級
4. 指針可以為空,引用不能為空
5. 引用必須初始化,指針不必
6. 引用在定義時初始化一次,之后不可變,指針可以改變所指的對象
二十. string簡單實現
String { public: String(const char* str = NULL); String(const String& other); ~String(); String& operator=(const String& rhs); private: char* _str; }; String::String(const char* str) { if (str == NULL) { _str = new char(1); _str = '\0'; } else { _str = new char(strlen(str) + 1); strcpy(_str, str); } } String::String(const String& other) { _str = new char(strlen(other._str) + 1); strcpy(_str, other._str); } String::~String() { delete[]_str; } String& String::operator=(const String& rhs) { if (this == &rhs) return *this; delete []_str; _str = new char(strlen(_str) + 1); strcpy(_str, rhs._str); }
二十一. TCP和UDP的區別
1. TCP面向連接;UDP是無連接的,即發送數據之前不需要建立連接
2. TCP提供可靠的服務。也就是說,通過 TCP連接傳輸的數據,無差錯,不丟失,不重復,且按序到達;UDP盡最大努力交付,即不保證可靠交付
TCP通過校驗和,重傳控制,序號標識,滑動窗口,確認應答實現可靠傳輸。如丟包時重發控制,還可以對次序亂掉的分包進行順序控制。
3. UDP具有較好的實時性,工作效率比TCP高,適用於對高速傳輸和實時性有較高的通信或廣播通信
4. 每一條TCP連接只能是點到點的;UDP支持一對一,一對多,多對一和多對多的交互通信
5. TCP對系統資源要求較多,UDP對系統資源要求較少
簡單回答:TCP是面向連接的可靠傳輸,UDP是盡最大努力傳輸的不可靠傳輸
二十二. 智能指針總結
1.簡述智能指針的原理
智能指針是一種資源管理類,這個類在構造函數中傳入一個原始指針,在析構函數中釋放傳入的指針。智能指針都是棧上的對象,所以當函數(或者程序)結束時,會自動釋放。
2.C++中常用的智能指針有哪些?
C++中常用的智能指針有,在C++11中的<memory>中有unique_ptr、shared_ptr、weak_ptr
unique_ptr:同一時刻只能由唯一的unique_ptr指向給定對象,不支持拷貝和賦值操作。
shared_ptr:可以有多個指針指向相同的對象,通過引用計數機制,支持拷貝和賦值操作。每使用一次,內部引用計數器加1,析構一次,引用計數減1,當計數為0時,釋放所指的堆空間。
weak_ptr:弱引用。引用計數器有一個問題就是相互引用形成環,這樣兩個指針指向的內存都無法釋放。需要手動打破循環引用或者使用weak_ptr。顧名思義,weak_ptr是一個弱引用,只引用不計數。如果一塊內存被shared_ptr和weak_ptr同時引用,當所有的shared_ptr析構了之后,不管還有沒有weak_ptr引用該內存,內存也會被釋放,所以weak_ptr不保證它指向的內存一定有效,在使用之前需要檢查weak_ptr是否為空指針。