C++基礎知識

haoyuhuang發表於2021-11-11

1 static關鍵字

1.1 程式導向的static

1.1.1 全域性靜態變數

  • 宣告使用:在全域性變數前,加上關鍵字static,該變數就被定義成為一個靜態全域性變數;
  • 儲存位置:靜態全域性變數儲存在全域性靜態儲存區,全域性靜態儲存區在程式執行期間一直存在,即main函式執行期間全域性靜態儲存區的資料不會被系統銷燬回收空間;
  • 初始化:未經初始化的全域性靜態變數會被預設初始化為0;
  • 可見性:全域性靜態變數在整個檔案中都是可見的,但是不被其他檔案可見,因此其他檔案不能extern一個靜態變數,其他檔案可以重新定義一個相同名字的變數。

1.1.2 區域性靜態變數

  • 宣告使用:在區域性變數前,加上關鍵字static,該變數就被定義成為一個靜態區域性變數;
  • 儲存位置:與全域性靜態變數一樣,儲存在全域性靜態儲存區;這樣有一個好處就是,但區域性變數不再被使用時,並不會被系統回收記憶體,因此其可以一直被保留,直到程式執行結束。我們常見的一種情況是,我們需要在一個函式中保留某個區域性變數的值(因為函式可能被重複呼叫,但要保留上一次的資訊),這時不得不使用全域性變數或者入口引數進行儲存。而靜態區域性變數完美解決這個問題,因為靜態區域性變數宣告定義語句只執行一次,並且不會被回收記憶體,所以解決上述問題比較方便;
  • 初始化:未經初始化的區域性靜態變數會被預設初始化為0;
  • 可見性:與區域性變數定義域相類似,只不過當定義域結束,記憶體不會被回收,仍然能被保留,同樣只能在一個檔案中可見。

1.1.3 靜態函式

  • 宣告使用:在函式的返回型別前加上static關鍵字,函式即被定義為靜態函式;
  • 可見性:靜態函式與普通函式不同,它只能在宣告它的檔案當中可見,不能被其它檔案使用;
  • 好處:只能在定義宣告它的檔案中使用,在其他檔案定義不會有名字衝突。

1.2 物件導向的static

1.2.1 類中的靜態成員

  • 宣告使用:在類內資料成員的宣告前加上關鍵字static,該資料成員就是類內的靜態資料成員;
  • 特點:
    1.靜態資料成員是屬於類的,非靜態資料成員是屬於物件的。也就是說,對於非靜態資料成員,在例項化一個類物件時,非靜態資料成員都會進行拷貝一次,只供例項化出來的類物件使用;而對於靜態資料成員來說,只進行分配一次記憶體,所有類物件共享這一塊記憶體,並不單獨屬於某一個類物件,而是屬於這個類的所有物件;
    2.靜態資料成員是儲存在全域性資料區,因此,其不能在類宣告的時候進行初始化定義;
    3.靜態資料成員和普通資料成員一樣遵從public,protected,private訪問規則;
    4.由於靜態資料成員是在全域性資料區,其在類沒有例項化之前就已經初始化存在,因此我們在沒有例項化類之前就可見,並且可操作它;
    5.靜態資料成員初始化與一般資料成員初始化不同。靜態資料成員初始化的格式為:<資料型別><類名>::<靜態資料成員名>=<值>;
    6.類的靜態資料成員有兩種訪問形式:<類物件名>.<靜態資料成員名> 或 <類型別名>::<靜態資料成員名>;
  • 優勢:
    1.靜態資料成員沒有進入程式的全域性名字空間,因此不存在與程式中其它全域性名字衝突的可能性;
    2.可以實現資訊隱藏。靜態資料成員可以是private成員,而全域性變數不能;

1.2.2 類中的靜態成員函式

  • 說明:與靜態資料成員一樣,我們也可以建立一個靜態成員函式,它為類的全部服務而不是為某一個類的具體物件服務。靜態成員函式與靜態資料成員一樣,都是類的內部實現,屬於類定義的一部分。普通的成員函式一般都隱含了一個this指標,this指標指向類的物件本身,因為普通成員函式總是具體的屬於某個類的具體物件的。通常情況下,this是預設的。如函式fn()實際上是this->fn()。但是與普通函式相比,靜態成員函式由於不是與任何的物件相聯絡,因此它不具有this指標。從這個意義上講,它無法訪問屬於類物件的非靜態資料成員,也無法訪問非靜態成員函式,它只能呼叫其餘的靜態成員函式。
  • 總結為以下幾點:
    1.出現在類體外的函式定義不能指定關鍵字static;
    2.靜態成員之間可以相互訪問,包括靜態成員函式訪問靜態資料成員和訪問靜態成員函式;
    3.非靜態成員函式可以任意地訪問靜態成員函式和靜態資料成員;
    4.靜態成員函式不能訪問非靜態成員函式和非靜態資料成員;
    5.由於沒有this指標的額外開銷,因此靜態成員函式與類的全域性函式相比速度上會有少許的增長;
    6.呼叫靜態成員函式,可以用成員訪問操作符(.)和(->)為一個類的物件或指向類物件的指標呼叫靜態成員函式,也可以直接使用如下格式:
    <類名>::<靜態成員函式名>(<參數列>)
    呼叫類的靜態成員函式。

