2018-11-05 C++常考筆試面試題

幾種語言的特性

匯編程序:將匯編語言源程序翻譯成目標程序
編譯程序:將高級語言源程序翻譯成目標程序
解釋程序:將高級語言源程序翻譯成機器指令,邊翻譯邊執行
Java語言:半編譯半解釋,跨平臺

面向對象和面向過程

面向對象:面向主要是以目標對象為研究體,這一思想的實現需要對各種不同屬性的類進行封裝,進而分析每種類型事物的屬性和功能方法,
這種思想將計算機軟件系統與外界系統一一對應,進行有針對性的研究。核心在于 (對象 + 消息)
面向過程:C語言是面向過程的編程語言,這種思想主要是為了去實現某種功能或目標去一步步研究算法流程,步步求精,
進而用一種最為簡捷的過程來實現最終的目標,核心為 (算法+數據)

C++語言的優勢

https://www.cnblogs.com/ckings/p/3632997.html
1.代碼可讀性好。
2.可重用性好。
3.可移植。
4.C++設計成無需復雜的程序設計環境
5.運行效率高,高效安全
6.語言簡潔,編寫風格自由。
7.提供了標準庫stl
8.面向對象機制
9.很多優秀的程序框架包括Boost、Qt、MFC、OWL、wxWidgets、WTL就是使用的C++。
感覺在面試時說C++面向對象的三大特性,以及STL庫,優秀的程序框架即可。

main主函數運行之前先會干什么

  1. 全局對象的構造函數會在main 函數之前執行,
    全局對象的析構函數會在main函數之后執行;
    用atexit注冊的函數也會在main之后執行。
  2. 一些全局變量、對象和靜態變量、對象的空間分配和賦初值就是在執行main函數之前,而main函數執行完后,還要去執行一些諸如釋放空間、釋放資源使用權等操作
  3. 進程啟動后,要執行一些初始化代碼(如設置環境變量等),然后跳轉到main執行。全局對象的構造也在main之前。
    https://blog.csdn.net/huang_xw/article/details/8542105

C++源碼到可執行程序的過程

https://www.cnblogs.com/smile233/p/8423015.html
這篇博客講的太詳細了,我覺得實際可以精簡為:
源代碼-->預處理-->編譯-->匯編-->鏈接-->可執行文件

  1. 預處理
    (1)宏定義指令,如#define Name TokenString,#undef等。對于前一個偽指令,預編譯所要做的是將程序中的所有Name用TokenString替換,但作為字符串常量的Name則不被替換。對于后者,則將取消對某個宏的定義,使以后該串的出現不再被替換。
    (2)條件編譯指令,如#ifdef,#ifndef,#else,#elif,#endif,等等。這些偽指令的引入使得程序員可以通過定義不同的宏來決定編譯程序對哪些代碼進行處理。預編譯程序將根據有關的文件,將那些不必要的代碼過濾掉
    (3)頭文件包含指令,如#include "FileName"或者#include <FileName>等。在頭文件中一般用偽指令#define定義了大量的宏(最常見的是字符常量),同時包含有各種外部符號的聲明。采用頭文件的目的主要是為了使某些定義可以供多個不同的C源程序使用。因為在需要用到這些定義的C源程序中,只需加上一條#include語句即可,而不必再在此文件中將這些定義重復一遍。預編譯程序將把頭文件中的定義統統都加入到它所產生的輸出文件中,以供編譯程序對之進行處理。
    包含到c源程序中的頭文件可以是系統提供的,這些頭文件一般被放在/usr/include目錄下。在程序中#include它們要使用尖括號。(<>)。另外開發人員也可以定義自己的頭文件,這些文件一般與c源程序放在同一目錄下,此時在#include中要用雙引號("")。
    (4)特殊符號,預編譯程序可以識別一些特殊的符號。例如在源程序中出現的LINE標識將被解釋為當前行號(十進制數),FILE則被解釋為當前被編譯的C源程序的名稱。預編譯程序對于在源程序中出現的這些串將用合適的值進行替換。
  2. 編譯
    編譯過程就是把預處理完的文件進行一系列的詞法分析,語法分析,語義分析及優化后生成相應的匯編代碼。經過預編譯得到的輸出文件中,將只有常量。如數字、字符串、變量的定義,以及C語言的關鍵字,如main,if,else,for,while,{,},+,-,*,\,等等。編譯程序所要作得工作就是通過詞法分析和語法分析,在確認所有的指令都符合語法規則之后,將其翻譯成等價的中間代碼表示或匯編代碼。
  3. 匯編
    匯編過程實際上指把匯編語言代碼翻譯成目標機器指令的過程。對于被翻譯系統處理的每一個C語言源程序,都將最終經過這一處理而得到相應的目標文件。目標文件中所存放的也就是與源程序等效的目標的機器語言代碼。
  4. 鏈接
    由匯編程序生成的目標文件并不能立即就被執行,其中可能還有許多沒有解決的問題。例如,某個源文件中的函數可能引用了另一個源文件中定義的某個符號(如變量或者函數調用等);在程序中可能調用了某個庫文件中的函數,等等。所有的這些問題,都需要經鏈接程序的處理方能得以解決。
    通過調用鏈接器ld來鏈接程序運行需要的一大堆目標文件,以及所依賴的其它庫文件,最后生成可執行文件。

內存分區

