1. 程式人生 > >C和C++的區別和聯絡(擴充套件知識)

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的十點區別

    1. malloc是從堆上開闢空間,而new是從自由儲存區開闢;(自由儲存區是
      C++抽象出來的概念,不僅可以是堆,還可以是靜態儲存區);

    2. malloc/free是函式,而new/delete是關鍵字;

    3. malloc對開闢的空間大小需要嚴格指定,而new只需要物件名;

    4. malloc開闢的空間即可以給單個物件用也可以給陣列用,釋放的方式都是

free();而new開闢物件陣列用的是new[size] ,釋放的的時候是 delete[]

(儘管內建型別可能不會引起問題,但是自定義型別的話,delete[]需要知道有

多少個物件,而這個計數就被放在這塊空間的頭部);

  1. 返回值問題,malloc開闢成功返回void*,需要強轉,失敗返回NULL,new

成功返回物件指標,失敗丟擲異常(這就可能會提到C++的new_handler機

制),雖然為了最大程度的相容C,C++的new也支援失敗返回NULL,但是一般不

被使用,大家可以瞭解一下;

  1. 是否呼叫構造和析構,這點應該放在前面,new和free不但負責開闢空間,

還會呼叫物件的建構函式和解構函式;最好了解一下new的三種表達形式(new運

算符,operator new(); placement new();)還有定位new表示式的

使用;

  1. 是否可以相互呼叫,new的實現可以用malloc,malloc的實現不可以使用

new;

  1. 是否可以被過載,我們可以過載自己的operator new/delete,但是不可

以過載new/delete/malloc/free;

  1. malloc開闢 的記憶體如果太小,想要換一塊大一點的,可以呼叫relloc實

現,但是new沒有直觀的方法來改變;

  1. 第十點其實前面已經提到,當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友元;被一個類中宣告為友元的非本類函式和類,可以訪問本類的私有成員,這個關鍵字的存在感覺有些破壞類的封
裝性,而且,友元屬性不能被傳遞和繼承;(但是實際中在有些編譯器下友元函式被子類繼承了,我也很納悶,讀者可以自己驗證一下,留言!)