C/C++ Tips(1)

1、關於vector的內部實現

         對於vector,任何插入刪除的操作都會使迭代器失效,所以要小心

        vector內部實現其實就是一塊連續的內存,它和傳統的array不同的它可以擴容,不用考慮越界。

        vector的迭代器就是最簡單的指向容器內類型的指針。其內部有三個指針,start(指向數據存儲區的首地址),finish(已使用數據區的尾端),end_of_storage(指向容量尾端,容量一般富餘)。當finish指針和end_of_storage指針相等的時候,容量滿載,如果繼續插入元素,就得擴容。

        需要擴容的時候,空間適配器會重新尋找一塊更大的內存空間,然後start指向新內存首地址,原始數據複製,新數據插入,同時更新finish和end_of_storage即可。擴容的規模一般是原始容量的兩倍。

2、Debug和Release

         Debug通常稱爲調試版本,它包含調試信息,並且不對代碼進行任何優化,便於程序員調試程序。

         Release稱爲發佈版本,它往往是將代碼進行了各種優化,使程序在代碼大小和運行速度上是最優的,以便用戶很好的使用。

         只有debug版的程序才能設置斷點,單步執行。

        實際上debug和release沒有本質上的界限,它們只是一組編譯選項的集合,編譯器只是按照預定的選項行動,事實上,我們甚至可以修改這些選項,得到優化的可調試版本或是帶跟蹤語句的發佈版本。

3、引用和指針的區別

        從概念上講,指針本質上就是存放地址的一個變量,在邏輯上是獨立的,它可以被改變,包括其所指向地址的改變和其指向地址中所存放數據的改變。

        而引用是一個別名,在邏輯上是不獨立的,它的存在具有依附性,所以引用必須在一開始就初始化,而且其引用的對象在整個生命期中是不可能改變的。

        在C++中,指針和引用經常用於函數的參數傳遞,然而指針傳遞參數和引用傳遞參數在本質上是不同的。

        指針傳遞參數本質上是值傳遞,它傳遞的是一個地址值。傳遞過程中,被調函數的形參作爲被調函數的局部變量處理,即在棧中開闢了內存空間以存放主調函數放進來的實參的值,從而形成了實參的一個副本。值傳遞的特點是被調函數對形式參數的操作都是作爲局部變量進行,不會影響主調函數的實參的值。

       總結一下

          相同點,都是地址的概念

          指針指向一塊內存,它的內容是指向內存的地址,而引用是某塊內存的別名。

          不同點:

          1)指針是一個實體,引用僅是個別名

          2)引用只能在定義時被初始化一次,之後不可變,指針可變。

          3)引用沒有const,指針有const,const的指針不可變。

          4)引用不能爲空,指針可以爲空

          5)sizeof(引用)得到的是所指向變量的大小,而sizeof指針得到的是指針本身的大小。

          6)指針和引用的自增操作運算結果不一樣

          7)引用是類型安全的,而指針不是。

4、複製構造機制

        拷貝構造函數,是一種特殊的構造函數,它由編譯器調用來完成一些基於同一類的其它對象的構建及初始化。唯一的參數(對象的引用)是不可變的(const)類型。

        拷貝構造函數調用的三種形式。

            1)一個對象作爲參數,以值傳遞的方式傳入函數體

            2)一個對象作爲函數返回值,以值傳遞的方式從函數返回

            3)一個對象給另一個對象進行初始化(常稱爲複製初始化)

       總結:當某對象是按值傳遞時(無論是作爲函數參數,還是作爲函數返回值),編譯器都會先建立一個此對象上的臨時拷貝,而建立臨時拷貝時就會調用拷貝構造函數。

       如果在類中沒有顯式的聲明一個拷貝構造函數,那麼編譯器就會自動生成一個默認的拷貝構造函數,該構造函數完成對象之間的淺拷貝。

       在某些狀況下,類成員變量需要動態開闢內存,如果實行淺拷貝,就是把一個對象裏的值完全複製給另一個對象,如A=B。這時如果B中有一個成員變量指針已經申請了內存,那麼A中的成員變量也指向那塊內存。這就出現了問題:當B把內存釋放了,這時A的指針就是野指針了,出現運行錯誤。這時候就要使用深拷貝了,要自定義拷貝構造函數。

      深拷貝和淺拷貝可以簡單理解爲:如果一個類擁有資源,當這個類發生複製過程的時候,如果重新分配了資源,這個過程就是深拷貝。反之,沒有重新分配資源,就是淺拷貝。

       對於凡是包含動態分配成員或包含指針成員的類都應該提供拷貝構造函數。

       在提供拷貝構造函數的同時,還應該考慮重載“=”賦值操作符號