2 說一下C++與C的區別

  • 主要區別:C語言是面向結構的語言, C++是物件導向語言,C++已經從根本上發生了質的飛躍,對C進行了豐富的擴充套件;
  • 語法上的區別:
1. C++具有封裝、繼承和多型三種特性
2. C++相比C,增加多許多型別安全的功能,比如強制型別轉換
3. C++支援正規化程式設計,比如模板類、函式模板等
4. 不能用""來代替<>包含標頭檔案
5. main()函式的返回值不能設定為void,一般設定為int main(void)  void表示不需要從命令列獲取引數;如果需要則int main(int argc,char *argv[])。
6. 如果標頭檔案是C++持有,則去掉.h字尾,並放入std名稱空間,比如iostream;如果這個標頭檔案是C持有的,去掉.h並在前面加上c字元,如cstring;注意標頭檔案不要搞混淆

3 說一說C++四種cast轉換

c風格的型別轉換統一為 TYPE B = (TYPE)A,而C++種這種語法標識太多,並且應對的情況也很多,所以C++針對不同的情況,引入了4種新的型別轉換操作符 static_cast、const_cast、dynamic_cast、reinterpret_cast

  • static_cast
1. 代替隱式轉換,
	a.void*轉換為任意型別的指標;b.任意指標轉為void*;
	c.編譯器允許的跨型別轉換,比如char型別轉為int型別,double轉int型;
2. 做基類與派生類之間的轉換,
	a.派生類轉換為基類是安全的;
	b.基類轉換為派生類是不安全的,結果未知。這是由於派生類的成員一般比基類要多造成的。
  • const_cast
1. const_cast用於去除const(volatile)屬性,將只讀變為可讀寫;
2. const_cast只針對指標、引用、this指標,其他情況會出錯。
  • dynamic_cast
1. dynamic_cast和static_cast的效果是一樣的;在進行下行轉換時,
	dynamic_cast具有型別檢查的功能,彌補了static_cast型別不安全的缺陷,比static_cast更安全
2. 多用於有虛擬函式的基類與其派生類之間的轉換,特點是進行執行時檢測轉換型別是否安全,
	如果轉換失敗返回nullptr,依賴於RTTI技術,但是有額外的函式開銷,所以非必要的時候不使用。

RTTI是一種意思是執行時型別資訊,它提供了執行時確定物件型別的方法,換句話說,RTTI是一種可以獲取變數在執行時的實際指向的機制,使用了typeid()函式

  • reinterpret_cast
1. reinterpret代替顯示轉換,用於轉換各種高風險的轉換(隱式轉換無法轉換的)
2. 不進行型別檢查,只進行強制複製,有安全隱患
3. 是四種轉換種功能最為強大的。

4 C/C++中引用和指標的區別

  1. 指標有自己獨立的記憶體空間,引用只是一個別名(對於引用是否佔記憶體空間有爭議,應該是根據編譯器來決定,但是C++設計的本意是其不佔用記憶體空間);
  2. sizeof呼叫,指標一般佔4個位元組,而引用在用sizeof時,顯示的是被引用的變數的實際佔用記憶體大小;
  3. 指標可以被初始化為NULL,而引用必須是對一個例項的引用;
  4. 有const指標,但是沒有const引用;
  5. 有多級指標 **p,但沒有多級引用;
  6. 指標需要解引用才能操作物件,但是引用相當於別名可直接進行操作;
  7. 作為返回值,應該用指標,而不應採用引用,採用引用容易造成記憶體洩露。

5 說一說C++中的智慧指標

C++中共有4個智慧指標, 分別是auto_ptr、shared_ptr、unique_ptr、weak_ptr,目前auto_ptr已經被C++11標準廢除

為什麼要使用智慧指標?
1. 動態記憶體的管理是通過一堆運算子new和delete來完成的。但是在保證動態記憶體在合適的時間釋放是一件極其困難的事情,所以C++標準庫提供了智慧指標來進行管理動態物件。智慧指標的行為類似於一般的指標,重要的區別就是其能自動的釋放所指向的物件。對於shared_ptr、unique_ptr、weak_ptr都定義在memory標頭檔案中