https://www.cnblogs.com/madonion/articles/2269195.html
分為五大塊:棧,堆,全局區,常量區和代碼區

  1. 棧區
    由系統進行內存的管理,主要存放函數的參數以及局部變量。棧區由系統進行內存管理,在函數完成執行,系統自行釋放棧區內存,不需要用戶管理。整個程序的棧區的大小可以在編譯器中由用戶自行設定,默認的棧區大小為3M。
  2. 全局/靜態區
    初始化的全局變量和靜態變量是在一起的。未初始化的全局變量和靜態變量是在相鄰的空間中。全局變量和靜態全局變量的存儲方式是一致的,但是其區別在于,全局變量在整個源代碼中都可以使用,而靜態全局變量只能在當前文件中有效。比如我們的一個程序有5個文件,那么某個文件中申請了靜態全局變量,這個靜態全局變量只能在當前文件中使用,其他四個文件均不可以使用。而某個文件中申請了全局變量,那么其他四個文件中都可以使用該全局變量(只需要通過關鍵字extern申明一下就可以使用了)。事實上static改變了變量的作用范圍。
  3. 字符串常量區
    存放字符串常量,程序結束后,由系統進行釋放。比如我們定義const char * p = “Hello World”; 這里的“Hello World”就是在字符串常量中,最終系統會自動釋放。
  4. 代碼區:存放程序體的二進制代碼。比如我們寫的函數,都是在代碼區的。
  5. 堆區:由用戶手動申請,手動釋放。在C中使用malloc,在C++中使用new(當然C++中也可以使用malloc)。
    new操作符本質上還是使用了malloc進行內存的申請
    1)malloc是C語言中的函數,而new是C++中的操作符。
    2)malloc申請之后返回的類型是void*,而new返回的指針帶有類型。
    3)malloc只負責內存的分配而不會調用類的構造函數,而new不僅會分配內存,而且會自動調用類的構造函數。

堆和棧的區別

  1. 管理方式:棧是由編譯器自動管理,無需我們手工控制;對于堆來說,釋放工作由程序員控制,容易產生內存泄漏。
  2. 空間大小:堆內存比棧大得多。
  3. 能否產生碎片:對于堆來講,頻繁的new/delete勢必會造成內存空間的不連續,從而造成大量的碎片,使程序效率降低。對于棧來講,則不會存在這個問題,因 為棧是先進后出的隊列,他們是如此的一一對應,以至于永遠都不可能有一個內存塊從棧中間彈出,在他彈出之前,在他上面的后進的棧內容已經被彈出。
  4. 生長方向:堆生長方向是向上的,也就是向著內存地址增加的方向;棧的生長方向是向下的,是向著內存地址減小的方向增長。
  5. 分配方式:堆都是動態分配的,沒有靜態分配的堆。棧有2種分配方式:靜態分配和動態分配。靜態分配是編譯器完成的,比如局部變量的分配。動態分配由alloca函數進行分配,但是棧的動態分配和堆是不同的,他的動態分配是由編譯器進行釋放,無需我們手工實現。
  6. 分配效率:棧是機器系統提供的數據結構,計算機會在底層對棧提供支持:分配專門的寄存器存放棧的地址,壓棧出棧都有專門的指令執行,這就決定了棧的效率比較高。堆則是C/C++函數庫提供的,它的機制是很復雜的,例如為了分配一塊內存,庫函數會按照一定的算法(具體的算法可以參考數據結構/操作系統)在堆內 存中搜索可用的足夠大小的空間,如果沒有足夠大小的空間(可能是由于內存碎片太多),就有可能調用系統功能去增加程序數據段的內存空間,這樣就有機會分到 足夠大小的內存,然后進行返回。顯然,堆的效率比棧要低得多。

棧溢出

當我們定義的數據所需要占用的內存超過了棧的大小時,就會發生棧溢出。
https://blog.csdn.net/dongtuoc/article/details/79132137

內存泄露和內存碎片

1. 內存泄漏

內存泄漏一般是指堆內存的泄漏。堆內存是指程序從堆中分配的,大小任意的(內存塊的大小可以在程序運行期決定),使用完后必須顯示釋放的內存。如果沒有及時釋放,那么這一塊內存便不能使用,從而造成內存泄漏。

2. 內存碎片

內存碎片一般是由于空閑的連續空間比要申請的空間小,導致這些小內存塊不能被利用。
假設有一塊100個單位的連續空閑內存空間,范圍是0-99。從中申請一塊內存,10個單位,那么申請出來的內存塊就為0-9區間。繼續申請一塊內存,5個單位,第二塊得到的內存塊就應該為10~14區間。
如果你把第一塊內存塊釋放,然后再申請一塊20個單位的內存塊。因為剛被釋放的內存塊不能滿足新的請求,所以只能從15開始分配出20個單位的內存塊。現在整個內存空間的狀態是0-9空閑,10-14被占用,15-24被占用,25-99空閑。其中0-9就是一個內存碎片了。如果10-14一直被占用,而以后申請的空間都大于10個單位,那么0-9就永遠用不上了,造成內存浪費。

內存池

https://www.cnblogs.com/pilipalajun/p/5418286.html
內存池是在真正使用內存之前,先申請分配一定數量的、大小相等(一般情況下)的內存塊留作備用。當有新的內存需求時,就從內存池中分出一部分內存塊,若內存塊不夠再繼續申請新的內存。這樣做的一個顯著優點是,使得內存分配效率得到提升。