5、虛機制是怎麼實現的

        虛函數在C++中的實現機制就是用虛表和虛指針,每個類定義一個虛表,每個對象定義一個虛指針。

6、虛析構函數的作用

        如果一個基類的析構函數被說明爲虛析構函數,則它派生類中的析構函數也是虛析構函數。

        說明虛析構函數的目的在於使用delete運算符刪除一個對象時,能保證析構函數被正確的執行。因爲設置析構函數後,可以採用動態聯編方式選擇析構函數。

        先調用子類析構函數,再調用子類的析構函數,防止析構不完全。

7、數組和鏈表區別

        C++中可以用數組處理一組數據類型相同的數據,但不允許動態定義數組的大小,即在使用數組之前必須確定數組的大小。而在實際應用中,有時無法確定數組的大小,只能將數組定義爲足夠大。這樣數組中有些空間可能不被使用,從而造成內存空間浪費。

         鏈表是一種常用的數據分配方式,它採用動態分配內存的形式實現。需要時可以用new分配內存空間,不需要時用delete將內存釋放,不會造成內存空間的浪費。

         數組中數據是連續存儲的,而鏈表是隨機存儲的。

         數組隨機訪問,插入刪除效率較低。鏈表訪問某個元素需要從頭遍歷,較數組慢,但插入刪除不需要移動元素。

8、面向過程和麪向對象

        面向過程就是分析出解決問題所需要的步驟,然後用函數把這些步驟一步一步實現,使用的時候一個個依次調用就可以了。

        面向對象是把構成問題事物分解成一個個對象,建立對象的目的不是爲了完成一個步驟,而是爲了描述某個事物在解決整個問題的步驟中的行爲。

        面向過程和麪向對象的區別並沒有人們想象中的大。面向對象的大部分思想在面向過程中也能體現,但面向過程最大的問題在隨着系統的膨脹,面向過程將無法應付,最終導致系統的崩潰。面向對象的提出就是爲了解決這一軟件危機。

9、虛函數和純虛函數

        1)虛函數和純虛函數可以定義在同一個類中,含有純虛函數的類被稱爲抽象類。而只含有虛函數的類不能稱爲抽象類。

        2)虛函數可以被直接使用,也可以被子類重載以後以多態的形式調用,而純虛函數必須在子類中實現該函數纔可以被調用,因爲純虛函數在基類只有聲明而沒有定義。

        3)虛函數和純虛函數都可以在子類中重載,以多態的方式被調用

        4)虛函數和純虛函數通常存在於抽象基類之中,被繼承的子類重載,目地是提供一個統一的接口

        5)virtual {method body}

              virtual {}=0;

        在虛函數和純虛函數的定義中不能含有static標識符,被static修飾的函數在編譯時要求前期綁定,然而虛函數卻是動態綁定,被兩者修飾的函數的生命週期也不一樣。

        6)如果一個類中含有純虛函數,那麼任何對該類實例化的語句都會有錯誤的產生。因爲抽象基類是不能直接被調用的,必須被子類繼承重載之後,根據要求調用子類的方法。

10、C++靜態全局變量、靜態局部變量、全局變量、局部變量

          按存儲區域分:全局變量、靜態全局變量和靜態局部變量都存放在內存的全局數據區,局部變量放在內存的棧區。

          按作用域分:全局變量在整個工程文件內都有效;靜態全局變量只在定義它的文件內有效;靜態局部變量只在定義它的函數內有效,只是程序僅分配一次內存,函數返回後,該變量不會消失。局部變量在定義它的函數內有效,函數返回後消失。

         全局變量和靜態變量如果沒有手動初始化,則由編譯器初始化爲0,局部變量的值不可知。