2. 智慧指標的作用是管理一個指標,因為存在以下這種情況:申請的空間在函式結束時忘記釋放,造成記憶體洩漏。使用智慧指標可以很大程度上的避免這個問題,因為智慧指標就是一個類,當超出了類的作用域是,類會自動呼叫解構函式,解構函式會自動釋放資源。所以智慧指標的作用原理就是在函式結束時自動釋放記憶體空間,不需要手動釋放記憶體空間。

  • auto_ptr
    C++98的方案,C++11已經廢棄。auto_ptr採用所有權方式,其缺點是存在潛在的記憶體崩潰問題

  • unique_ptr(用來替換auto_ptr)
    unique_ptr實現獨佔式擁有或嚴格擁有概念,保證同一時間內只有一個智慧指標可以指向該物件。它對於避免資源洩露(例如“以new建立物件後因為發生異常而忘記呼叫delete”)特別有用。對於臨時右值編譯器允許將unique_ptr賦值。注意我們雖然不能對unique_ptr進行拷貝或者賦值操作,但我們能通過release或者reset將一個unique_ptr的所有權進行轉移給另一個unique_ptr

  • shared_ptr
    shared_ptr實現共享式擁有概念。多個智慧指標可以指向相同物件,該物件和其相關資源會在“最後一個引用被銷燬”時候釋放。從名字share就可以看出了資源可以被多個指標共享,它使用計數機制來表明資源被幾個指標共享。可以通過成員函式use_count()來檢視資源的所有者個數。除了可以通過new來構造,還可以通過傳入auto_ptr, unique_ptr,weak_ptr來構造。當我們呼叫release()時,當前指標會釋放資源所有權,計數減一。當計數等於0時,資源會被釋放。

  • weak_ptr
    weak_ptr 是一種不控制物件生命週期的智慧指標, 它指向一個 shared_ptr 管理的物件. 進行該物件的記憶體管理的是那個強引用的 shared_ptr. weak_ptr只是提供了對管理物件的一個訪問手段。weak_ptr 設計的目的是為配合 shared_ptr 而引入的一種智慧指標來協助 shared_ptr 工作, 它只可以從一個 shared_ptr 或另一個 weak_ptr 物件構造, 它的構造和析構不會引起引用記數的增加或減少。weak_ptr是用來解決shared_ptr相互引用時的死鎖問題,如果說兩個shared_ptr相互引用,那麼這兩個指標的引用計數永遠不可能下降為0,資源永遠不會釋放。它是對物件的一種弱引用,不會增加物件的引用計數,和shared_ptr之間可以相互轉化,shared_ptr可以直接賦值給它,它可以通過呼叫lock函式來獲得shared_ptr。

6 智慧指標有沒有記憶體洩露的情況?如何解決?

  • 當兩個物件互相使用shared_ptr指向對方,會造成迴圈引用,導致引用計數失效,從而導致記憶體洩露。
  • 解決方法:
    為了解決迴圈引用導致的記憶體洩漏,引入了weak_ptr弱指標,weak_ptr的建構函式不會修改引用計數的值,從而不會對物件的記憶體進行管理,其類似一個普通指標,但不指向引用計數的共享記憶體,但是其可以檢測到所管理的物件是否已經被釋放,從而避免非法訪問。

7 為什麼建構函式不能是虛擬函式?解構函式必須設定為虛擬函式?

1 建構函式不能是虛擬函式

  • 虛擬函式表vtable是在物件例項化出來之後才進行簡歷的,而虛擬函式的呼叫是通過查vtable來進行呼叫。對於建構函式來講,如果將其設計為虛擬函式,則相當於在物件未被例項化的時候進行呼叫虛擬函式,從實現機制上不支援。
  • 對於建構函式來說,執行順序是先例項化基類然後再例項化派生類。這樣看由於將建構函式設計為虛擬函式會造成在執行的時候不能確定該類是基類還是派生類亦或者是派生類的派生類,也就無法進行動態繫結。

2 解構函式被設定為虛擬函式

  • 為什麼C++中預設的解構函式不是虛擬函式? C++中預設的解構函式不是虛擬函式,這是因為虛擬函式表和虛表指標會佔用額外的空間。
  • 虛擬函式是在類結束生命週期自動呼叫,並且規定解構函式只會釋放該類的成員,所以需要虛擬函式來遞迴的將父類的成員釋放。
  • 釋放順序為派生類->父類。

對於在解構函式或者建構函式中呼叫了虛擬函式,我們會執行與解構函式或者建構函式相符合的類的虛擬函式版本