同步和異步

同步:在發出一個功能調用時,在沒有得到結果之前,該調用就不返回。
異步。
異步:當一個異步過程調用發出后,調用者不會立刻得到結果。實際處理這個調用的部件是在調用發出后,通過狀態、通知來通知調用者,或通過回調函數處理這個調用。異步的實現方式有兩種:通過多進程和timmer。

C++常考關鍵字

https://blog.csdn.net/cc_clear/article/details/76943425

auto

auto申明的變量必須初始化,程序會根據初始化的值的數據類型來自動確定該變量的數據類型。

const

https://www.cnblogs.com/xudong-bupt/p/3509567.html

  1. const修飾變量
    int a1=3;
  2. const修飾指針
    int * const p1 = &a1;
  3. const修飾指針指向的變量
    const int * p1 = &a1; 等價于 int const * p1 = &a1;
    int const*p; 等價于 const int *p; 都表示指針p指向的是一個常量
    int *const p; 表示指針p是一個常量,不能更改指向
  4. const修飾函數
    int func () const ;
    表示該成員函數不能修改任何成員變量,除非成員變量用mutable修飾。const修飾的成員函數也不能調用非const修飾的成員函數,因為可能引起成員變量的改變。
static

https://www.cnblogs.com/songdanzju/p/7422380.html

  1. 隱藏:可以在不同的文件中定義同名函數和同名變量,而不必擔心命名沖突。
  2. 保持變量內容的持久:存儲在靜態數據區的變量會在程序剛開始運行時就完成初始化,也是唯一的一次初始化。共有兩種變量存儲在靜態存儲區:全局變量和static變量,只不過和全局變量比起來,static可以控制變量的可見范圍,說到底static還是用來隱藏的。
  3. 默認初始化為0:在靜態數據區,內存中所有的字節默認值都是0x00,某些時候這一特點可以減少程序員的工作量。比如初始化一個稀疏矩陣,我們可以一個一個地把所有元素都置0,然后把不是0的幾個元素賦值。如果定義成靜態的,就省去了一開始置0的操作。再比如要把一個字符數組當字符串來用,但又覺得每次在字符數組末尾加‘\0’;太麻煩。如果把字符串定義成靜態的,就省去了這個麻煩,因為那里本來就是‘\0’;
  4. C++中的類成員聲明static
    主要用于所有對象共享,詳見后續的靜態成員變量和靜態成員函數。
