C++面試基礎知識整理(6)
阿新 • 來源:網路 • 發佈:2021-02-19
隱藏與覆蓋
- 隱藏:父子關係、成員同名
class Person
{
public:
void play();
void work();
}
class Soldier:public Person
{
public:
void play(); // 隱藏
void work(int x);
}
int main(void)
{
Soldier s;
s.play();// 呼叫子類的成員函式
s.Person::play();// 呼叫父類的成員函式
s.work(7);// 呼叫子類的成員函式
s.work();// 錯誤!不能形成過載
return 0;
}
- 覆蓋:當子類重新定義了父類的虛擬函式時,子類的虛擬函式表中該虛擬函式的入口地址就會覆蓋掉父類的虛擬函式的地址,從而實現多型。
過載與重寫
-
從定義上來說:
- 過載:是指允許存在多個同名函式,而這些函式的引數表不同(或許引數個數不同,或許引數型別不同,或許兩者都不同)。
- 重寫:是指子類重新定義父類虛擬函式的方法。
-
從實現原理上來說:
- 過載:編譯器根據函式不同的引數表,對同名函式的名稱做修飾,然後這些同名函式就成了不同的函式(至少對於編譯器來說是這樣的)。如,有兩個同名函式:function func(p:integer):integer;和function func(p:string):integer;。那麼編譯器做過修飾後的函式名稱可能是這樣的:int_func、str_func。對於這兩個函式的呼叫,在編譯器間就已經確定了,是靜態的。也就是說,它們的地址在編譯期就綁定了(早繫結),因此,過載和多型無關!
- 重寫:和多型真正相關。當子類重新定義了父類的虛擬函式後,父類指標根據賦給它的不同的子類指標,動態的呼叫屬於子類的該函式,這樣的函式呼叫在編譯期間是無法確定的(呼叫的子類的虛擬函式的地址無法給出)。因此,這樣的函式地址是在執行期繫結的(晚繫結)。
抽象類、介面類
- 抽象類:含有純虛擬函式的類為抽象類。抽象類不能例項化物件。抽象類的子類也可以是抽象類。
- 介面類:僅含有純虛擬函式的類為介面類。介面類更多的表達一種能力或協議。
使用
class Flyable // 會飛的
{
public:
virtual void takeoff()=0; // 起飛
virtual void land()=0; // 降落
}
class CanShot // 會射擊的
{
public:
virtual void aim()=0; // 瞄準
virtual void reload()=0; // 裝彈
}
class Plane : public Flyable // 飛機
{
...
virtual void takeoff(){...}
virtual void land(){...}
}
class FighterJet : public Plane, public Canshot // 戰鬥機
{
...
virtual void aim(){...}
virtual void reload(){...}
}
void airBattle(FighterJet *a, FighterJet *b) // 兩架戰鬥機空戰
{
...
// 呼叫Flyable中約定的函式
// 呼叫CanShot中約定的函式
}
執行時型別識別RTTI
作用
- 通過基類的指標或引用來檢索其所指物件的實際型別
- 通過typeid和dynamic_cast來實現
- typeid:返回指標或引用所指物件的實際型別
- dynamic_cast:將基類型別的指標或引用安全的轉換為派生型別的指標或引用
使用
class Bird : public Flyable // 鳥
{
public:
void foraging(){...} // 覓食
virtual void takeoff(){...}
virtual void land(){...}
}
class Plane : public Flyable // 飛機
{
public:
void carry(){...} // 運輸
virtual void takeoff(){...}
virtual void land(){...}
}
void doSomething(Flyable *obj)
{
obj->takeoff();
// 如果是Bird,則覓食
if(typeid(*obj) == typeid(Bird))
{
Bird *bird = dynamic_cast<Bird *>(obj);
bird->foraging();
}
// 如果是Plane,則運輸
if(typeid(*obj) == typeid(Plane))
{
Bird *plane = dynamic_cast<Plane *>(obj);
plane->carry();
}
obj->land();
}
-
dynamic_cast:
-
只能用於指標和引用的轉換
-
要轉換的型別中必須包含虛擬函式,即用於多型型別的轉換
-
轉換成功返回子類地址,失敗返回nullptr
-
-
typeid:
-
可以檢視任何一個變數的資料型別,包括基本資料型別
-
typeid返回一個type_info物件的引用
-
如果想通過基類的指標獲得派生類的資料型別,基類必須帶有虛擬函式,即用於多型型別的轉換
-
只能獲取物件的實際型別
-
Flyable *p = new Bird();
cout<< typeid(p).name()<<endl; // class Flyable *
cout<< typeid(*p).name()<<endl; // class Bird
異常處理
-
異常處理:對有可能發生異常的地方做出預見性的安排
-
基本思想:主邏輯與異常處理分離
-
關鍵字:
- try:嘗試執行正常的邏輯。(主邏輯)
- catch:如果出現異常,則捕獲。(異常處理邏輯)
- throw:丟擲異常
-
常見異常:陣列下標越界、除數為0、記憶體不足
使用

- Exception類是一個介面類,HardwareErr、SizeErr、MemoryErr、NetworkErr均繼承於Exception類,都分別實現了父類中的異常處理函式。
void fun1()
{
throw new SizeErr();
}
void fun2()
{
throw new MemoryErr();
}
int main()
{
try
{
fun1();
}
catch(Exception &e)// 均可以使用父類來捕獲異常
{
e.xxx();
}
}
友元函式與友元類
友元函式
- 類具有封裝、繼承、多型、資訊隱藏的特性,只有類的成員函式才可以訪問類的私有成員,非成員函式只能訪問類的公有成員。為了使類的非成員函式訪問類的成員,唯一的做法就是將成員定義為public,但這樣做會破壞資訊隱藏的特性。基於以上原因,引入友元函式解決。
- 如果一個全域性函式被定義為友元函式,則稱為友元全域性函式。
- 如果一個類的成員函式被定義為友元函式,則稱為友元成員函式。
- 友元函式定義在類外部,宣告在類內部,有權訪問類的私有和保護成員。
使用
class Circle
{
public:
// 友元成員函式定義在其他類中
void printXY(Coordinate &c)
{
cout<<c.m_iX<<c.m_iY<<endl;// 可直接訪問類的任何成員
}
}
class Coordinate
{
friend void printXY(Coordinate &c); // 友元全域性函式
friend void Circle::printXY(Coordinate &c);// 友元成員函式
public:
Coordinate(int x, int y);
private:
int m_iX;
int m_iY;
}
// 友元全域性函式定義在全域性
void printXY(Coordinate &c)
{
cout<<c.m_iX<<c.m_iY<<endl;// 可直接訪問類的任何成員
}
int main()
{
Coordinate coor(3,5);
Circle circle;
printXY(coor); // 使用友元全域性函式
circle.printXY(coor); // 使用友元成員函式
return 0;
}
友元類
使用
class Circle;
class Coordinate
{
friend Circle;
public:
Coordinate(int x, int y);
private:
int m_iX;
int m_iY;
}
class Circle
{
public:
void printXY()
{
cout<<m_coor.m_iX<<m_coor.m_iY<<endl;
}
private:
Coordinate m_coor;
}
- 友元函式不是類的成員函式,因此類與類之間的友元關係不能被繼承,即友元關係不可傳遞
- 友元關係具有單向性
- 友元宣告的形式及數量不受限制
- 友元是不得已而為之的辦法,破壞了封裝性,同時體現了定向暴露的思想。
