天天看點

c/c++面試

1. static在c,c++中有什麼不同點

2. 堆和棧的差別

3. 純虛函數

4. 指針和引用的差別

5. 如果構造函數出錯,如何處理?

6. 對設計模式是否熟悉,用過哪些?

7. c++如何使用c中的函數,為什麼?

整理:

1.

靜态資料成員/成員函數,c++特有

2.

3.

在面向對象的c++語言中,虛函數(virtual function)是一個非常重要的概念。因為它充分展現了面向對象思想中的繼承和多态性這兩大特性,在c++語言裡應用極廣。有人甚至稱虛函數是c++語言的精髓。

定義一個函數為虛函數,不代表函數為不被實作的函數

定義他為虛函數是為了允許用基類的指針來調用子類的這個函數

定義一個函數為純虛函數,才代表函數沒有被實作

定義他是為了實作一個接口,起到一個規範的作用,規範繼承這個

類的程式員必須實作這個函數。

有純虛函數的類是不可能生成類對象的,如果沒有純虛函數則可以。比如:

class ca

{

public:

virtual void fun() = 0; // 說明fun函數為純虛函數

virtual void fun1();

};

class cb

virtual void fun();

// ca,cb類的實作

...

void main()

ca a; // 不允許,因為類ca中有純虛函數

cb b; // 可以,因為類cb中沒有純虛函數

}

虛函數在多态中使用:

多态一般就是通過指向基類的指針來實作的。

有一點必須明白,就是用父類的指針在運作時刻來調用子類。父類指針通過虛函數來決定運作時刻到底是誰而指向誰的函數。

有純虛函數的類是抽象類,不能生成對象,隻能派生。他派生的類的純虛函數沒有被改寫,那麼,它的派生類還是個抽象類。 定義純虛函數就是為了讓基類不可執行個體化。

-------------------------------------------

純虛函數

一、引入原因:

  1、為了友善使用多态特性,我們常常需要在基類中定義虛拟函數。

  2、在很多情況下,基類本身生成對象是不合情理的。例如,動物作為一個基類可以派生出老虎、孔雀等子類,但動物本身生成對象明顯不合常理。

  為 了解決上述問題,引入了純虛函數的概念,将函數定義為純虛函數(方法:virtual returntype function()= 0;),則編譯器要求在派生類中必須予以重載以實作多态性。同時含有純虛拟函數的類稱為抽象類,它不能生成對象。這樣就很好地解決了上述兩個問題。

  二、純虛函數實質:

  類中含有純虛函數則它的vtable表不完全,有一個空位,是以,不能生成對象(編譯器絕對不允許有調用一個不存在函數的可能)。在它的派生類中,除非重載這個函數,否則,此派生類的vtable表亦不完整,亦不能生成對象,即它也成為一個純虛基類。

  三、 虛函數與構造、析構函數:

  1、構造函數本身不能是虛拟函數;并且虛機制在構造函數中不起作用(在構造函數中的虛拟函數隻會調用它的本地版本)。

  想一想,在基類構造函數中使用虛機制,則可能會調用到子類,此時子類尚未生成,有何後果!?。

  2、析構函數本身常常要求是虛拟函數;但虛機制在析構函數中不起作用。

  若類中使用了虛拟函數,析構函數一定要是虛拟函數,比如使用虛拟機制調用delete,沒有虛拟的析構函數,怎能保證delete的是你希望delete的對象。

  虛機制也不能在析構函數中生效,因為可能會引起調用已經被delete掉的類的虛拟函數的問題。

  四、對象切片:

  向上映射(子類被映射到父類)的時候,會發生子類的vtable 完全變成父類的vtable的情況。這就是對象切片。

  原因:向上映射的時候,接口會變窄,而編譯器絕對不允許有調用一個不存在函數的可能,是以,子類中新派生的虛拟函數的入口在vtable中會被強行“切”掉,進而出現上述情況。

  五、虛拟函數使用的缺點

  虛函數最主要的缺點是執行效率較低,看一看虛拟函數引發的多态性的實作過程,就能體會到其中的原因。

----------------------------------------------

4.

指針與引用看上去完全不同(指針用操作符“*”和“->”,引用使用操作符“. ”),但是它們似乎有相同的功能。指針與引用都是讓你間接引用其他對象。你如何決定在什麼時候使用指針,在什麼時候使用引用呢?

