面向對象 C++ 面試

面向對象:抽象、封裝,繼承,多態

抽象:是對具體問題進行概括,抽象出一類對象的公共性質並加以描述的過程。

封裝:將抽象得到的數據和行爲相結合,形成一個有機的整體,也就是將數據和操作數據的函數代碼進行有機的結合,形成類。隱藏實現細節,使代碼模塊化     (代碼重用)

繼承:子類繼承所有基類的成員,並擴展已存在的類             (代碼重用)

    繼承類的類對象執行順序

     在多繼承方式下,派生類的構造函數和析構函數調用順序是怎麼樣的?

     1.若類有直接或間接虛基類,先執行虛基類的構造函數;

     2.如果該類有其他基類,按繼承聲明列表中出現的次序,分別執行他們的 

       構造函數,但構造過程中不再執他們的虛基類的構造函數。

     3.按類定義中出現的先後順序,對派生類新增的成員對象初始化。對於類 

       類型的成員對象,若出現在構造函數初始化列表中,以其指定的參數執

       行構造函數,或默認構造函數;對於基本類型的成員對象,若出現在初

       始化列表中,以其指定值爲其賦初值,否則什麼也不做。

     4.執行構造函數函數體

      is-a:繼承

      has-a:組合  類對象

虛繼承:多重繼承特有概念

class B1:public virtual B

class B2:public B

    class A:public B1,public B2

      繼承方式         |public    |protected   |private

 基類成員權限  public |public    |protected   |private(子類的私有)

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

              protected |protected |protected   |private

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

                private |不可訪問的|不可訪問的  |不可訪問的

多態:一個接口,多種方法(不同對象接受同一消息的不同反應)

      多態類型指具有虛函數的類類型。

     重載多態,強制多態,包含多態,參數多態(接口重用)

     運行多態滿足的3個條件:

     1.基類與派生類---複製兼容規則;

     2.同名同參的函數加virtural---覆蓋;

     3.通過基類指針或引用訪問基類

虛函數:

允許被子類重新定義成員函數,即覆蓋或重寫,父類指針根據賦值給他的不同子類指針動態調用子     類的該函數虛函數都有虛表,類中每一個對象都有一個虛指針指向虛表,其中含有虛函數入口地址

多態類中的虛函數表是Compile-Time,還是Run-Time時建立的?

虛擬函數表是在Compile-Time就建立了,各個虛擬函數這時被組織成了一個虛擬函數的入口地址的數組.而對象的隱藏成員--虛擬函數表指針是在Run-Time--也就是構造函數被調用時進行初始化的,這是實現多態的關鍵. 

    1.只有成員函數爲虛函數。

2.不能爲虛函數:靜態成員函數,內聯函數(靜態處理),構造函數(創建是 

  不可以實現多態化,需一一創建),友元函數,全局函數

    3.析構函數可以且通常爲虛函數(用指針引用時動態綁定,實現運行時多態)

      即:若基類指針調用對象的析構函數(delete),就需要讓基類的析構函

       數爲虛函數否則產生不確定後果

4.構造函數不可以聲明爲虛函數,虛函數採用虛調用方式,其允許調用只知

      接口而不知其準確對象類型的函數,但創建對象時,必須知道對象的準確

      數據類型,所以不可以聲明爲虛函數。

5.基類爲virtual,子類不聲明也是虛函數

純虛函數:eg:shape 類中的Draw函數是純虛函數 virtual void Draw=0,則shape類不可以直接實例化對象,shape s  (X)

   基類的指針可以指向派生類對象,調用基類虛函數時,實際執行的操作由派生類決定,繼承基類接口,不必繼承虛函數的實現

C++語言採用重載機制.

1.在C++ 程序中,可以將EatBeef,Eat類型的參數加以區別。 

2.一個理由是:類的構造函數需要重載機制。因爲C++ 規定構造函數與類同名(請參見第 9 章),構造函數只能有一個名字。如果想用幾種不同的方法創建對象該怎麼辦?別無選擇,只能用重載機制來實現。所以類可以有多個同名的構造函數。


