C++基礎(chǔ)
C++面向?qū)ο缶幊痰乃拇筇匦裕?/p>
封裝:封裝是隱藏對象的屬性和實(shí)現(xiàn)細(xì)節(jié),僅對外公開接口,控制程序?qū)︻悓傩缘淖x取和修改。良好的分裝能減少耦合,同時隱藏實(shí)現(xiàn)細(xì)節(jié)。
抽象:抽象包括數(shù)據(jù)抽象和過程抽象。數(shù)據(jù)抽象關(guān)注于目標(biāo)的特性信息,過程抽象關(guān)注于目標(biāo)的功能是什么,而不是功能怎么實(shí)現(xiàn)。
繼承:繼承是子類繼承父類的特征和行為,使得子類具有父類的成員變量和方法。繼承可以分為單繼承和多繼承,單繼承一個子類繼承一個父類,多繼承一個子類繼承多個父類。
多態(tài):多態(tài)是同一個行為具有不同表現(xiàn)形式或形態(tài)的能力。多態(tài)的表現(xiàn)形式有重載和覆蓋。覆蓋是指子類重寫從父類繼承過來的函數(shù),函數(shù)名、返回值、參數(shù)列表都必須和父類相同。
繼承
- 子類擁有父類的所有屬性和行為,子類是特殊的父類;
- 子類對象可以直接當(dāng)做父類對象使用;
- 子類對象可以賦值或初始化父類對象;
- 父類對象的指針和引用可以指向子類對象;
- 子類對象的指針和引用不能直接指向父類對象,但是可以通過強(qiáng)制類型轉(zhuǎn)換完成。
子類不能繼承父類的構(gòu)造函數(shù),但是可以調(diào)用父類的構(gòu)造函數(shù)。子類的構(gòu)造函數(shù)執(zhí)行順序:
- 父類的構(gòu)造函數(shù),按照它們在子類中的先后順序一次調(diào)用;
- 調(diào)用成員對象的構(gòu)造函數(shù),按照它們在類定義中聲明的先后順序一次調(diào)用;
- 執(zhí)行子類構(gòu)造函數(shù)。
如果父類沒有定義構(gòu)造函數(shù),或者使用無參數(shù)的構(gòu)造函數(shù),子類不需要顯示的調(diào)用父類的構(gòu)造函數(shù)。如果父類沒有定義構(gòu)造函數(shù),子類也可以不定義,全部使用默認(rèn)的構(gòu)造函數(shù)。如果父類定義了帶有參數(shù)的構(gòu)造函數(shù),子類必須定義構(gòu)造函數(shù),并在初始化列表中顯示的調(diào)用父類的構(gòu)造函數(shù)。
除了構(gòu)造函數(shù),父類中定義的友元函數(shù)也不能被子類繼承,因?yàn)橛言P(guān)系是明確指定的,不會因?yàn)槔^承而傳遞。同時父類的靜態(tài)成員和靜態(tài)函數(shù)也無法繼承,因?yàn)殪o態(tài)成員在整個繼承體系中只有一個實(shí)體,如果子類中重新定義了該靜態(tài)成員,子類中定義將覆蓋父類的定義,但是并不會影響父類本身。
虛基類
在C++中,虛基類是一種特殊的基類,主要用于解決菱形繼承問題。
在菱形繼承中,一個類通過兩條路徑繼承了同一個基類,這回導(dǎo)致基類的數(shù)據(jù)成員在派生類中出現(xiàn)兩次,從而引發(fā)歧義。為了解決這個問題,我們將共同的基類聲明為虛基類。當(dāng)一個類被聲明為虛基類后,無論它被繼承層次機(jī)構(gòu)中繼承了多少次,它的數(shù)據(jù)成員在派生類中只出現(xiàn)一次。
class Base {
public:
int data;
};
class Derived1 : virtual public Base {
};
class Derived2 : virtual public Base {
};
class MostDerived : public Derived1, public Derived2 {
};
初始化列表
初始化列表用于構(gòu)造函數(shù)中初始化成員變量。它在構(gòu)造函數(shù)的參數(shù)列表之后、函數(shù)體之前使用‘:’引出。初始化列表的有點(diǎn):
- 效率:直接初始化成員變量,而不是先調(diào)用默認(rèn)構(gòu)造函數(shù)再賦值;
- 常量成員:可以初始化const成員變量;
- 引用成員:可以初始化引用成員變量;
- 基類構(gòu)造:可以調(diào)用基類的構(gòu)造函數(shù)。如果基類沒有默認(rèn)構(gòu)造函數(shù),或者派生類需要調(diào)用基類的構(gòu)造函數(shù)初始化基類的私有成員,必須在初始化列表中調(diào)用基類的構(gòu)造函數(shù)。
注意:成員變量是按照它們在類中出現(xiàn)的順序進(jìn)行初始化的,而不是按照它們在初始化列表中出現(xiàn)的順序初始化。
多態(tài)
在C++中,多態(tài)是面向?qū)ο缶幊痰囊粋€重要特性,它允許我們使用一個基類的指針或引用來操作派生類對象。多態(tài)分為靜態(tài)多態(tài)和動態(tài)多態(tài)。
靜態(tài)多態(tài):通過模版和函數(shù)重載實(shí)現(xiàn)。在編譯時,編譯器根據(jù)參數(shù)的類型決定調(diào)用的函數(shù)。
動態(tài)多態(tài):通過虛函數(shù)和繼承實(shí)現(xiàn)。在運(yùn)行時,根據(jù)對象的實(shí)際類型決定調(diào)用函數(shù)。
模版
模板是基于用戶為模板參數(shù)提供的參數(shù)在編譯時生成普通類型或函數(shù)的構(gòu)造。(參考)
template <typename T>
T minimum(const T& lhs, const T& rhs)
{
return lhs < rhs ? lhs : rhs;
}
int a = 0;
int b = 1;
int c = minimum(a, b);//mininum<int>(a, b);
上面的代碼描述了一個具有單個類型參數(shù) T 的泛型函數(shù)的模板,其返回值和調(diào)用參數(shù)(lhs 和 rhs)都具有此類型。 可以隨意命名類型參數(shù),但按照約定,最常使用單個大寫字母。 T 是模板參數(shù);關(guān)鍵字 typename 表示此參數(shù)是類型的占位符。 調(diào)用函數(shù)時,編譯器會將每個 T 實(shí)例替換為由用戶指定或編譯器推導(dǎo)的具體類型參數(shù)。 編譯器從模板生成類或函數(shù)的過程稱為“模板實(shí)例化”;minimum<int> 是模板 minimum<T> 的實(shí)例化。
這個用例中由于編譯器可以根據(jù)參數(shù)a,b推導(dǎo)出類型,所以可以像普通函數(shù)一樣調(diào)用。
模版參數(shù)
模版可以使用類型參數(shù)和非類型參數(shù)定義。任何內(nèi)置類型或者用戶定義的類型都可以作為類型參數(shù)。
類型參數(shù)的數(shù)量沒有實(shí)際限制。 以逗號分隔多個參數(shù):
template <typename T, typename U, typename V> class Foo{};
在此上下文中,關(guān)鍵字 class 等效于 typename。 可以將前面的示例表示為:
template <class T, class U, class V> class Foo{};
可以使用省略號運(yùn)算符 (...) 定義采用任意數(shù)量的零個或多個類型參數(shù)的模板:
template<typename... Arguments> class vtclass;
vtclass< > vtinstance1;
vtclass<int> vtinstance2;
vtclass<float, bool> vtinstance3;
與其他語言(如 C# 和 Java)中的泛型類型不同,C++ 模板支持非類型參數(shù),也稱為值參數(shù)。 例如,可以提供常量整型值來指定數(shù)組的長度,例如在以下示例中,它類似于標(biāo)準(zhǔn)庫中的 std::array 類:
template<typename T, size_t L>
class MyArray
{
T arr[L];
public:
MyArray() { ... }
};
記下模板聲明中的語法。 size_t 值在編譯時作為模板參數(shù)傳入,必須是 const 或 constexpr 表達(dá)式。 可以如下所示使用它:
MyArray<MyClass*, 10> arr;
其他類型的值(包括指針和引用)可以作為非類型參數(shù)傳入。 例如,可以傳入指向函數(shù)或函數(shù)對象的指針,以自定義模板代碼中的某些操作。
模板可以是模板參數(shù)。 在此示例中,MyClass2 有兩個模板參數(shù):類型名稱參數(shù) T 和模板參數(shù) Arr:
template<typename T, template<typename U, int I> class Arr>
class MyClass2
{
T t; //OK
Arr<T, 10> a;
U u; //Error. U not in scope
};
默認(rèn)模版自變量
類和函數(shù)模板可以具有默認(rèn)自變量。 如果模板具有默認(rèn)自變量,可以在使用時不指定該自變量。 例如,std::vector 模板有一個用于分配器的默認(rèn)自變量:
template <class T, class Allocator = allocator<T>> class vector;
在大多數(shù)情況下,默認(rèn)的 std::allocator 類是可接受的,因此可以使用向量,如下所示:
vector<int> myInts;
但如有必要,可以指定自定義分配器,如下所示:
vector<int, MyAllocator> ints;
對于多個模板參數(shù),第一個默認(rèn)參數(shù)后的所有參數(shù)必須具有默認(rèn)參數(shù)。
使用參數(shù)均為默認(rèn)值的模板時,請使用空尖括號:
template<typename A = int, typename B = double>
class Bar
{
//...
};
...
int main()
{
Bar<> bar; // use all default type arguments
}
模版特殊化
在某些情況下,模板不可能或不需要為任何類型都定義完全相同的代碼。 例如,你可能希望定義在類型參數(shù)為指針、std::wstring 或派生自特定基類的類型時才執(zhí)行的代碼路徑。 在這種情況下,可以為該特定類型定義模板的專用化。 當(dāng)用戶使用該類型對模板進(jìn)行實(shí)例化時,編譯器使用該專用化來生成類,而對于所有其他類型,編譯器會選擇更常規(guī)的模板。 如果專用化中的所有參數(shù)都是專用的,則稱為“完整專用化”。 如果只有一些參數(shù)是專用的,則稱為“部分專用化”。
template <typename K, typename V>
class MyMap{/*...*/};
// partial specialization for string keys
template<typename V>
class MyMap<string, V> {/*...*/};
...
MyMap<int, MyClass> classes; // uses original template
MyMap<string, MyClass> classes2; // uses the partial specialization
只要每個專用類型參數(shù)是唯一的,模板就可以具有任意數(shù)量的專用化。 只有類模板可能是部分專用。 模板的所有完整專用化和部分專用化都必須在與原始模板相同的命名空間中聲明。
函數(shù)重載
參考
C++ 允許在同一作用域中的某個 函數(shù) 和 運(yùn)算符 指定多個定義,分別稱為 函數(shù)重載 和 運(yùn)算符重載 。
重載聲明是指一個與之前已經(jīng)在該作用域內(nèi)聲明過的函數(shù)或方法具有相同名稱的聲明,但是它們的參數(shù)列表和定義(實(shí)現(xiàn))不相同。
當(dāng)您調(diào)用一個 重載函數(shù) 或 重載運(yùn)算符 時,編譯器通過把您所使用的參數(shù)類型與定義中的參數(shù)類型進(jìn)行比較,決定選用最合適的定義。選擇最合適的重載函數(shù)或重載運(yùn)算符的過程,稱為 重載決策 。
//函數(shù)重載
void print(int i) { cout << i; }
void print(double i) { cout << i; }
//運(yùn)算符重載
class Box
{
public:
double getVolume(void)
{
return length * breadth * height;
}
void setLength( double len )
{
length = len;
}
void setBreadth( double bre )
{
breadth = bre;
}
void setHeight( double hei )
{
height = hei;
}
// 重載 + 運(yùn)算符,用于把兩個 Box 對象相加
Box operator+(const Box& b)
{
Box box;
box.length = this->length + b.length;
box.breadth = this->breadth + b.breadth;
box.height = this->height + b.height;
return box;
}
private:
double length; // 長度
double breadth; // 寬度
double height; // 高度
};
運(yùn)算符重載詳細(xì)內(nèi)容參照:運(yùn)算符重載
虛函數(shù)
虛函數(shù)是C++中的一種機(jī)制,它允許在基類中聲明一個函數(shù),并在派生類中重新定義該函數(shù)。虛函數(shù)使得在運(yùn)行時可以通過基類指針或引用調(diào)用派生類的函數(shù),實(shí)現(xiàn)多態(tài)性。
class Base {
public:
virtual void show() { cout << "base"; }
};
class Derived : public Base {
public:
void show() override { cout << "Derived"; }
}:
int main()
{
Base *b;
Derived d;
b = &d;
b->show(); //call Derived::show()
return 0;
}
虛函數(shù)表
每個包含虛函數(shù)的類都有一個虛函數(shù)表,表中存儲了該類的虛函數(shù)地址。給個對象都有一個指向其所屬類的虛函數(shù)表的指針,稱為虛表指針(vptr)。
虛函數(shù)表的工作原理:
- 當(dāng)編譯器看到類中有虛函數(shù)時,會為該類生成一個虛函數(shù)表,虛函數(shù)表中存儲了類的虛函數(shù)地址;
- 每個對象都有一個指向所屬類的虛函數(shù)表的指針vptr;
- 當(dāng)通過基類的指針或者引用調(diào)用虛函數(shù)時,程序會通過對象的虛表指針找到虛函數(shù)表,然后在虛函數(shù)表中查找實(shí)際要調(diào)用的函數(shù)地址。
注意:虛函數(shù)表和虛表指針的具體實(shí)現(xiàn)時由編譯器決定的,不同編譯器可能有不同的實(shí)現(xiàn)方式。
如果派生類沒有重寫基類的虛函數(shù),也沒有定義新的虛函數(shù),那么派生類將共享基類的虛函數(shù)表。只有派生類重寫基類的虛函數(shù)或定義自己的虛函數(shù),派生類才會有自己的虛函數(shù)表。
在C++中,有幾種情況不能將函數(shù)聲明為虛函數(shù):
- 構(gòu)造函數(shù):構(gòu)造函數(shù)用于初始化對象,而虛函數(shù)機(jī)制依賴于對象的虛表指針vptr,在構(gòu)造函數(shù)執(zhí)行時,虛表指針還沒有完全初始化。
- 靜態(tài)成員函數(shù):虛函數(shù)是與對象實(shí)例相關(guān)的,而靜態(tài)成員函數(shù)是與類相關(guān)的,不依賴于具體的對象實(shí)例。
- 內(nèi)聯(lián)函數(shù):雖然技術(shù)上可以將內(nèi)聯(lián)函數(shù)聲明為虛函數(shù),但這通常沒有意義。內(nèi)聯(lián)函數(shù)的目的是在編譯的時候展開,而虛函數(shù)的調(diào)用通常在運(yùn)行時解析,這兩者目的相沖突。
- 友元函數(shù):友元函數(shù)不是類的成員函數(shù),因此不能參與類的虛函數(shù)機(jī)制。
純虛函數(shù)
純虛函數(shù)是一種沒有實(shí)現(xiàn)的虛函數(shù),必須在派生類中重寫。純虛函數(shù)的聲明方式是在函數(shù)聲明的末尾加上 ‘= 0’。包含純虛函數(shù)的類稱為抽象類,不能實(shí)例化對象。
純虛函數(shù)的用途和原因:
- 定義接口:純虛函數(shù)用于定義接口,確保派生類必須實(shí)現(xiàn)這些函數(shù)。它們提供一種機(jī)制,使得類可以定義一些必須在派生類中實(shí)現(xiàn)的函數(shù),從而確保派生類具有特定的行為。
- 抽象類:抽象類通常用于定義一組相關(guān)類的公共接口,而不提供具體實(shí)現(xiàn)。它們用于設(shè)計(jì)層次機(jī)構(gòu)中的基類。
- 多態(tài)性:純虛函數(shù)支持運(yùn)行時多態(tài)。通過基類的指針或引用調(diào)用純虛函數(shù)時,實(shí)際調(diào)用的是派生類的實(shí)現(xiàn),這使得代碼更加靈活和可擴(kuò)展。
- 強(qiáng)制重寫:純虛函數(shù)強(qiáng)制派生類提供自己的實(shí)現(xiàn)。如果派生類沒有實(shí)現(xiàn)所有的純虛函數(shù),那么派生類本身也將成為抽象類,無法實(shí)例化。
// 抽象基類 AbstractBase
class AbstractBase {
public:
// 純虛函數(shù) display,沒有默認(rèn)實(shí)現(xiàn)
virtual void display() = 0;
};
// 派生類 Derived 實(shí)現(xiàn)抽象基類
class Derived : public AbstractBase {
public:
// 實(shí)現(xiàn)抽象基類中的純虛函數(shù) display
void display() override {
std::cout << "Display function of Derived class" << std::endl;
}
};
浙公網(wǎng)安備 33010602011771號