【C++ 筆記】建構子(Constructors) - part 18

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

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

建構子(Constructors)

建構子(Constructors),又稱建構函數、建構式,為一種特別的 class members,每次實例化該類別的物件時,編譯器都會呼叫建構子(表示每次建立物件都會用到此函數)。建構子與 class 具有相同的名稱,並且可以在 class 中進行內部定義或外部定義。

另外建構子也不會回傳任何型態,也不會回傳 void。

:::success
可以想像建構子就是做初始化的動作。
:::

在 C++ 中,共有下列四種建構子:

  • 預設建構子(Default Constructors)
  • 參數化建構子(Parameterized Constructors)
  • 複製建構子(Copy Constructors)
  • 移動建構子(Move Constructors) -> 此篇不談

以下是一個練習範例(展示預設建構子),新手適用的建構子教學

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
#include <iostream>
#include <string>
using namespace std;

// 定義 class 叫 Person
class Person {
private:
string name;
int age;

public:
// 預設建構子(Default Constructors)
Person() {
name = "Unknown";
age = 0;
cout << "預設建構子被呼叫了" << endl;
}

// 內建成員函數(方法):顯示成員資訊
void displayInfo() const {
cout << "姓名: " << name << ", 年齡: " << age << endl;
}
};

int main() {
// 建立物件時會自動呼叫預設建構子
Person p1;

// 呼叫方法顯示資訊
p1.displayInfo();

return 0;
}

輸出結果:

1
2
預設建構子被呼叫了
姓名: Unknown, 年齡: 0

參數化建構子(Parameterized Constructors)


就是帶有參數的建構子,因為他本身還是個函數嘛,以下是個範例:

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
#include <iostream>
#include <string>
using namespace std;

// 定義 class Car
class Car {
private:
string brand;
string model;
int year;

public:
// 參數化建構子
Car(string b, string m, int y) {
brand = b;
model = m;
year = y;
}

// 方法:顯示車輛資訊
void displayInfo() {
cout << "品牌: " << brand
<< ", 車型: " << model
<< ", 出廠年份: " << year << endl;
}
};

int main() {
// 用參數化建構子建立物件
Car car1("Toyota", "Corolla", 2020);
Car car2("Tesla", "Model 3", 2022);

car1.displayInfo();
car2.displayInfo();

return 0;
}

輸出結果:

1
2
品牌: Toyota, 車型: Corolla, 出廠年份: 2020
品牌: Tesla, 車型: Model 3, 出廠年份: 2022

複製建構子(Copy Constructors)


複製建構子(Copy Constructor)是一種特殊的建構子,用來建立一個新物件,並以同型態的另一個已存在的物件來初始化它。

格式:

1
2
3
classname (const classname &obj) {
// Body of Copy Constructor
}

ClassName:類別名稱。

const ClassName& obj:以參考方式傳遞的另一個物件,通常使用 const 使源物件不被修改。

const 看你要不要加,但加它是為了讓程式設計師不會錯誤地修改 obj

這個 obj 就是 object,物件的意思。

複製建構子用於以下三種情形:

  • 透過使用另一個同型態的物件來初始化新建立的物件。
  • 複製物件把它當作參數傳遞給函數。
  • 複製對象,並從函數回傳這個對象。

若無明確定義複製建構子,編譯器會自動產生,該建構子會執行淺層複製(Shallow Copy):

  • 將 class 中的每個成員的值逐一複製。
  • 若 class 中包含指標成員,預設複製建構子會直接複製指標的地址,導致兩物件共享記憶體區域。

簡言之,淺複製只會複製指針,而不會複製他實際上的”源頭”,若刪除原本的物件,就可能導致空指針(NULL Pointer)。

與淺複製相反的,就是深層複製(Deep Copy),要使用深層複製,就是直接將 Copy Constructor 明確定義出來。

另外 Deep Copy 會為新物件分配獨立的記憶體空間,並複製資料內容與指標。

以下是一個範例:

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
#include <iostream>
using namespace std;

// 建立一個測試 class
// Create a demo class
class A {
public:
int x;
};

int main() {
// 建立一個物件 a1
// Creating an a1 object
A a1;
a1.x = 10;
cout << "a1's x = " << a1.x << endl;

// 用 a1 建立另一個物件
// Creating another object using a1
// 正在呼叫複製建構子
// Copy Constructor Calling
A a2(a1);
cout << "a2's x = " << a2.x;

return 0;
}

Source:https://www.geeksforgeeks.org/copy-constructor-in-cpp/

輸出結果:

1
2
a1's x = 10
a2's x = 10

透過上述範例,可發現我們並未定義「建構子」,但卻能夠使用同一個類別現有的物件來建立一個物件。那此時 C++ 編譯器就會建立一個簡單的複製建構子,也稱隱式複製建構子(implicit copy constructor)。

以下是一個範例,當我們定義 Copy Constructor:

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
#include <iostream>
using namespace std;