首先,要認識到在任何情況下都不能使用指向空值的引用。一個引用必須總是指向某些對象。是以如果你使用一個變量并讓它指向一個對象,但是該變量在某些時候也可能不指向任何對象,這時你應該把變量聲明為指針,因為這樣你可以賦空值給該變量。相反,如果變量肯定指向一個對象,例如你的設計不允許變量為空,這時你就可以把變量聲明為引用。

“但是,請等一下”,你懷疑地問,“這樣的代碼會産生什麼樣的後果?”

char *pc = 0; // 設定指針為空值

char& rc = *pc; // 讓引用指向空值

這是非常有害的,毫無疑問。結果将是不确定的(編譯器能産生一些輸出,導緻任何事情都有可能發生)。應該躲開寫出這樣代碼的人,除非他們同意改正錯誤。如果你擔心這樣的代碼會出現在你的軟體裡,那麼你最好完全避免使用引用,要不然就去讓更優秀的程式員去做。我們以後将忽略一個引用指向空值的可能性。

因為引用肯定會指向一個對象,在c++裡,引用應被初始化。

string& rs; // 錯誤,引用必須被初始化

string s("xyzzy");

string& rs = s; // 正确,rs指向s

指針沒有這樣的限制。

string *ps; // 未初始化的指針

// 合法但危險

不存在指向空值的引用這個事實意味着使用引用的代碼效率比使用指針的要高。因為在使用引用之前不需要測試它的合法性。

void printdouble(const double& rd)

cout << rd; // 不需要測試rd,它

} // 肯定指向一個double值

相反,指針則應該總是被測試,防止其為空:

void printdouble(const double *pd)

