這是我在准備C++考試時整理的提綱,如果是通過搜索引擎搜索到這篇博客的師弟師妹,建議還是先參照PPT和課本,這個大綱也不是很准確,自己總結會更有收獲,多去理解含義,不要死記硬背,否則遇到概念辨析題會特別吃虧,如果覺得有收獲點贊關注,祝考試順利。
1.敘述面向對象編程的特點是什么?(提示:封裝、繼承、多態。)
對比面向過程具有抽象、封裝、繼承和多態的特點。
封裝是將抽象得到的數據和行為相結合,形成了一個有機整體,使得一部分成員充當類與外部的接口,而將其他成員隱藏了起來達到了對成員訪問權限的合理控制,使得不同類之間的影響最小,加強數據安全,簡化編程。
繼承允許在保持原有類特性的基礎上,進行更具體、更詳細的說明,能夠很好反映出特殊概念和一般概念之間的關系,是代碼復用的基礎機制。
多態使得一段程序能夠具有處理多種類型對象的能力,相同的消息在不同的對象下會有不同的動作,增強了編程的靈活性。
2.使用const定義常量與用使用define定義常量相比,有什么優點?
a. const常量有數據類型,而宏常量沒有數據類型。編譯器可以對const常量進行類型安全檢查,而對宏常量只能字符替換
b. 有些集成化的調試工具能對const常量進行調試,對宏常量不能調試
c.const定義的常量在程序運行的過程中只有一份拷貝,而define定義的常量在內存中有若干拷貝。
3.用代碼說明在標准C++中如何進行輸入輸出,並解釋各語句的含義是什么?
cout<<"hello!"<<"world"; cin>>a>>b;
在輸入時,從鍵盤輸入的數據先放在鍵盤緩沖區中,當按回車鍵時,鍵盤緩沖區中的數據輸入到程序中的輸入緩沖區,形成cin流,然后用流提取運算符“>>”從輸入緩沖區中提取數據送給程序中的有關變量。
當用cout和流插入運算符“<<”向顯示器輸出數據時,先將這些數據送到程序中的輸出緩沖區保存,直到緩沖區滿了或遇到endl,就將緩沖區中的全部數據送到顯示器顯示出來。
4.C++中如何進行靜態類型轉換,解釋並舉例說明。
(1)用於類層次結構中基類和派生類之間指針或引用的轉換。進行上行轉換(把派生類的指針或引用轉換成基類表示)是安全的;進行下行轉換(把基類指針或引用轉換成派生類表示)時,由於沒有動態類型檢查,所以是不安全的。
class Base { }; class Derived:public Base {}; int main() { Derived D; Base* B = static_cast<Base*> (&D); return 0; }
將派生類型的指針轉化為基類型的指針
(2)用於基本數據類型之間的轉換,如把int轉換成char,把int轉換成enum。這種轉換的安全性也要開發人員來保證。
double a; int b=100; a = static_cast<double> (b);
(3)把空指針轉換成目標類型的空指針(不安全!!)。
(4)把任何類型的表達式轉換成void類型。
5.闡述C++中函數三種調用的方式實現機制、特點及其實參、形參的格式,最好用代碼說明。(提示:傳址、傳值、引用傳遞)
在C++中調用函數時有三種參數傳遞方式:
(1)傳值調用;
int main( ) { void swap(int,int); //函數聲明
int i=3,j=5; swap(i,j); //調用函數swap
return 0; } void swap(int a,int b) //企圖通過形參a和b的值互換,實現實參i和j的值互換
{ int temp; temp=a; //以下3行用來實現a和b的值互換
a=b; b=temp; }
(2)傳址調用(傳指針);
用指針類型作為形參的值調用方式,可以通過參數返回修改后的值。
void main( ) { void swap(int *,int *); int i=3,j=5; swap(&i,&j); //實參是變量的地址
} void swap(int *p1,int *p2) //形參是指針變量
{ int temp; temp=*p1; //以下3行用來實現i和j的值互換
*p1=*p2; *p2=temp; }
(3)引用傳遞;
按引用傳遞,引用實參的引用參數傳遞給函數,而不是進行參數拷貝。引用類型的形參與相應的實參占用相同的內存空間,改變引用類型形參的值,相應實參的值也會隨着變化。
int main( ) { void swap(int &,int &); int i=3,j=5; swap(i,j); return 0; } void swap(int &a,int &b) //形參是引用類型
{ int temp; temp=a; a=b; b=temp; }
6.什么是內聯函數?為什么要使用內聯函數?
在編譯時將所調用函數的代碼直接嵌入到主調函數中,而不是將流程轉出去,這種嵌入到主調函數中的函數成為內聯函數。
為了節省參數傳遞、控制轉換等開銷,比如:壓棧、彈棧、保存現場與恢復現場。
7.什么是類的前向聲明?使用類的前向聲明時,要注意什么?
遇到倆個類相互引用的循環依賴情況
class B; //前向引用聲明
class A//A類的定義
{ public://外部接口
void f(B b);//以B類對象b為形參的成員函數
}; class B//B類的定義
{ public://外部接口
void g(A a);//以A類對象a為形參的成員函數
};
前向引用聲明,是在引用未定義的類之前,聲明該類,使編譯器知道那是一個類名。這樣,當程序中使用這個類名時,編譯器就不會認為是錯誤,而類的完整定義可以在程序的其他地方。
注意:盡管使用了前向引用聲明,但是在提供一個完整的類聲明之前,不能定義該類的對象,也不能在成員函數中使用該類的對象。只能用於定義指針、引用、以及用於函數形參的指針和引用。當你使用前向引用聲明時,你只能使用被聲明的符號,而不能涉及類的任何細節。
8.什么是先驗條件(Precondition),什么是后置條件(Postcondition)?(google)
先演條件是在執行某段代碼或正式規范操作之前必須始終為真的條件或謂詞。比如輸入一個時間必須小於24。
后置條件是在執行某段代碼或正式規范操作之后必須始終為真的條件或謂詞。比如計算輸入數字的平方根程序可能具有結果為數字的后置條件,且其平方等於輸入。
9.什么是名稱空間(namespace)?它的主要作用是什么?要使用一個名稱空間中聲明的標識符,方式有哪些?
A namespace is a scope.
C++ provides namespaces to prevent name conflicts
名字空間實質上是一種作用域。名字空間是一種描述邏輯分組的機制,是為了解決C++中的變量、函數命名沖突而服務的。
C++標准程序庫中的所有標識符都被定義於一個名為std的namespace中。
由於namespace的概念,使用C++標准程序庫的任何標識符時,可以有三種選擇:
a、直接指定標識符。例如std::ostream而不是ostream。完整語句如下:
#include <iostream> std::cout << "hello!!"<< std::endl;
b、使用using關鍵字進行聲明
顯然,當某個名字在它自己的名字空間之外頻繁使用時,在反復寫它時都要加上名字空間來作限定詞,是一件令人厭煩的事情。這時,可以通過一個使用聲明而清楚掉,只需要在某個地方說明,在這個作用域其后的代碼中,使用此名字時可以自動解析出此名字所在的空間。例如:
#include <iostream>
using std::cout; using std::endl;
此后在使用cout和endl時,都無需再加上名字空間前緣了:
cout <<"hello!!"<< endl;
c、最方便的就是使用指令using namespace std;
一個使用指令能把來自另一個名字空間的所有名字都變成在當前名字空間內可用,就像這些名字就在當前名字空間中一樣。
例如,在一個名字空間內有命令 using namespace std; 則在此后的代碼中(當前空間最小局部內),對於所有名字都無需有名字空間前綴即可使用。
#include <iostream>
using namespace std;
10.什么是重載(Overloading),解釋並舉例說明?能否根據返回值不同,對函數進行重載,為什么?
C++有兩種重載:函數重載和運算符重載。
C++允許用同一函數名定義多個函數,這些函數的參數個數和參數類型不同。這就是函數的重載(function overloading)。
運算符重載實質上是函數的重載,運算符重載通過運算符函數實現。
int max(int a,int b, int c); double max(double a,double b,double c); long max(long a,long b,long c);
不能根據返回值不同進行重載。
因為調用時不能指定類型信息,編譯器不知道你要調用哪個函數。
例如
float max(int a, int b); int max(int a, int b);
當調用max(1, 2);時無法確定調用的是哪個,單從這一點上來說,僅返回值類型不同的重載是不應該允許的。
11.關鍵字const的用法有哪些?(google)
一、定義常量
常量不可修改
const int val = 5; int const val = 5;
與#define宏定義常量的區別:
(1)const常量具有類型,編譯器可以進行安全檢查;#define宏定義沒有數據類型,只是簡單的字符串替換,不能進行安全檢查。
(2)有些集成化的調試工具能對const常量進行調試,對宏常量不能調試
二、修飾指針
(1)const int* p; //指針p指向的內容是常量,不可改變。
(2)int* const p; //指針本身是一個常量,不可改變。
(3)const int* const p; //指針本身和指向的內容都是常量,都不可以改變。
區分方法,*p代表對象內容,p代表指針本身,看const修飾的是哪個。
三、在函數中使用const
修飾函數參數
void function(const int Var);
表明參數在函數體內不能被修改,但此處沒有任何意義,Var本身就是形參,在函數內不會改變。
包括傳入的形參是指針也是一樣。
(1)使用引用參數,可以防止創建副本,減少內存開銷,同時可以在函數中對引用參數修改,函數結束后,引用參數的修改仍然存在。
(2)如果為了防止對引用參數進行修改,可以對該參數加上const關鍵字。
修飾函數返回值
與修飾普通變量和指針意義差不多,而在傳引用時,如果不希望函數返回值被改變,就可以添加關鍵字 const 。
四、在類中使用const
修飾類成員變量
class A { const int nValue; }
(1)成員常量不可被修改。
(2)只能在初始化列表中被賦值。
修飾類成員函數
class A { void function()const; }
(1)常成員函數, 它不改變對象的成員變量. 代表只讀函數,增加程序的可讀性。
(2)不能調用類中任何非const成員函數。
12.操作符new的作用是什么?如何申請單個空間?如何申請動態數組?用new創建一個類的對象時,會發生哪些操作?必要時,請用代碼說明。
作用:在堆中申請一段空間,動態分配內存
申請單個空間int *i = new int;
申請動態數組int *a = new int[10];
new創建類對象需要指針接收,一處初始化,多處使用,作用域是全局,且需要手動釋放空間,在堆中動態分配內存,調用構造函數。
13.操作符delete的作用是什么?如何刪除單個用new申請的空間?如何刪除申請的動態數組?用delete刪除一個類的對象時,會發生哪些操作?必要時,請用代碼說明。
作用:釋放所申請的空間
釋放單個空間delete i;
釋放動態數組delete []a;
釋放在堆中分配的內存,調用析構函數。
14.什么是懸掛指針(又稱為野指針,Dangling Pointers),其危害是什么?(google)
指針指向非法的內存地址,那么這個指針就是懸掛指針,也叫野指針。意為無法正常使用的指針。野指針造成的危害程度和危害時間未知,因為野指針指向的內存空間,有可能是某個重要的數據或其他程序。嚴重的情況下會造成程序崩潰。
15.什么是類?通常一個類中,包含什么樣的內容?定義一個類的語法是什么,試舉例說明。
類是邏輯上相關的函數與數據的封裝,描述了所創建對象共同的屬性和方法。類中聲明或定義的變量和函數稱為成員,類的成員包括數據成員和函數成員,數據成員描述問題的屬性,函數成員描述問題的行為。
16.什么是對象?什么是類?類與對象的關系是什么?
類是邏輯上相關的函數與數據的封裝,它是對問題的抽象描述。
對象是類的某一特定實體。
將整個公司的雇員看成一個類,那么每一個雇員就是該類的一個特定實體,也就是一個對象。
類對象的關系:類是對象的抽象,而對象是類的具體實例。類是抽象的,不占用內存,而對象是具體的,占用存儲空間。類是用於創建對象的藍圖,它是一個定義包括在特定類型的對象中的方法和變量的軟件模板。
17.類中的成員可以用public/protected/private分別進行修飾,這三種成員在什么情況下是可以被訪問的?類中沒有用public/protected/private修飾的成員,其可訪問性是什么,結構體中沒有用public/protected/private修飾的成員,其可訪問性是什么?
public修飾的成員可以在任何地方被訪問
private修飾的成員只能由該類中的函數、其友元函數訪問;不能被任何其他訪問,該類對象也不能訪問。
protected修飾的成員可以被該類中函數、子類函數、友元函數訪問;但不能被該類對象訪問。
public可以被訪問,沒有修飾,類的默認為private,struct默認為public。
18.什么是封裝?其作用是什么?(google)
封裝就是將抽象得到的數據和行為(或功能)相結合,形成一個有機的整體,也就是將數據與操作數據的函數代碼進行有機結合,形成類。
作用:
使一部分成員充當類與外部的接口,而將其他成員隱藏起來,這樣就達到了對成員訪問權限的合理控制,使不同類之間的相互影響減少到最低限度,進而保護數據增強數據的安全性和簡化程序編寫工作。
19.什么是構造函數?構造函數有返回值嗎?構造函數如何命名?構造函數可以重載嗎?什么是缺省構造函數(default constructor)?什么情況下,類中會有缺省構造函數?
構造函數主要用來在創建對象時初始化對象, 即為對象成員變量賦初始值。
構造函數沒有返回值。
構造函數是一個與其所在的類同名的函數。
構造函數可以重載。但是, 每個構造函數必須有不同的函數簽名。
如果構造函數沒有參數,或者構造函數的所有參數都有默認值,就可以稱其為缺省構造函數。一個類中,只能有一個缺省構造函數。
當沒有定義構造函數或者定義的構造函數沒有參數時,類中會有缺省構造函數。
20.若父類中沒有缺省構造函數,則對派生類的構造函數有什么要求?
如果父類是一個無缺省參數的構造函數,那么對於派生類一旦沒有構造函數,那么就不會自動的先構造父類的構造函數,這是不允許的。
派生類中一定要有構造函數。
BaseballTeam(const string s[], int si) : Team(si)
派生類的構造函數通過初始化列表,對基類進行初始化。
21.構造函數的作用是什么?什么時候會被調用?構造函數的執行順序是什么(父類與子類的構造函數、類自身與其數據成員的構造函數)?
構造函數主要用來在創建對象時初始化對象, 即為對象成員變量賦初始值。
當類被創建時,自動調用。
執行構造函數的順序:
1. 父類的構造函數
2. 數據成員的初始化(成員中有類,執行該類的構造函數)
2. 子類的構造函數
22.什么是類作用域(Class scope)、文件作用域(file scope)、函數作用域(function scope)?
類作用域:
類是有名成員的集合,類X的成員m具有類作用域,對成員m的訪問方式有如下三種:
1)如果X的成員函數中沒有聲明同名的局部作用域標識符,那么可以直接使用成員m
2)通過表達式x.m或X::m(訪問靜態成員)
3)通過ptr->m,其中ptr為指向X類的一個對象的指針
文件作用域:
在函數外部聲明的變量只在當前文件范圍內(包括文件內所有定義的函數)可用
在其他文件不可用。要使變量具有文件作用域,必須在變量的聲明前加static關鍵字。
當多個源文件鏈接成一個程序時,static可以避免一個文件中的全局變量與其它文件中的變量同名而發生沖突。
函數作用域:
(1)指在函數定義或者復合語句中,從標識符的定義點開始到函數或者一對花括號之間的程序段。
(2)在同一個局部作用域內不能出現相同名字的兩個局部變量(包括形參)。
(3)一個函數內的復合語句又是一個局部作用域,也就是在函數內有某個變量時,復合語句中可以有另外一個同名字的變量。
23.為什么拷貝構造函數(copy constructor)的參數必須是按引用傳遞(by reference)而不能是按值傳遞(by value)?
1.無限遞歸調用:
當一個對象需要以值方式傳遞時編譯器會生成代碼調用它的拷貝構造函數以生成一個復本。如果類A的拷貝構造函數是以值方式傳遞一個類A對象作為參數的話,當需要調用類A的拷貝構造函數時,需要以值方式傳進一個A的對象作為實參;而以值方式傳遞需要調用類A的拷貝構造函數;結果就是調用類A的拷貝構造函數導致又一次調用類A的拷貝構造函數,這就是一個無限遞歸。
2在某些狀況下,類內成員變量需要動態開辟堆內存,如果實行位拷貝,也就是把對象里的值完全復制給另一個對象,如A=B。這時,如果B中有一個成員變量指針已經申請了內存,那A中的那個成員變量也指向同一塊內存。這就出現了問題:當B把內存釋放了(如:析構),這時A內的指針就是野指針了,出現運行錯誤。
24.拷貝構造函數(復制構造函數)的作用是什么?什么是淺拷貝?什么是深拷貝?(google)
復制構造函數由編譯器調用來完成一些基於同一類的其他對象的構件及初始化。
淺拷貝只是對指針的拷貝,拷貝后兩個指針指向同一個內存空間,深拷貝不但對指針進行拷貝,而且對指針指向的內容進行拷貝,經深拷貝后的指針是指向兩個不同地址的指針。
25.全局對象(Global scope objects)的構造函數、析構函數分別是什么時候被調用的?
自動局部對象(Automatic local objects)的構造函數、析構函數分別是什么時候被調用的?
靜態局部對象(static local objects)的構造函數、析構函數分別是什么時候被調用的?
a.全局變量構造函數程序運行前被調用,在main()函數返回后才被中對象才被銷毀,析構函數在程序結束前最后被調用。
b.自動局部變量,當程序執行到對象定義時,調用自動局部對象的構造函數。該對象的析構函數在對象離開范圍時調用(即離開定義對象的塊時)。自動對象的構造函數與析構函數在每次對象進人和離開范圍時調用。
c.靜態局部對象的構造函數只在程序執行首次到達對象定義時調用一次,對應的析構函數在main終止或調用exit函數時調用。
26.什么是初始化列表(Initialization Sections)?它的作用是什么?(提示:一般數據成員的初始化、常成員的初始化,對象成員構選函數的選擇、父類構造函數的選等)。
構造函數初始化列表以一個冒號開始,接着是以逗號分隔的數據成員列表,每個數據成員后面跟一個放在括號中的初始化式。
class A { public: int a; float b; A(): a(0),b(9.9) {} //構造函數初始化列表
};
初始化列表作用:一般數據成員的初始化、常成員的初始化,對象成員構選函數的選擇、父類構造函數的選擇。
27.什么是純虛函數?什么是抽象數據類型(ADT)?抽象類的作用是什么?抽象類是否可實例化?抽象類的什么樣子類可以實例化?(google)
純虛函數是沒有函數體的虛函數,它的實現留給該基類的派生類去做,這就是純虛函數的作用。
抽象類是一種特殊的類,它是為了抽象和設計的目的而建立的,它處於繼承層次結構的較上層。
抽象類不可實例化,只可以派生。
抽象類派生的子類必須重置基類的純虛函數才能實現實例化。
抽象數據類型是具有類似行為的特定類型的數據結構的數學模型:或者具有類似語義的一種或者多種程序設計語言的數據類型。
抽象數據類型的描述包括給出抽象數據類型的名稱、數據的集合、數據之間的關系和操作的集合等方面的描述。抽象數據類型的設計者根據這些描述給出操作的具體實現,抽象數據類型的使用者依據這些描述使用抽象數據類型。
抽象數據類型描述的一般形式如下:
ADT 抽象數據類型名稱
{
數據對象:
……
數據關系:
……
操作集合:
操作名1:
……
……
操作名n:
}ADT抽象數據類型名稱
抽象數據類型定義(ADT)
作用:抽象數據類型可以使我們更容易描述現實世界。例:用線性表描述學生成績表,用樹或圖描述遺傳關系。
定義:一個數學模型以及定義在該模型上的一組操作。
關鍵:使用它的人可以只關心它的邏輯特征,不需要了解它的存儲方式。定義它的人同樣不必要關心它如何存儲。
例:線性表這樣的抽象數據類型,其數學模型是:數據元素的集合,該集合內的元素有這樣的關系:除第一個和最后一個外,每個元素有唯一的前趨和唯一的后繼。可以有這樣一些操作:插入一個元素、刪除一個元素等。
28.什么是this指針,其作用是什么?
this指針是一個隱含於每一個成員函數中的特殊指針。它是一個指向正在被該成員函數操作的對象,也就是要操作該成員函數的對象。通過this指針可以訪問當前對象的所有成員。
this作用域是在類內部,當對一個對象調用成員函數時,編譯程序先將對象的地址賦給this指針,編譯器會自動將對象本身的地址作為一個隱含參數傳遞給函數。
在以下場景中,經常需要顯式引用this指針:
(1)在類的非靜態成員函數中返回類對象本身的時候,直接使用 return *this,例如:實現對象的鏈式引用。
(2)當參數與成員變量名相同時,如this->x = x,不能寫成x = x。
(3)避免對同一對象進行賦值操作。
if(&pointer!=this) //同一對象之間的賦值沒有意義,所以要保證pointer不等於this
{ X=pointer.X; Y=pointer.Y; }
29.什么是友元(friend)函數?為什么要使用友員函數?
友元函數是在類聲明中由關鍵字friend修飾說明的非成員函數或其它類的成員函數,在它的函數體中能夠通過對象名訪問 private 和 protected成員
友元函數可以訪問這個類中的私有成員,增加靈活性,使程序員可以在封裝和快速性方面做合理選擇。
友元是C++提供的一種對數據封裝和數據隱藏的破壞機制。
30.如何防止一個頭文件被多重包含?舉例說明。
#include "a.h"
#include "b.h"
如果a.h和b.h都包含了一個頭文件x.h。那么x.h在此也同樣被包含了兩次,只不過它的形式不是那么明顯而已。
可以使用條件編譯。
#ifndef _HEADERNAME_H #define _HEADERNAME_H ..//(頭文件內容)
#endif
當頭文件第一次被包含時,它被正常處理,符號_HEADERNAME_H被定義為1。如果頭文件被再次包含,通過條件編譯,它的內容被忽略。
符號_HEADERNAME_H按照被包含頭文件的文件名進行取名,以避免由於其他頭文件使用相同的符號而引起的沖突。但是,你必須記住預處理器仍將整個頭文件讀入,即使這個頭文件所有內容將被忽略。由於這種處理將托慢編譯速度,所以如果可能,應該避免出現多重包含。
31.什么是運算符重載?為什么要使用運算符重載?如何進行運算符重載,舉例說明。
運算符重載,就是對已有的運算符重新進行定義,賦予其另一種功能,以適應不同的數據類型。
擴展C++中提供的運算符的適用范圍,以用於類所表示的抽象數據類型。同一個運算符,對不同類型的操作數,所發生的行為不同。
運算符重載的函數一般地采用如下兩種形式:成員函數形式和友元函數形式。這兩種形式都可訪問類中的私有成員。
class Complex { public: Complex( ) { real=0; imag=0; } Complex(double r,double i) { real=r; imag=i; } Complex operator+( const Complex & ) const; //重載為成員函數
friend Complex operator+(Complex &c1,Complex &c2); //重載為友員函數
void display( ); private: double real; double imag; };
32.為什么重載為全局函數的運算符通常要比重載為成員函數的運算符多一個參數?舉例說明。
當重載為成員函數時,會有一個this指針,指向當前的類,所以只需要一個參數就可以了。
而當重載為全局函數時,將沒有隱含的參數this指針,這樣將會多一個參數。
Complex operator +(Complex&); friend Complex operator+(Complex &c1,Complex &c2);
33.什么是析構函數?析構函數有返回值嗎?析構函數如何命名?析構函數可以重載嗎?
與構造函數相反,當對象結束其生命周期,如對象所在的函數已調用完畢時,系統會自動執行析構函數。
析構函數沒有返回值。
名字與類名相同,在前面加‘~’。
析構函數不返回任何值,沒有函數類型,也沒有函數參數,因此它不能被重載。
34.析構函數的作用是什么?什么時候會被調用?為什么析構函數通常是虛函數,如果不是虛函數,會如何?(google)
析構函數對象消亡時即自動被調用。
作用:清空並釋放對象先前創建或者占用的內存資源。
如果析構函數不被聲明成虛函數,則編譯器采用的綁定方式是靜態綁定,在刪除基類指針時,只會調用基類析構函數,而不調用派生類析構函數,這樣就會導致基類指針指向的派生類對象析構不完全。
若是將析構函數聲明為虛函數,不管派生類的析構函數前是否加virtual(可以理解為編譯器優化),都構成重寫。基類的指針指向派生類的對象,然后調用重寫虛函數——析構函數,構成了多態,而多態與類型無關,只與對象有關,所以就能夠調用的就是派生類的析構函數了。
35.在一個類中,為什么靜態成員函數(static member function)中不能使用this指針?
靜態成員函數並不是針對某個類的實例對象,而是屬於整個類的,為所有的對象實例所共有。他在作用域的范圍內是全局的,獨立於類的對象之外的。他只對類內部的靜態成員變量做操作。當實例化一個類的對象時候,里面不存在靜態成員的。this指針是相當於一個類的實例的指針,this是用來操作對象實例的內容的,既然靜態成員函數和變量都是獨立於類的實例對象之外的,他就不能用this指針。也不能操作非靜態成員。
36.如果要編寫一段程序,跟蹤類A所創建的實例的個數,請敘述編寫程序的大體思路。
#include<iostream> #include<string.h> #include<stdio.h>
using namespace std; class A { public: A() { i++; } ~A() { i--; } int get() { return i; } private: static int i; }; int A::i(0); int main() { A c; A b; A e; cout<<c.get()<<endl; A *p=new A; cout<<c.get()<<endl; delete p; cout<<c.get()<<endl; return 0; }
37.什么是C++中的三大函數(The Big Three)?(google)
Big Three: 是指 如果一個類要實現下面某一個成員函數,一般都要一起實現另外兩個:
1)Desconstructor 析構函數
2) copy constructor 拷貝構造函數
3) operator = 賦值函數
38.什么是UML?如何畫UML中的類圖?什么是類與類之間依賴關系、關聯關系、包含關系?試舉例說明這三種類之間的關系。這三種關系如何和UML圖表示?
UML統一建模語言,UML語言是一種可視化的的面向對象建模語言,描述軟件模型的靜態結構、動態行為及模塊組織與管理。
依賴關系:一個事物的變化可能會影響到使用它的另一個事物。舉例:駕駛員(源)開車(目標)。
關聯關系:一個類的對象和另一個類的對象之間相互作用。舉例:老師和學生,小明的語文老師是張老師,張老師的學生有小明。
包含關系:聚集和組合
聚集表示類之間的關系是整體與部分的關系。舉例:班級成員和學生。
組合是指整體擁有各個部分,整體和部分共存,整體不存在了,部分也會隨之消失。舉例:打開一個視窗口,它由標題、外框和顯示區域組成,視窗口是一個整體,它消失了,部分也就隨之消失了。
繼承關系:在UML中稱為泛化。舉例:鴨子和鳥,鴨子是一種鳥,繼承了鳥的特性。
39.常見的類與類之間的關系有哪幾種,舉例說明每種關系的對應UML圖如何畫?兩個什么樣的類可以實現為繼承關系?(google)
依賴關系、關聯關系、包含關系、繼承關系。
具有共同屬性的兩個類可以實現繼承關系。
40.父類成員中的public、protected、private成員,哪些在子類中是可以訪問的?
在公有繼承、私有繼承、受保護繼承三種繼承方式下,父類成員中的public、protected、private成員被繼承到子類后,其可訪問性分別是什么?
派生類是否可以繼承父類的構造函數和析構函數?
public 和protected是可以訪問的,private不可訪問。
公有繼承:public、protected、private
私有繼承:private、private、private
保護繼承:protected、protected、private
派生類不能繼承父類的構造函數和析構函數。
41.多重繼承會帶來什么問題?在C++中是如何解決的?
問題1:類DC的對象中存在多個同名成員 x, 應如何使用?
問題2:類DC的對象中,存在兩份來自類BC0的成員K,如何區分?
解決方案:
在BC1類和BC2類繼承BC0時,其前面加上virtual關鍵字就可以實現虛擬繼承,使用虛擬繼承后,當系統碰到多重繼承的時候就會先自動加一個BC0的拷貝,當再次請求一個BC0的拷貝時就會被忽略,以保證繼承類成員函數的唯一性。
class BC0 { public: int K; }; class BC1 : virtual public BC0 { public: int x; }; class BC2 : virtual public BC0 { public: int x; }; class DC : public BC1, public BC2 { }; void main( ) { DC d; //虛繼承使得BC0僅被DC間接繼承一份
d.K = 13; // OK
}
42.對於函數調用,什么是前期綁定(Early Binding,又稱為靜態聯編)?什么是的后期綁定(Late Binding,又稱為動態聯編)?重載函數是后期綁定嗎,如果不是為什么?
綁定:程序自身彼此關聯的過程,確定程序中的操作調用與執行該操作的代碼間的關系。例如把一個標示符名和一個存儲地址聯系在一起的過程。
用面向對象的術語講,就是把一條消息和一個對象的方法相結合的過程。
按照綁定進行的階段的不同,可以分為靜態綁定和動態綁定兩種。
靜態綁定:綁定工作在編譯連接階段完成。
因為綁定過程是在程序開始執行之前進行的,因此也稱為早期綁定或前綁定。
在編譯、連接過程中,系統就可以根據類型匹配等特征確定程序中操作調用與執行該操作代碼的關系,即確定了某一個同名標識到底是要調用哪一段程序代碼。
動態綁定:和靜態綁定相對應,綁定工作在程序運行階段完成的。
class A { public: virtual void Get( ); }; class B : public A { public: virtual void Get( ); }; void MyFunction( A * pa ) { pa->Get( ); }
pa->Get( ) 調用的是 A::Get( )還是B::Get( ),編譯時無法確定,因為不知道MyFunction被調用時,形參會對應於一個 A 對象還是B對象。
所以只能等程序運行到 pa->Get( )了,才能決定到底調用哪個Get( )。
重載函數是靜態綁定。
43.要讓一個函數調用表現出多態特征,必須滿足哪些條件?
a.必須存在繼承關系;
b.子類重寫父類的方法。繼承關系中必須有同名的虛函數,並且它們是覆蓋關系(重載不行)。
c.存在基類的指針,通過該指針調用虛函數。
44.簡述虛函數動態綁定的實現原理。
構造函數中為對象的虛指針賦值,通過多態類型的指針或引用調用成員函數時,通過虛指針找到虛表,進而找到所調用的虛函數的入口地址,通過該入口地址調用虛函數。
45.什么是隱藏(hiding)、覆蓋(overriding)、重載(overloading)?對比它們的異同?以C++代碼為例進行說明。
若基類 B 定義了非虛方法 m,同時其派生類 D 也定義了方法m,此時,我們說派生類方法 D::m 隱藏了繼承自基類的同名方法 B::m 。由於函數簽名不同,所以二者不構成重置。故D::m隱藏了B::m。
class B { public: void m(int x) { … } }; class D : public B { public: void m ( )//由於函數簽名不同,所以二者不構成重置。
{ … } }; int main( ) { D d1 ; d1.m(); // invokes D::m()
d1.m(10); // ERROR
d1.B::m(10); // OK
return 0; }
覆蓋(override)是指派生類中存在重新定義的函數,其函數名、參數列、返回值類型必須同父類中的相對應被覆蓋的函數嚴格一致,覆蓋函數和被覆蓋函數只有函數體不同,當派生類對象調用派生類中該同名函數時會自動調用派生類中的覆蓋版本,而不是父類中的被覆蓋函數版本,這種機制就叫做覆蓋。
class B { public: virtual void m( ) { … } }; class D : public B { public: void m ()//重置了基類方法,仍然為虛函數
{ } }; int main( ) { B*p= new D; p -> m(); // 動態綁定 D::m()
return 0; }
重載:如果頂層函數有不同的簽名,則函數名可以相同。
class B { public: virtual void m( ) { … } }; class D : public B { public: void m ()//重置了基類方法,仍然為虛函數
{ } }; int main( ) { B*p= new D; p -> m(); // 動態綁定 D::m()
return 0; }
如果同一類中的函數有不同的簽名,則函數名可以相同。
class C { public: C( ) { … } // default constructor
C( int x ) { … } // convert constructor
}; void print( double d ); void print( char * ); int main( ) { C c1,c2(26); print(100.123); print( "100.123" ); }
編譯過程中綁定函數調用和對應的函數體
46.什么是多態?
一個組合的希臘詞。含義:一種物質有多種形態。在專業術語中,多態是一種運行時綁定機制(run-time binding) ,通過這種機制,實現將函數名綁定到函數具體實現代碼的目的。
47.什么是切片(Slicing)?(注意參考講義)
派生類的存儲結構與基類的存儲結構存在着“粘接(splice)”關系:
當子類對象拷貝到父類對象時,父類對象中只存在父類定義的成員,而不會出現任何子類中的成員。
48.使用異常處理機制的好處是什么?
1.將常規代碼與錯誤處理代碼的分離
2.實現在調用棧中傳播異常
3.實現對不同的錯誤類型進行分類
49.簡述C++中的異常處理機制。要捕獲某段代碼中的所有異常,應該如何編寫代碼?
C++ 用try和catch進行異常處理,當try塊出現異常,則catch中匹配相應的異常處理,若catch塊中沒有匹配該異常對象的語句,則轉向外一層的try、catch語句,若一直退回到主函數都無法處理異常,則由系統調用terminate()函數終止程序。用異常規格(exception specification)列出函數可能會拋出所有異常的類型。
50.分別舉例說明用於算法抽象的函數模板和用於數據抽象的類模板。(google)
函數模板:
template<class T> T add( T a, T b )//函數模板
{ return a + b; } add<int> ( 10, 17 );//模板實例
complex c1, c2; add<complex> ( c1, c2 );
函數模板是 對算法相似,但支持的數據類型不同的一組操作的提煉,以提高程序的重用性。
函數模板的實例就是一個用於特定類型的普通函數。
通常,編譯器可根據實參類型確定模板參數;
add ( 10, 17 ); // add<int> (10,17);
complex c1, c2; add ( c1, c2 ); // add<complex>(c1,c2);
類模板:
使用類模板使用戶可以為類定義一種模式,使得類中的某些數據成員、某些數據成員函數的參數、返回值和局部變量能夠取任意類型(包括系統預定義和用戶自定義)
有時候,有兩個或多個類,其功能是相同的,僅僅是數據類型不同,可以使用模板類。
template<class T>//聲明模板
class Array { T* array; int size; public: Array( int ); T& operator[ ]( int ); }; class charArray { char *array; int size; public: charArray( int ); char& operator[](int); }; class intArray { int *array; int size; public: intArray( int ); int& operator[](int); };
51.dynamic-cast的作用是什么?試舉例說明。
dynamic_cast < Type-id > ( expression )
該運算符把expression轉換為type-id類型,並且可以在運行期間檢測類型轉換是否安全。dynamic_cast要求轉型的目的類型必須是指針或者引用。
將基類指針轉換為派生類指針,將基類引用轉換為派生類引用;
轉換是有條件的
如果指針(或引用)所指對象的實際類型與轉換的目的類型兼容,則轉換成功進行;
否則如執行的是指針類型的轉換,則得到空指針;如執行的是引用類型的轉換,則拋出異常。