[C++] クラスに定義する関数の書き方をわかりやすく解説
C++でクラスに定義する関数は、クラスのメンバ関数と呼ばれます。
クラス内で宣言し、クラス外で定義することが一般的です。
クラス内で宣言する際には、関数のプロトタイプを記述し、クラス外で定義する際には「クラス名::関数名」という形式で記述します。
例えば、class MyClass
のメンバ関数myFunction
を定義する場合、MyClass::myFunction()
のように書きます。
コンストラクタやデストラクタも特別なメンバ関数です。
- クラス内でのメンバ関数の宣言方法
- メンバ関数のオーバーロードの実装
- 静的メンバ関数の特徴と使い方
- コンストラクタとデストラクタの役割
- フレンド関数の定義と利用方法
クラスの基本構造とメンバ関数の役割
クラスとは何か
クラスは、C++におけるオブジェクト指向プログラミングの基本的な構造です。
クラスは、データ(属性)とそれに関連する操作(メソッド)をまとめたもので、オブジェクトを生成するための設計図として機能します。
クラスを使用することで、プログラムの構造を整理し、再利用性を高めることができます。
メンバ関数の役割
メンバ関数は、クラス内で定義される関数で、クラスのオブジェクトに対して操作を行うための手段です。
メンバ関数は、オブジェクトのデータメンバにアクセスしたり、オブジェクトの状態を変更したりすることができます。
これにより、オブジェクト指向プログラミングの特徴であるカプセル化を実現します。
メンバ関数とデータメンバの関係
メンバ関数は、クラス内のデータメンバ(属性)にアクセスするための手段です。
データメンバは、クラスの状態を表す変数であり、メンバ関数はそれらのデータを操作するためのロジックを提供します。
メンバ関数を通じてデータメンバにアクセスすることで、データの整合性を保ちながら、オブジェクトの振る舞いを制御できます。
メンバ関数の宣言と定義の違い
メンバ関数の宣言は、関数の名前、戻り値の型、引数の型を指定するもので、クラス内で行います。
一方、メンバ関数の定義は、実際の処理内容を記述するもので、クラス外で行うこともできます。
以下に、メンバ関数の宣言と定義の例を示します。
#include <iostream>
using namespace std;
class MyClass {
public:
void displayMessage(); // メンバ関数の宣言
};
void MyClass::displayMessage() { // メンバ関数の定義
cout << "こんにちは、C++の世界!" << endl;
}
int main() {
MyClass obj;
obj.displayMessage(); // メンバ関数の呼び出し
return 0;
}
こんにちは、C++の世界!
この例では、MyClass
というクラスを定義し、その中にdisplayMessage
というメンバ関数を宣言しています。
メンバ関数の定義はクラス外で行い、main関数
内でオブジェクトを生成してメンバ関数を呼び出しています。
クラス内でのメンバ関数の宣言
メンバ関数の基本的な書き方
メンバ関数は、クラス内で宣言され、クラスのオブジェクトに関連付けられます。
基本的な書き方は以下の通りです。
class ClassName {
public:
void functionName(); // メンバ関数の宣言
};
この例では、ClassName
というクラス内にfunctionName
というメンバ関数を宣言しています。
public
はアクセス修飾子で、外部からアクセス可能であることを示しています。
戻り値と引数の指定方法
メンバ関数は、戻り値の型と引数を指定することができます。
戻り値の型は関数が返す値の型を示し、引数は関数に渡す値の型を示します。
以下に例を示します。
class Calculator {
public:
int add(int a, int b); // 戻り値と引数の指定
};
この例では、add
というメンバ関数が整数型の引数a
とb
を受け取り、整数型の戻り値を返すことを示しています。
アクセス修飾子(public, private, protected)の使い方
アクセス修飾子は、クラスのメンバに対するアクセスの可否を制御します。
主な修飾子は以下の通りです。
修飾子 | 説明 |
---|---|
public | 外部からアクセス可能 |
private | クラス内からのみアクセス可能 |
protected | クラス内および派生クラスからアクセス可能 |
以下に、アクセス修飾子を使った例を示します。
class MyClass {
public:
void publicFunction(); // publicメンバ関数
private:
void privateFunction(); // privateメンバ関数
protected:
void protectedFunction(); // protectedメンバ関数
};
constメンバ関数の宣言
const
メンバ関数は、オブジェクトの状態を変更しないことを示すために使用されます。
const
を関数の後ろに付けることで宣言します。
以下に例を示します。
class MyClass {
public:
void display() const; // constメンバ関数の宣言
};
この例では、display
メンバ関数がconst
として宣言されており、オブジェクトのデータメンバを変更しないことが保証されています。
オーバーロードされたメンバ関数の宣言
オーバーロードとは、同じ名前のメンバ関数を異なる引数の型や数で定義することを指します。
これにより、同じ機能を持つが異なる引数を受け取る関数を作成できます。
以下に例を示します。
class Printer {
public:
void print(int value); // 整数を印刷
void print(double value); // 浮動小数点数を印刷
void print(const string& value); // 文字列を印刷
};
この例では、print
というメンバ関数が異なる引数の型で3回宣言されています。
これにより、異なるデータ型に対して同じ名前の関数を使用することができます。
クラス外でのメンバ関数の定義
クラス外での定義の基本
メンバ関数は、クラス内で宣言された後、クラス外で定義することができます。
クラス外での定義は、関数の実装をクラスの宣言から分離するために使用され、コードの可読性を向上させます。
クラス外での定義には、クラス名とスコープ解決演算子::
を使用します。
クラス名::メンバ関数名の書き方
クラス外でメンバ関数を定義する際は、以下のようにクラス名とメンバ関数名を指定します。
class MyClass {
public:
void display(); // メンバ関数の宣言
};
void MyClass::display() { // クラス外での定義
cout << "Hello, World!" << endl;
}
この例では、MyClass
のdisplay
メンバ関数がクラス外で定義されています。
MyClass::display
という形式で、どのクラスのメンバ関数であるかを明示しています。
インライン関数として定義する方法
インライン関数は、関数の定義をクラス内に書くことができ、コンパイラが関数呼び出しを展開することでパフォーマンスを向上させることができます。
インライン関数は、inline
キーワードを使用して宣言します。
以下に例を示します。
class MyClass {
public:
inline void display() { // インライン関数の定義
cout << "インライン関数の例" << endl;
}
};
この例では、display
メンバ関数がインライン関数として定義されています。
インライン関数は、呼び出しのたびに関数のコードが展開されるため、オーバーヘッドが減少します。
コンストラクタとデストラクタの定義
コンストラクタとデストラクタは、クラスのオブジェクトが生成されるときと破棄されるときに自動的に呼び出される特別なメンバ関数です。
以下に、コンストラクタとデストラクタの定義の例を示します。
class MyClass {
public:
MyClass(); // コンストラクタの宣言
~MyClass(); // デストラクタの宣言
};
MyClass::MyClass() { // コンストラクタの定義
cout << "オブジェクトが生成されました。" << endl;
}
MyClass::~MyClass() { // デストラクタの定義
cout << "オブジェクトが破棄されました。" << endl;
}
この例では、MyClass
のコンストラクタとデストラクタがクラス外で定義されています。
オブジェクトが生成されるとコンストラクタが呼び出され、破棄されるとデストラクタが呼び出されます。
静的メンバ関数の定義
静的メンバ関数は、クラスのインスタンスに依存せずに呼び出すことができるメンバ関数です。
静的メンバ関数は、static
キーワードを使用して宣言されます。
以下に例を示します。
class MyClass {
public:
static void staticFunction(); // 静的メンバ関数の宣言
};
void MyClass::staticFunction() { // 静的メンバ関数の定義
cout << "静的メンバ関数の例" << endl;
}
この例では、staticFunction
という静的メンバ関数がクラス外で定義されています。
静的メンバ関数は、クラス名を使って直接呼び出すことができます。
コンストラクタとデストラクタの特別な役割
コンストラクタの基本的な書き方
コンストラクタは、クラスのオブジェクトが生成される際に自動的に呼び出される特別なメンバ関数です。
コンストラクタは、クラス名と同じ名前を持ち、戻り値を持ちません。
基本的な書き方は以下の通りです。
class MyClass {
public:
MyClass() { // コンストラクタの定義
cout << "コンストラクタが呼ばれました。" << endl;
}
};
この例では、MyClass
のコンストラクタが定義されており、オブジェクトが生成されるとメッセージが表示されます。
デフォルトコンストラクタと引数付きコンストラクタ
コンストラクタには、引数を持たないデフォルトコンストラクタと、引数を持つ引数付きコンストラクタがあります。
デフォルトコンストラクタは、引数なしでオブジェクトを生成する際に使用され、引数付きコンストラクタは、特定の値を設定するために使用されます。
以下に例を示します。
class MyClass {
public:
MyClass() { // デフォルトコンストラクタ
cout << "デフォルトコンストラクタが呼ばれました。" << endl;
}
MyClass(int value) { // 引数付きコンストラクタ
cout << "引数付きコンストラクタが呼ばれました。値: " << value << endl;
}
};
int main() {
MyClass obj1; // デフォルトコンストラクタが呼ばれる
MyClass obj2(10); // 引数付きコンストラクタが呼ばれる
return 0;
}
デフォルトコンストラクタが呼ばれました。
引数付きコンストラクタが呼ばれました。値: 10
デストラクタの基本的な書き方
デストラクタは、オブジェクトが破棄される際に自動的に呼び出される特別なメンバ関数です。
デストラクタは、クラス名の前に~
を付けて定義し、戻り値を持ちません。
基本的な書き方は以下の通りです。
class MyClass {
public:
~MyClass() { // デストラクタの定義
cout << "デストラクタが呼ばれました。" << endl;
}
};
この例では、MyClass
のデストラクタが定義されており、オブジェクトが破棄されるとメッセージが表示されます。
コンストラクタの初期化リストの使い方
コンストラクタの初期化リストは、データメンバを初期化するための特別な構文です。
初期化リストを使用することで、コンストラクタの本体が実行される前にデータメンバを初期化できます。
以下に例を示します。
class MyClass {
private:
int value;
public:
MyClass(int v) : value(v) { // 初期化リストの使用
cout << "コンストラクタが呼ばれました。値: " << value << endl;
}
};
int main() {
MyClass obj(20); // コンストラクタが呼ばれる
return 0;
}
コンストラクタが呼ばれました。値: 20
この例では、コンストラクタの初期化リストを使用して、value
データメンバを引数v
で初期化しています。
初期化リストを使うことで、より効率的にデータメンバを初期化することができます。
メンバ関数のオーバーロードとオーバーライド
メンバ関数のオーバーロードの基本
メンバ関数のオーバーロードは、同じ名前のメンバ関数を異なる引数の型や数で定義することを指します。
これにより、同じ機能を持つが異なるデータ型や引数を受け取る関数を作成できます。
オーバーロードは、関数の呼び出し時に引数の型や数に基づいて適切な関数が選択されます。
以下に例を示します。
class Printer {
public:
void print(int value) { // 整数を印刷
cout << "整数: " << value << endl;
}
void print(double value) { // 浮動小数点数を印刷
cout << "浮動小数点数: " << value << endl;
}
void print(const string& value) { // 文字列を印刷
cout << "文字列: " << value << endl;
}
};
この例では、print
というメンバ関数が異なる引数の型で3回定義されています。
オーバーロードの条件と注意点
オーバーロードを行う際には、以下の条件を満たす必要があります。
条件 | 説明 |
---|---|
引数の型が異なる | 同じ名前の関数でも、引数の型が異なればオーバーロード可能 |
引数の数が異なる | 同じ名前の関数でも、引数の数が異なればオーバーロード可能 |
戻り値の型は無関係 | 戻り値の型が異なってもオーバーロードにはならない |
注意点として、オーバーロードされた関数が引数の型や数によって曖昧になる場合、コンパイラはエラーを出します。
例えば、引数が整数と浮動小数点数のどちらにも変換可能な場合、どちらの関数を呼び出すかが不明確になります。
仮想関数とオーバーライドの違い
仮想関数は、基底クラスで定義され、派生クラスで再定義(オーバーライド)される関数です。
オーバーライドは、基底クラスのメンバ関数を派生クラスで再実装することを指します。
オーバーライドされた関数は、基底クラスのポインタや参照を通じて呼び出すことができ、実行時に適切な関数が選択されます。
これに対して、オーバーロードは同じクラス内での関数の定義の仕方です。
オーバーライドの書き方とoverrideキーワード
オーバーライドを行う際は、基底クラスのメンバ関数と同じシグネチャ(戻り値の型、関数名、引数の型と数)で派生クラスにメンバ関数を定義します。
C++11以降、override
キーワードを使用することで、オーバーライドであることを明示的に示すことができます。
以下に例を示します。
class Base {
public:
virtual void show() { // 仮想関数
cout << "Baseクラスのshow関数" << endl;
}
};
class Derived : public Base {
public:
void show() override { // オーバーライド
cout << "Derivedクラスのshow関数" << endl;
}
};
この例では、Baseクラス
のshow
メンバ関数が仮想関数として定義され、Derivedクラス
でオーバーライドされています。
override
キーワードを使用することで、基底クラスの関数を正しくオーバーライドしているかをコンパイラがチェックします。
静的メンバ関数と非静的メンバ関数の違い
静的メンバ関数の特徴
静的メンバ関数は、クラスのインスタンスに依存せずに呼び出すことができる特別なメンバ関数です。
静的メンバ関数は、static
キーワードを使用して宣言され、以下の特徴があります。
- インスタンスなしで呼び出し可能: 静的メンバ関数は、クラス名を使って直接呼び出すことができます。
- クラス全体に関連付けられる: 静的メンバ関数は、特定のオブジェクトではなく、クラス全体に関連付けられています。
- 非静的メンバにはアクセスできない: 静的メンバ関数は、非静的メンバ変数や非静的メンバ関数にアクセスすることができません。
これは、静的メンバ関数が特定のオブジェクトに依存しないためです。
非静的メンバ関数との違い
非静的メンバ関数は、クラスのインスタンスに関連付けられた関数であり、オブジェクトを通じて呼び出されます。
以下に、静的メンバ関数と非静的メンバ関数の主な違いを示します。
特徴 | 静的メンバ関数 | 非静的メンバ関数 |
---|---|---|
呼び出し方法 | クラス名を使って呼び出す | オブジェクトを通じて呼び出す |
インスタンス依存 | インスタンスに依存しない | インスタンスに依存する |
アクセス可能なメンバ | 静的メンバのみアクセス可能 | 非静的メンバにもアクセス可能 |
静的メンバ関数の使いどころ
静的メンバ関数は、以下のような場面で使用されます。
- ユーティリティ関数: 特定のオブジェクトに依存しない処理を行う関数を定義する際に便利です。
- ファクトリメソッド: オブジェクトを生成するためのメソッドを静的に定義することで、クラスのインスタンスを生成する際の柔軟性を高めます。
- カウンタや集計: クラス全体で共有されるデータを管理するために、静的メンバ関数を使用することがあります。
静的メンバ変数との関係
静的メンバ関数は、静的メンバ変数と密接に関連しています。
静的メンバ変数は、クラス全体で共有される変数であり、静的メンバ関数はこれらの変数にアクセスするための手段を提供します。
静的メンバ関数は、静的メンバ変数を操作するために使用されることが一般的です。
以下に例を示します。
#include <iostream>
using namespace std;
class MyClass {
private:
static int count; // 静的メンバ変数
public:
MyClass() {
count++; // コンストラクタでカウントを増やす
}
static void showCount() { // 静的メンバ関数
cout << "オブジェクトの数: " << count << endl;
}
};
int MyClass::count = 0; // 静的メンバ変数の初期化
int main() {
MyClass obj1;
MyClass obj2;
MyClass::showCount(); // 静的メンバ関数の呼び出し
return 0;
}
オブジェクトの数: 2
この例では、MyClass
の静的メンバ変数count
がオブジェクトの数をカウントし、静的メンバ関数showCount
がその値を表示しています。
静的メンバ関数を使用することで、クラス全体で共有されるデータにアクセスすることができます。
メンバ関数の応用例
演算子オーバーロードの実装
演算子オーバーロードは、C++の強力な機能の一つで、ユーザー定義の型に対して演算子の動作を変更することができます。
演算子オーバーロードを使用することで、クラスのオブジェクト同士の演算を自然に行うことができます。
以下に、+
演算子をオーバーロードする例を示します。
#include <iostream>
using namespace std;
class Point {
private:
int x, y;
public:
Point(int x, int y) : x(x), y(y) {} // コンストラクタ
// +演算子のオーバーロード
Point operator+(const Point& p) {
return Point(x + p.x, y + p.y);
}
void display() const {
cout << "(" << x << ", " << y << ")" << endl;
}
};
int main() {
Point p1(1, 2);
Point p2(3, 4);
Point p3 = p1 + p2; // 演算子オーバーロードを使用
p3.display(); // 結果を表示
return 0;
}
(4, 6)
この例では、Pointクラス
の+
演算子をオーバーロードし、2つのPoint
オブジェクトを加算することができるようにしています。
フレンド関数の定義と使い方
フレンド関数は、特定のクラスのプライベートメンバやプロテクテッドメンバにアクセスできる関数です。
フレンド関数を使用することで、クラスの外部からも内部のデータにアクセスすることができます。
以下にフレンド関数の例を示します。
#include <iostream>
using namespace std;
class Box {
private:
int width;
public:
Box(int w) : width(w) {} // コンストラクタ
// フレンド関数の宣言
friend void printWidth(Box b);
};
// フレンド関数の定義
void printWidth(Box b) {
cout << "ボックスの幅: " << b.width << endl;
}
int main() {
Box box(10);
printWidth(box); // フレンド関数を呼び出す
return 0;
}
ボックスの幅: 10
この例では、printWidth
というフレンド関数がBoxクラス
のプライベートメンバwidth
にアクセスしています。
テンプレートを使ったメンバ関数の定義
テンプレートを使用することで、異なるデータ型に対して同じ処理を行うメンバ関数を定義することができます。
以下に、テンプレートを使ったメンバ関数の例を示します。
#include <iostream>
using namespace std;
template <typename T>
class Calculator {
public:
T add(T a, T b) { // テンプレートメンバ関数
return a + b;
}
};
int main() {
Calculator<int> intCalc;
cout << "整数の合計: " << intCalc.add(5, 10) << endl; // 整数の加算
Calculator<double> doubleCalc;
cout << "浮動小数点数の合計: " << doubleCalc.add(5.5, 10.5) << endl; // 浮動小数点数の加算
return 0;
}
整数の合計: 15
浮動小数点数の合計: 16
この例では、Calculatorクラス
のテンプレートメンバ関数add
が、異なるデータ型に対して加算を行っています。
クラス内でラムダ式を使う方法
C++11以降、ラムダ式を使用することで、クラス内で簡潔に関数を定義することができます。
ラムダ式は、無名関数として定義され、特定の処理を簡単に記述できます。
以下に、クラス内でラムダ式を使用する例を示します。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class MyClass {
public:
void processVector() {
vector<int> vec = {1, 2, 3, 4, 5};
// ラムダ式を使用してベクターの要素を2倍にする
for_each(vec.begin(), vec.end(), [](int& n) { n *= 2; });
// 結果を表示
for (int n : vec) {
cout << n << " ";
}
cout << endl;
}
};
int main() {
MyClass obj;
obj.processVector(); // ラムダ式を使用したメソッドを呼び出す
return 0;
}
2 4 6 8 10
この例では、processVectorメソッド
内でラムダ式を使用して、ベクターの要素を2倍にしています。
ラムダ式を使うことで、コードが簡潔になり、可読性が向上します。
よくある質問
まとめ
この記事では、C++におけるクラスのメンバ関数の定義や使い方について詳しく解説しました。
特に、メンバ関数のオーバーロードやオーバーライド、静的メンバ関数の特徴、そしてフレンド関数やラムダ式の活用方法について触れました。
これらの知識を活用することで、より効率的で柔軟なプログラムを作成することが可能になります。
ぜひ、実際のプロジェクトや学習において、これらの概念を積極的に取り入れてみてください。