面試總結(C++基礎)

Brave-girl發表於2016-08-18

C++面試知識點彙總:

一、多型性:實現了一個介面,多種方法。程式在執行時才決定呼叫的函式

從實現的角度來講,多型可以分為兩類:編譯時的多型性和執行時的多型性。前者是通過靜態聯編來實現的,比如C++中通過函式的過載和運算子的過載。後者則是通過動態聯編來實現的,在C++中執行時的多型性主要是通過虛擬函式來實現的

1)虛擬函式:(1)因為虛擬函式使用的基礎是賦值相容,而賦值相容成立的條件是派生類從基類公有派生而來。所以使用虛擬函式,派生類必須是基類公有派生的;(2)定義虛擬函式,不一定要在最高層的類中,而是看在需要動態多型性的幾個層次中的最高層類中宣告虛擬函式;(3)雖然在上述示例程式碼中main()主函式實現部分,我們也可以使用相應圖形物件和點運算子的方式來訪問虛擬函式,如:rectangcle.showArea(),但是這種呼叫在編譯時進行靜態聯編,它沒有充分利用虛擬函式的特性。只有通過基類物件的指標或者引用來訪問虛擬函式,才能獲得動態聯編的特性;(4)一個虛擬函式無論配公有繼承了多少次,它仍然是虛擬函式;(5)虛擬函式必須是所在類的成員函式,而不能是友元函式,也不能是靜態成員函式。因為虛擬函式呼叫要靠特定的物件類決定該啟用哪一個函式;(6)行內函數不能是虛擬函式,因為行內函數是不能在執行中動態確定其位置的即使虛擬函式在類內部定義,編譯時將其看作非內聯;(7)建構函式不能是虛擬函式,但解構函式可以是虛擬函式;

    純虛擬函式:類內的虛擬函式宣告語句的分號前加“=0”即可,必須在類的外部定義函式體。

    至少含有一個純虛擬函式的類,為抽象類。

抽象類只能作為其他類的基類來使用,不能建立抽象類物件;(2)不允許從具體類中派生出抽象類(不包含純虛擬函式的普通類);(3)抽象類不能用作函式的引數型別、返回型別和顯示轉化型別;(4)如果派生類中沒有定義純虛擬函式的實現,而只是繼承成了基類的純虛擬函式。那麼該派生類仍然為抽象類。一旦給出了對基類中虛擬函式的實現,那麼派生類就不是抽象類了,而是可以建立物件的具體類;

2)運算子過載:在實際的運算子過載函式宣告當中,要不定義其為要操作類的成員函式或類的友元函式。                                                                                                                                       

3)函式過載:是指在同一作用域內,可以有一組具有相同函式名,不同引數列表的函式,這組函式被稱為過載函式。過載函式通常用來命名一組功能相似的函式,這樣做減少了函式名的數量,避免了名字空間的汙染,對於程式的可讀性有很大的好處。

多型有兩個好處:

1. 應用程式不必為每一個派生類編寫功能呼叫只需要對抽象基類進行處理即可。大大提高程式的可複用性//繼承 
2. 派生類的功能可以被基類的方法或引用變數所呼叫,這叫向後相容,可以提高可擴充性和可維護性 //多型的真正作用

二、動態繫結怎麼實現?

C++的函式呼叫預設不使用動態繫結。要觸發動態繫結,必須滿足兩個條件:

1)只有指定為虛擬函式的成員函式才能進行動態繫結

2)必須通過引用或指標進行函式呼叫

  因為每個派生類物件中都擁有基類部分,所以可以使用基類型別的指標或引用來引用派生類物件。

三、指標和引用的區別:

1首先,引用不可以為空,但指標可以為空。前面也說過了引用是物件的別名,引用為空——物件都不存在,怎麼可能有別名!故定義一個引用的時候,必須初始化。而宣告指標是可以不指向任何物件,也正是因為這個原因,使用指標之前必須做判空操作,而引用就不必。

