C++ では、クラスとオブジェクトがオブジェクト指向プログラミングの基本概念です。クラスは、オブジェクトのプロパティと動作を記述するために使用される抽象データ型です。オブジェクトはクラスのインスタンスであり、クラスのプロパティと動作を具体化します。この記事では、オブジェクト参照を中心に、C++ のクラスとオブジェクトのオブジェクトの特性を紹介します。
記事ディレクトリ
関連リンク:
クラスとオブジェクトのカプセル化を理解するための 1 つの記事、
C++ での
参照関数の高度なアプリケーションを理解するための 1 つの記事
1. コンストラクターとデストラクター
クラスを作成するとき、クラスにはいくつかのメンバー変数とメンバー関数が含まれる場合があります。コンストラクターとデストラクターは、オブジェクトの初期化とクリーンアップに使用されるクラスの特別なメンバー関数です。
-
コンストラクターの機能は、オブジェクトの作成時にオブジェクトのメンバー変数を初期化することです。これはクラスと同じ名前を持ち、戻り値の型はなく、パラメーターを持つことができます。コンストラクターには複数のオーバーロードされたバージョンを含めることができ、どのコンストラクターが使用されるかは、渡されるパラメーターの型と数によって異なります。コンストラクターは、オブジェクトの作成時に自動的に呼び出されます。
-
デストラクターの役割は、オブジェクトが破棄されるときにオブジェクトのリソースをクリーンアップすることです。名前はクラスと同じで、先頭にチルダ (~) が付きます。戻り値の型はなく、パラメータも受け入れません。デストラクターは 1 つだけ存在でき、オーバーロードすることはできません。デストラクターは、オブジェクトが破棄されるときに自動的に呼び出されます。
サンプルコード:
class Person {
public:
string name;
int age;
// 默认构造函数
Person() {
name = "Unknown";
age = 0;
cout << "Default constructor called" << endl;
}
// 带参数的构造函数
Person(string n, int a) {
name = n;
age = a;
cout << "Parameterized constructor called" << endl;
}
// 析构函数
~Person() {
cout << "Destructor called" << endl;
}
};
int main() {
// 使用默认构造函数创建对象
Person p1;
cout << "Name: " << p1.name << ", Age: " << p1.age << endl;
// 使用带参数的构造函数创建对象
Person p2("John", 25);
cout << "Name: " << p2.name << ", Age: " << p2.age << endl;
return 0;
}
出力結果:
Default constructor called
Name: Unknown, Age: 0
Parameterized constructor called
Name: John, Age: 25
Destructor called
Destructor called
コードの説明:
上記の例では、とPerson
という 2 つのメンバー変数を持つ という名前のクラスを定義しました。コンストラクターとデストラクターを使用して、これらのメンバー変数を初期化し、クリーンアップします。name
age
まず、パラメーターを受け取らないデフォルトのコンストラクターを定義します。デフォルトのコンストラクターでは、 0name
にUnknown
設定しage
、コンストラクターが呼び出されたことを示すメッセージを出力します。
次に、文字列パラメータと整数パラメータを受け入れるパラメータ化されたコンストラクターを定義します。パラメーターを含むコンストラクターでは、渡されたパラメーター値をメンバー変数に割り当てname
、age
コンストラクターが呼び出されたことを示すメッセージを出力します。
main 関数では、まずp1
デフォルトのコンストラクターを使用して名前を付けたオブジェクトを作成します。パラメーターが渡されないため、デフォルトのコンストラクターが呼び出されます。p1
次に、オブジェクトの値name
とメンバー変数の値 (age
それぞれUnknown
0 と 0)を出力します。
次に、パラメータを指定したコンストラクターを使用して、 という名前のオブジェクトを作成しますp2
。"John"
文字列と整数をパラメータとして渡す25
ので、パラメータを持つコンストラクタが呼び出されます。p2
次に、オブジェクトの値name
とメンバー変数の値 (age
それぞれJohn
25 と 25)を出力します。
プログラムの終了時に、オブジェクトはスコープ外になるため、破棄されますp1
。p2
オブジェクトの破棄中に、デストラクターが自動的に呼び出されます。この例では、デストラクターが呼び出されたことを示すメッセージをデストラクター内に出力しました。
2. 関数の分類と呼び出し
1. 分類
-
デフォルトのコンストラクター: クラスでコンストラクターが明示的に定義されていない場合、コンパイラーはデフォルトのコンストラクターを自動的に生成します。デフォルトのコンストラクターにはパラメーターがなく、初期化操作も実行されません。オブジェクトの作成時に暗黙的に呼び出されます。
-
パラメーター化されたコンストラクター: パラメーター化されたコンストラクターは 1 つ以上のパラメーターを受け入れ、これらのパラメーターを使用してオブジェクトのメンバー変数を初期化します。これらはオブジェクトの作成時に呼び出され、どのコンストラクターが使用されるかは、渡されたパラメーターのタイプと数に基づいて決定されます。
-
コピー コンストラクター: コピー コンストラクターは、同様のオブジェクトへの参照をパラメーターとして受け取り、オブジェクトの値を使用して新しく作成されたオブジェクトを初期化する特別なコンストラクターです。コピー コンストラクターは、次の場合に呼び出されます。
- あるオブジェクトを使用して別のオブジェクトを初期化する場合
- オブジェクトを関数パラメータとして関数に渡します
- 関数からオブジェクトを返す
2.呼び出し方法
-
直接呼び出し: クラス名に括弧を追加することで、コンストラクターを直接呼び出すことができます。例えば:
MyClass obj(10);
-
暗黙的な呼び出し: コンストラクターは、オブジェクトの作成時に暗黙的に呼び出されます。例:
MyClass obj = MyClass(10);
、パラメータを持つコンストラクターがオブジェクトを作成するために呼び出されます。 -
コピーの初期化: あるオブジェクトを使用して別のオブジェクトを初期化する場合、コピー コンストラクターが呼び出されます。例えば:
MyClass obj1(10); MyClass obj2 = obj1;
-
関数パラメータの受け渡し: オブジェクトが関数パラメータとして関数に渡されると、コピー コンストラクターが呼び出されます。例:
void func(MyClass obj);
、関数を呼び出すときにコピー コンストラクターが呼び出されますfunc(obj)
。 -
関数がオブジェクトを返す: 関数からオブジェクトが返されると、コピー コンストラクターが呼び出されます。例えば:
MyClass func() { return MyClass(10); }
3. サンプルコード
#include <iostream>
using namespace std;
class MyClass {
public:
int num;
// 默认构造函数
MyClass() {
num = 0;
cout << "Default constructor called" << endl;
}
// 带参数的构造函数
MyClass(int n) {
num = n;
cout << "Parameterized constructor called" << endl;
}
// 拷贝构造函数
MyClass(const MyClass& obj) {
num = obj.num;
cout << "Copy constructor called" << endl;
}
// 析构函数
~MyClass() {
cout << "Destructor called" << endl;
}
};
int main() {
// 直接调用构造函数
MyClass obj1(10);
// 隐式调用构造函数
MyClass obj2 = MyClass(20);
// 拷贝初始化
MyClass obj3(obj1);
// 函数参数传递
void func(MyClass obj);
func(obj1);
// 函数返回对象
MyClass func();
MyClass obj4 = func();
return 0;
}
4. 結果を出力します。
Parameterized constructor called
Parameterized constructor called
Copy constructor called
Copy constructor called
Destructor called
Destructor called
Destructor called
Destructor called
5. コードの説明
上の例では、MyClass
1 つのメンバー変数と複数のコンストラクターを含む という名前のクラスを定義しましたnum
。複数のオブジェクトを作成し、さまざまな方法でコンストラクターを呼び出します。
まず、コンストラクターを直接呼び出してオブジェクトを作成しますobj1
。これは、パラメーターを使用してコンストラクターを呼び出します。次に、コンストラクターを暗黙的に呼び出してオブジェクトを作成しますobj2
。これにより、パラメーターを指定してコンストラクターも呼び出されます。
次に、 copy-initialization を使用してオブジェクトを作成しobj3
、obj1
オブジェクトの値で初期化します。コピーの初期化プロセス中に、コピー コンストラクターが呼び出されます。
次に、オブジェクトをパラメータとしてfunc
受け取る関数を定義します。MyClass
関数が呼び出されるとfunc(obj1)
、コピー コンストラクターが呼び出され、obj1
オブジェクトが関数に渡されます。
func
最後に、オブジェクトを返す関数を定義しますMyClass
。コピー コンストラクターは、関数が呼び出されfunc()
、返されたオブジェクトが割り当てられるときに呼び出されます。obj4
プログラムの終了時に、すべてのオブジェクトはスコープ外に出るため、破棄されます。オブジェクトの破棄中に、デストラクターが自動的に呼び出されます。この例では、デストラクターが呼び出されたことを示すメッセージをデストラクター内に出力しました。
3. コピーコンストラクタのタイミング
- オブジェクトを使用して別のオブジェクトを初期化する場合: 既存のオブジェクトを使用して新しいオブジェクトを初期化する場合、コピー コンストラクターが呼び出されます。例えば:
class MyClass {
public:
MyClass(int value) : m_value(value) {
}
MyClass(const MyClass& other) : m_value(other.m_value) {
std::cout << "Copy constructor called" << std::endl;
}
private:
int m_value;
};
MyClass obj1(10);
MyClass obj2 = obj1; // 调用拷贝构造函数
- オブジェクトを関数の引数として関数に渡す: オブジェクトが関数の引数として関数に渡されると、コピー コンストラクターが呼び出されます。例えば:
class MyClass {
public:
MyClass(int value) : m_value(value) {
}
MyClass(const MyClass& other) : m_value(other.m_value) {
std::cout << "Copy constructor called" << std::endl;
}
private:
int m_value;
};
void func(MyClass obj) {
// Do something with obj
}
MyClass obj1(10);
func(obj1); // 调用拷贝构造函数
- 関数からオブジェクトを返す: 関数からオブジェクトが返されると、コピー コンストラクターが呼び出されます。例えば:
class MyClass {
public:
MyClass(int value) : m_value(value) {
}
MyClass(const MyClass& other) : m_value(other.m_value) {
std::cout << "Copy constructor called" << std::endl;
}
private:
int m_value;
};
MyClass func() {
MyClass obj(10);
return obj; // 调用拷贝构造函数
}
- 代入にクラス オブジェクトを使用する場合、コピー コンストラクターも呼び出されます。例えば:
class MyClass {
public:
MyClass(int value) : m_value(value) {
}
MyClass(const MyClass& other) : m_value(other.m_value) {
std::cout << "Copy constructor called" << std::endl;
}
private:
int m_value;
};
MyClass obj1(10);
MyClass obj2;
obj2 = obj1; // 调用拷贝构造函数
コンパイラは、コピー コンストラクターへの不必要な呼び出しを回避するために最適化を実行する場合があることに注意してください。この最適化は「コピー省略」と呼ばれます。場合によっては、コンパイラは、コピー コンストラクター呼び出しを行う代わりに、オブジェクトの値をある場所から別の場所に直接移動することがあります。これによりパフォーマンスが向上しますが、コピー コンストラクターは呼び出されません。
4. コンストラクター呼び出し規則
コンストラクターの呼び出し規則は次のとおりです。
-
デフォルトのコンストラクター: コンストラクターが明示的に定義されていない場合、コンパイラーはデフォルトのコンストラクターを自動的に生成します。デフォルトのコンストラクターにはパラメーターがなく、デフォルトの初期化操作を実行します。オブジェクトの作成時にパラメーターが指定されていない場合は、デフォルトのコンストラクターが呼び出されます。
-
パラメーター化されたコンストラクター: パラメーター化されたコンストラクターは 1 つ以上のパラメーターを受け入れ、これらのパラメーターを使用してオブジェクトのメンバー変数を初期化します。オブジェクトの作成時にパラメーターが指定されている場合は、対応するパラメーター化されたコンストラクターが呼び出されます。
-
コピー コンストラクター: コピー コンストラクターは、同じ型のオブジェクトをパラメーターとして受け取り、オブジェクトの値を使用して新しいオブジェクトを初期化します。コピー コンストラクターは、オブジェクトのコピーの初期化、関数パラメーターの受け渡し、関数のオブジェクトの戻りなどのシナリオで使用できます。
-
移動コンストラクター: 移動コンストラクターは、C++11 で導入された新機能であり、右辺値参照をパラメーターとして受け入れ、パラメーターの値を使用して新しいオブジェクトを初期化します。移動コンストラクターは、オブジェクトのリソース所有権が譲渡されるときにパフォーマンスを向上させるためによく使用されます。
コンストラクターの呼び出し規則は次のとおりです。
- オブジェクトが作成されると、指定されたパラメーターのタイプと数に基づいて、適切なコンストラクターが選択され、呼び出されます。パラメーターが指定されていない場合は、デフォルトのコンストラクターが呼び出されます。
- コピー コンストラクターは、あるオブジェクトを使用して別のオブジェクトを初期化するときに呼び出されます。
- コピー コンストラクターは、オブジェクトが関数の引数として関数に渡されるときに呼び出されます。
- オブジェクトが関数から返されると、コピー コンストラクターが呼び出されます。
- 代入にクラス オブジェクトを使用する場合、コピー コンストラクターも呼び出されます。
- 場合によっては、コンパイラは、コピー コンストラクターへの不必要な呼び出しを回避するために最適化を実行します。この最適化は「コピー省略」と呼ばれます。
5.ディープコピーとシャローコピー
浅いコピーとは、オブジェクトのメンバー変数を含む、あるオブジェクトの値を別のオブジェクトにコピーすることを指します。これは、両方のオブジェクトが同じメモリ アドレスを共有し、一方を変更すると他方にも影響を与えることを意味します。浅いコピーでは、オブジェクトの表面レベルのみがコピーされ、オブジェクトが所有するリソースはコピーされません。
ディープ コピーとは、あるオブジェクトの値を別のオブジェクトにコピーし、新しいオブジェクトに独立したメモリ領域を割り当てることを指します。このように、2 つのオブジェクトは独立したメモリ空間を持ち、一方のオブジェクトを変更してももう一方のオブジェクトには影響しません。ディープ コピーは、オブジェクトが所有するリソースを含む、オブジェクトのすべてのメンバー変数を再帰的にコピーします。
サンプルコード:
#include <iostream>
#include <cstring>
class Person {
public:
Person(const char* name, int age) {
m_name = new char[strlen(name) + 1];
strcpy(m_name, name);
m_age = age;
}
// 拷贝构造函数
Person(const Person& other) {
m_name = new char[strlen(other.m_name) + 1];
strcpy(m_name, other.m_name);
m_age = other.m_age;
}
// 析构函数
~Person() {
delete[] m_name;
}
// 打印信息
void printInfo() {
std::cout << "Name: " << m_name << ", Age: " << m_age << std::endl;
}
private:
char* m_name;
int m_age;
};
int main() {
Person person1("Alice", 25);
Person person2 = person1; // 浅拷贝
person1.printInfo(); // 输出:Name: Alice, Age: 25
person2.printInfo(); // 输出:Name: Alice, Age: 25
person2.printInfo(); // 输出:Name: Alice, Age: 25
person2.printInfo(); // 输出:Name: Alice, Age: 25
person1.printInfo(); // 输出:Name: Bob, Age: 30
person2.printInfo(); // 输出:Name: Alice, Age: 25
return 0;
}
上記の例では、Person
文字列型のメンバー変数m_name
と整数のメンバー変数を含むクラスを定義しましたm_age
。コンストラクターでは、new
演算子を使用してm_name
別のメモリ空間を割り当て、文字列をそのメモリ空間にコピーします。
次に、person1
オブジェクトを作成し、その値をperson2
オブジェクトに割り当てます。デフォルトのコピー コンストラクターは浅いコピーであるため、person2
オブジェクトのポインターはオブジェクトと同じメモリ空間m_name
を指します。オブジェクトperson1
を変更するときは、実際にオブジェクトを変更します。これがシャローコピーの特徴です。person2
m_name
person1
m_name
ディープ コピーを実装するには、コピー コンストラクターをカスタマイズし、その中にm_name
別のメモリ領域を割り当て、文字列をこの領域にコピーする必要があります。このように、person2
オブジェクトには独自の独立したm_name
メモリ空間があり、それを変更してもperson1
オブジェクトには影響しません。
要約すると、浅いコピーはオブジェクトの表面レベルのみをコピーしますが、深いコピーはオブジェクトが所有するリソースを含むオブジェクトのすべてのメンバー変数を再帰的にコピーします。ディープ コピーを実装するには、カスタム コピー コンストラクターが必要です。
6. 初期化リスト
初期化リストは、コンストラクターでメンバー変数を初期化する方法であり、ディープ コピーを実装するために使用できます。
上記の例では、初期化リストを使用して、手動でメモリを割り当てたり、コピー コンストラクターで文字列をコピーしたりせずに、ディープ コピーを実装できます。
次に、初期化リストを使用してディープ コピーを実装する例を示します。
#include <iostream>
#include <cstring>
class Person {
public:
Person(const char* name, int age) : m_age(age) {
m_name = new char[strlen(name) + 1];
strcpy(m_name, name);
}
// 拷贝构造函数
Person(const Person& other) : m_age(other.m_age) {
m_name = new char[strlen(other.m_name) + 1];
strcpy(m_name, other.m_name);
}
// 析构函数
~Person() {
delete[] m_name;
}
// 打印信息
void printInfo() {
std::cout << "Name: " << m_name << ", Age: " << m_age << std::endl;
}
private:
char* m_name;
int m_age;
};
int main() {
Person person1("Alice", 25);
Person person2 = person1; // 深拷贝
person1.printInfo(); // 输出:Name: Alice, Age: 25
person2.printInfo(); // 输出:Name: Alice, Age: 25
person2.printInfo(); // 输出:Name: Alice, Age: 25
person2.printInfo(); // 输出:Name: Alice, Age: 25
person1.printInfo(); // 输出:Name: Alice, Age: 25
person2.printInfo(); // 输出:Name: Alice, Age: 25
return 0;
}
上記の例では、コンストラクターの初期化リストに別のメモリ領域を割り当て、その領域に文字列をコピーします。このように、person2
オブジェクトには独自の独立したm_name
メモリ空間があり、それを変更してもperson1
オブジェクトには影響しません。
初期化リストを使用すると、コードが簡素化され、オブジェクトの構築時にメンバー変数が正しく初期化されるようになります。これは、ディープ コピーを実装する場合に役立ちます。
7. クラスメンバーとしてのクラスオブジェクト
あるクラスのメンバー変数が別のクラスのオブジェクトである場合、コピー コンストラクターでこれらのメンバー変数を正しくコピーする必要があります。
Person
以下は、クラスのメンバー変数の 1 つがそのAddress
クラスのオブジェクトである例です。
#include <iostream>
#include <cstring>
class Address {
public:
Address(const char* city, const char* street) {
m_city = new char[strlen(city) + 1];
strcpy(m_city, city);
m_street = new char[strlen(street) + 1];
strcpy(m_street, street);
}
Address(const Address& other) {
m_city = new char[strlen(other.m_city) + 1];
strcpy(m_city, other.m_city);
m_street = new char[strlen(other.m_street) + 1];
strcpy(m_street, other.m_street);
}
~Address() {
delete[] m_city;
delete[] m_street;
}
void printInfo() {
std::cout << "City: " << m_city << ", Street: " << m_street << std::endl;
}
private:
char* m_city;
char* m_street;
};
class Person {
public:
Person(const char* name, int age, const Address& address) : m_age(age), m_address(address) {
m_name = new char[strlen(name) + 1];
strcpy(m_name, name);
}
Person(const Person& other) : m_age(other.m_age), m_address(other.m_address) {
m_name = new char[strlen(other.m_name) + 1];
strcpy(m_name, other.m_name);
}
~Person() {
delete[] m_name;
}
void printInfo() {
std::cout << "Name: " << m_name << ", Age: " << m_age << std::endl;
m_address.printInfo();
}
private:
char* m_name;
int m_age;
Address m_address;
};
int main() {
Address address("New York", "Broadway");
Person person1("Alice", 25, address);
Person person2 = person1; // 深拷贝
person1.printInfo(); // 输出:Name: Alice, Age: 25, City: New York, Street: Broadway
person2.printInfo(); // 输出:Name: Alice, Age: 25, City: New York, Street: Broadway
return 0;
}
上記の例では、Person
クラスのメンバー変数の 1 つがAddress
クラスのオブジェクトです。Person
クラスのコピー コンストラクターでは、コピー コンストラクターを使用してオブジェクトを正しくコピーしますAddress
。このようにして、オブジェクトをコピーするとPerson
、Person
オブジェクトとそのメンバー変数Address
オブジェクトが深くコピーされます。
メンバ変数のメモリ空間はPerson
クラスのデストラクタ内で解放されるため、クラスのデストラクタではメンバ変数のメモリ空間を解放するだけでよいことに注意してください。m_name
m_address
Address
要約すると、あるクラスのメンバー変数が別のクラスのオブジェクトである場合、ディープ コピーを実装するには、コピー コンストラクターでこれらのメンバー変数を正しくコピーする必要があります。
8. 静的メンバー
静的メンバー変数は、クラスのインスタンスではなく、クラス自体に属します。したがって、静的メンバー変数はクラスのすべてのインスタンス間で共有されるため、コピー コンストラクターで静的メンバー変数をコピーする必要はありません。
Person
以下は、クラスに静的メンバー変数がある例ですcount
。
#include <iostream>
class Person {
public:
Person(const char* name, int age) : m_age(age) {
m_name = new char[strlen(name) + 1];
strcpy(m_name, name);
count++;
}
Person(const Person& other) : m_age(other.m_age) {
m_name = new char[strlen(other.m_name) + 1];
strcpy(m_name, other.m_name);
count++;
}
~Person() {
delete[] m_name;
count--;
}
static int getCount() {
return count;
}
private:
char* m_name;
int m_age;
static int count;
};
int Person::count = 0;
int main() {
Person person1("Alice", 25);
Person person2 = person1; // 深拷贝
std::cout << "Count: " << Person::getCount() << std::endl; // 输出:Count: 2
return 0;
}
上の例では、クラスには、作成されたオブジェクトの数を記録するPerson
静的メンバー変数があります。コンストラクターではオブジェクトの数をインクリメントして追跡し、デストラクターではオブジェクトの数をデクリメントして更新します。count
Person
count
count
count
コピー コンストラクターでは、静的メンバー変数はクラスのインスタンスではなくクラス自体に属しているため、静的メンバー変数をコピーする必要はありません。したがって、コピー コンストラクターでコピーする必要があるのは、非静的メンバー変数のみです。
要約すると、静的メンバー変数は、クラスのインスタンスではなくクラス自体に属しているため、コピー コンストラクターでコピーする必要はありません。