重載:在同一作用域內,函數名相同,參數列表不同的兩個或者兩個以上函數,稱爲重載。

覆蓋:在不同的作用於中,子類中的函數和父類中的函數同名同參,並且父類中的函數有

      virtual關鍵字修飾。

隱藏:不同作用域,子類中的函數和父類中的函數同名,參數列表相同(則有virtual即爲覆蓋)或不同(不用加virtual)

(1 )如果派生類的函數與基類的函數同名,但是參數不同。此時,不論有無virtual關鍵字,基類的函數將被隱藏(注意別與重載混淆)。 

(2 )如果派生類的函數與基類的函數同名,並且參數也相同,但是基類函數沒有virtual關鍵字。此時,基類的函數被隱藏(注意別與覆蓋混淆)。 

**************

//用派生類對象初始化基類指針:Derived  a; Base*p =&a

// 對於同名同參有virtual的函數(覆蓋),p->f(),輸出派生類的

//對於同名同參(無virtual)或同名不同參(有無virtual均可)的函數(隱藏),p->f(),輸出基類的

//用派生類對象初始化派生類指針:Derived  a; Derived *p =&a

//對於同名同參有virtual的函數(覆蓋),p->f(),輸出派生類的

//對於同名同參(無virtual)|同名不同參(有無virtual均可)函數(隱藏),p->f(),輸出派生類的

構造函數:

創建類對象並對其初始化(傳參,按聲明順序構造對象(申請空間),執行函數體(賦值)),類對象大小=各成員大小之和;

函數名爲類名;

無返回值,

定義時系統自動調用;

參數不定,

有默認構造函數,帶參產生不同結果,不唯一。

創建類對象時無參不用括號

不可以聲明爲虛函數,虛函數採用虛調用方式,其允許調用只知接口而不知其準確對象類型的函數,但創建對象時,必須知道對象的準確數據類型,所以不可以聲明爲虛函數

派生類構造函數:

  調用基類構造,按其繼承聲明順序

  對派生類新增成員對象初始化,按類中聲明的順序。

  執行派生類構造函數體的內容

析構函數

   唯一,有默認,無需參數則不可重載,可以是內聯函數

   可以爲虛函數---Base*p;child c;p=&c;撤銷p時調用基類析構,子類析構未調用,造成內存泄露;若基類虛構爲虛,先調用子類析構,再調基類析構拷貝、賦值構造函數:

Point a(4,5);

Point b=a; //1 用類對象初始化類對象;調用拷貝構造函

fn1(b);    //2,類對象作爲實參,調用拷貝構造函數,再執行函數體 4

b=fn2(a);  //3,返回值爲類類型,調用拷貝構造函數(對實參),再執行函數

             體(返回值)

總結const的用法

1、C++中:const修飾的標識符爲常量;C中修飾的標識符爲只讀變量

2、Const修飾的函數爲常成員函數。

3、若將一個對象聲明爲常對象,則該對象只可調用它的常成員函數。

4、在常成員函數調用期間,目的對象均視爲常對象,目的對象的數據成員不可更新。

5常引用:常引用所引用的對象不可以更新

  可用於對重載函數的區別void print();void print() const

6、const int *p; i=9; p=&I;不可變指向內容,*p=6(error)

但可改變指針本身 p=&j     Int * const p 可改變指向的內容,

*:指針運算符

   出現在聲明語句中,變量之前,表聲明的是指針 int *p

   出現在執行或聲明語句的初始化表達式中,表示訪問指針所指對象的內容*p

&:取地址運算符 

   在被聲明變量左邊,表示聲明的是引用。int &ref 

   在等號右邊表取對象的地址                  int *p=&i