2)其次,引用不可以改變指向,對一個物件"至死不渝";但是指標可以改變指向,而指向其它物件。說明:雖然引用不可以改變指向,但是可以改變初始化物件的內容。例如就++操作而言,對引用的操作直接反應到所指向的物件,而不是改變指向;而對指標的操作,會使指標指向下一個物件,而不是改變所指物件的內容。

3)再次,引用的大小是所指向的變數的大小,因為引用只是一個別名而已;指標是指標本身的大小,4個位元組。

4)最後,引用比指標更安全。由於不存在空引用,並且引用一旦被初始化為指向一個物件,它就不能被改變為另一個物件的引用,因此引用很安全。對於指標來說,它可以隨時指向別的物件,並且可以不被初始化,或為NULL,所以不安全。const 指標雖然不能改變指向,但仍然存在空指標,並且有可能產生野指標(即多個指標指向一塊記憶體,free掉一個指標之後,別的指標就成了野指標)。

5)作為引數傳遞時,兩者不同。指標傳遞引數的本質是值傳遞,被調函式對形參的任何操作作為區域性變數進行的,不會影響到主調函式的實參。如果想通過指標引數傳遞來改變主調函式中的相關變數,就得使用指標的指標,或者指標引用。引用傳遞引數的本質是傳遞實參的地址,被調函式對形參的任何操作都影響了主調函式的實參。

總而言之,言而總之——它們的這些差別都可以歸結為"指標指向一塊記憶體,它的內容是所指記憶體的地址;而引用則是某塊記憶體的別名,引用不改變指向。"

四、四種強制型別轉換:(網易兩次面試均出現)

C風格的型別轉換——強制型別轉換(type) expression.

C++中型別轉換四種分別為:static_cast ,const_cast,dynamic_cast,reinterpret_cast.

1static_cast. 最常用的型別轉換符,在正常狀況下的型別轉換,如把int轉換為float,如:int ifloat ff=floati;或者f=static_cast<float>(i);

   還可以用於將編譯器無法自動執行的型別轉換。

例如,void *p=&d; double *dp=static_cast(double *)(p) //void型別指標轉換為double型別。

2const_cast. 用於取出底層const屬性,把const型別的指標變為非const型別的指標.如:const int *fun(int x,int y){}int *ptr=const_cast<int *>(fun(2,3)).

3dynamic_cast.用於將基類的指標或引用型別安全地轉換成派生類的指標或引用,在執行時檢查該轉換是否型別安全,但只在多型型別時合法,即該類至少具有一個虛擬函式。

例如:基類Base(至少有一個虛擬函式),派生類Derived

    Base *bp;   Derived *dp=dynamic_cast<Base*>(bp)

1)其他三種都是編譯時完成的,dynamic_cast是執行時處理的,執行時要進行型別檢查。

2不能用於內建的基本資料型別的強制轉換

3dynamic_cast轉換如果成功的話返回的是指向類的指標或引用,轉換失敗的話前者返回0,後者丟擲bad_cast異常。

4)使用dynamic_cast進行轉換的,基類中一定要有虛擬函式,否則編譯不通過。

     B中需要檢測有虛擬函式的原因:類中存在虛擬函式,就說明它有想要讓基類指標或引用指向派生類物件的情況,此時轉換才有意義。

這是由於執行時型別檢查需要執行時型別資訊,而這個資訊儲存在類的虛擬函式表(關於虛擬函式表的概念,詳細可見<Inside c++ object model>)中,

(5)只有定義了虛擬函式的類才有虛擬函式表。dynamic_caststatic_cast具有相同的基本語法,dynamic_cast主要用於類層次間的上行轉換和下行轉換,還可以用於類之間的交叉轉換。在類層次間進行上行轉換時,dynamic_caststatic_cast的效果是一樣的;在進行下行轉換時,dynamic_cast具有型別檢查的功能,比static_cast更安全。如:

 

 

