C++基礎知識

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++中默認的析構函數不是虛函數,這是因為虛函數表和虛表指針會佔用額外的空間。
  • 虛函數是在類結束生命周期自動調用,並且規定析構函數只會釋放該類的成員,所以需要虛函數來遞歸的將父類的成員釋放。
  • 釋放順序為派生類->父類。

對於在析構函數或者構造函數中調用了虛函數,我們會執行與析構函數或者構造函數相符合的類的虛函數版本

Tags: