【C++ 筆記】抽象類別(Abstract Class, ABC) - part 26

很感謝你點進來這篇文章。

你好,我並不是什麼 C++、程式語言的專家,所以本文若有些錯誤麻煩請各位鞭大力一點,我極需各位的指正及指導!!本系列文章的性質主要以詼諧的口吻,一派輕鬆的態度自學程式語言,如果你喜歡,麻煩留言說聲文章讚讚吧!

純虛函數(Pure Virtual Function)

在講抽象類別(簡稱 ABC)之前,先來複習一下這個鬼東西。

:::info
純虛函數(Pure Virtual Functions):

  • 一個包含純虛函數的類別被稱為抽象類別(Abstract Class),它不能被直接實例化。
  • 純虛函數沒有函數體,宣告時使用 = 0。
  • 它強制衍生類別提供具體的實作。
    :::

來源:菜鳥教程

純虛函數宣告例子:

1
2
3
4
class Shape {
public:
virtual void draw() = 0; // 純虛擬函式
};

以上這段是來自本篇筆記系列的 part 23 多型。

抽象類別(Abstract Class, ABC)

定義

抽象類別是至少含有一個純虛函式的類別。它不能被用來建立物件,只能作為其他類別的基類別,提供介面和部分實作。

設計抽象類(通常稱為 ABC)的目的,是為了給其他類別提供一個可以繼承的適當的基類。抽象類不能被用於實例化對象,它只能作為介面使用。如果試圖實例化一個抽象類別的對象,會導致編譯錯誤。
From 菜鳥教程

反之,可以被實例化(instantiated)的類別叫做具體類。

性質

  • 無法實例化:
    • 抽象類別無法直接建立物件。
  • 必有純虛函式:
    • 至少有一個純虛函式。
  • 可含具體函式與資料成員:
    • 抽象類別可以有普通成員函式和資料成員。
  • 衍生類必須有純虛函式:
    • 否則衍生類也是抽象類別。
  • 用於多型與介面設計:
    • 抽象類提供統一介面,允許透過基類指標或參考操作不同衍生類物件。
  • 不完整類別:
    • 因為純虛函式沒有實作,抽象類別本身是不完整的。

抽象類的使用方式

  • 可宣告抽象類的 pointer 或 reference,用於多型呼叫。
  • 衍生類必須實作所有純虛函式,才能被實例化。
  • 抽象類可有 constructor、destructor(純虛 destructor 也可定義實作)與其他成員函式。

範例 1:基本款抽象類

class Vehicle 因為內部有至少一個純虛函式 start(),所以是一個抽象類。

Car、Motorcycle 都繼承自 Vehicle,所以他們兩個必須要實作出純虛函式,否則就會變成抽象類,不能被實例化。

也可看到抽象類定義了一個介面 start(),讓 Car、Motorcycle 只能根據這個介面去做一些設計。

以下程式碼用白話來說,就是假設有一套交通規範,規定 Car 跟 Motorcycle 一定都要有 start() 的存在,至於怎麼去實作內部的 start() 這交由開發商去決定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <iostream>
using namespace std;

class Vehicle {
public:
virtual void start() = 0;

void displayType() {
cout << "This is a vehicle." << endl;
}
};

class Car : public Vehicle {
public:
void start() {
cout << "Car starts with a key." << endl;
}
};

class Motorcycle : public Vehicle {
public:
void start() {
cout << "Motorcycle starts with a button." << endl;
}
};

int main() {
Car c;
Motorcycle m;

c.displayType();
c.start();

m.displayType();
m.start();

return 0;
}

Output:

1
2
3
4
This is a vehicle.
Car starts with a key.
This is a vehicle.
Motorcycle starts with a button.

範例 2:抽象類可有建構子、指標

如題,抽象類可以有建構子跟指標。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include <iostream>
#include <cmath>
using namespace std;

class Shape {
public:
Shape() {
cout << "Shape constructor called." << endl;
}

virtual double area() const = 0;

virtual ~Shape() {
cout << "Shape destructor called." << endl;
}
};

class Circle : public Shape {
private:
double radius;

public:
Circle(double r) : radius(r) {
cout << "Circle constructor called." << endl;
}

double area() const override {
return M_PI * radius * radius;
}

~Circle() {
cout << "Circle destructor called." << endl;
}
};

class Rectangle : public Shape {
private:
double width, height;

public:
Rectangle(double w, double h) : width(w), height(h) {
cout << "Rectangle constructor called." << endl;
}

double area() const override {
return width * height;
}

~Rectangle() {
cout << "Rectangle destructor called." << endl;
}
};

int main() {
Shape* s1 = new Circle(5.0);
Shape* s2 = new Rectangle(4.0, 6.0);

cout << "Circle Area: " << s1->area() << endl;
cout << "Rectangle Area: " << s2->area() << endl;

delete s1;
delete s2;

return 0;
}

Output:

1
2
3
4
5
6
7
8
9
10
Shape constructor called.
Circle constructor called.
Shape constructor called.
Rectangle constructor called.
Circle Area: 78.5398
Rectangle Area: 24
Circle destructor called.
Shape destructor called.
Rectangle destructor called.
Shape destructor called.

範例 3:抽象類可以有 reference

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>
using namespace std;

class Animal {
public:
virtual void speak() const = 0;
};

class Dog : public Animal {
public:
void speak() const override {
cout << "Woof!" << endl;
}
};

class Cat : public Animal {
public:
void speak() const override {
cout << "Meow!" << endl;
}
};

class Human : public Animal {
public:
void speak() const override {
cout << "讓你叫!" << endl;
}
};

void makeSound(const Animal& a) {
a.speak();
}

int main() {
Dog d;
Cat c;
Human _87;
makeSound(d);
makeSound(c);
makeSound(_87);
return 0;
}

Output:

1
2
3
Woof!
Meow!
讓你叫!

設計策略

我覺得下面這段話說的挺不錯的:

物件導向的系統可能會使用一個抽象基類為所有的外部應用程式提供一個適當的、通用的、標準化的介面。然後,衍生類透過繼承抽象基類,就把所有類似的操作都繼承下來。
外部應用程式提供的功能(即公有函數)在抽象基類別中是以純虛函數的形式存在的。這些純虛函數在對應的衍生類中被實作。這個架構也使得新的應用程式可以輕鬆地被添加到系統中,即使在系統被定義之後仍然可以如此。
From 菜鳥教程

純虛函數與抽象類之間的關係

純虛函數抽象類別
定義沒有實作的虛擬函式,宣告時以 = 0 標示至少包含一個純虛函式的類別
是否可實例化不可實例化不可實例化
作用定義介面,強制衍生類一定要實作作為介面與基類,提供多型的支援
是否必須被實作衍生類必須實作純虛函數衍生類若未實作純虛函數,仍是抽象類
是否可有函數實作不可有函數體可有普通函數與資料成員

總結

純虛函數(Pure Virtual Function)

  • 是沒有函數實作的虛擬函數,宣告時使用 = 0。
  • 含有純虛函數的類別即為抽象類別。
  • 衍生類必須實作所有純虛函式才能被實例化。
  • 用途:定義介面,強制衍生類實作。
1
2
3
4
class Shape {
public:
virtual void draw() = 0; // 純虛函數
};

抽象類別(Abstract Class, ABC)

  • 定義:
    • 至少含有一個純虛函式的類別,不能直接建立物件實例,常用來提供統一介面與多型行為。
  • 性質整理:
性質說明
無法實例化不能直接用 new 或宣告物件建立
至少含一個純虛函數否則就不是抽象類
可含有具體函式或資料成員也可以有 constructor / destructor
衍生類若未實作所有純虛函數該衍生類仍是抽象類
用途多型、多型介面設計、模版模式

設計策略

  • 抽象類設計的是「做什麼」,非「怎麼做」。
  • 是一種設計契約,定義了衍生類的基本行為規範。
  • 可用來隔離變動、提升擴充性與維護性。

純虛函數 vs 抽象類別

純虛函數抽象類別
定義= 0 的虛函式含純虛函式的類別
是否可實例化
目的強制衍生類實作特定介面提供共通架構與多型支援
是否一定需被實作否(衍生類若不實作,仍是抽象類)
可否包含具體內容可以有普通函數與資料成員

結語

抽象類別是「介面+部分實作」的集合,透過純虛函式提供「必須實作的規範」,是實現 C++ 多型與物件導向架構設計的核心工具之一。

參考資料

C++ 類別 class 抽象類別-程式語言教學|痞客邦

Pure Virtual Functions and Abstract Classes in C++ - GeeksforGeeks

C++ 接口(抽象类) | 菜鸟教程