class C

{

  //C沒有虛擬函式

}

class T{

  //

}

int main()

{

  dynamic_cast<T*> (new C);//錯誤

}

此時如改為以下則是合法的:

class C

{

public:

  virtual void m() {};// C現在是 多型

}

4interpret_cast

interpret是解釋的意思,reinterpret即為重新解釋,此識別符號的意思即為資料的二進位制形式重新解釋,但是不改變其值。如:int i; char *ptr="hello freind!"; i=reinterpret_cast<int>(ptr);這個轉換方式很少使用。

五、操作符過載

    operatorC++的關鍵字,它和運算子一起使用,表示一個運算子函式。

理解時應將operator=整體上視為一個函式名。

操作符過載的實現方式有兩種,即通過“友元函式”或者“類成員函式”。

利用友元函式過載二元操作符”-“時,形式引數是兩個,而利用類成員函式時,形式引數卻只有一個。這時因為類成員函式中存在this指標,這相當於一個引數,所以類成員實現操作符過載需要的形式引數比原來少一個,這比如:利用類成員函式實現一元操作符”-“,就不需要引數了。也正是因為這個原因,友元函式實現的操作符過載是有限制的,比如:[] ,(),->=不能利用友元函式實現運算子的過載。

運算子過載也是要遵循一些原則的:

1.C++中只能對已有的C++運算子進行過載,不允許使用者自己定義新的運算子。

2.C++中絕大部分的運算子可過載,除了成員訪問運算子.,作用域運算子::,長度運算子sizeof以及條件運算子?:

3.運算子過載後不能改變運算子的操作物件(運算元)的個數。如:"+"是實現兩個運算元的運算子,過載後仍然為雙目運算子。

4.過載不能改變運算子原有的優先順序和原有的結合性

6.運算子過載不能全部是C++中預定義的基本資料,這樣做的目的是為了防止使用者修改用於基本型別資料的運算子性質。

為了區分前後,增量運算子中,放上一個整數形參,就是後增量執行符,它是值返回,對於前增量沒有形參,而且是引用返回。即用++()表示前自增,用++(int)後自增。

 

 

六、記憶體對齊原則

顯示規定採用幾個位元組對齊

#pragma pack(n) c編譯器將按照n個位元組對齊。

•原則1、資料成員對齊規則:結構(struct或聯合union)的資料成員,第一個資料成員放在offset0的地方,以後每個資料成員儲存的起始位置從該成員大小的整數倍開始(比如int32位機為4位元組,則要從4的整數倍地址開始儲存)。

•原則2、結構體作為成員:如果一個結構裡有某些結構體成員,則結構體成員要從其內部最大元素大小的整數倍地址開始儲存。(struct a裡存有struct bb裡有charintdouble等元素,那b應該從8的整數倍開始儲存。)

•原則3、收尾工作:結構體的總大小,也就是sizeof的結果,必須是其內部最大成員的整數倍,不足的要補齊。

 

 

七、模板怎麼實現的?

模板是C++支援引數化多型的工具,使用模板可以使使用者為類或者函式宣告一種一般模式,使得類中的某些資料成員或者成員函式的引數、返回值取得任意型別。

模板是一種對型別進行引數化的工具;通常有兩種形式:函式模板和類别範本

函式模板針對僅引數型別不同的函式

類别範本針對資料成員和成員函式型別不同的類

注意:模板的宣告或定義只能在全域性,名稱空間或類範圍內進行。即不能在區域性範圍,函式內進行,比如不能在main函式中宣告或定義一個模板。

1函式模板通式

1、函式模板的格式:

template <class 形參名,class 形參名,......> 返回型別 函式名(引數列表)

{

函式體

}