11、常用設計模式

          單件模式,抽象工廠模式和工廠模式 適配器模式 裝飾模式 觀察者模式 外觀模式

          單件模式,這是用得最多的模式,每一個正式軟件都要用到它,全局配置、唯一資源。

          單件模式中構造函數應該設置爲protected,這樣纔可以擴張這個構造函數。

           1)對於一個類,佔用的系統資源非常多,而且這些資源可以被全局共享,則可以設置爲單件模式,強迫全局只有一個實例。

           2)對於一個類,需要對實例進行計數,可以在createInstanse中進行並可以對實例的個數進行限制。

           3)對於一個類,需要對其實例的具體行爲進行控制,例如期望返回的實例實際上是自己子類的實例。這樣可以通過單件模式,對用戶端代碼保持透明。

           如果是多線程的情況下怎麼使用單件模式。

           ans:多線程應該也是單件的吧,因爲線程之間是共享內存的。

           如果限制實例不超過十個該怎麼辦

           ans:使用類似線程池的東西

12、關於圖形學

            直方圖均衡化:改變圖像像素的灰度分佈

            空域濾波:對空間圖像進行加強

            線性銳化濾波,非線性平滑濾波

13、C++中函數體存放在哪兒

       C++程序的內存格局通常爲4個區

       1)全局數據區:存放全局變量,靜態變量,常量

       2)代碼區:存放所有類成員函數和非成員函數代碼(想知道這個代碼區具體是哪兒)

       3)棧區:存放爲運行函數而分配的局部變量,函數參數,返回類型,返回地址

       4)堆區:餘下的就是堆(使用new和delete)

14、將引用作爲函數返回值類型的好處以及需要遵守的規則

          好處:內存中不產生返回值的副本

         注意:

             1)不能返回局部變量的引用

             2)不能返回函數內部new分配的內存的引用(因爲函數返回的引用知識作爲一個局部變量出現,而沒有賦予給一個實際的變量,那麼這個引用指向的空間就無法釋放,造成內存泄漏)

             3)可以返回類成員的引用,但最好設爲const

             4)流操作符重載返回值申明爲引用的作用

         因爲操作符通常被希望連續使用,指針需要重複創建副本,不可取,唯一的方法就是使用引用了

15、程序運行的時候突然崩潰,如何查找錯誤

          貌似這個題俺沒有聽明白麪試官說的什麼意思,就說用斷點、逐行定位到錯誤的區域

          後來他解釋說是要根據崩潰的點將錯誤範圍一步一步縮小,還說這是一種思想。。~~~俺就開始畫圈圈了。。。很多時候我們是這麼做的但是說不出來。

16、map的內部實現

         vector封裝了數組,list封裝了鏈表,map和set封裝了二叉樹(完全木有想到啊~~)

        進一步提問爲什麼map和set插入刪除效率比其它序列容器高

         因爲map和set之中的所有元素都是以節點形式存儲,其結構和鏈表節點結構差不多。之所以效率高是因爲不需要做內存拷貝和內存移動,只要修改節點指針指向就可以了。

17、圖形的基本變換包括哪些方面

          放大 縮小 旋轉 拉伸 視點變換 座標映射……

         這以後就問了好多這方面的問題。。~諸如變換矩陣之類、三維圖形旋轉之類、圖形算法之類

18、請說一下extern C的作用

         extern "C"的主要作用就是爲了能夠正確實現C++代碼調用其他C語言代碼。加上extern "C"後,會指示編譯器這部分代碼按C語言的進行編譯,而不是C++的。由於C++支持函數重載,因此編譯器編譯函數的過程中會將函數的參數類型也加到編譯後的代碼中,而不僅僅是函數名;而C語言並不支持函數重載,因此編譯C語言代碼的函數時不會帶上函數的參數類型,一般之包括函數名。
        這個功能十分有用處,因爲在C++出現以前,很多代碼都是C語言寫的,而且很底層的庫也是C語言寫的,爲了更好的支持原來的C代碼和已經寫好的C語言庫,需要在C++中儘可能的支持C,而extern "C"就是其中的一個策略。

19、請說一下#ifdef...的作用

        防止頭文件被重複引用。

20、關鍵字static有什麼作用

       靜態全局變量不能被其它文件所用
       其它文件可以定義相同名字的變量,不會發生衝突
       靜態函數不能被其它文件所用
       其它文件中可以定義相同名字的函數,不會發生衝突
       大家知道函數在棧上分配的空間在此函數執行結束時會釋放掉。如果想將函數中變量的值保存到下一次調用,就可以使用靜態變量。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章