// 建立一個測試 class
// Create a demo class
class A {
public:
int x;

A(){};

// 定義一個 Copy Constructor
// Copy Constructor definition
A (A& t) {
x = t.x;
cout << "Calling copy constructor" << endl;
}
};

int main() {

// 建立 a1 物件
// Creating an a1 object
A a1;
a1.x = 10;
cout << "a1's x = " << a1.x << endl;

// 用 a1 建立另一個物件
// Creating another object using a1
// 正在呼叫 Copy Constructor
// Copy Constructor Calling
A a2(a1);
cout << "a2's x = " << a2.x;

return 0;
}

Source:https://www.geeksforgeeks.org/copy-constructor-in-cpp/

輸出結果:

1
2
3
a1's x = 10
Calling copy constructor
a2's x = 10

以上程式碼第十行,A(){}; 為定義一個預設建構子,但他是空的。那他有什麼屁用呢?

若刪掉此行,則會因為 class 中已定義了一個複製建構子 A(A& t),編譯器將不再自動產生預設建構子。所以,A a1; 物件會無法建立,編譯器直接報錯。

這是先後順序問題,你要先建立物件才能用 Copy Constructor,因此需要預設建構子為基礎。

以下是個範例,有關於深層複製(Deep Copy):

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
#include <cstring>
#include <iostream>
using namespace std;

class String {
private:
char* s;
int size;

public:
String(const char* str = NULL);
~String() { delete[] s; }

// 複製建構子
// Copy constructor
String(const String&);
void print() {
cout << s << endl;
}
void change(const char*);
};

// 成員函數及建構子的定義
// Definitions of constructor and memeber functions
String::String(const char* str) {
size = strlen(str);
s = new char[size + 1];
strcpy(s, str);
}

String::String(const String& old_str) {
size = old_str.size;
s = new char[size + 1];
strcpy(s, old_str.s);
}

void String::change(const char* str) {
delete[] s;
size = strlen(str);
s = new char[size + 1];
strcpy(s, str);
}

int main() {
String str1("GeeksQuiz");

// 從 str1 建立 str2
// Create str2 from str1
String str2 = str1;

str1.print(); // what is printed ? 印出了啥 ?
str2.print();

str2.change("GeeksforGeeks"); // 注意此行只有 change str2

str1.print(); // what is printed now ? 現在他印出了啥 ?
str2.print();
return 0;
}

Source:https://www.geeksforgeeks.org/copy-constructor-in-cpp/

輸出結果:

1
2
3
4
GeeksQuiz
GeeksQuiz
GeeksQuiz
GeeksforGeeks

此範例使用到了 new、delete 運算子,這兩個運算子的意義分別是「自行對記憶體配置」、「自行刪除掉記憶體」。

有關 new、delete 運算子的使用上,是要非常小心的,若一直用 new 一直給記憶體資源,而不用 delete 刪除的話,那麼記憶體會吃滿。

詳情可至此網站了解:https://openhome.cc/Gossip/CppGossip/newDelete.html

第十二行中,用到了解構子的語法:~String() { delete[] s; }

:::success
解構子(Destructor),語法就是用一個波浪符號 ~ 後面加上類別名稱,與建構子宣告方式類似。它不會回傳任何值,也不能帶有任何參數。解構子有助於在跳出程式(例如關閉檔案、釋放記憶體等)前釋放資源。
:::

那第十二行就是在說當離開作用域或程式結束時,會自動刪除掉 s 這個成員。

總結

建構子(Constructors) 是 C++ 類別中特別的成員函數,每次建立物件時都會自動被呼叫,用於初始化物件。

:::success
建構子與類別名稱相同,沒有回傳值。 -> 非常重要
:::

C++ 提供了四種建構子:

  1. 預設建構子(Default Constructor):不帶參數,將物件設定預設值。
  2. 參數化建構子(Parameterized Constructor):允許在建立物件時傳入參數進行初始化。
  3. 複製建構子(Copy Constructor):用於建立新物件,並以現有的同型態物件初始化它。若未定義,編譯器會使用淺層複製(Shallow Copy),但當類別包含動態記憶體分配時,應使用深層複製(Deep Copy)來避免記憶體錯誤。
  4. 移動建構子(Move Constructor)->本篇不談

Copy Constructor Application:

  • 以現有物件初始化新物件。-> 如:myClass a2 = a1
  • 當物件作為參數傳遞給函數時。
  • 當函數回傳物件時。

若類別包含指標成員,應用深層複製確保每物件有獨立記憶體區域,避免記憶體共享導致的錯誤。

參考資料

new 與 delete

Copy Constructor in C++ - GeeksforGeeks

C++ Classes and Objects - GeeksforGeeks

C++ Constructors | W3Schools

C++ 类构造函数 & 析构函数 | 菜鸟教程

C++ 拷贝构造函数 | 菜鸟教程