其中templateclass是關鍵字,class可以用typename 關鍵字代替,在這裡typename class沒區別,<>括號中的引數叫模板形參,模板形參和函式形參很相像,模板形參不能為空。一旦宣告瞭模板函式就可以用模板函式的形參名宣告類中的成員變數和成員函式,即可以在該函式中使用內建型別的地方都可以使用模板形參名。模板形參需要呼叫該模板函式時提供的模板實參來初始化模板形參,一旦編譯器確定了實際的模板實參型別就稱為例項化了函式模板的一個例項。比如swap的模板函式形式為

template <class T> void swap(T& a, T& b){}

2、注意:對於函式模板而言不存在 h(int,int) 這樣的呼叫,不能在函式呼叫的引數中指定模板形參的型別,對函式模板的呼叫應使用實參推演來進行,即只能進行 h(2,3) 這樣的呼叫,或者int a, b; h(a,b)

2類别範本通式

1、類别範本的格式為:

template<class  形參名,class 形參名,…>   class 類名

{ ... };

類别範本和函式模板都是以template開始後接模板形參列表組成,模板形參不能為空,一旦宣告瞭類别範本就可以用類别範本的形參名宣告類中的成員變數和成員函式,即可以在類中使用內建型別的地方都可以使用模板形參名來宣告。比如

template<class T> class A{public: T a; T b; T hy(T c, T &d);};

在類A中宣告瞭兩個型別為T的成員變數ab,還宣告瞭一個返回型別為T帶兩個引數型別為T的函式hy

2類别範本物件的建立:比如一個模板類A,則使用類别範本建立物件的方法為A<int> m;在類A後面跟上一個<>尖括號並在裡面填上相應的型別,這樣的話類A中凡是用到模板形參的地方都會被int 所代替。當類别範本有兩個模板形參時建立物件的方法為A<int, double> m;型別之間用逗號隔開。

3、對於類别範本,模板形參的型別必須在類名後的尖括號中明確指定。比如A<2> m;用這種方法把模板形參設定為int是錯誤的(編譯錯誤:error C2079: 'a' uses undefined class 'A<int>'),類别範本形參不存在實參推演的問題。也就是說不能把整型值2推演為int 型傳遞給模板形參。要把類别範本形參調置為int 型必須這樣指定A<int> m

4、在類别範本外部定義成員函式的方法為:

template<模板形參列表> 函式返回型別 類名<模板形參名>::函式名(引數列表){函式體}

比如有兩個模板形參T1T2的類A中含有一個void h()函式,則定義該函式的語法為:template<class T1,class T2> void A<T1,T2>::h(){}

注意:當在類外面定義類的成員時template後面的模板形參應與要定義的類的模板形參一致。

5、再次提醒注意:模板的宣告或定義只能在全域性,名稱空間或類範圍內進行。即不能在區域性範圍,函式內進行,比如不能在main函式中宣告或定義一個模板。

有三種型別的模板形參:型別形參,非型別形參和模板形參。

八、指標和const的用法(四種情況說了一下)

const是一個C語言的關鍵字,它限定一個變數不允許被改變。使用const在一定程度上可以提高程式的安全性和可靠性

指向常量的指標

const int *pa;

int const *pa;

兩者等價。因為指向常量的指標有時候會指向常量,所以它具有這個性質:“不能靠解引用改變它指向的物件的值”,以此保護它所指向的常量的常量性:

*pa =d; // 不可行(d是已經宣告過的整型)

指標本身的值是可變的:

pa=& d; // 可行(d是已經宣告過的整型)

而且指向常量的指標有時候也會指向變數,如下:

int t,u;

const int *pa;

pa =&t; //可行,指向變數t

pa =&u; //也可行,指向變數u

我們可以把它理解成:“為了指向常量而發明的指標”,這樣比較貼切。

常量指標

int *const pa =&n; // n是之前已經宣告過的整型變數,注意必須是變數,理由見下

“常量指標”即指標本身的值是常量,但“能靠解引用改變它指向的物件的值”,如下:

pa=&d; // 不可行(d是已經宣告過的整型)

