前言
本篇文章作基礎復習用,主要是在C++學習中遇到的概念總結,后續會繼續補充。如有不足,請前輩指出,萬分感謝。
1、什么是封裝,有何優點,在C++中如何體現封裝這一特性?
封裝是面向對象編程(OOP)中的一個重要概念。 它將數據(成員變量)和操作這些數據的函數(成員函數)捆綁在一起,形成一個類。并且對外部隱藏了類的內部實現細節,只提供一些公共的接口來訪問和操作類中的數據。
例如,把汽車看成一個類,駕駛員不需要知道發動機是如何工作的(內部細節),只需要知道通過操作方向盤、油門、剎車等接口來駕駛汽車。
優點: 數據隱藏和安全性,代碼的可維護性和可修改性,提高代碼的復用性。
在C++中的實現: C++ 通過訪問修飾符來實現封裝。
主要有public(公共的)、private(私有的)和protected(受保護的)三種。
(補充:繼承具有傳遞性,繼承不具有對稱性)
2、什么是虛基類?有何作用?
語法上,在繼承方式前加上virtual關鍵字
來聲明虛基類
例如,假設有類A,類B和類C都虛繼承自A,然后類D又同時繼承自B和C。此時類A就是類B和類C 的虛基類。
作用: 多重繼承會出現菱形繼承問題會導致二義性
,如果A不是虛基類,那么在D中會有兩份A的副本。使用虛基類可以避免這種情況,確保基類A在派生類B、C中只有一個實例。
3、什么是友元函數?友元函數的作用是什么?它是否破壞了封裝性?
友元函數的定義:
在 C++ 中,友元函數是一種定義在類外部,但可以訪問類的私有和受保護成員的函數。
通過在類中使用friend關鍵字
來聲明友元函數,并且友元函數在類中聲明時的屬性是public
。
作用:
增強靈活性
(有時候,我們可能需要一個外部函數來訪問類的私有成員
,以實現一些特定的功能。例如,在操作符重載中
,當我們想讓某個操作符能夠直接訪問類的私有成員進行運算時,友元函數就很有用。)
方便實現不同類之間的交互
(當多個類之間需要緊密協作,并且需要互相訪問對方的私有數據來完成某個功能時,友元函數可以提供一種方便的途徑。)
是否破壞封裝性:
友元打破了類的封裝性,使得類的非成員函數可以訪問類的私有成員,但也為實現一些復雜邏輯提供了便利。
4、什么是模板?C++中模板分為哪兩種類型?它們的作用是什么?
模板的定義:
模板就像是一個模具,根據不同的參數(數據類型等)可以生成不同具體類型的代碼。
它允許程序員編寫能夠處理多種不同數據類型的代碼,而不必為每種數據類型都重復編寫相似的代碼。
兩種類型:
函數模板template <typename T >
類模板template <typename T1> class class_name{ };//其中template T1表示模板參數表,由類型參數和非類型參數組成。
作用是什么:
函數模板: 函數模板是用于創建通用函數的模板。它可以根據調用時提供的參數類型自動生成相應的函數版本。
類模板:可以使用類模板為類定義一種模式,使得類中的一些數據成員、成員函數的參數、以及返回值可以取任意類型。
(由于類模板需要一個或多個類型的參數,所以類模板也稱為參數化類,類模板可以看成是類的抽象。)
5、什么是const成員函數?它的作用是什么?如何聲明和調用const成員函數?
在類中使用const關鍵字修飾的函數,被稱為常成員函數
作用:
確保數據的完整性和一致性:當一個對象的狀態不應該被改變時,使用 const 成員函數可以防止意外地修改對象的數據。
(例如在函數調用過程中只需要獲取對象的某些屬性或者進行一些不改變對象狀態的計算,)
與 const 對象配合使用:如果一個對象被聲明為 const,那么它只能調用 const 成員函數。確保了 const 對象的狀態不會被改變。
聲明格式:
類型說明 成員函數名(參數表)const;(也就是在一般的函數()后加一個const)
調用方式: 調用常成員函數的方式與普通成員函數相同,可以通過常對象和非常對象來調用。實例對象.const成員函數。
6、什么是消息?什么是消息映射?
消息的定義:
在 C++ 編程的某些特定場景下(如在圖形用戶界面(GUI)編程或者事件驅動編程的環境中),消息是對象之間通信的一種方式
。它表示一個事件或者請求,從一個對象發送到另一個對象或者一組對象。
消息映射:
消息映射是一種將消息與對應的處理函數關聯起來的機制。
在面向對象編程,特別是在處理事件驅動的應用程序(如 Windows 下的 MFC 或者 Qt 框架)中廣泛使用。
? 例如,在 MFC(Microsoft Foundation Classes)中,消息映射用于將 Windows
系統發送的各種消息(如鼠標移動、鍵盤按鍵等消息)和類中的成員函數(消息處理函數)聯系起來。當一個消息到達時,系統通過消息映射找到對應的處理函數并執行它。
? 消息映射通常是通過一些宏或者特定的代碼結構來實現的。
7、什么是異常處理?C++中如何使用try、catch和throw進行異常處理?
異常處理:
異常處理是一種程序設計機制,用于處理程序執行過程中出現的意外或錯誤情況。
在一個復雜的程序中,可能會遇到各種運行時錯誤,例如文件不存在、網絡連接中斷、內存不足、算術運算錯誤(如除數為 0)等。異常處理允許程序在遇到這些錯誤時,以一種可控的方式做出響應,而不是直接崩潰。
它提供了一種將正常的程序流程與錯誤處理流程分離的方法,使得代碼的主邏輯更加清晰,同時也提高了程序的健壯性和可靠性。
處理方式
需要包含標準異常類的頭文件#include < stdexcept >
throw表達式用于拋出一個異常對象。
這個對象可以是基本數據類型(如int、char*等),也可以是用戶自定義的類類型。當程序執行到throw語句時,當前函數的執行會立即停止,并開始在調用棧中查找能夠處理這個異常的catch塊。
8、什么是虛析構函數?為什么基類的析構函數通常應該聲明為虛函數?
虛析構函數
析構函數通常用于釋放對象占用的資源,并在對象生命周期結束時執行清理操作。
當在析構函數前加上virtual關鍵字
,它就被稱為虛析構函數。
原因
首先虛函數的目的是為了基類可以訪問子類(因為通常情況基類是無法訪問子類的成員對象的),如果是將基類的析構函數聲明為虛析構函數,可以通過基類的指針調用子類的析構函數,保證子類的資源可以被正常釋放。
補充:(當我們使用基類指針來刪除派生類對象時,如果基類的析構函數不是虛函數,那么只會調用基類的析構函數,而不會調用派生類的析構函數。這會導致派生類特有的資源沒有被正確釋放,從而造成內存泄漏或其他資源泄露問題。)
9、什么是拷貝構造函數?什么情況下會調用拷貝構造函數?【學習通】
拷貝構造函數:
拷貝構造函數是一個特殊的構造函數,用于創建一個新的對象,并將其初始化為另一個同類型對象的副本。
ClassName(const ClassName& other); other 是對同類型對象的常量引用,它是要被拷貝的對象。
調用拷貝構造函數
1、 比如通過值傳遞對象給函數
Void func(class_name obj) {}
2、 從對象初始化另一個對象
Clss_name obj1;
Obj2 =obj1;
10、什么是靜態聯編,什么是動態聯編,C++中的多態性體現在哪些方面?
靜態聯編: 在編譯階段將函數實現和函數調用進行關聯,即在程序運行之前就確定好調用的函數。
(速度快效率高,但是不夠靈活,不過在函數重載的情況下,編譯器會根據函數名和參數類型來確定要調用的函數,這個過程需要在編譯階段進行,此時屬于靜態聯編)
動態聯編: 指的是在程序運行時才確定函數的調用關系,也就是在程序執行時才將函數實現和函數關聯起來(靈活性強,可以實現多態性)
。動態聯編通常是通過虛函數
實現的(動態聯編實現的條件:成員函數必須是virtual;存在父子關系的類,并且自雷重寫了父類的虛函數;使用指針或者引用指向派生類對象,并通過基類指針或引用調用虛函數)
什么是多態性
多態性指的是同樣的消息被不同類型的對象接受時導致不同的行為。
在C++中,多態性允許基類通過指針或引用來調用怕派生類中的重寫函數,從而實現不同的行為。比如函數重載、運算符重載、虛函數的使用
都可以體現多態性的機制。(多態性是比較復雜的一個機制,那么在后面的文章會繼續探討這個特點)
可以提高代碼的可維護性、可擴展性以及靈活性。
11、 何時應該將成員函數聲明為虛函數?聲明的虛函數該如何調用?
聲明虛函數的情景
當使用基類的指針或引用來調用派生類中的重寫函數時,需要將基類中成員函數聲明為虛函數。
調用方式
使用基類的指針進行調用,比如A是基類,B是其派生類,fun()函數是A類的虛函數
B obj;A *obj1 = &obj;obj1->fun();或者A *obj1 = new B; obj1->fun(); delete obj1;
使用父類引用綁定子類對象的方式進行調用
B obj; A &obj1 = obj; obj1.fun();
#include <iostream>
using namespace std;
class Animal
{public :virtual void speak(){cout<<"Animal speak"<<endl;}virtual void run(){cout<<"Animal runs"<<endl;}
};class Dog : public Animal
{public :void speak(){cout<<"Dog bark"<<endl;}void run(){cout<<"Dog runs"<<endl;}
};class Cat : public Animal
{public :void speak(){cout<<"Cat meows"<<endl;}void run(){cout<<"Cat runs"<<endl;}};int main()
{
// Dog d;
// Cat c;
// d.Dog::speak();Dog d;//以下是多態 //父類指針指向子類對象 cout<<"父類指針指向子類對象"<<endl;Animal *p = &d;p->speak();cout<<endl;//使用new關鍵字cout<<"使用new關鍵字"<<endl; Animal *p1 = new Dog;p1->speak();delete p1;cout<<endl;//父類引用綁定(指向)子類對象cout<<"父類引用綁定(指向)子類對象"<<endl;Dog d1;Animal &p2 = d1;p2.speak(); cout<<endl;// cout<<"使用數組輸出"<<endl;
// Animal *a[2];
// a[0] = &d;
// a[1] = &c;
// for(int i= 0;i<2;i++){
// a[i]->speak();
// a[i]->run();
// }return 0;
}
12、簡述操作符重載的注意事項有哪些。
1、
只能重載C++中已有的運算符
2、不能改變運算符原有的優先級、結合性以及操作數。
3、一般運算符重載為類的成員函數時,函數參數個數要比該運算符需要的操作數個數少一個。
雙目運算符重載函數只需要一個參數,單目運算符重載函數不需要參數。
4、如果是在類外運算符重載為類的友元函數,友元函數所需參數一般和該運算符所需參數個數一致。
5、重載運算符函數的返回類型一般是自定義或與操作數兼容的類型。
13、簡述構造函數的概念、作用及其特征。
構造函數的概念
構造函數是一種特殊的成員函數,在創建對象時自動調用,用于初始化對象。
類名(參數表){構造函數體}
類名(參數表):成員初始化表 {構造函數體}
構造函數的作用
類的構造函數的作用是在對象創建后,對對象的非靜態數據成員初始化
對于需要動態分配資源的對象,構造函數可以負責分配這些資源。
構造函數的特征
1、
構造函數的名稱必須與類名完全相同,并且沒有返回類型(包括void)。
2、構造函數在創建完成類的對象時會被自動調用
3、構造函數可以有多個,只要它們的參數列表不同即可(也成為函數的重載)
4、當一個類中沒有定義任何構造函數時,編譯器會在類的實例對象創建完成后自動調用類的默認構造函數。
5、每個通過構造函數創建的對象,在其生命周期結束時,都會調用相應的析構函數來釋放資源。
14、什么是賦值兼容原則?該原則一般應用在何處?
賦值兼容原則
是指在需要基類對象的任何地方,都可以使用公有派生類的對象來替代。
通過公有繼承,派生類不僅繼承了基類的成員(除了構造函數和析構函數),而且繼承了基類成員的訪問控制屬性。公有派生類實際上具備了基類的所有功能。賦值兼容原則常用于實現多態性
應用場景
對象賦值
:派生類的對象可以直接賦值給基類對象。但請注意,這種賦值后,只能使用從基類繼承的成員。
引用初始化
:派生類的對象可以直接初始化基類的引用。
指針指向
:派生類對象的地址可以賦給指向基類的指針。
15 、在含有組合對象的派生類中,構造函數執行的次序是怎樣的?
調用次序
當派生類的對象被創建完成后,會先調用基類的構造函數
(如果基類本身也是一種繼承,那么將順次先調用基類的基類的構造函數
);
然后是派生類中的成員對象的構造函數
,調用完成基類的構造函數后,在調用本類的構造函數之前,會先根據派生類中所有成員對象的構造函數在類中的聲明順序進行調用
;
最后是派生類本類的構造函數
。
16、比較繼承與組合的異同。
繼承與組合的相同點
代碼的復用
,開發者可以基于已有的類來構建新的類,減少代碼冗余。
繼承與組合的不同點
多態性:
繼承支持多態性,
可以通過基類的指針指向派生類的對象;
組合本身不直接支持多態性,不過可以通過成員對象的多態性來實現較為復雜的行為
靈活性:
繼承在提供代碼復用的同時,會限制子類的靈活性;
繼承可以更方便的創建更多新的子類;組合提供了更高的靈活性,可以無需改變類的定義,直接通過組合不同的對象來創建復雜的行為 組合可以更靈活的在已有類中添加新的成員對象
耦合性:
繼承通常具有較高的耦合性,子類的創建需要依賴父類的創建,父類的 變化可能會影響子類;
組合有較低的耦合度,組合對象之間的依賴關系相對較弱,一個對象的變化通常不會影響另一個對象。
17、比較類的普通成員函數、普通成員變量、靜態成員變量存儲方式的異同。
不同點:
普通成員函數:
在編譯時被存儲在代碼段中,不直接存儲在對象實例中,函數體代碼在內存中只有一份,供所有對象共享
;調用成員函數時,函數代碼通過對象或指針this指針
來訪問對象的成員變量。普通成員函數的生命周期與程序的生命中周期相同。
函數代碼在程序加載時被加載到內存中,在程序結束時被卸載。
普通成員變量:
普通成員變量存儲在對象的實例中,每個類的實例對象在內存中都有自己獨立的一塊空間用于存儲普通成員變量。
對于局部對象所在的存儲空間位于棧,對于動態分配的對象位于堆(new關鍵字)。
普通成員變量的生命周期與對象的生命周期相同,對象創建時,成員變量被分配內存并進行初始化;對象銷毀時,成員變量所占用的內存被釋放。
靜態成員變量:
靜態成員變量存儲在全局數據區(靜態存儲區)(它不屬于任何一個具體的對象,而是被類的所有對象共享。)靜態成員變量可以通過類名直接訪問,也可以通過對象通過對象的成員訪問運算符(. 或 ->)訪問。
(不過要注意,靜態成員變量在類內聲明,在類外初始化)
相同點:
靜態成員變量和普通成員變量都定義在類的范圍內
,但是靜態成員變量不屬于任何對象實例;普通成員函數和靜態成員函數都定義在類的范圍內
,但靜態成員函數不依賴于任何對象實例。
18、C++中有哪三種繼承方式,其區別是什么?
繼承方式:
公有繼承(public),私有繼承(private),保護繼承(protected)
區別:
公有繼承中,
基類的公有成員在派生類中仍然是公有成員,基類的保護成員在派生類中仍然是保護成員,基類的私有成員在派生類中不可訪問。
保護繼承中,
基類的公有成員和保護成員在派生類中都變成保護成員。這意味著基類的公有成員在派生類中的訪問權限變窄了。main函數中創建的派生類的對象不能直接訪問基類的公有成員(和公有繼承不同),但是派生類的成員函數可以訪問這些變成保護成員的原基類成員。
私有繼承中,
基類的公有成員和保護成員在派生類中都變成私有成員。派生類的對象不能訪問基類的公有成員,派生類的成員函數可以訪問這些變成私有成員的原基類成員,
19、什么是靜態成員變量和靜態成員函數?它們的特點是什么?
靜態成員變量
在類的成員變量前加上static關鍵字,并在類外定義并初始化靜態成員變量。
靜態成員變量是類級別的變量,而非對象級別的變量。靜態成員變量在所有對象之間可共享,并且只有一個副本存于內存當中,無論創建多少對象。
特點
共享性:靜態成員變量屬于類,所有對象之間可共享靜態成員變量;
訪問方式:可以通過類名直接訪問靜態成員變量,也可以通過對象訪問。
存儲位置:靜態成員變量存儲在全局數據區,而不是堆或棧。
初始化:必須在類外部定義或初始化。
靜態成員函數
在類的普通成員函數前面加上static關鍵字,在類內進行定義或初始化,
即可將普通成員函數定義為靜態成員函數。靜態成員函數是類級別的函數,不依賴于類的任何特定對象。
特點
訪問限制:只能訪問靜態成員變量和其他靜態成員函數,不能訪問非靜態成員變量或非靜態成員函數
無this指針:相較于普通成員函數,靜態成員函數沒有this指針,因為它們是獨立于對象的。
20、什么是多態性?C++中如何實現多態性?
什么是多態性
多態性指的是同樣的消息被不同類型的對象接受時導致不同的行為。
提高代碼的可維護性、可擴展性以及靈活性。
在C++中,多態性允許基類通過指針或引用來調用怕派生類中的重寫函數,從而實現不同的行為。
函數重載
? 函數重載是指在同一個作用域內,可以有多個函數具有相同的函數名,但參數列表不同
(參數類型、參數個數、參數順序至少有一個不同)。
? 編譯器根據函數調用時的參數類型和數量來決定調用哪個函數。
? 函數重載實現了靜態多態性(編譯時多態性)。
運算符重載
? 運算符重載是指可以為已有的運算符賦予新的含義,使其能夠用于特定的類類型。
? 通過運算符重載,可以使系統預定義的運算符可操作于類對象。
? 運算符重載同樣實現了靜態多態性。
虛函數和純虛函數的使用
? 虛函數是指在基類中聲明的函數,并在派生類中可以被重寫(Override),從而實現不同的行為。
? 當使用基類指針或引用調用虛函數時,實際調用的是派生類中重寫的函數,而不是基類中的函數。
? 虛函數實現了動態多態性(運行時多態性),即在程序運行時才確定調用哪個函數。
? 虛函數是C++中實現多態性的關鍵機制。
總結
以上就是本篇文章介紹的有關C++中常見的概念,C++中的特性遠不止于此,后續會持續更新。