宏定義(#define)

宏定義,#define命令是C語言中的一個宏定義命令,它用來將一個標識符定義為一個字符串,該標識符被稱為宏名,被定義的字符串稱為替換文本。
該命令有兩種格式:一種是簡單的宏定義,另一種是帶參數的宏定義。
(1) 簡單的宏定義:#define <宏名> <字符串>
例: #define PI 3.1415926
(2) 帶參數的宏定義:#define <宏名> (<參數表>) <宏體>
例如:#define max(x,y) (x)>(y)?(x):(y)

inline

https://blog.csdn.net/qq_33757398/article/details/81390151
inline一般用于內聯函數的聲明和定義,內聯函數就是在程序編譯時,編譯器將程序中出現的內聯函數的調用表達式用內聯函數的函數體來代替。在函數聲明和定義前加上關鍵字inline,若代碼執行時間很短,且代碼塊比較短小,則可以使用內聯函數。

final

https://blog.csdn.net/u012333003/article/details/28696521
C++11中允許將類標記為final,方法時直接在類名稱后面使用關鍵字final,如此,意味著繼承該類會導致編譯錯誤。 class Super final {};
C++中還允許將方法標記為final,這意味著無法再子類中重寫該方法。這時final關鍵字至于方法參數列表后面: virtual void SomeMethod() final;

volatile

https://blog.csdn.net/k346k346/article/details/46941497
定義為volatile的變量是說這變量可能會被意想不到地改變,即在你程序運行過程中一直會變,你希望這個值被正確的處理,每次從內存中去讀這個值,而不是因編譯器優化從緩存的地方讀取,比如讀取緩存在寄存器中的數值,從而保證volatile變量被正確的讀取。
在單任務的環境中,一個函數體內部,如果在兩次讀取變量的值之間的語句沒有對變量的值進行修改,那么編譯器就會設法對可執行代碼進行優化。由于訪問寄存器的速度要快過RAM(從RAM中讀取變量的值到寄存器),以后只要變量的值沒有改變,就一直從寄存器中讀取變量的值,而不對RAM進行訪問。
而在多任務環境中,雖然在一個函數體內部,在兩次讀取變量之間沒有對變量的值進行修改,但是該變量仍然有可能被其他的程序(如中斷程序、另外的線程等)所修改。如果這時還是從寄存器而不是從RAM中讀取,就會出現被修改了的變量值不能得到及時反應的問題。
(1)并行設備的硬件寄存器(如狀態寄存器);
(2)一個中斷服務子程序中訪問到的變量;
(3)多線程應用中被多個任務共享的變量。

mutable

mutalbe的中文意思是“可變的,易變的”,跟constant(既C++中的const)是反義詞。在C++中,mutable也是為了突破const的限制而設置的。被mutable修飾的變量(mutable只能由于修飾類的非靜態數據成員),將永遠處于可變的狀態,即使在一個const函數中。
詳見 https://www.cnblogs.com/xkfz007/articles/2419540.html

explicit

只能修飾構造函數,防止單參數的構造函數隱式類型轉換,把一個常量轉換成一個對象。
在沒有加explicit之前,可以把一個常量賦給一個對象。
推薦構造函數前最好加explict

size_t

size_t是一些C/C++標準在stddef.h中定義的,這個類型足以用來表示對象的大小,sizeof操作符的結果類型是size_t
size_t的真實類型與操作系統有關,size_t在32位架構上是4字節,在64位架構上是8字節,而int在不同架構下都是4字節,且int為帶符號數,size_t為無符號數。

sizeof

sizeof(類型名)或者sizeof 表達式,返回的是類型或者表達式占用字節的大小。

宏和內聯函數

先說宏和函數的區別:

  1. 宏做的是簡單的字符串替換(注意是字符串的替換,不是其他類型參數的替換),而函數是參數的傳遞,參數是有數據類型的,可以是各種各樣的類型。
  2. 宏的參數替換是不經計算而直接處理的,而函數調用是將實參的值傳遞給形參,既然說是值,自然是計算得來的.
  3. 宏在編譯之前進行,即先用宏體替換宏名,然后再編譯的,而函數顯然是編譯之后,在執行時才調用的。因此,宏占用的是編譯的時間,而函數占用的是執行時的時間。
  4. 宏的參數是不占內存空間的,因為只是做字符串的替換,而函數調用時的參數傳遞則是具體變量之間的信息傳遞,形參作為函數的局部變量,顯然是占用內存的。
  5. 函數的調用是需要付出一定的時空開銷的,因為系統在調用函數時,要保留現場,然后轉入被調用函數去執行,調用完,再返回主調函數,此時再恢復現場,這些操作,顯然在宏中是沒有的。

https://blog.csdn.net/qq_33757398/article/details/81390151
內聯函數就是在程序編譯時,編譯器將程序中出現的內聯函數的調用表達式用內聯函數的函數體來代替。在函數聲明和定義前加上關鍵字inline,若代碼執行時間很短,且代碼塊比較短小,則可以使用內聯函數。
內聯函數與宏的區別:

  1. 內聯函數在運行時可調試,而宏定義不可以;
  2. 編譯器會對內聯函數的參數類型做安全檢查或自動類型轉換(同普通函數),而宏定義則不會;
  3. 內聯函數可以訪問類的成員變量,宏定義則不能;
  4. 在類中聲明同時定義的成員函數,自動轉化為內聯函數。

原碼,反碼,補碼

正整數,三者一樣
負整數 -1100110,原碼 11100110(用1表示符號位),反碼 10011001(符號位不變,原碼按位取反),補碼 10011010(反碼加1)

按位運算符用法

  1. 按位與(&)
    將變量某個位置零:a = a & 0xfe
    取出int型a的低字節:c = a & 0xff
  2. 按位或(|)
    將int型變量a的低字節置1:a = a | 0xff
  3. 按位異或(^)
    使01111010低四位翻轉:01111010 ^ 00001111 = 01110101
    還可以用于兩個變量a和b的交換:a = a ^ b; b = b ^ a; a = a ^ b;
    當然,變量的交換還可以用加減法:a = a + b; b = a - b; a = a - b;

new和malloc的區別

  1. 屬性
    new/delete是C++關鍵字,需要編譯器支持。malloc/free是庫函數,需要頭文件支持。
  2. 參數
    使用new操作符申請內存分配時無須指定內存塊的大小,編譯器會根據類型信息自行計算。而malloc則需要顯式地指出所需內存的尺寸。
  3. 返回類型
    new操作符內存分配成功時,返回的是對象類型的指針,類型嚴格與對象匹配,無須進行類型轉換,故new是符合類型安全性的操作符。而malloc內存分配成功則是返回void * ,需要通過強制類型轉換將void*指針轉換成我們需要的類型。
  4. 分配失敗
    new內存分配失敗時,會拋出bac_alloc異常。malloc分配內存失敗時返回NULL。
  5. 自定義類型
    new會先調用operator new函數,申請足夠的內存(通常底層使用malloc實現)。然后調用類型的構造函數,初始化成員變量,最后返回自定義類型指針。delete先調用析構函數,然后調用operator delete函數釋放內存(通常底層使用free實現)。
    malloc/free是庫函數,只能動態的申請和釋放內存,無法強制要求其做自定義類型對象構造和析構工作。
  6. 重載
    C++允許重載new/delete操作符,特別的,布局new的就不需要為對象分配內存,而是指定了一個地址作為內存起始區域,new在這段內存上為對象調用構造函數完成初始化工作,并返回此地址。而malloc不允許重載。
  7. 內存區域
    new操作符從自由存儲區(free store)上為對象動態分配內存空間,而malloc函數從堆上動態分配內存。自由存儲區是C++基于new操作符的一個抽象概念,凡是通過new操作符進行內存申請,該內存即為自由存儲區。而堆是操作系統中的術語,是操作系統所維護的一塊特殊內存,用于程序的內存動態分配,C語言使用malloc從堆上分配內存,使用free釋放已分配的對應內存。自由存儲區不等于堆,如上所述,布局new就可以不位于堆中。
    關于自由存儲區與堆的區別:https://www.cnblogs.com/QG-whz/p/5060894.html

指針和引用的區別

引用是一個變量的別名,不能為空,必須在聲明的時候初始化,而且之后不能修改為其他的變量的別名;
指針的值是一塊內存的地址,可以為空,可以先聲明后初始化,后面可以修改其指向的內存地址。

野指針

野指針指向一個已刪除的對象或未申請訪問受限內存區域的指針。與空指針不同,野指針無法通過簡單地判斷是否為NULL避免,而只能通過養成良好的編程習慣來盡力減少。對野指針進行操作很容易造成程序錯誤。

  1. 指針變量沒有被初始化。任何指針變量剛被創建時不會自動成為NULL指針,它的缺省值是隨機的,它會亂指一氣。所以,指針變量在創建的同時應當被初始化,要么將指針設置為NULL,要么讓它指向合法的內存。
  2. 指針p被free或者delete之后,沒有置為NULL,讓人誤以為p是個合法的指針。別看free和delete的名字惡狠狠的(尤其是delete),它們只是把指針所指的內存給釋放掉,但并沒有把指針本身干掉。通常會用語句if (p != NULL)進行防錯處理。很遺憾,此時if語句起不到防錯作用,因為即便p不是NULL指針,它也不指向合法的內存塊。釋放內存后必須把指針指向NULL,防止指針在后面不小心又被解引用了。具體參考博文:https://blog.csdn.net/dangercheng/article/details/12618161
  3. 指針超過了變量的作用范圍。即在變量的作用范圍之外使用了指向變量地址的指針。這一般發生在將調用函數中的局部變量的地址傳出來引起的。局部變量的作用范圍雖然已經結束,內存已經被釋放,然而地址值仍是可用的,不過隨時都可能被內存管理分配給其他變量。

因此指針的使用需要注意:
a. 函數的返回值不能是指向棧內存的指針或引用,因為棧內存在函數結束時會被釋放.
b. 在使用指針進行內存操作前記得要先給指針分配一個動態內存。
c. 聲明一個指針時最好初始化,指向NULL或者一塊內存。

智能指針

https://www.cnblogs.com/wxquare/p/4759020.html
使用普通指針,容易造成堆內存泄露(忘記釋放),二次釋放,程序發生異常時內存泄露等問題等。C++11中引入了智能指針的概念,能更好的管理堆內存。

深拷貝和淺拷貝

https://blog.csdn.net/libin66/article/details/53140284
淺拷貝是只對指針進行拷貝,兩個指針指向同一個內存塊,深拷貝是對指針和指針指向的內容都進行拷貝,拷貝后的指針是指向不同內的指針。

形參和實參

概念
  1. 形參
    形參出現在函數定義的地方,多個形參之間以逗號分隔,形參規定了一個函數所接受數據的類型和數量。
    形參和函數體內部定義的變量統稱為局部變量,僅在函數的作用域內可見,同時局部變量還會隱藏在外層作用域中同名的其他所有聲明(局部變量和全局變量可以重名)
  2. 實參
    出現在函數調用的地方,實參的數量與類型與形參一樣,實參用于初始化形參。
實參與形參值傳遞的方式
  1. 值傳遞
    在值傳遞過程中,實參和形參位于內存中兩個不同地址中,參數傳遞的實質是將原函數中變量的值,復制到被調用函數形參所在的存儲空間中,這個形參的地址空間在函數執行完畢后,會被回收掉。整個被調用函數對形參的操作,只影響形參對應的地址空間,不影響原來函數中的變量的值,因為這兩個不是同一個存儲空間。
  2. 地址傳遞(指針傳遞)
    這種參數傳遞方式中,實參是變量的地址,形參是指針類型的變量,在函數中對指針變量的操作,就是對實參(變量地址)所對應的變量的操作,,函數調用結束后,原函數中的變量的值將會發生改變。
  3. 引用傳遞
    這種參數傳遞方式中,形參是引用類型變量,其實就是實參的一個別名,在被調用函數中,對引用變量的所有操作等價于對實參的操作,這樣,整個函數執行完畢后,原先的實參的值將會發生改變。
    如果在實參前加上const關鍵字修飾,則引用傳遞可以不改變實參的值,既達到了傳值的目的,提高了效率,還保證了原實參不會被修改。
    https://blog.csdn.net/u012677715/article/details/73825856
    https://www.cnblogs.com/kane0526/p/3913284.html
    https://www.cnblogs.com/tanjuntao/p/8678927.html

類與結構體

區別:類的缺省訪問權限是private,結構體的缺省訪問權限是public。
一般在定義用來保存數據而沒有什么操作的類型時使用結構體。

sizeof 類或者結構體

https://blog.csdn.net/EVEcho/article/details/81115683
http://www.cnblogs.com/0201zcr/p/4789332.html
默認情況下:

  1. 結構體變量中成員的偏移量必須是成員大小的整數倍(0被認為是任何數的整數倍)
  2. 結構體大小必須是所有成員大小的整數倍,也即所有成員大小的公倍數。

如果是使用了#pragma pack (n)指令指定對齊字節數,則取n和sizeof(類型)的較小值作為對齊字節。

#include<iostream>
#pragma pack (2)
using namespace std;
struct TEST
{
    char a;
    int b;
    short c;
};
int main() {
    cout << sizeof(TEST);
    return 0;
}
#pragma pack (1)時輸出7
#pragma pack (2)時輸出8
#pragma pack (2)時輸出12

面向對象三大特性

  1. 封裝
    將抽象出的數據成員、代碼成員相結合,將它們視為一個整體。使用者不必了解具體的實現細節,只需要通過外部接口,以特定的訪問權限使用類的成員。
  2. 多態
    同一名稱,不同的功能實現方式。達到行為標識統一,減少程序中標識符的個數。
    編譯時的多態:重載
    運行時的多態:虛函數
  3. 繼承
    基類與派生類,父類與子類的關系

繼承的三種方式

  1. 公有繼承(public)
    訪問控制:基類的public和protected成員訪問屬性在派生類中保持不變,private成員不可直接訪問。
    訪問權限:派生類中的成員函數可以訪問基類中的public和protected成員,不能直接訪問基類的private成員。派生類的對象只能訪問public成員。
  2. 私有繼承(private)
    訪問控制:基類的public和protected成員都以private身份出現在派生類中,private成員不可直接訪問。
    訪問權限:派生類的成員函數可以直接訪問基類中的public和protected成員,不能直接訪問private成員。派生類的對象不能直接訪問從基類繼承的任何成員。
  3. 保護繼承(protected)
    訪問控制:基類的public和protected成員都以protected身份出現在派生類中,private成員不可直接訪問。
    訪問權限:派生類的成員函數可以直接訪問基類中的public和protected成員,不能直接訪問private成員。派生類的對象不能直接訪問從基類繼承的任何成員。
    對于類的對象來說,protected成員與private成員性質相同,都不能通過對象直接訪問,對于派生類成員來說,基類的protected成員與public成員性質相同。

基類與派生類轉換

https://blog.csdn.net/Jaihk662/article/details/79826056
公有派生類對象可以被當作基類的對象使用,反之則不可
派生類的對象可以隱含轉換為基類對象
派生類的對象可以初始化基類的引用
派生類的指針可以隱含轉換為基類的指針
通過基類對象名,指針只能使用從基類繼承的成員

#include<iostream>
using namespace std;
class BaseA
{
public:
    BaseA(int x, int y)
    {
        this->x = x;
        this->y = y;
    }
    void Disp()
    {
        printf("x = %d   y = %d\n", x, y);
    }
private:
    int x, y;
};
class B : public BaseA
{
public:
    B(int x, int y, int z) : BaseA(x, y), z(z) {}
    void Disp()
    {
        BaseA::Disp();
        printf("z = %d\n", z);
    }
private:
    int z;
};
void Print1(BaseA &Q) { Q.Disp(); }
void Print2(B &Q) { Q.Disp(); }
void Print3(BaseA Q) { Q.Disp(); }
void Print4(B Q) { Q.Disp(); }

int main()
{
    BaseA a(3, 4);
    a.Disp();
    B b(10, 20, 30);
    b.Disp();   puts("");

    BaseA * pa;
    B * pb;
    pa = &a;
    pa->Disp();
    pb = &b;
    pb->Disp(); puts("");

    pa = &b;                //pa為基類指針,指向派生類對象是合法的,因為派生類對象也是基類對象
    pa->Disp(); puts("");   //這里調用的是基類的Disp(),如果要實現調用派生類的Disp函數,需要用虛函數實現多態性
                            //  pb = &a;                //非法,派生類指針不能指向基類對象

    Print1(b);          //因為派生類對象也是基類對象,所以可以將派生類對象賦值給基類引用,函數仍然會調用基類的Disp()
    Print2(b);
    Print3(b);          //派生類對象賦值給基類對象時,派生類對象基類部分被復制給形參
    Print4(b);  puts("");

    Print1(a);
    Print3(a);  puts("");
    //  Print2(a);  Print4(a);      //非法,不能用基類對象給派生類引用賦值,也不能用基類對象給派生類對象賦值

    //pb = pa;              //非法,不能用基類指針給派生類指針賦值
    pa = &a;
    pb = (B*)pa;          //可以強制轉換,但是非常不安全
    pb->Disp();             //a中沒有p成員,而這里會調用派生類的Disp(),導致輸出p為垃圾值
    return 0;
}

虛基類

當派生類從多個基類派生,而這些基類又有共同基類,則在訪問共同基類成員時,會產生冗余。因而需要在繼承基類的時候將基類聲明為虛基類:
class B1: virtual public B
這樣就可以為最遠的派生類提供唯一的基類成員,不產生多次復制。在建立對象時,只有最遠派生類的構造函數調用虛基類的構造函數,其他類對虛基類構造函數的調用被忽略。

重載,重寫和重定義

https://www.cnblogs.com/weizhixiang/articles/5760286.html

  1. 成員函數重載特征:
    a 相同的范圍(在同一個類中)
    b 函數名字相同
    c 參數不同
    d virtual關鍵字可有可無
    e 不能重載的運算符 ".", ".*", "::", "?:"
  2. 重寫(覆蓋)是指派生類函數覆蓋基類函數,特征是:
    a 不同的范圍,分別位于基類和派生類中
    b 函數的名字相同
    c 參數相同
    d 基類函數必須有virtual關鍵字
    e 方法被定義為final不能被重寫
  3. 重定義(隱藏)是指派生類的函數屏蔽了與其同名的基類函數,規則如下:
    a 如果派生類的函數和基類的函數同名,但是參數不同,此時,不管有無virtual,基類的函數被隱藏。
    b 如果派生類的函數與基類的函數同名,并且參數也相同,但是基類函數沒有vitual關鍵字,此時,基類的函數被隱藏。

函數重載 變量前有無const是否可以重載

https://www.cnblogs.com/qingergege/p/7609533.html

  1. fun(int i) 和 fun(const int i),不能重載
    二者是一樣的,是因為函數調用中存在實參和形參的結合。假如我們用的實參是int a,那么這兩個函數都不會改變a的值,這兩個函數對于a來說是沒有任何區別的,所以不能通過編譯,提示重定義。
  2. fun(char *a) 和 fun(const char *a),可以重載
    因為char *a 中a指向的是一個字符串變量,而const char *a指向的是一個字符串常量,所以當參數為字符串常量時,調用第二個函數,而當函數是字符串變量時,調用第一個函數。
  3. char *a和char * const a,不能重載
    這兩個都是指向字符串變量,不同的是char *a是指針變量 而char *const a是指針常量,這就和int i和const int i的關系一樣了,所以也會提示重定義。
  4. 對于引用,比如int &i 和const int & i 也是可以重載的,原因是第一個 i 引用的是一個變量,而第二個 i 引用的是一個常量,兩者是不一樣的,類似于上面的指向變量的指針的指向常量的指針。

虛函數

  1. 虛函數的原理
    https://www.cnblogs.com/malecrab/p/5572730.html
    http://www.cnblogs.com/malecrab/p/5572119.html
    虛函數的動態綁定實現機制:
    只有通過基類的引用或者指針調用虛函數時,才能發生動態綁定,如果使用對象來操作虛函數的話,仍然會采用靜態綁定的方式。因為引用或者指針既可以指向基類對象,也可以指向派生類對象的基類部分。絕對不要重新定義一個繼承而來的virtual函數的缺省參數值,因為缺省參數值都是靜態綁定(為了執行效率),而virtual函數卻是動態綁定。
  2. 虛函數的使用
    https://blog.csdn.net/LC98123456/article/details/81143102
    http://www.lxweimin.com/p/f85bd1728bf0
    虛函數是聲明時前面加了virtual關鍵字的函數,作用是實現運行時動態綁定。一般有兩種函數會聲明為虛函數,一種是基類的析構函數,另一種是在派生類重寫了基類的普通成員函數,而且使用了一個基類指針調用該成員函數,要想調用派生類對象的同名成員函數,則需要將基類的該成員函數聲明為虛函數。

a. 虛析構函數

#include <iostream>
using namespace std;

class Base
{
public:
    Base()
    {
        cout << "create Base " << endl;
    }
    virtual ~Base()
    {

        cout << "delete Base" << endl;
    }
};
class Inherit :public Base
{
public:
    Inherit()
    {
        cout << "create Inherit" << endl;
    }
    ~Inherit()
    {
        cout << "delete Inherit" << endl;

    }
};
int main()
{
    Base *p;
    p = new Inherit;
    delete p;
    return 0;
}

運行結果:

Base count
Inherit count
Inherit descout
Base descount

上面的代碼我將指針的聲明和初始化拆開成兩行,調試的時候發現聲明的時候并沒有調用基類的構造函數,在初始化的時候一次性調用了基類和派生類的構造函數,而delete p的時候就調用了派生類和基類的析構函數。有點蒙圈,哪位大佬可以幫我解釋一下?

b. 基類虛函數成員:

#include <iostream>
using namespace std;
class A {
public:
    A() {
        ver = 'A';
    }
    void print() const {
        cout << "The A version is: " << ver << endl;
    }
protected:
    char ver;
};


class D1 :public A {
public:
    D1(int number) {
        info = number;
        ver = '1';
    }
    void print() const {
        cout << "The D1 info: " << info << " version: " << ver << endl;
    }
private:
    int info;
};


class D2 :public A {
public:
    D2(int number) {
        info = number;
    }
    void print() const {
        cout << "The D2 info: " << info << " version: " << ver << endl;

    }
private:
    int info;
};

int main() {
    A a;
    D1 d1(4);
    D2 d2(100);

    A *p = &a;
    p->print();
    p = &d1;
    p->print();
    p = &d2;
    p->print();

    system("pause");
    return 0;
}
The A version is: A
The A version is: 1
The A version is: A

我們先看上述代碼,派生類中重新定義了基類中的函數,我們在使用一個基類指針指向派生類的時候,本義想要調用派生類中的重定義函數,但是由于基類中此函數不是虛函數,因此指針會靜態綁定此函數,結果就不是我們的本意。而如果我們將基類中的方法改成虛函數,如下:

virtual void print() const {
    cout << "The A version is: " << ver << endl;
}
The A version is: A
The D1 info: 4 version: 1
The D2 info: 100 version: A

可以看到,將基類方法改成虛函數,那么就會動態綁定,在運行時才決定調用哪個函數。

補充:上面函數后面有個const修飾,表示該成員函數不能修改任何成員變量,除非成員變量用mutable修飾。const修飾的成員函數也不能調用非const修飾的成員函數,因為可能引起成員變量的改變。
mutalbe的中文意思是“可變的,易變的”,跟constant(既C++中的const)是反義詞。在C++中,mutable也是為了突破const的限制而設置的。被mutable修飾的變量(mutable只能由于修飾類的非靜態數據成員),將永遠處于可變的狀態,即使在一個const函數中。
詳見 https://www.cnblogs.com/xkfz007/articles/2419540.html

純虛函數和抽象類

https://blog.csdn.net/qq_36221862/article/details/61413619
純虛函數的聲明:
virtual 函數類型 函數名 (參數表列) = 0;
一般用于抽象類中,抽象類不可用于實例化。

復制構造函數(拷貝構造函數)

復制構造函數的作用是用一個已存在的對象去初始化同類型的新對象,形參為本類的對象引用。一般聲明形式為:類名 (const 類名 &對象名);
其被調用的三種情況:

  1. 定義一個對象時,以本類的另一個對象作為初始值;
  2. 函數的形參是類的對象,調用函數時,將使用實參對象初始化形參對象;
  3. 如果函數的返回值是類的對象,函數執行完返回主調函數時,將使用return語句中的對象初始化一個臨時無名對象,傳遞給主調函數,此時發生復制構造。

拷貝構造函數的參數為引用而不是值傳遞的原因:如果是值傳遞,那么在調用拷貝構造函數的時候會用實參對象初始化形參對象,而這個過程又滿足第一個條件,則繼續調用拷貝構造函數,這里面又會有實參初始化形參,繼續下去就會無限循環調用。

靜態變量,靜態函數

https://www.cnblogs.com/secondtonone1/p/5694436.html
https://www.cnblogs.com/ppgeneve/p/5091794.html

  1. 靜態局部變量和靜態全局變量
    靜態局部變量具有局部作用域。它只被初始化一次,自從第一次初始化直到程序結束都一直存在,即它的生命周期是程序運行就存在,程序結束就結束,
    靜態全局變量具有全局作用域,他與全局變量的區別在于如果程序包含多個文件的話,他作用于定義它的文件里,不能作用到其他文件里,即被static關鍵字修飾過的變量具有文件作用域。
  2. 類的靜態成員變量和靜態成員函數
    靜態成員變量和靜態成員函數主要是為了解決多個對象數據共享的問題,他們都不屬于某個對象,而是屬于類的。
    靜態成員變量定義的時候前面加static關鍵字,初始化必須在類外進行,前面不加static。其初始化格式如下:
    <數據類型><類名>::<靜態數據成員名>=<值> //靜態變量的初始化
    靜態成員函數也是屬于類的成員,不能直接引用類的非靜態成員,如果靜態成員函數中要引用非靜態成員時,可通過對象來引用。靜態成員函數使用如下格式:<類名>::<靜態成員函數名>(<參數表>);

全局變量,靜態全局變量可以被其他文件調用么,為什么

https://blog.csdn.net/candyliuxj/article/details/7853938
https://www.cnblogs.com/invisible2/p/6905892.html

  1. 全局變量
    在a.h中聲明全局變量:extern int a; 一般需要加上extern,否則編譯器可能默認給一個初始化值。那樣會導致多個cpp文件在包含此頭文件時發生重復定義。
    在a.cpp文件中定義全局變量:int a =10;
    在b.cpp中想要使用這個全局變量,有兩種方法,第一種是使用extern關鍵字,例如extern int a; 代表當前變量a 的定義來自于其他文件,當進行編譯時,會去其他文件里面找。且在當前文件僅做聲明,而不是重新定義一個新的變量。第二種方法是使用include "a.h",這種方法的好處是a里面的方法可以直接拿過來使用。
    extern作用
    作用一:當它與"C"一起連用時,如extern "C" void fun(int a, int b);,則編譯器在編譯fun這個函數名時按C的規則去翻譯相應的函數名而不是C++的。
    作用二:當它不與"C"在一起修飾變量或函數時,如在頭文件中,extern int g_nNum;,它的作用就是聲明函數或變量的作用范圍的關鍵字,其聲明的函數和變量可以在本編譯單元或其他編譯單元中使用。
  2. 靜態全局變量(static)
    注意使用static修飾變量,就不能使用extern來修飾,即static和extern不可同時出現。 static修飾的全局變量的聲明與定義同時進行,即當你在頭文件中使用static聲明了全局變量,同時它也被定義了。
    static修飾的全局變量的作用域只能是本身的編譯單元。在其他編譯單元使用它時,只是簡單的把其值復制給了其他編譯單元,其他編譯單元會另外開個內存保存它,在其他編譯單元對它的修改并不影響本身在定義時的值。即在其他編譯單元A使用它時,它所在的物理地址,和其他編譯單元B使用它時,它所在的物理地址不一樣,A和B對它所做的修改都不能傳遞給對方。
    多個地方引用靜態全局變量所在的頭文件,不會出現重定義錯誤,因為在每個編譯單元都對它開辟了額外的空間進行存儲。

友元函數和友元類

友元函數是在類聲明中由關鍵字friend修飾的非成員函數,在它的函數體中能夠通過對象名訪問private和protected成員。
友元類是將一個類A聲明為另一個類B的友元,那么類A的所有成員都可以訪問類B中被隱藏的信息。一般是在類A中定義了一個B的對象,通過此對象訪問類B的所有成員。

STL中vector和list

https://www.cnblogs.com/shijingjing07/p/5587719.html
vector是一片連續的內存空間,相當于數組。隨機訪問方便,插入和刪除效率低。
list是不連續的內存空間,是雙向鏈表。隨機訪問效率低,插入和刪除方便。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容