*pa =d; // 可行(d是已經宣告過的整型)

 

因為常量指標也是一種const常量,所以它同樣必須在第一次宣告時就初始化,不過它的初始值縮小為只能是變數(的地址),因為只有變數才能確保以後能靠解引用而改變它指向的物件的值。這使得常量指標不象一般的const常量,用變數或常量初始化都可以。

也就是說,常量指標反而總是指向變數的

九、虛擬函式、純虛擬函式、虛擬函式與解構函式?(純虛擬函式如何定義,為什麼解構函式要定義成虛擬函式)

見程式設計師面試寶典4P117

 

 

 

 

 

 

 

十、行內函數(講了一下行內函數的優點以及和巨集定義的區別)

1)什麼是行內函數?

答:定義在類宣告之中的成員函式,在函式返回型別前加上inline關鍵字,將自動地成為行內函數。將函式定義為行內函數,一般就是將在程式中每個呼叫點上“內聯地”展開。

2)行內函數適用情況

1.一個函式被重複呼叫;

2.函式只有幾行,且不包含forwhileswitch語句。

行內函數應該放在標頭檔案中定義,這一點不同於其他函式。

 

行內函數可能在程式中定義不止一次,只要行內函數的定義在某個原始檔中只出現一次,而且在所有的原始檔中,其定義必須是相同的。如果inline函式的定義和宣告是分開的,而在另外一個檔案中需要呼叫這些inline函式得時候,內聯是無法在這些呼叫函式內展開的。這樣行內函數在全域性範圍內就失去了作用。解決的辦法就是把行內函數得定義放在標頭檔案中,當其它檔案要呼叫這些行內函數的時候,只要包含這個標頭檔案就可以了。把行內函數的定義放在標頭檔案中,可以確保在呼叫函式時所使用的定義是相同的,並保證在呼叫點該函式的定義對呼叫點可見。

 

行內函數具有一般函式的特性,它與一般函式所不同之處只在於函式呼叫的處理:

一般函式進行呼叫時,要將程式執行權轉到被呼叫函式中,然後再返回到呼叫它的函式中;而行內函數在呼叫時,是將呼叫表示式用行內函數體來替換。

 

 

內聯機制被引入C++作為對巨集(Macro)機制的改進和補充(不是取代)。行內函數的引數傳遞機制與普通函式相同。但是編譯器會在每處呼叫行內函數的地方將行內函數的內容展開。這樣既避免了函式呼叫的開銷又沒有巨集機制的前三個缺陷。

 

但是程式程式碼中的關鍵字"inline"只是對編譯器的建議:被"inline"修飾的函式不一定被內聯(但是無"inline"修飾的函式一定不是)。

內聯使用不恰當是會有副作用的:會帶來程式碼膨脹,還有可能引入難以發現的程式臭蟲。

a.inline的原理,我們可以看出,inline的原理,是用空間換取時間的做法,是以程式碼膨脹(複製)為代價,僅僅省去了函式呼叫的開銷,從而提高函式的執行效率。如果執行函式體內程式碼的時間,相比於函式呼叫的開銷較大,那麼效率的收穫會很少。所以,如果函式體程式碼過長或者函式體重有迴圈語句,if語句或switch語句或遞迴時,不宜用內聯

b.關鍵字inline 必須與函式定義體放在一起才能使函式成為內聯,僅將inline 放在函式宣告前面不起任何作用。行內函數呼叫前必須宣告。

 

行內函數:

1)行內函數定義和作用:

將一個函式宣告為inline,那麼函式就成為行內函數。行內函數通常就是它在程式中每個呼叫點上“內聯地”展開。從定義上看,行內函數跟一般函式不一樣,一般函式呼叫的時候是需要呼叫開銷的(比如出棧入棧等操作),行內函數從定義上看更像是巨集,但是跟巨集不一樣。