指針,引用

   引用必須初始化,指針可以不用;

   初始化後知不可以改變,指針可以修改指針所指向的對象;

   不存在指向空的引用;

   其佔內存空間長度是對象的大小,指針是4;

   傳引用,無需新開闢空間,引用是直接訪問,而指針是間接訪問。

   case:

   1、對於數據參數傳遞,減少大對象的參數傳遞開銷兩個用途來講,引用更好

      用,簡潔,安全。

   2、使用指針:若指針所指對象,需用分支語句加以確定,或需改變其指向;

              用空指針的時候;

              使用函數指針時;

              new動態創建的對象、數組,用指針存儲地址;

              以數組傳遞大批量數據用指針類型接收;

 

       一般:指針的值只能賦值於相同變量的指針。

       void 不可以聲明變量

       void 類型指針(一般只用於指針所指的數據類型不明時)可存儲任何類

       型對象的地址。但必須使用顯式類型化,eg:

              void *p;

              p=&i;

              int *pp=static_cast<int *>(p);

         

作用域:一個標識符在程序正文中的有效區域。

1:函數原型作用域;

2:局部作用域;

3:類作用域;x:X....x::X(訪問類的靜態成員);ptr->m,ptr:指向X類的一個對象的指針

4:命名空間作用域:全局(默認)和匿名(顯式聲明) 命名空間

可見性:從標識符有效範圍:程序運行到某點時可引用到的標識符,就是可見標識符規則:

標識符聲明在前,引用在後;

同一作用域,不可有同名標識符;

未互相包含且處於不同作用域的同名標識符互不影響;

互相包含且處於不同作用域的同名標識符,外層在內層不可見;

Static

==把局部變量改變爲靜態變量後是改變了它的存儲方式即改變了它的生存期。static局部變量只被初始化一次,下一次依據上一次結果值;

==把全局變量改變爲靜態變量後是改變了它的作用域, 限制了它的使用範圍。

static全局變量只初使化一次,防止在其他文件單元中被引用;

==修飾類的成員變量,在內存中只有一份拷貝,爲所有對象共享。

==static函數與普通函數作用域不同,僅在本文件。只在當前源文件中使用的函數應該說明爲內部函數(static),內部函數應該在當前源文件中說明和定義。對於可在當前源文件以外使用的函數,應該在一個頭文件中說明,要使用這些函數的源文件要包含這個頭文件 ,static函數在內存中只有一份,普通函數在每個被調用中維持一份拷貝

==Static修飾類的成員函數,此函數不接受this指針,只能訪問類的static成員變量。

類的靜態成員和非靜態成員有何區別?

(1)類的數據方面。類的靜態數據成員爲所有類的所有對象共享,內存中只有一份。 

     而非靜態數據成員每個類對象都擁有自己的一份。

(2)類的成員函數。類的靜態成員函數沒有默認的this指針,不能在類中對成員進行  

     直接訪問。而類的成員函數有一個默認的this指針。

(3)調用方式。靜態成員用類名加上作用域調用||類對象調用,而非靜態成員必須要  

    用類對象調用。 c.showCount;

                   Point ::showCount ; 

在類定義後還需再類外加以定義。

static int count ;

int Point ::count=0;  類名::標識符。

若類的靜態常量是int 或enum,直接在類定義中指定常量值

運算符:+-*/%++--   &|~^<<>> !&&|| ><==  = [] () -> , new delete ->*  

1.不能重載的運算符 "." ".*" "::" "?:" 4個

2. 不能造新的運算符

3. 不會改變三特性(目數,優先級,結合方向)

4*.功能一致(相當) 

5. 至少有一個操作數是自定義類型

6. "=" "->" "[]" "()" 只能重裝爲成員函數

Point operator ++(int )

Point &operator ++( )

friend ostream &operator<<(ostream &out,const Point &p);

在C++ 語言中,可以用關鍵字operator加上運算符來表示函數,運算符重載。

例如兩個複數相加函數: 

Complex Add(const Complex &a, const Complex &b); 

可以用運算符重載來表示: 

Complex operator +(const Complex &a, const Complex &b); 

運算符與普通函數在調用時的不同之處是:對於普通函數,參數出現在圓括號內;

