- C++
- 參考了https://interview.huihut.com
1.const
- 作用
1. 修飾變量,說明該變量不可以被改變;
2. 修飾指針,分爲指向常量的指針(const T *指向常量的指針,先是常量類型,再是指針)和指針常量(T *const指針常量:指針在前 常量在後);
3. 常量引用(const T&),經常用於形參類型,即避免了拷貝,又避免了函數對值的修改;
4. 修飾成員函數(放在函數簽名後),說明該成員函數內不能修改成員變量。
- 使用
1.常成員變量,只能在初始化列表賦值
2.常成員函數,不得修改類中的任何數據成員的值
3.常對象,只能調用常成員函數
2. static
- 作用
1. 修飾普通變量,修改變量的存儲區域和生命週期(main運行之前就分配空間),如果有初始值就用初始值初始化它,如果沒有初始值系統用默認值初始化它。
2. 修飾普通函數,表明函數的作用範圍,僅在定義該函數的文件內才能使用。可防止與他人命名空間裏的函數重名。
3. 修飾成員變量,修飾成員變量使所有的對象只保存一個該變量,而且不需要生成對象就可以訪問該成員。
4. 修飾成員函數,修飾成員函數使得不需要生成對象就可以訪問該函數,但是在 static 函數內不能訪問非靜態成員。
3.this 指針
1. this指針是一個隱含於每一個非靜態成員函數中的特殊指針。它指向調用該成員函數的那個對象。
2. 當對一個對象調用成員函數時,編譯程序先將對象的地址賦給 this 指針,然後調用成員函數,每次成員函數存取數據成員時,都隱式使用 this 指針。
3. 當一個成員函數被調用時,自動向它傳遞this指針。
4. this 指針被隱含地聲明爲: `ClassName *const this`,這意味着不能給 `this` 指針賦值;在 `ClassName` 類的 const 成員函數中,this 指針的類型爲:const ClassName* const,這說明不能對 `this` 指針所指向的這種對象是不可修改的(即不能對這種對象的數據成員進行賦值操作);
5. `this` 並不是一個常規變量,而是個右值,所以不能取得 this 的地址(不能 &this)。
6. 在以下場景中,經常需要顯式引用 this 指針:
1. 爲實現對象的鏈式引用;在成員函數中 return *this 實現鏈式引用。
2. 爲避免對同一對象進行賦值操作;在拷貝構造函數中先判斷傳進來的對象與當前對象是否爲同一個對象。
3. 在實現一些數據結構時,如 list
4. 成員函數形參變量與成員變量同名,通過this->區分。
- 爲什麼使用return *this 只能返回使用該成員函數的對象的引用,而不是直接返類型對象?
答: 類定義體結束前,該類型是不完全類型,只能使用該類型的指針或引用
4.inline 內聯函數
- 特徵
1.相當於把內聯函數裏面的內容寫在調用內聯函數處;
2.相當於不用執行進入函數的步驟,直接執行函數體;
3.相當於宏,卻比宏多了類型檢查,真正具有函數特性;
4.編譯器一般不內聯包含循環、遞歸、switch 等複雜操作的內聯函數;
5.在類聲明中定義的函數,除了虛函數的其他函數都會自動隱式地當成內聯函數。
類外定義,需要顯式內聯
inline int A::doA() { return 0; } // 需要顯式內聯
6. 內聯是一個請求,編譯器不一定採納
- 編譯器對 inline 函數的處理步驟
1. 將 inline 函數體複製到 inline 函數調用點處;
2. 爲所用 inline 函數中的局部變量分配內存空間;
3. 將 inline 函數的輸入參數和返回值映射到調用方法的局部變量空間中;
4. 如果 inline 函數有多個返回點,將其轉變爲 inline 函數代碼塊末尾的分支(使用 GOTO)。
- 優點
1. 內聯函數同宏函數一樣將在被調用處進行代碼展開,省去了參數壓棧、棧幀開闢與回收,結果返回等,從而提高程序運行速度。
2. 內聯函數相比宏函數來說,在代碼展開時,會做安全檢查或自動類型轉換(同普通函數),而宏定義則不會。
3. 在類中聲明同時定義的成員函數,自動轉化爲內聯函數,因此內聯函數可以訪問類的成員變量,宏定義則不能。
4. 內聯函數在運行時可調試,而宏定義不可以。
- 缺點
1. 代碼膨脹。內聯是以代碼膨脹(複製)爲代價,消除函數調用帶來的開銷。如果執行函數體內代碼的時間,相比於函數調用的開銷較大,那麼效率的收穫會很少。另一方面,每一處內聯函數的調用都要複製代碼,將使程序的總代碼量增大,消耗更多的內存空間。
2. inline 函數無法隨着函數庫升級而升級。inline函數的改變需要重新編譯,不像 non-inline 可以直接鏈接。
3. 是否內聯,程序員不可控。內聯函數只是對編譯器的建議,是否對函數內聯,決定權在於編譯器。
- 虛函數(virtua)可以是內聯函數(inine)嗎
虛函數可以是內聯函數,內聯是可以修飾虛函數的,但是當虛函數表現多態性的時候不能內聯。原因:內聯是在編譯期建議編譯器內聯,而虛函數的多態性在運行期,編譯器無法知道運行期調用哪個代碼,因此虛函數表現爲多態性時(運行期)不可以內聯。`inline virtual` 唯一可以內聯的時候是:編譯器知道所調用的對象是哪個類(如 `Base::who()`),這隻有在編譯器具有實際對象而不是對象的指針或引用時纔會發生。
5.volatile
1.volatile 關鍵字是一種類型修飾符,不用它聲明的類型變量表示可以被某些編譯器未知的因素(操作系統、硬件、其它線程等)更改。所以使用 volatile 告訴編譯器不應對這樣的對象進行優化。
2.volatile 關鍵字聲明的變量,每次訪問時都必須從內存中取出值(沒有被 volatile 修飾的變量,可能由於編譯器的優化,從 CPU 寄存器中取值)
3.const 可以是 volatile (如只讀的狀態寄存器)
4.指針可以是 volatile
6. assert()
斷言,是宏,而非函數。assert 宏的原型定義在 <assert.h>(C)、<cassert>(C++)中,其作用是如果它的條件返回錯誤,則終止程序執行。可以通過定義 NDEBUG 來關閉 assert,但是需要在源代碼的開頭,`include <assert.h>` 之前。
7. sizeof()運算符
sizeof 對數組,得到整個數組所佔空間大小(以字節爲單位)。
sizeof 對指針,得到指針本身所佔空間大小。(32位下是4)
sizeof是運算符,發生在編譯期,數組實際分配內存大小,與裏面的值無關。strlen是函數,發生在運行時,用來計算字符串的長度,遇到第一個NULL('\0')爲止,不包括‘\0’
8. #pragma pack(n)
設定結構體、聯合以及類成員變量以 n 字節方式對齊,與push、和pop一塊用可以控制每個類型的對齊字節數,如果不用push、pop的話可能整個文件都以同一個對齊方式來
#pragma pack(push) // 保存對齊狀態
#pragma pack(4) // 設定爲 4 字節對齊
struct test
{
char m1;
double m4;
int m3;
};
#pragma pack(pop) // 恢復對齊狀態
9.位域
類可以將其數據成員定義成位域,在一個位域中有一定數量的二進制位。位域的聲明形式是在成員名字後緊跟一個冒號以及一個常量表達式,該表達式用於指定成員所佔的二進制位數。位域可以節省內存資源,使數據結構更緊湊。當一個程序需要向其他程序或硬件設備傳遞二進制數據時,通常會用到位域。
struct Date
{
unsigned int nWeekDay : 33;
};
未命名的位域字段可以起到填充作用,位數爲 0 時則起到強制對齊的效果。
struct Date
{
unsigned int nWeekDay : 3; // 0..7 (3 bits)
unsigned int nMonthDay : 6; // 0..31 (6 bits)
unsigned int : 0; // Force alignment to next boundary.
unsigned int nMonth : 5; // 0..12 (5 bits)
unsigned int nYear : 8; // 0..100 (8 bits)
};
-
- 位域在內存中的佈局是與機器有關的
- 位域的類型必須是整型或枚舉類型,帶符號類型中的位域的行爲將因具體實現而定,位域字段位數不能超過類型的最大位數。位域定義中的數據類型如果是有符號的,那麼其位數就不能少於兩位(因爲其中一個是符號位)
- 取地址運算符(&)不能作用於位域,任何指針都無法指向類的位域
- 位域字段在內存中的位置是按照從低位向高位的順序放置的
- 位域字段不能是類的靜態成員。
10. extern "C"
- 被 extern 限定的函數或變量是 extern 類型的
- 被 `extern "C"` 修飾的變量和函數是按照 C 語言方式編譯和鏈接的
`extern "C"` 的作用是讓 C++ 編譯器將 `extern "C"` 聲明的代碼當作 C 語言代碼處理,可以避免 C++ 因符號修飾導致代碼不能和C語言庫中的符號進行鏈接的問題。
11. struct 和 typedef struct
C中:
typedef struct Student {
int age;
}S
等價於
struct Student {
int age;
};
typedef struct Student S;// S和struct Student的名稱空間不同
同時可以定義同名函數,void Student() {} 使用不衝突。
C++中:
編譯器定位符號的規則(搜索規則)改變,導致不同於C語言。
struct Student {...}; // 可以使用Student 也可以使用struct Student
typedef struct Student S;
- 可以定義名爲Student的函數
若定義了與 `Student` 同名函數之後,且Student只代表函數,不代表結構體。
- 不可以定義名爲S的函數 void S(){}錯誤,因爲標識符S已經被定義爲struct Student的別名。
struct與class區別:
* 最本質的一個區別就是默認的訪問控制
1. 默認的繼承訪問權限。struct 是 public 的,class 是 private 的。
2. struct 作爲數據結構的實現體,它默認的數據訪問控制是 public 的,而 class 作爲對象的實現體,它默認的成員變量訪問控制是 private 的。
11. explicit(顯式)關鍵字
* explicit 修飾構造函數時,可以防止隱式轉換和複製初始化(包括複製初始化和複製列表初始化)
* explicit 修飾轉換函數時,可以防止隱式轉換,按語境轉換除外。
12.friend友元類和友元函數
1.訪問私有成員
2.破壞封裝性
3.友元關係不可傳遞
4.友元關係的單向性
5.友元聲明的形式及數量不受限制
13.using
- using聲明
using std::cout;
class Derived Base{
public:
using Base::Base;
/* ... */
};
如上using 聲明,對於基類的每個構造函數,編譯器都生成一個與之對應(形參列表完全相同)的派生類構造函數。生成如下類型構造函數:
Derived(parms) : Base(args) { }
- using指示 //儘量少用
using namespace name;
使用using聲明時只導入指定的名稱,當其與局部名稱衝突時,編譯器會發出警告。
使用using指示會導入全部名稱,當其與局部名稱衝突時,局部名稱將覆蓋命名空間且編譯器不會發出警告
14.::
1. 全局作用域符(::name):用於類型名稱(類、類成員、成員函數、變量等)前,表示作用域爲全局命名空間
2. 類作用域符(class::name):用於表示指定類型的作用域範圍是具體某個類的
3. 命名空間作用域符(`namespace::name`):用於表示指定類型的作用域範圍是具體某個命名空間的
15.enum枚舉類型
- 限定作用域:
enum class open_modes { input, output, append };
- 不限定作用域
enum color { red, yellow, green };
enum { floatPrec = 6, doublePrec = 10 };
15.說明
decltype/左值引用/右值引用見博客:C++11/14新特性
https://blog.csdn.net/qq_36616692/article/details/100971722
16. 成員初始化列表
* 更高效:少了一次調用默認構造函數的過程。
* 有些場合必須要用初始化列表:
1. 常量成員,因爲常量只能初始化不能賦值,所以必須放在初始化列表裏面
2. 引用類型,引用必須在定義的時候初始化,並且不能重新賦值,所以也要寫在初始化列表裏面
3. 沒有默認構造函數的類類型,因爲使用初始化列表可以不必調用默認構造函數來初始化
17. initializer_list 列表初始化
-
18.虛函數、純虛函數、虛繼承、抽象類、模板類
1.類裏如果聲明瞭虛函數,這個函數是實現的,哪怕是空實現,它的作用就是爲了能讓這個函數在它的子類裏面可以被覆蓋(override),這樣的話,編譯器就可以使用後期綁定來達到多態了。純虛函數只是一個接口,是個函數的聲明而已,它要留到子類裏去實現。
2.虛函數在子類裏面可以不重寫;但純虛函數必須在子類實現纔可以實例化子類。
3.虛函數的類用於 “實作繼承”,繼承接口的同時也繼承了父類的實現。純虛函數關注的是接口的統一性,實現由子類完成。
4.帶純虛函數的類叫抽象類,這種類不能直接生成對象,而只有被繼承,並重寫其虛函數後,才能使用。抽象類被繼承後,子類可以繼續是抽象類,也可以是普通類。
5.虛基類是虛繼承中的基類,虛繼承用於解決多繼承條件下的菱形繼承問題(浪費存儲空間、存在二義性)。
- 虛繼承和虛函數異同
* 相同之處:都利用了虛指針(均佔用類的存儲空間)和虛表(均不佔用類的存儲空間)
* 不同之處:
* 虛繼承
* 虛基類依舊存在繼承類中,只佔用存儲空間
* 虛基類表存儲的是虛基類相對直接繼承類的偏移
* 虛函數
* 虛函數不佔用存儲空間
* 虛函數表存儲的是虛函數地址
- 模板類中可以使用虛函數
一個類(無論是普通類還是類模板)的成員模板(本身是模板的成員函數)不能是虛函數
- * 抽象類:含有純虛函數的類
接口類:僅含有純虛函數的抽象類
聚合類:用戶可以直接訪問其成員,並且具有特殊的初始化語法形式。滿足如下特點:
1.所有成員都是 public
2.沒有定義任何構造函數
3.沒有類內初始化
4.沒有基類,也沒有 virtual 函數
19.內存相關
- 1. malloc:申請指定字節數的內存。申請到的內存中的初始值不確定。
2. calloc:爲指定長度的對象,分配能容納其指定個數的內存。申請到的內存的每一位(bit)都初始化爲 0。
3. realloc:更改以前分配的內存長度(增加或減少)。當增加長度時,可能需將以前分配區的內容移到另一個足夠大的區域,而新增區域內的初始值則不確定。
4. alloca:在棧上申請內存。程序在出棧的時候,會自動釋放內存。但是需要注意的是,alloca 不具可移植性, 而且在沒有傳統堆棧的機器上很難實現。alloca 不宜使用在必須廣泛移植的程序中。C99 中支持變長數組 (VLA),可以用來替代 alloca。
- 1. new / new[]:完成兩件事,先底層調用 malloc 分配了內存,然後調用構造函數(創建對象)。
2. delete/delete[]:也完成兩件事,先調用析構函數(清理資源),然後底層調用 free 釋放空間。
3. new 在申請內存時會自動計算所需字節數,而 malloc 則需我們自己輸入申請內存空間的字節數。
20.智能指針
- auto_ptr 與 unique_ptr 比較
* auto_ptr 可以賦值拷貝,複製拷貝後所有權轉移;unqiue_ptr 無拷貝賦值語義,但實現了`move` 語義;
* auto_ptr 對象不能管理數組(析構調用 `delete`),unique_ptr 可以管理數組(析構調用 `delete[]` );
21.強制類型轉換
- static_cast
用於非多態類型的轉換
不執行運行時類型檢查(轉換安全性不如 dynamic_cast)
通常用於轉換數值數據類型(如 float -> int)
可以在整個類層次結構中移動指針,子類轉化爲父類安全(向上轉換向上轉換是一種隱式轉換。),父類轉化爲子類不安全(因爲子類可能有不在父類的字段或方法)
- dynamic_cast
用於多態類型的轉換
執行行運行時類型檢查
適用於指針或引用
不明確的指針的轉換將失敗(返回 nullptr),但不引發異常
可以在整個類層次結構中移動指針,包括向上轉換、向下轉換
強制轉換爲引用類型失敗,dynamic_cast 運算符引發 bad_cast 異常。
- const_cast
用於刪除 const、volatile 和 __unaligned(__unaligned修飾的指針,編譯器假定指針解已經對齊,只解決數據對齊的問題) 特性(如將 const int 類型轉換爲 int 類型 )
- reinterpret_cast
用於位的簡單重新解釋
濫用reinterpret_cast 運算符可能很容易帶來風險。 除非所需轉換本身是低級別的,否則應使用其他強制轉換運算符之一。
允許將任何指針轉換爲任何其他指針類型(如 `char*` 到 `int*` 或 `One_class*` 到 `Unrelated_class*` 之類的轉換,但其本身並不安全)
也允許將任何整數類型轉換爲任何指針類型以及反向轉換。
reinterpret_cast 運算符不能丟掉 const、volatile 或 __unaligned 特性。
reinterpret_cast 的一個實際用途是在哈希函數中:通過讓兩個不同的值幾乎不以相同的索引結尾的方式將值映射到索引。
22.運行時類型信息
- dynamic_cast
多態類型的轉換
- typeid
typeid 運算符允許在運行時確定對象的類型
- type_id 返回一個 type_info 對象的引用
如果想通過基類的指針獲得派生類的數據類型,基類必須帶有虛函數
只能獲取對象的實際類型
- type_info
type_info 類描述編譯器在程序中生成的類型信息。 此類的對象可以有效存儲指向類型的名稱的指針。 type_info 類還可存儲適合比較兩個類型是否相等或比較其排列順序的編碼值。 類型的編碼規則和排列順序是未指定的,並且可能因程序而異。
頭文件:typeinfo