行內函數的作用主要就是使用在一些短小而使用非常頻繁的函式中,為了減少函式呼叫的開銷,為了避免使用巨集(在c++中,巨集是不建議使用的)。比如行內函數inline int  func(int x){return x*x;} 在呼叫的時候cout<<func(x)<<endl,在編譯時將被展開為:

cout<<(x*x)<<endl;

3)如何使用行內函數和禁止內聯:

要讓一個函式稱為行內函數,有兩種方法:一種是把函式加上inline關鍵字;一種是在類的說明部分定義的函式,預設就是內聯的。

要禁止編譯器進行內聯,可以使用#pragma auto_inline編譯指令或者改變編譯引數。

行內函數注意事項

1)行內函數一定會內聯展開嗎?答案是否定的。對於行內函數,程式只是提供了一個“內聯建議”,即建議編譯器把函式用內聯展開,但是真正是否內聯,是由編譯器決定的,對於函式體過大的函式,編譯器一般不會內聯,即使制定為行內函數。

2)在行內函數內部,不允許用迴圈語句和開關語句(ifswitch。行內函數內部有迴圈和開關,也不會出錯,但是編譯器會把它當做非行內函數的。

3關鍵字inline必須與函式定義體放在一起才能使函式真正內聯,僅把inline放在函式宣告的前面不起任何作用。因為inline是一種用於實現的關鍵字,不是一種用於宣告的關鍵字。行內函數的宣告是不需要加inline關鍵字的,行內函數的定義是必須加inline的(除了類的定義部分的預設行內函數),儘管很多書宣告定義都加了,要注意理解宣告和定義的區別。

4) 在一個檔案中定義的行內函數不能在另一個檔案中使用。它們通常放在標頭檔案中共享。

5) 行內函數的定義必須在第一次呼叫之前。注意,這裡是定義之前,不僅僅是宣告之前。對於普通函式,可以在呼叫之前宣告,呼叫程式碼之後具體定義(實現函式),但是行內函數要實現內聯,必須先定義再呼叫,否則編譯器會把在定義之前呼叫的行內函數當做普通函式進行呼叫。

6) 說明:上面這些inline的注意事項,在程式設計時要自己注意,因為上面的注意事項不遵守很多並不會引起編譯錯誤,只是會導致寫了inline的函式不是行內函數,從而與預期的目的不一樣。所以很多沒法用程式例項說明到底編譯器是按照inline還是非inline呼叫的,或許分析彙編程式碼能看出,但是水平有限,就不多分析了。

 

使用行內函數至少有如下兩個優點

1減少因為函式呼叫引起開銷,主要是引數壓棧、棧幀開闢與回收,以及暫存器儲存與恢復等。從而提高函式的執行效率。

2)內聯後,編譯器在處理呼叫行內函數的函式(如上例中的foo()函式)時,因為可供分析的程式碼更多,因此它能做的優化更深入徹底。前一條優點對於開發人員來說往往更顯而易見一些,但往往這條優點對最終程式碼的優化可能貢獻更大。

行內函數與巨集定義的區別

巨集是在預處理時進行的程式碼替換,內聯是在編譯時進行的;

行內函數是真正的函式,只是在呼叫時,沒有呼叫開銷,在編譯階段插入程式碼,像巨集一樣進行展開;

行內函數會進行引數匹配檢查,巨集定義沒有引數匹配檢查。

 

十一、consttypedef(主要講了const的用處,有那些優點)

1)#define:

巨集不僅可以用來代替常數值,還可以用來代替表示式,甚至是程式碼段。(巨集的功能很強大,但也容易出錯,所以其利弊大小頗有爭議。)

巨集的語法為:

#define 巨集名稱 巨集值

注意,巨集定義不是CC++嚴格意義上的語句,所以其行末不用加分號結束

作為一種建議和一種廣大程式設計師共同的習慣,巨集名稱經常使用全部大寫的字母。

利用巨集的優點:

1)讓程式碼更簡潔明瞭

