C和C++的區別和聯絡(擴充套件知識)
2017/3/17 複習整理:C/C++區別與聯絡;
關於C和C++的區別是面試中經常會被問到的問題,本著即將面試的心態,進行知識整理,並對小知識點進行擴充套件;
C/C++的聯絡:
- C++是C的超集,相容大部分C的語法的結構;
聯絡嘛我只能想到這個,畢竟cplusplus嘛!
C/C++區別:
第一點就應該想到C是面向過程的語言,而C++是面向物件的語言,一般簡歷上第一條都是熟悉C/C++基本語法,瞭解C++面向物件思想,那麼,請問什麼是面向物件?
C和C++動態管理記憶體的方法不一樣,C是使用malloc/free函式,而C++除此之外還有new/delete關鍵字;(關於malooc/free與new/delete的不同又可以說一大堆,最後的擴充套件_1部分列出十大區別);
接下來就不得不談到C中的struct和C++的類,C++的類是C所沒有的,但是C中的struct是可以在C++中正常使用的,並且C++對struct進行了進一步的擴充套件,使struct在C++中可以和class一樣當做類使用,而唯一和class不同的地方在於struct的成員預設訪問修飾符是public,而class預設的是private;
C++支援函式過載,而C不支援函式過載,而C++支援過載的依仗就在於C++的名字修飾與C不同,例如在C++中函式int fun(int ,int)經過名字修飾之後變為 _fun_int_int ,而C是
_fun,一般是這樣的,所以C++才會支援不同的引數呼叫不同的函式;C++中有引用,而C沒有;這樣就不得不提一下引用和指標的區別(文後擴充套件_2);
當然還有C++全部變數的預設連結屬性是外連結,而C是內連線;
C 中用const修飾的變數不可以用在定義陣列時的大小,但是C++用const修飾的變數可以(如果不進行&,解引用的操作的話,是存放在符號表的,不開闢記憶體);
當然還有區域性變數的宣告規則不同,多型,C++特有輸入輸出流之類的,很多,下面就不再列出來了; “`
小知識點補充擴充套件
擴充套件_1: 細數malloc/free和new/delete的十點區別
malloc是從堆上開闢空間,而new是從自由儲存區開闢;(自由儲存區是
C++抽象出來的概念,不僅可以是堆,還可以是靜態儲存區);malloc/free是函式,而new/delete是關鍵字;
malloc對開闢的空間大小需要嚴格指定,而new只需要物件名;
malloc開闢的空間即可以給單個物件用也可以給陣列用,釋放的方式都是
free();而new開闢物件陣列用的是new[size] ,釋放的的時候是 delete[]
(儘管內建型別可能不會引起問題,但是自定義型別的話,delete[]需要知道有
多少個物件,而這個計數就被放在這塊空間的頭部);
- 返回值問題,malloc開闢成功返回void*,需要強轉,失敗返回NULL,new
成功返回物件指標,失敗丟擲異常(這就可能會提到C++的new_handler機
制),雖然為了最大程度的相容C,C++的new也支援失敗返回NULL,但是一般不
被使用,大家可以瞭解一下;
- 是否呼叫構造和析構,這點應該放在前面,new和free不但負責開闢空間,
還會呼叫物件的建構函式和解構函式;最好了解一下new的三種表達形式(new運
算符,operator new(); placement new();)還有定位new表示式的
使用;
- 是否可以相互呼叫,new的實現可以用malloc,malloc的實現不可以使用
new;
- 是否可以被過載,我們可以過載自己的operator new/delete,但是不可
以過載new/delete/malloc/free;
- malloc開闢 的記憶體如果太小,想要換一塊大一點的,可以呼叫relloc實
現,但是new沒有直觀的方法來改變;
- 第十點其實前面已經提到,當new中的底層實現如果獲取不到更多的記憶體,
會觸發new_handler機制,留有一個set_new_handler控制代碼,看看使用者是否設
置了這個控制代碼,如果設定了就去執行,控制代碼的目的是看看能不能嘗試著從操作系
統釋放點記憶體,找點記憶體,如果實在不行就丟擲bad_alloc異常;而malloc就
沒有這種嘗試了;——-
擴充套件_2 指標和引用的區別
1.指標有自己的一塊空間,而引用只是一個別名;
2.使用sizeof看一個指標的大小是4,而引用則是被引用物件的大小;
3.指標可以被初始化為NULL,而引用必須被初始化且必須是一個已有物件
的引用;
4.作為引數傳遞時,指標需要被解引用才可以對物件進行操作,而直接對引
用的修改都會改變引用所指向的物件;
5.可以有const指標,但是沒有const引用;
6.指標在使用中可以指向其它物件,但是引用只能是一個物件的引用,不能
被改變;
7.指標可以有多級指標(**p),而引用至於一級;
8.指標和引用使用++運算子的意義不一樣;
擴充套件_3 常見關鍵字的作用
**1.static 關鍵字:**
修飾全域性變數時,會將變數的連結屬性變為內部連結屬性,並且變數
的儲存位置變為全域性靜態區;
修飾 區域性變數,改變區域性變數的儲存位置為靜態儲存區,改變區域性變
量的生命週期為整個程式開始到結束;
修飾類的成員變數和函式:屬於類而不屬於物件,屬於所有例項類;
**2.const關鍵字**
修飾全域性變數:C/C++略有不同,在上文已經提到,即C++的const修
飾的全域性變數可以作為屬組的初始化的大小,而C不可以;同時變數的
值不能被修改;C++利用const的這一屬性,代替C中的define進行
全域性常量的定義;擴充套件_4會就 define,const,inline進行對比
和分析;
修飾區域性變數:代表區域性變數的值不能被修改;
修飾指標:這個是經常問道的,我舉的例子可能不全面,但是是比較
常見的例子:
cons t int *p; //修飾的是p所指向的內容不能被改變,p可
以;
int const *p; //和上面是一樣的;
int* const p; //修飾的p指標不能改變;
修飾類的成員變數:必須在初始化列表初始化,除此之外,必須在初
始化列表初始化的還有,引用型別的資料成員,沒有預設建構函式的
物件成員,如果存在繼承關係,如果父類沒有預設的建構函式,則也
必須在初始化列表中被初始化,初始化列表對資料成員的初始化順序
是按照資料成員的宣告順序嚴格執行的;
修飾類的成員函式:一般放在成員函式的最後面,修飾的是類的成員
函式中的隱藏引數this指標,代表不可以通過this指標修改類的資料
成員,宣告形式例如:
Base::void fun() const;
關於const還有一個問題就是傳參和賦值的問題,一般來說,const
修飾的變數是安全的,沒有const修飾的變數是不安全的,一般在傳
參的時候,非const修飾的的變數可以傳給const修飾的,而const
修飾的不可以傳給非const修飾的形參,這就相當於把安全的東西交
給了不安全的人;而賦值的話更不用說了,const修飾的不可以傳給
沒有const修飾的變數;
**3.volatile**
volatile一般修飾變數,而它存在的原因是因為,我們的程式在進行
編譯的時候,編譯器會進行一系列的優化,比如,某個變數被修飾為
const的,編譯器就認為,這個值是隻讀的,就會在暫存器中儲存這
個變數的值,每次需要的時候從暫存器直接讀取,但是有時候,我們
可能會在不經意間修改了這個變數,比如說我們去了這個變數的地
址,然後強行改變這個變數在記憶體中的值,那麼編譯器並不知道,讀
取還是從暫存器中讀取,這就造成了結果的不匹配,而volatile宣告
的變數就會告訴編譯器,這個變數隨時會改變,需要每次都從內從中
讀取,就是不需要優化,從而避免了這個問題,其實,volatile應用
更多的場景是多執行緒對共享資源的訪問的時候,避免編譯器的優化,
而造成多執行緒之間的通訊不匹配!;
explicit關鍵字
首先需要了解什麼是隱式轉換,即在你沒有進行顯示的強轉的情況
下,賦值運算子左右兩個型別不一致的物件進行了型別轉換;或者
函式傳參的時候進行了型別轉換; 而explicit關鍵字存在的目的就是
禁止類的建構函式進行隱式的型別轉換,常見的就是string類的物件
就可以隱式型別轉換,比如:
strig s = "hello world";
因為string 有一個單參的char*建構函式,所有可以用hello w
orld構造一個string物件,然後呼叫string 類的拷貝建構函式;
有時候我們並不希望這種不是我們預期的情況發生,所以,我們可以
在類的建構函式之前+explicit關鍵字。禁止隱式轉換;
---希望讀者提建議繼續補充!(補充文章中講到的所有內容的不足之處)
擴充套件_4 define /const/inline 對比和分析
define作用於程式的預處理階段,而預處理階段做的主要工作為下面幾個
方面:巨集替換,去註釋以及條件編譯; define起作用的地方就在巨集替換階
段,只是單純的將巨集替換為程式碼,例如:
#define Add(a+b) a+b
下面這段程式碼用到了這個巨集:
int main()
{
int a = 1; //如果char a = '1';
int b = 2;
cout<<Add(a+b);
/* 替換為cout<<1+2; */
return 0;
}
從上面這個例子我們可以看出define的缺點很明顯,首先,define只 是單純的程式碼替換,不會進行型別的檢查,再者,我們上面的巨集定義 也很粗糙,嚴格點應該定義為: #define Add(a,b) (a)+(b) C++中一般建議使用const,列舉定義常量,這樣就會有型別檢查; 而巨集定義也可以定義出和函式一樣的功能: #define Swap(type,a,b) {type tmp, tmp = a; a = b; b = tmp;} 其實就算這樣寫,也還是存在上面提到的問題,並且這樣還不能進行調 試,因為巨集在預處理階段就替換了;
於是C++中又提供了一個inline內聯關鍵字 ,可以實現和define相同的
功能,並且支援型別檢查和除錯,一般宣告在函式的定義的前面,不過, inline只是對編譯器的一種建議,一般如果程式碼在3-5行左右,且沒有復 雜的邏輯結構,例如迴圈啊,遞迴啊,就可以宣告為inline,inline也
是在函式呼叫的地方替換程式碼塊,所以程式碼太長的話,容易造成程式膨脹,那麼inline為什麼可以支援除錯呢?
其實支援除錯也只是在dbug模式下,inline真正起作用是在release模
式,正好和assert相反;
提到inline,就不得不提friend友元;被一個類中宣告為友元的非本類函式和類,可以訪問本類的私有成員,這個關鍵字的存在感覺有些破壞類的封
裝性,而且,友元屬性不能被傳遞和繼承;(但是實際中在有些編譯器下友元函式被子類繼承了,我也很納悶,讀者可以自己驗證一下,留言!)