而對於運算符,參數出現在其左、右側。例如 

  Complex a, b, c; 

  … 

  c = Add(a, b); //  用普通函數 

  c = a + b;   //  用運算符 + 

如果運算符被重載爲全局函數,那麼只有一個參數的運算符叫做一元運算符,有兩個參數的運算符叫做二元運算符。 

如果運算符被重載爲類的成員函數,那麼一元運算符沒有參數,二元運算符只有個右側參數,因爲對象自己成了左側參數。 

從語法上講,運算符既可以定義爲全局函數,也可以定義爲成員函數。


所有的一元運算符  建議重載爲成員函數 

= () [] ->  只能重載爲成員函數 

+= -= /= *= &= |= ~= %= >>= <<=  建議重載爲成員函數 

所有其它運算符  建議重載爲全局函數 

表8-4-1 運算符的重載規則 


由於C++ 語言支持函數重載,才能將運算符當成函數來用,C 語言就不行。我們要以平常心來對待運算符重載: 

(1 )不要過分擔心自己不會用,它的本質仍然是程序員們熟悉的函數。 

(2 )不要過分熱心地使用,如果它不能使代碼變得更加易讀易寫,那就別用,否則會自找麻煩

C++中爲什麼用模板類?

(1)可用來創建動態增長和減小的數據結構 

(2)它是類型無關的,因此具有很高的可複用性。 

(3)它在編譯時而不是運行時檢查數據類型,保證了類型安全 

(4)它是平臺無關的,可移植性 

(5)可用於基本數據類型

函數模板與類模板有什麼區別?

函數模板的實例化是由編譯程序在處理函數調用時自動完成的,而類模板的實例化必須由程序員在程序中顯式地指定

const #define 均可以定義常量 區別:

   const只讀變量有數據類型,編譯器可進行安全檢查;有集成化的調試工

     具可對const常量進行調試
   
define只是字符替換

內聯函數、宏的區別

  內聯可以加快程序運行速度,編譯時直接嵌入目標代碼(調用函數時不是 

  跳轉);要做參數類型檢查,安全。(短程序不斷被調,函數無while,     

  for,switch等)

  只是簡單替換

指針和句柄

  句柄:32b的整數,是windows在內存中維護的對象內存物理地址列表的整數索引。對象駐留在內存,但是windows經常會將空閒對象釋放來滿足和應用對內存的需求,下一次用的對象物理地址改變,所以用句柄將對象新地址保存。知道句柄地址便間接知道對象位置。

  指針:標記某物理內存地址

描述內存分配方式以及他們的區別?

內存分成5個區,他們分別是堆、棧、代碼區、全局/靜態存儲區和常量存儲區。 

棧,就是那些由編譯器在需要的時候分配,在不需要的時候自動清楚的變量的存儲區。裏面的變量通常是局部變量、函數參數等。 

堆,就是那些由new/malloc分配的內存塊,他們的釋放編譯器不去管,由我們的應用程序去控制,一般一個new就要對應一個delete/free。如果程序員沒有釋放掉,那麼在程序結束後,操作系統會自動回收。 

代碼區:存放函數代碼,程序代碼。我們調用一個函數,函數指針指向的就是這片內存區。

全局/靜態存儲區,全局變量和靜態變量被分配到同一塊內存中。

常量存儲區,這是一塊比較特殊的存儲區,他們裏面存放的是常量。

 

內存泄露一般是如何產生的?如何避免?

動態開闢的內存沒有釋放,造成內存泄露。爲了防止泄露可以定義智能指針或者自己寫一個類,將申請和釋放封裝在一個類中。

堆棧溢出一般是由什麼原因導致的?

沒有回收垃圾資源;

層次太深的遞歸調用。

extern "C"

是連接申明(linkage declaration),被extern "C"修飾的變量和函數是按照C語言方式編譯和連接的,作爲一種面向對象的語言,C++支持函數重載(同名不同參),函數被C++編譯後在符號庫中的名字與C語言的不同。

 

 

 

 

 

 

 

 

 

 

 


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