當然,這有賴於你為巨集取一個適當的名字。一般來說,巨集的名字更要注重有明確直觀的意義,有時寧可讓它長點。

2)方便程式碼維護

對巨集的處理,在編譯過程中稱為“預處理”。也就是說在正式編譯前,編譯器必須先將程式碼出現的巨集,用其相應的巨集值替換。所以在程式碼中使用巨集表達常數,歸根結底還是使用了立即數,並沒有明確指定這個量的型別。

2)typedefine:

typedef#define的區別

typedef則常用來定義關鍵字、冗長的型別的別名,是在編譯時處理的。

巨集定義只是簡單的字串代換(原地擴充套件),而typedef則不是原地擴充套件,它的新名字具有一定的封裝性,以致於新命名的識別符號具有更易定義變數的功能。

typedef    (int*)      pINT;

以及下面這行:

#define    pINT2    int*

效果相同?實則不同!實踐中見差別:pINT a,b;的效果同int *a; int *b;表示定義了兩個整型指標變數。而pINT2 a,b;的效果同int *a, b;

表示定義了一個整型指標變數a和整型變數b

注意:兩者還有一個行尾;號的區別哦!

 

3)const:

const部分

常量定義的格式為:

const 資料型別 常量名 = 常量值;

const定義的常量具有資料型別,定義資料型別的常量便於編譯器進行資料檢查,使程式可能出現錯誤進行排查。常量必須一開始就指定一個值,然後,在以後的程式碼中,我們不允許改變此常量的值。

區別:

1.記憶體空間的分配上。define進行巨集定義的時候,不會分配記憶體空間,編譯時會在main函式裡進行替換,只是單純的替換,不會進行任何檢查,比如型別,語句結構等,即巨集定義常量只是純粹的置放關係,如#define null 0;編譯器在遇到null時總是用0代替null它沒有資料型別.const定義的常量具有資料型別,定義資料型別的常量便於編譯器進行資料檢查,使程式可能出現錯誤進行排查,所以constdefine之間的區別在於const定義常量排除了程式之間的不安全性.

2.const常量存在於程式的資料段#define常量存在於程式的程式碼段

3. 有些整合化的除錯工具可以對const常量進行除錯,但是不能對巨集常量進行除錯。

 

 

 

 

 

 

十二、排序演算法有哪些?快速排序怎麼實現的?最好時間複雜度,平均時間複雜度

十三、連結指示:extern C”(作用)

C++程式有時需要呼叫其他語言編寫的函式,最常見的是呼叫C語言編寫的函式。像所有其他名字一樣,其他語言中的函式名字也必須C++中進行宣告,並且該宣告必須指定返回型別和形參列表。對於其他語言編寫的函式來說,編譯器檢查其呼叫方式與處理普通C++函式的方式相同,但生成的程式碼有所區別。C++使用連結指示(linkage directive)指出任意非C++函式所用的語言。

宣告一個非C++函式

連結指示可以有兩種形式:單個或複合。連結指示不能出現在類定義或函式定義的內部。

例如:

單語句:extern "C" size_t strlen(const char *);

複合語句:extern "C" {

      int strcmp(const char*, const char*);

 

      char *strcat(char*, const char*);

                }

連結指示與標頭檔案

複合語句:

extern "C" {

 

     #include <string.h>

      }

 

指向extern "C"函式的指標

編寫函式所用的語言是函式型別的一部分。(指向其他語言編寫的函式的指標必須與函式本身使用相同的連結指示)

 

  extern "C" void (*pf)(int)

 

  當我們使用pf呼叫函式時,編譯器認定當前呼叫的是一個C函式。

 

  指向C函式的指標與指向C++函式的指標是不一樣的型別。

 

連結指示對整個宣告都有效

當我們使用連結指示時,他不僅對函式有效,而且對作為返回型別或形參型別的函式指標也有效。

//f1是一個C函式,它的形參是一個指向C函式的指標

  extern "C" void f1( void(*)(int) );

