在 C++ 中實現類建構函式
本文將介紹如何在 C++ 中實現類建構函式。
什麼是建構函式以及它們如何在 C++ 中工作
建構函式是定義類物件應該如何初始化的特殊成員函式。建構函式通常初始化類的資料成員,並在建立類物件時執行它們。建構函式的一些特定特性是它們與類本身具有相同的名稱並且不能具有返回型別。通常,一個類具有多個相互過載的建構函式,但它們必須具有不同數量或型別的引數。
請注意,建構函式通常由使用者在任何稍微複雜的類中明確指定。儘管如此,當使用者沒有定義任何建構函式時,編譯器會自動生成預設建構函式。預設建構函式通常是不帶引數的建構函式,它被呼叫用於類的預設初始化。但是請注意,編譯器生成的預設建構函式正式稱為合成預設建構函式。後者是專門為每個類根據其資料成員推斷出來的,它使用類內初始化器或使用預設值來初始化成員。因此,自動生成的建構函式不是通用的解決方案,但它們對於簡單的類結構可以正常工作。
在下面的示例中,我們定義了一個帶有兩個建構函式的 MyClass1
類。請注意,第一個不帶任何引數,這意味著它是一個預設建構函式,但我們仍然指定了一個 default
關鍵字。後者向編譯器指示此特定建構函式應為預設建構函式。通常,如果使用者定義了任何建構函式,編譯器不會生成預設建構函式。在這種情況下,使用者應該明確要求為給定的建構函式指定 default
。
MyClass1
的第二個建構函式將單個 string
值作為引數並用它初始化 name
資料成員。它將特殊字串文字列印到 cout
流,只是為了使函式執行時刻可見以供觀察。m2
物件建立觸發建構函式一。m1
物件使用預設建構函式初始化,由於編譯器本身生成後一個,我們在 cout
流上看不到任何列印字串。
#include <iostream>
#include <utility>
using std::cout; using std::endl;
using std::string;
class MyClass1 {
private:
string name;
string nickname;
public:
MyClass1() = default;
explicit MyClass1(string n) : name(std::move(n)) {
cout << "Constructor 1 is called" << endl;
};
string getName() {
return name;
}
string getNickname() {
return nickname;
}
~MyClass1() {
cout << "Destructor is called" << endl;
}
};
int main() {
MyClass1 m1{};
cout << m1.getName() << endl;
cout << m1.getNickname() << endl;
cout << "------------------" << endl;
string n1("Maude");
MyClass1 m2(n1);
cout << m2.getName() << endl;
cout << m2.getNickname() << endl;
cout << "------------------" << endl;
return EXIT_SUCCESS;
}
輸出:
------------------
Constructor 1 is called
Maude
------------------
Destructor is called
Destructor is called
在 C++ 中使用過載實現多個類建構函式
MyClass1
具有名為 nickname
的第二個 string
資料成員。假設我們建立了另一個建構函式,它接受單個 string
值並定義它來初始化 nickname
。在這種情況下,編譯器將引發錯誤,即我們無法過載具有相同引數的函式。因此,我們需要定義另一個建構函式,例如,我們選擇了接受兩個 string
引用並初始化兩個資料成員的建構函式。當下面的程式碼片段執行時,我們可以看到第二個建構函式被執行了。
建構函式有更詳細的特性,我們在本文中只介紹了主要特性。其他特殊函式的名稱中包含單詞 - constructor
,例如 move-constructor 和 copy-constructor。這兩個是統稱為複製控制
的特殊操作的一部分。請注意,解構函式的行為與建構函式的行為相反。即,它們釋放類成員,並且通常在物件超出範圍時自動呼叫它們。可以使用給定的程式碼片段輕鬆觀察建構函式-解構函式呼叫行為。
#include <iostream>
#include <utility>
#include <vector>
using std::cout; using std::endl;
using std::string;
class MyClass1 {
private:
string name;
string nickname;
public:
MyClass1() = default;
explicit MyClass1(string n) : name(std::move(n)) {
cout << "Constructor 1 is called" << endl;
};
// ERROR: Does not Compile
// MyClass1(string nk) : nickname(nk) {
// cout << "Constructor 3 is called" << endl;
// };
MyClass1(string &n, string &nk) : name(n), nickname(nk) {
cout << "Constructor 2 is called" << endl;
};
string getName() {
return name;
}
string getNickname() {
return nickname;
}
~MyClass1() {
cout << "Destructor is called" << endl;
}
};
int main() {
string n1("Maude");
string n2("Bibi");
MyClass1 m4(n1, n2);
cout << m4.getName() << endl;
cout << m4.getNickname() << endl;
cout << "------------------" << endl;
return EXIT_SUCCESS;
}
輸出:
Constructor 2 is called
Maude
Bibi
------------------
Destructor is called
Destructor is called
Founder of DelftStack.com. Jinku has worked in the robotics and automotive industries for over 8 years. He sharpened his coding skills when he needed to do the automatic testing, data collection from remote servers and report creation from the endurance test. He is from an electrical/electronics engineering background but has expanded his interest to embedded electronics, embedded programming and front-/back-end programming.
LinkedIn