if (pd) { // 檢查是否為null

cout << *pd;

指針與引用的另一個重要的不同是指針可以被重新指派以指向另一個不同的對象。但是引用則總是指向在初始化時被指定的對象,以後不能改變。

string s1("nancy");

string s2("clancy");

string& rs = s1; // rs 引用 s1

string *ps = &s1; // ps 指向 s1

rs = s2; // rs 仍舊引用s1,

// 但是 s1的值現在是

// "clancy"

ps = &s2; // ps 現在指向 s2;

// s1 沒有改變

總的來說,在以下情況下你應該使用指針,一是你考慮到存在不指向任何對象的可能(在這種情況下,你能夠設定指針為空),二是你需要能夠在不同的時刻指向不同的對象(在這種情況下,你能改變指針的指向)。如果總是指向一個對象并且一旦指向一個對象後就不會改變指向,那麼你應該使用引用。

還有一種情況,就是當你重載某個操作符時,你應該使用引用。最普通的例子是操作符[]。這個操作符典型的用法是傳回一個目标對象,其能被指派。

vector<int> v(10); // 建立×××向量(vector),大小為10;

// 向量是一個在标準c庫中的一個模闆(見條款m35)

v[5] = 10; // 這個被指派的目标對象就是操作符[]傳回的值

如果操作符[]傳回一個指針,那麼後一個語句就得這樣寫:

*v[5] = 10;

但是這樣會使得v看上去象是一個向量指針。是以你會選擇讓操作符傳回一個引用。(這有一個有趣的例外,參見條款m30)

當你知道你必須指向一個對象并且不想改變其指向時,或者在重載操作符并為防止不必要的語義誤解時,你不應該使用指針。而在除此之外的其他情況下,則應使用指針。

5.

使用異常捕捉,具體問題具體分析。

6.

設計模式基本思想

好的系統設計追求如下特性:

可擴充性( extensibility ):新的功能或特性很容易加入到系統中來;

靈活性( flexibility ):可以允許代碼修改平穩發生,對一處的修改不會波及到很多其他子產品;

可插入性( pluggability ):可以很容易地将一個類或元件抽出去,同時将另一個有相同接口的類 / 接口加入進來。

具有如上特性的系統才有真正的可維護性和可複用性。而可維護性和可複用性對一個持續接入新需求,現有功能逐漸完善,新功能不斷豐富,版本不會終止的大型軟體産品來說至關重要。

傳統的複用包括:代碼的 copy 複用,算法的複用,資料結構的複用。

在面向對象領域,資料的抽象化、封裝、繼承和多态性是幾項最重要的語言特性,這些特性使得一個系統可以在更高的層次上提供可複用性。資料的抽象化和繼承關系使得概念和定義可以複用;多态性使得實作和應用可以複用;而抽象化和封裝可以保持和促進系統的可維護性。這樣,複用的焦點不再集中在函數和算法等具體實作細節上,而是集中在最重要的宏觀的業務邏輯的抽象層次上。複用焦點的倒轉不是因為實作細節的複用不再重要,而是因為這些細節上的複用往往已經做的很好(例如,很容易找到并應用成熟的資料結構類庫等),而真正沖擊系統的是其要實作業務的千變萬化。

本質上說,如果說一個軟體的需求是永不變更或發展的,該軟體也就不需要任何設計,怎麼編碼實作都行,隻要需求滿足,性能達标。但事實上,軟體的本性就是不斷增強,不斷拓展的,不斷變化的。我們可以控制指尖流淌出的每行代碼,但控制不了奉為上帝的使用者的需求。編碼結束,測試全部通過,使用者在試用過程中才發現原來的需求有問題,需要變更或提出新需求,怎麼辦?向使用者抗議:需求總在變,沒法做!?平抑心中的抱怨,加班加點大量的修改代碼,瘋狂的測試,依然是時間緊迫,心中沒底?抑或了然于胸:這個變更或小需求合理,系統很友善納入;于是坦然地和使用者協商下一個傳遞時間點?

要使系統最大程度的适應需求的變更或新增,就必須在其要實作的宏觀業務邏輯的抽象複用上下功夫。而設計模式就是綜合運用面向對象技術和特性來提高業務邏輯可複用性的常用方法和經驗的提取和彙總。

掌握 23 種設計模式的關鍵是了解它們的共通目的:使所設計的軟體系統在一般或特定(系統将來在特定點上擴充的可能性大)場景下,盡可能的對擴充開放,對修改關閉。即面對新需求或需求變更時,容易開發獨立于既有代碼的新代碼接入到現有系統或對現有代碼做可控的少量修改,而不是在現有代碼基礎上做大量的增、删、改。為了這一目的, 23 種設計模式貫穿了面向對象程式設計的基本原則:

面向接口或抽象程式設計,而不是面向實作程式設計。

一個對象應當對其他對象有盡可能少的了解,即松耦合。

純粹的功能複用時,盡量不要使用繼承,而是使用組合。使已有的對象成為新對象的一部分;新的對象通過向這些對象的委派達到複用已有功能的目的。

oo設計模式和設計原則

1.1 設計正在“腐爛”的征兆(symptoms of rotting design)

    有四個主要的征兆告訴我們該軟體設計正在“腐爛”中。它們并不是互相獨立的,而是互相關聯,它們是過于僵硬、過于脆弱、不可重用性和粘滞性過高。

    1. 過于僵硬 rigidity 緻使軟體難以更改,每一個改動都會造成一連串的互相依靠的子產品的改動,項目經理不敢改動,因為他永遠也不知道一個改動何時才能完成。

    2. 過于脆弱 fragility 緻使當軟體改動時,系統會在許多地方出錯。并且錯誤經常會發生在概念上與改動的地方沒有聯系的子產品中。這樣的軟體無法維護,每一次維護都使軟體變得更加難以維護。(惡性循環)

    3. 不可重用性immobility 緻使我們不能重用在其它項目中、或本項目中其它位置中的軟體。工程師發現将他想重用的部分分離出來的工作量和風險太大,足以抵消他重用的積極性,是以軟體用重寫代替了重用。

    4. 粘滞性過高 viscosity有兩種形式:設計的viscosity和環境的viscosity.當需要進行改動時,工程師通常發現有不止一個方法可以達到目的。但是這些方法中,一些會保留原有的設計不變,而另外一些則不會(也就是說,這些人是hacks)。一個設計如果使工程師作錯比作對容易得多,那麼這個設計的viscosity 就會很高。

    環境的viscosity高是指開發環境速度很慢且效率很低。

    2 面向對象的類設計原則

    2.1 開放關閉原則the open closed principle (ocp)

    a module should be open for extension but closed for modification.一個子產品應該隻在擴充的時候被打開(暴露子產品内部),在修改的時候是關閉的(子產品是黑盒子)。

    在所有的面向對象設計原則中,這一條最重要。該原則是說:我們應該能夠不用修改子產品的源代碼,就能更改子產品的行為。

    2.1.1 動态多态性(dynamic polymorphism)

    2.1.2 靜态多态性(static polymorphism)

    另外一種使用ocp的技術就是使用模闆或範型,如listing 2-3.logon函數不用修改代碼就可以擴充出多種類型的modem. 2.1.3 ocp的體系結構目标(architectural goals of the ocp)

    通過遵照ocp應用這些技術,我們能建立不用更改内部代碼就可以被擴充的子產品。這就是說,在将來我們給子產品增添新功能是,隻要增加新的代碼,而不用更改原先的代碼。 第 3 頁,共 17 頁使軟體完全符合ocp可能是很難的,但即使隻是部分符合ocp,整個軟體的結構性能也會有很大的提高。我們應該記住,讓變化不要波及已經正常工作的代碼總是好的。

    2.2 liskov 替換原則the liskov substitution principle(lsp)

    subclasses should be substitutable for their base classes.子類應該可以替換其基類。

    如下圖2-14所示。derived類應該能替換其base類。也就是說,base基類的一個使用者user如果被傳遞給一個devrived類而不是base類作為參數,也能正常的工作。

    2.3 依賴性倒置原則the dependency inversion principle (dip)1

     depend upon abstractions. do not depend upon concretions.依賴抽象,不要依賴具體。

    如果說ocp聲明了oo體系結構的目的,dip則闡述了其主要機制。依賴性倒置的政策就是要依賴接口、或抽象函數、或抽象類,而不是依賴于具體的函數和類。這條原則就是支援元件設計、com、corba、ejb等等的背後力量。

    2.3.1 依賴抽象depending upon abstractions.

    實作該原則十分簡單。設計中的每一個依賴都應該是接口、抽象類,不要依賴任何一個具體類。

    顯然這樣的限制比較嚴峻,但是我們應該盡可能的遵守這條原則。原因很簡單,具體的子產品變化太多,抽象的則變化少得多。而且,抽象是“鉸鍊”點,在這些位置,設計可以彎曲或者擴充,而不用進行更改(ocp)。

    2.4 接口隔離原則the interface segregation principle (isp)

    ‘many client specific interfaces are better than one general purpose interface多個和客戶相關的接口要好于一個通用接口。

    isp是另一條在底層支援元件如com技術的原則。沒有它,元件和類的易用性和重用性都會大打折扣。該原則的實質很簡單:如果一個類有幾個使用者,與其讓這個類載入所有使用者需要使用的所有方法,還不如為每一個使用者建立一個特定的接口,并讓該類分别實作這些接口。

    3 包體系結構的原則principles of package architecture

    類是必不可少的,但對于組織一個設計來說還不夠,粒度更大的包有助于此。但是我們應該怎樣協調類和包之間的從屬關系?下面的三條原則都屬于包聚合原則,能對我們有所幫助。

    3.1 包聚合原則

    3.1.1 釋出重用等價原則the release reuse equivalency principle (rep)1

    重用的粒度就是釋出的粒度。the granule of reuse is the granule of release.一個可重用的元件(元件、一個類、一組類等),隻有在它們被某種釋出(release)系統管理以後,才能被重用。使用者不願意使用那些每次改動以後都要被強迫更新的元件。是以,即使開發者釋出了可重用元件的新版本,他也必須支援和維護舊版本,這樣才有時間讓使用者熟悉新版本。

    是以,将什麼類放在一個包中的判斷标準之一就是重用,并且因為包是釋出的最小單元,它們同樣也是重用的最小單元。體系結構師應該将可重用的類都放在包中。

    3.1.2 共同封閉原則the common closure principle (ccp)2

   一起變化的類放在一起。classes that change together, belong together.一個大的開發項目通常分割成很多網狀互聯的包。管理、測試和釋出這些包的工作可不是微不足道的工作。在任何一個釋出的版本中,如果改動的包數量越多,重建、測試和部署也就會越多。是以我們應該盡量減少在産品的釋出周期中被改動的包的數量,這就要求我們将一起變化的類放在一起(同一個包)。

    3.1.3 共同重用原則the common reuse principle (crp)3

    不一起重用的類不應該放在一起。classes that aren‘t reused together should not be grouped together.對一個包的依賴就是對包裡面所有東西的依賴。當一個包改變時,這個包的所有使用者都必須驗證是否還能正常運作,即使它們所用到的沒有任何改變也不行。

    比如我們就經常遇到作業系統需要更新。當開發商釋出一個新版本以後,我們的更新是遲早的問題,因為開發商将會不支援舊版本,即使我們對新版本沒有任何興趣,我們也得更新。

    如果把不一起使用的類放在一起,同樣的事情我們也會遇到。一個和我們無關的類的改變也産生包的一個新版本,我們被強迫更新和驗證這個包是否影響正常的運作。

    3.1.4 包聚合原則之間的張力tension between the package cohesion principles

    這三條原則實際上是互斥的。它們不能被同時滿足,因為每一條原則都隻針對某一方面,隻對某一部分人有好處。rep和crp都想重用元件的人有好處,ccp對維護人員有好處。ccp使得包有盡可能大的趨勢(畢竟,如果所有的類都屬于一個包,那麼将隻會有一個包變化);crp盡量使得包更小。

    幸運的是,包并不是一成不變的。實際上,在開發過程中,包的轉義和增删都是很正常的。在項目開發的早期,軟體建築師建立包的結構體系,此時ccp占主導地位,維護隻是輔助。在體系結構穩定以後,軟體建築師會對包結構進行重構,此時盡可能的運用rep和crp,進而最大的友善重用元件的人員。

    3.2 包耦合原則the package coupling principles.

   下面三條原則主要關心包之間的關系。

    3.2.1 無依賴回路原則the acyclic dependencies principle (adp)1

    包與包之間的依賴不能形成回路。the dependencies between packages must not form cycles.因為包是釋出的粒度。人們傾向于節省人力資源,是以工程師們通常隻編寫一個包而不是十幾個包。這種傾向由于包聚合原則被放大,後來人們就将相關的類組成一組。

    是以,工程師發現他們隻會改動較少的幾個包,一旦這些改動完成,他們就可以釋出他們改動的包。但是在釋出前,他們必須進行測試。為了測試,他們必須編譯和連編他們的包所依賴的所有的包。

    3.2.2 依賴穩定原則(stable dependencies principle,sdp)

    朝穩定的方向依賴depend in the direction of stability.雖然這條原則看起來很明顯,但是關于這方面還是有很多需要說明的地方,穩定性并不一定為大家所了解。

    穩定性是什麼?站在一個硬币上,這穩定嗎?很可能你說不。然而,除非被打擾,硬币将保持那個位置很長時間。硬币沒有變化,但是很難認為它是穩定的。穩定性與需要改動所要做的工作量相關。硬币不穩定是因為隻需要很小的工作量就能把它翻過來。換個角度,桌子就要穩定得多。

    對于軟體這說明什麼?一個軟體包很難被改動受很多因素影響:代碼大小、複雜度、透明度等等。這些我們先不說,可以肯定的一點是,如果有很多其它的包依賴于一個軟體包,那麼該軟體包就難以改動。一個包如果被許多其它包依賴,那麼該包是很穩定的,因為這個包的任何一個改動都可能需要改動很多其它的包。

    3.2.3 穩定抽象原則( stable abstractions principle ,sap)

    穩定的包應該是抽象包。stable packages should be abstract packages.我們可以想象應用程式的包結構應該是一個互相聯系的包的集合,其中不穩定的包在頂端,穩定的包在底部,所有的依賴方向朝下。那些頂端的包是不穩定而且靈活的,但那些底部的包就很難改動。這就導緻一個兩難局面:我們想要将包設計為難以改動的嗎?

    明顯地,難以改動的包越多,我們整個軟體設計的靈活性就越差。但是好像有一點希望解決這個問題,位于依賴網絡最底部的高穩定性的包的确難以改動,但是如果遵從ocp,這樣的包并不難以擴充。

7.

假設某個c函數的聲明如下:

void foo(int x, int y);

該函數被c編譯器編譯後在庫中的名字為_foo,而c++編譯器則會産生像_foo_int_int之類的名字用來支援函數重載和類型安全連接配接。由于編譯後的名字不同,c++程式不能直接調用c函數。c++提供了一個c連接配接交換指定符号extern“c”來解決這個問題。例如:

extern “c”

   void foo(int x, int y);

   … // 其它函數

或者寫成

   #include “myheader.h”

   … // 其它c頭檔案

這就告訴c++編譯譯器,函數foo是個c連接配接,應該到庫中找名字_foo而不是找_foo_int_int。c++編譯器開發商已經對c标準庫的頭檔案作了extern“c”處理,是以我們可以用#include 直接引用這些頭檔案。

資料結構:

8. avl 平衡二叉樹

作業系統:

9. 程序和線程的差別

10. 程序間通信的方法,兩個程序,socket通信,一個程序将一個指針發送過去,另一個程序是否可用linux

11. /proc下的檔案是幹什麼用的?

12. 可執行程式的結構是什麼樣的?bss中有些什麼?

13. linux下定時程式用過沒,怎麼使用?

14. linux下如何調試程式?程式core dump後怎麼辦?

回答:

9.

程序和線程都是由作業系統所體會的程式運作的基本單元,系統利用該基本單元實作系統對應用的并發性。程序和線程的差別在于:

簡而言之,一個程式至少有一個程序,一個程序至少有一個線程.

線程的劃分尺度小于程序,使得多線程程式的并發性高。

另外,程序在執行過程中擁有獨立的記憶體單元,而多個線程共享記憶體,進而極大地提高了程式的運作效率。

線程在執行過程中與程序還是有差別的。每個獨立的線程有一個程式運作的入口、順序執行序列和程式的出口。但是線程不能夠獨立執行,必須依存在應用程式中,由應用程式提供多個線程執行控制。

從邏輯角度來看,多線程的意義在于一個應用程式中,有多個執行部分可以同時執行。但作業系統并沒有将多個線程看做多個獨立的應用,來實作程序的排程和管理以及資源配置設定。這就是程序和線程的重要差別。

程序是具有一定獨立功能的程式關于某個資料集合上的一次運作活動,程序是系統進行資源配置設定和排程的一個獨立機關.

線程是程序的一個實體,是cpu排程和分派的基本機關,它是比程序更小的能獨立運作的基本機關.線程自己基本上不擁有系統資源,隻擁有一點在運作中必不可少的資源(如程式計數器,一組寄存器和棧),但是它可與同屬一個程序的其他的線程共享程序所擁有的全部資源.

一個線程可以建立和撤銷另一個線程;同一個程序中的多個線程之間可以并發執行.

10.

信号:信号處理器   處理信号集中的信号

信号量:p、v操作   同步互斥

消息隊列:程序間傳遞消息,可被多個程序共享(ipc實作基礎)

共享記憶體

11.

/proc動态檔案系統是一種特殊的由程式建立的檔案系統,核心利用它向外界輸出資訊。/proc下每個檔案都被綁定一個核心函數,這個函數在此檔案被讀取時,動态生成檔案的内容。

12.

可執行程式結構一般分為三個段:

.text:存放程式二進制代碼

.data:存放全局的已初始化變量

.bss:存放全局的未初始化變量(block started by symbol)

13.

linux系統下讓程式定時自動執行:crontab

格式 :  minute hour day month year command

# mail the system logs at 4:30pm every june 15th.

30 16 15 06 * for x in /var/log/*; do cat ${x} | mail postmaster; done

# inform the administrator, at midnight, of the changing seasons.

00 00 20 04 * echo 'woohoo, spring is here!'

00 00 20 06 * echo 'yeah, summer has arrived, time to hit the beach!'

00 00 20 10 * echo 'fall has arrived.  get those jackets out.  :-('

00 00 20 12 * echo 'time for 5 months of misery.  ;-('

注意該指令會輸出到一個标準出口 (亦即. 一個終端機 ),像是上面使用 "echo" 的例子會将輸出寄

給 "root" 帳号。如果您想要避免它,隻要像下面将輸出導引到一個空的裝置 : 

  00 06 * * * echo 'i bug the system administrator daily at 6:00am!' >/dev/null

crontab -e 重新編輯定時執行程式

    14.

gdb調試