因為連結指示同時作用於宣告語句中的所有函式,所以如果我們希望給C++函式傳入一個指向C函式的指標,則必須使用型別別名。

//FC是一個指向C函式的指標

  extern "C" typedef void FC( int );

//f2是一個C++函式,該函式的形參是指向C函式的指標

  void f2(FC *);

十四、c語言和c++有什麼區別?(大體講了一下,繼承、多型、封裝、異常處理等)

 

封裝:是物件導向的特徵之一,是物件和類概念的主要特性。封裝,也就是把客觀事物封裝成抽象的類,並且類可以把自己的資料和方法只讓可信任的類或者物件操作,對不可信的進行資訊隱藏。

繼承:使用現有類的所有功能,並在無需重新編寫原來類的情況下,對這些功能進行擴充套件

繼承過程,是從一般到特殊的過程。

繼承的實現方式有三類:實現繼承、介面繼承和可視繼承。

1) 實現繼承:使用基類的屬性和方法而無需額外編碼的能力。

2) 介面繼承:僅使用屬性和方法的名稱,但是子類必須提供實現的能力。

3) 可視繼承:子窗體實現基窗體的外觀和實現程式碼的能力。

 

多型:允許將子類型別的指標賦給基類型別的指標。允許將父物件設定成為和一個或更多的他的子物件相等的技術,賦值之後,父物件就可以根據當前賦值給它的子物件的特性以不同的方式運作。

實現多型的兩種方式:覆蓋和過載。

覆蓋:子類重新定義基類虛擬函式的做法。

過載:允許存在多個同名函式,而這些函式的引數列表不同(或許引數個數不同,或者引數型別不同,或者兩者都不同)。

異常處理:主要用於針對程式在執行時刻出現錯誤而提供的語言層面的保護機制。它允許開發者最大限度地發揮,針對異常處理進行相應的修改調整。

C++語言除了提供異常的關鍵字語法支援以外,其標準庫中支援異常處理而封裝異常類也很好的為應用程式中異常處理判斷使用提供直接的幫助。

C++的異常類分別定義在標頭檔案中,如最通用的異常類exception,它只報告異常的發生,不提供任何額外資訊。

1)C++語言中針對異常處理提供了三個關鍵字,分別為trythrowcatch

異常檢測是採用throw表示式實現的。通常throw關鍵字後會跟隨著一個運算元,該運算元可以是一個表示式、一個C++內建型別資料或者為類型別的物件等。

try體中可以直接丟擲異常,或者在try體中呼叫的函式體中間接的丟擲try塊中程式碼體作為應用程式遵循正常流程執行。一旦該程式碼體中出現異常操作,會根據操作的判斷丟擲對應的異常型別。隨後逐步的遍歷catch程式碼塊,此步驟與switch控制結構有點相像。當遍歷到對應型別catch塊時,程式碼會跳轉到對應的異常處理中執行。如果try塊中程式碼沒有丟擲異常,則程式繼續執行下去。

  catch關鍵字的程式碼塊則主要負責異常的捕獲處理catch塊可以擁有多個,並且緊跟著try塊之後。每個catch塊部分主要由圓括號中引數型別,緊跟其後的大括號中異常程式碼處理部分構成。catch塊中隨後圓括號中引數型別,該型別可以擁有變數名稱或者物件名稱,也可以直接是對應的引數型別。如果是對應的型別物件,則在catch塊中可以引用該物件處理。而如果是隻有資料型別沒有相應引數名稱的引數,則直接匹配對應的引數型別即可,程式會從try塊中轉移至catch塊中執行。

前面小節在講述異常丟擲處理時已經對其使用的原理作過解釋,即當在可能丟擲異常的try程式碼塊中通過throw丟擲了異常隨後開始匹配catch塊中的引數型別是否符合異常丟擲時的型別,如果符合則執行對應catch塊中的程式碼處理。

相關文章