クラスに関する記事です。
初学者が分かりやすいように、基本的な単語の解説を行っています。
クラスとは?
クラスとは、複数の型の変数・関数をひとまとめにしたものです。
似たようなものに構造体があります。
- クラス
複数の型の変数・関数をひとまとめにしたもの - 構造体
複数の型の変数をひとまとめにしたもの
メンバとは?
クラスに含まれる変数を【メンバ変数】、関数を【メンバ関数】と言います。
クラスに含まれる【メンバ変数】【メンバ関数】を合わせて、【メンバ】とも呼びます。
オブジェクトとは?
クラスはあくまでもデータ型のようなもので、クラス自体に値を格納するといったことは、基本的に出来ません。
そのため、クラスを元に【オブジェクト】と呼ばれるものを作成する必要があります。
クラスという設計図を元に、オブジェクトという実物を作るようなイメージです。
なお、【オブジェクト】の代わりに【インスタンス】という言葉が使われるケースもあります。
微妙に意味合いが異なるのですが、ほぼ同義だと考えて良いと思います。
クラスの定義
class クラス名{
メンバ
};
クラスの内容を記述することを【クラスを定義する】と言います。
こちらで試しに、Countクラスを作ってみました。
【public】や【private】に関しては、記事の後半で解説しています。
// クラスの定義
class Count {
public:
void Plus(int PlusNum);
void Minus(int MinusNum);
void Reset();
void Sum();
private:
int total;
};
メンバ関数の定義
メンバ関数に関しては、関数の前に【クラス名::】を付けて、どのクラスのメンバ関数であるか明示したうえで処理を記述します。
// メンバ関数の定義
void Count::Plus(int PlusNum) {
total += PlusNum;
}
クラスの定義と同時に、メンバ関数の定義を行うことも可能です。
クラスの中で定義を行った関数は、自動的に【インライン関数】となります。
// クラスの定義
class Count {
public:
void Plus(int PlusNum) {
total += PlusNum;
}
void Minus(int MinusNum);
void Reset();
void Sum();
private:
int total;
};
インライン関数に関しては、上記の記事で解説しています。
オブジェクトの生成
クラス名 オブジェクト名;
オブジェクトを作成する際は、クラス名の後ろにオブジェクト名を記述します。
一つのクラスから複数のオブジェクトを生成することも可能です。
// クラスの定義
class Count {
public:
void Plus(int PlusNum);
void Minus(int MinusNum);
void Reset();
void Sum();
private:
int total;
};
// オブジェクトの生成
Count Count1;
Count Count2;
メンバ関数の呼び出し
同じクラスの場合
同じクラス内のメンバ関数は、関数名だけ記述します。
// メンバ関数の定義
void Count::Minus(int MinusNum) {
total -= MinusNum;
if (total < 0)
Reset(); // メンバ関数の呼び出し
}
void Count::Reset() {
total = 0;
}
オブジェクトの場合
オブジェクトのメンバ関数の場合は、ピリオド【.】を用いて、どのオブジェクトのメンバ関数なのか明示する必要があります。
オブジェクト名.関数;
// メンバ関数の定義
void Count::Plus(int PlusNum) {
total += PlusNum;
}
// オブジェクトの生成
Count Count1;
int main() {
// メンバ関数の呼び出し
Count1.Plus(5);
}
クラスの使用例
コーディング例①
#include <iostream>
// クラスの定義
class Count {
public:
void Plus(int PlusNum);
void Minus(int MinusNum);
void Reset();
void Sum();
private:
int total;
};
// メンバ関数の定義
void Count::Plus(int PlusNum) {
total += PlusNum;
}
void Count::Minus(int MinusNum) {
total -= MinusNum;
if (total < 0)
Reset();
}
void Count::Reset() {
total = 0;
}
void Count::Sum() {
std::cout << "合計は" << total << "です。\n";
}
// オブジェクトの生成
Count Count1;
int main() {
// メンバ関数の呼び出し
Count1.Plus(5);
Count1.Minus(2);
Count1.Sum();
return 0;
}
出力
合計は3です。
アクセス指定子
クラスのメンバへのアクセスを制御する際は、アクセス指定子を使用します。
アクセス指定子はクラス内で何度でも記述出来ますが、可読性が悪くなるので、まとめて記述することをおすすめします。
// クラスの定義
class Count {
public:
void Plus(int PlusNum);
void Minus(int MinusNum);
private:
int total;
protected:
void Reset();
void Sum();
};
public
【public】を指定したメンバは、クラスの内部だけでなく、外部からもアクセスすることが出来るようになります。
private
【private】を指定したメンバは、クラスの内部からのみアクセスすることが出来るようになります。
アクセス指定子を記述しない場合は、自動的に【private】が適用されます。
クラスの外部から【private】を指定したメンバ変数の値を直接変更することは出来ませんが、メンバ関数を介することで値を変更することは出来ます。
protected
【protected】を指定したメンバは、クラスの内部と派生クラス(継承したクラス)からのみアクセスすることが出来るようになります。
フレンド
アクセス指定子として【private】を設定したメンバにどうしてもアクセスしたい場合には、【friend】という機能を使います。
ただし、アクセス制限を無視することになるので、使用する際には注意する必要があります。
friend ○○○;
コーディング例①
FriendClassはMyClassのフレンドクラスですが、MyClassはFriendClassのフレンドクラスではありません。
フレンドは一方向だということに注意が必要です。
#include <iostream>
class MyClass {
private:
int PrivateData;
public:
// フレンドの宣言
friend class FriendClass;
void PrintPrivateData() {
std::cout << "Private Data: " << PrivateData << std::endl;
}
};
class FriendClass {
public:
void ModifyPrivateData(MyClass &obj, int NewValue) {
// フレンドクラスからprivateDataにアクセス
obj.PrivateData = NewValue;
}
};
int main() {
MyClass obj;
FriendClass FriendObj;
// FriendClassからprivateDataにアクセス
FriendObj.ModifyPrivateData(obj, 10);
// フレンドによって変更されたprivateDataを表示
obj.PrintPrivateData();
return 0;
}
出力
Private Data: 10
コンストラクタ
コンストラクタは、オブジェクト生成時に自動的に呼び出される特殊なメンバ関数です。
コンストラクタの名前はクラス名と同じであり、アクセス指定子は【public】にします。
コンストラクタには戻り値が無いため、データ型を記述する必要はありません。
オブジェクト生成時に呼び出されるので、メンバ変数の初期化やメモリの割り当てなどに使用されます。
また、オーバーロードの機能を使うことで、クラス内に複数のコンストラクタを定義することが可能です。
コンストラクタを何も記述しない場合は、デフォルトコンストラクタと呼ばれる、何もしないコンストラクタが自動的に生成されます。
// クラスの定義
class Count {
public:
Count(); // コンストラクタの宣言
void Plus(int PlusNum);
void Minus(int MinusNum);
void Sum();
private:
int total;
};
// コンストラクタの定義
Count::Count() {
total = 0;
}
コピーコンストラクタ
オブジェクトを既存のオブジェクトで初期化したい場合には、コピーコンストラクタと呼ばれるコンストラクタを使用します。
コピーコンストラクタは、以下のように記述します。
クラス名 オブジェクト名(コピーするオブジェクト名);
クラス名 オブジェクト名 = コピーするオブジェクト名;
既にあるオブジェクトに別のオブジェクトを代入する場合は、コピーコンストラクタは呼ばれません。
その場合は、operator=()というメンバ関数が呼ばれます。
コーディング例①
#include <iostream>
// クラスの定義
class Count {
public:
int num;
};
int main() {
Count Count1;
Count1.num = 5;
std::cout << Count1.num << std::endl;
// コピーコンストラクタ①
Count Count2(Count1);
std::cout << Count2.num << std::endl;
// コピーコンストラクタ②
Count Count3(Count1);
std::cout << Count3.num << std::endl;
return 0;
}
出力
5
5
5
デストラクタ
デストラクタは、オブジェクトが破棄される際に自動的に呼び出される特殊なメンバ関数です。
デストラクタの名前はクラス名にチルダ【~】を付けたもので、アクセス指定子は【public】にします。
デストラクタには戻り値が無いため、データ型を記述する必要はありません。
また、クラスの中に1つしか定義することが出来ません。
オブジェクトが破棄される際に呼び出されるので、メモリの解放などに使用されます。
// クラスの定義
class Count {
public:
Count(); // コンストラクタの宣言
~Count(); // デストラクタの宣言
void Plus(int PlusNum);
void Minus(int MinusNum);
void Sum();
private:
int total;
};
// デストラクタの定義
Count::~Count() {
// 終了処理やメモリの開放など
}
動的にオブジェクトを生成
new
変数と同じような記述でオブジェクトを生成する場合は、静的にメモリを確保しています。
オブジェクトのためのメモリ領域を動的に確保したい場合には、【new】を使います。
new クラス;
new クラス(引数);
// メモリの確保
Count *pCount;
pCount = new Count;
動的にメモリを確保した場合は、メモリを確保したタイミングでコンストラクタが呼ばれます。
静的にメモリを確保した場合は、宣言のタイミングでコンストラクタが呼ばれます。
引数のあるコンストラクタを呼び出したい場合は、クラスの後ろに(引数)を付けます。
似たような機能にmalloc()関数がありますが、コンストラクタが呼ばれないという欠点があります。
そのため、オブジェクトを生成する際には、【new】を使用します。
delete
【new】で確保したメモリを解放したい場合は、【delete】を使用します。
delete クラス;
【delete】を使用した場合は、メモリを解放する直前にデストラクタが呼ばれます。
コーディング例①
コンストラクタ・デストラクタが呼び出されるタイミングが分かりやすいかと思います。
#include <iostream>
// クラスの定義
class ClassA {
public:
ClassA() {
std::cout << "コンストラクタが呼び出されました。" << std::endl;
}
~ClassA() {
std::cout << "デストラクタが呼び出されました。" << std::endl;
}
};
int main() {
// メモリの確保
ClassA *pClassA = new ClassA;
std::cout << "動的にメモリを確保しました。" << std::endl;
// メモリの解放
delete pClassA;
return 0;
}
出力
コンストラクタが呼び出されました。
動的にメモリを確保しました。
デストラクタが呼び出されました。
静的メンバ変数
静的メンバ変数は、複数のオブジェクトで共通のメモリ領域を持つメンバ変数です。
そのため、静的メンバ変数は共通の値を保持します。
オブジェクトの生成をするたびに初期化されてはいけないので、静的メンバ変数の初期化は、コンストラクタで行ってはいけません。
記述方法
static メンバ変数;
静的メンバ変数は、メンバ変数の前に【static】を記述します。
アクセス方法
【public】が指定されている静的メンバ変数は、オブジェクトを生成しなくとも、スコープ解決演算子を用いてアクセスすることが可能です。
また、普通のメンバ変数と同様に、オブジェクトからアクセスすることも可能です。
コーディング例①
#include <iostream>
// 基底クラス(親クラス)
class Fruit {
public:
// 静的メンバ変数
static int price;
};
// 静的メンバ変数の初期化(データ型が必要)
int Fruit::price = 100;
int main() {
Fruit::price = 100;
std::cout << Fruit::price << std::endl;
Fruit MyFruit;
MyFruit.price = 200;
std::cout << MyFruit.price << std::endl;
return 0;
}
出力
100
200
静的メンバ関数
静的メンバ関数は、静的メンバ変数にだけアクセスする関数です。
静的メンバ関数から、メンバ変数を利用するためには、メンバ変数を静的メンバ変数にする必要があります。
記述方法
static メンバ関数;
静的メンバ関数は、メンバ関数の前に【static】を記述します。
アクセス方法
【public】が指定されている静的メンバ関数は、オブジェクトを生成しなくとも、スコープ解決演算子を用いてアクセスすることが可能です。
また、普通のメンバ関数と同様に、オブジェクトからアクセスすることも可能です。
コーディング例①
#include <iostream>
// 基底クラス(親クラス)
class Fruit {
public:
static void SetPrice(int p) {
price = p;
}
static int GetPrice() {
return price;
}
// 静的メンバ変数
static int price;
};
// 静的メンバ変数の初期化(データ型が必要)
int Fruit::price = 0;
int main() {
Fruit::SetPrice(100);
std::cout << Fruit::GetPrice() << std::endl;
Fruit MyFruit;
MyFruit.SetPrice(200);
std::cout << MyFruit.GetPrice() << std::endl;
return 0;
}
出力
100
200
オブジェクトをメンバにする
当然ですが、オブジェクトをクラスのメンバにすることも可能です。
メンバにしたオブジェクトは、コンストラクタで初期化を行います。
ただし、コンストラクタの初期化リストという部分で初期化を行います。
初期化リスト
コンストラクタ()
:オブジェクトのコンストラクタ(引数), 変数(値)
{
処理
}
初期化リストは、上記の赤下線部分のように記述します。
オブジェクトの初期化だけでなく、変数の初期化をすることも可能です。
これだと分かりにくいと思うので、実際の記述例を参考にしてみてください。
コーディング例①
#include <iostream>
class Date {
public:
// コンストラクタ
Date(int y, int m, int d) {
year = y;
month = m;
day = d;
}
int year, month, day;
};
class ClassA {
public:
// コンストラクタ
ClassA()
:MyDate(2023, 7, 1), num(100) // 初期化リスト
{
std::cout << "Hello" << std::endl;
}
// メンバ変数
Date MyDate;
int num;
};
int main() {
ClassA MyClass;
std::cout << MyClass.MyDate.year << std::endl;
std::cout << MyClass.MyDate.month << std::endl;
std::cout << MyClass.MyDate.day << std::endl;
std::cout << MyClass.num << std::endl;
return 0;
}
出力
Hello
2023
7
1
100
オブジェクトの配列化
オブジェクトを配列化することも可能です。
配列の初期化
オブジェクトの配列[〇] = {コンストラクタ(引数), ・・・}
オブジェクトの配列を初期化する場合は、上記のように記述します。
配列の途中までを初期化することも可能です。
また、コンストラクタの引数が一つだけの場合には、引数の内容だけを記述するだけでも大丈夫です。
コーディング例①
#include <iostream>
class Date {
public:
// コンストラクタ
Date(int y, int m, int d) {
year = y;
month = m;
day = d;
}
int year, month, day;
};
class Year {
public:
Year(int y) {
year = y;
}
int year;
};
int main() {
// オブジェクトの初期化
Date MyDate[2] = { Date(2000, 1, 1), Date(2020, 2, 2) };
Year MyYear[2] = { 2000, 2020 };
for (int i = 0; i < 2; i++) {
std::cout << MyDate[i].year << std::endl;
std::cout << MyDate[i].month << std::endl;
std::cout << MyDate[i].day << std::endl;
}
std::cout << MyYear[0].year << std::endl;
std::cout << MyYear[1].year << std::endl;
return 0;
}
出力
2000
1
1
2020
2
2
2000
2020
クラスの継承
クラスの継承に関しては、以下の記事でまとめています。
また、オーバーライドや仮想関数に関しても解説しています。