🖥️

【C++言語入門】 第3回 メソッド

2025/02/15に公開

https://youtu.be/jLnLxphzz70

四国めたん
\textcolor{pink}{四国めたん: }教師役ですわ

ずんだもん
\textcolor{lime}{ずんだもん: }生徒役なのだ

\footnotesize \textcolor{pink}{四国めたん:} こんにちは。四国めたんです

\footnotesize \textcolor{lime}{ずんだもん:} ずんだもんなのだ。こんにちはなのだ

\footnotesize \textcolor{pink}{四国めたん:} 今回は メソッド についてお話ししますわ

\footnotesize \textcolor{lime}{ずんだもん:} メソッド

\footnotesize \textcolor{pink}{四国めたん:} はい、クラスのメンバーの1つで、クラスに属する関数のことですわ

\footnotesize \textcolor{lime}{ずんだもん:} なるほど、さっそくお願いするのだ

メソッドはクラスの主要機能のひとつですよ

\footnotesize \textcolor{pink}{四国めたん:} C++言語はOOP(Object Oriented Programming)の代表的な言語のひとつですわ

\footnotesize \textcolor{lime}{ずんだもん:} そうなのか?

\footnotesize \textcolor{pink}{四国めたん:} はい、その中核の機能が クラス であり メソッド ですわ

\footnotesize \textcolor{lime}{ずんだもん:} なるほど、 メソッド について詳しく教えて欲しいのだ

構造体と関数を使いましょう

\footnotesize \textcolor{pink}{四国めたん:} メソッド について説明する前に、前回のプログラム例で使ったCircle構造体を使って、円の面積を求める関数を作ってみましょう

\footnotesize \textcolor{lime}{ずんだもん:} 円周率は3.14だったか?

\footnotesize \textcolor{pink}{四国めたん:} はい、でももう少し精度を上げるために20桁くらいの値を指定しましょう

\footnotesize \textcolor{lime}{ずんだもん:} 20桁も覚えていないのだ

\footnotesize \textcolor{pink}{四国めたん:} 今回は Copilot に教えてもらいましょう

20桁の円周率

\footnotesize \textcolor{lime}{ずんだもん:} 最近は便利になったのだ

\footnotesize \textcolor{pink}{四国めたん:} それでは実際にコードを書いてみましょう

#include<iostream>

#define PI (3.14159265358979323846)

/// @brief 円
struct Circle {
  double diameter_;     // 直径
};

double Area(Circle* pc) {
  double radius = pc->diameter_ / 2.0;
  double a = radius * radius * PI;
  return a;
}

int main(int argc, char* argv[]) {
  Circle c = {10.0};
  double area = Area(&c);
  std::cout << "円の直径は" << c.diameter_ << "cmです。" << std::endl;
  std::cout << "円の面積は" << area << "です。" << std::endl;
  return 0;
}

構造体での円の面積

\footnotesize \textcolor{lime}{ずんだもん:} 関数Areaで円の面積が得られているのだ

Area関数を解説します

\footnotesize \textcolor{pink}{四国めたん:} はい、円の面積を求める部分はArea関数にまとめていますわ

\footnotesize \textcolor{lime}{ずんだもん:} 引数にはCircle構造体へのポインタを指定しているのだ

\footnotesize \textcolor{pink}{四国めたん:} 円の面積はπr^2なので、まず、直径を示す"diameter_"メンバ変数をアロー演算子"->"で取得して1/2にして半径"radius"を計算していますわ

\footnotesize \textcolor{pink}{四国めたん:} その後、半径"radius"を2乗してπをかけて面積を取得していますわね

\footnotesize \textcolor{lime}{ずんだもん:} 円の面積の公式どおりなのだ

\footnotesize \textcolor{pink}{四国めたん:} なお、πは定数なので、"#define"ディレクティブで"PI"として定義していますわ

クラスと関数を使いましょう

\footnotesize \textcolor{lime}{ずんだもん:} Area関数では引数にCircle構造体を使っているのだ

\footnotesize \textcolor{pink}{四国めたん:} はい、その通りですわね

\footnotesize \textcolor{lime}{ずんだもん:} C言語では普通のプログラムなのだ

\footnotesize \textcolor{pink}{四国めたん:} それではクラスを使用した、C++言語のプログラムはどのようになるのでしょうか?

#include <iostream>

#define PI (3.14159265358979323846)

/// @brief 円
class Circle {
 public:
  double diameter_;  // 直径

  double Area() {
    double radius = diameter_ / 2.0;
    double a = radius * radius * PI;
    return a;
  }
};

int main(int argc, char* argv[]) {
  Circle c = {10.0};
  double area = c.Area();
  std::cout << "円の直径は" << c.diameter_ << "cmです。" << std::endl;
  std::cout << "円の面積は" << area << "です。" << std::endl;
  return 0;
}

クラスでの円の面積

\footnotesize \textcolor{lime}{ずんだもん:} 出力は変わっていないのだ

\footnotesize \textcolor{pink}{四国めたん:} その通りですわね

Areaメソッドを解説します

\footnotesize \textcolor{lime}{ずんだもん:} ところでArea関数がCircleクラスに含まれているのだ!

\footnotesize \textcolor{pink}{四国めたん:} ビックリですよね

\footnotesize \textcolor{pink}{四国めたん:} これがクラスの一番の特徴で、メンバ変数などのデータだけではなく、クラスの動作を関数として含めることができるのですわ

\footnotesize \textcolor{lime}{ずんだもん:} それは凄いのだ!

\footnotesize \textcolor{pink}{四国めたん:} はい、ちなみにクラスの内部に定義された関数は メンバ関数 もしくは メソッド と呼ばれますわ

\footnotesize \textcolor{lime}{ずんだもん:} なるほどなのだ

\footnotesize \textcolor{pink}{四国めたん:} なおAreaメソッドの処理の部分については概ね構造体の時と同じですわ

\footnotesize \textcolor{lime}{ずんだもん:} ところでdouble radius = diameter_ / 2.0;の行を見ると、メンバ変数"diameter_"を直接使っているのだ

\footnotesize \textcolor{pink}{四国めたん:} はい、メソッドからはクラス内のメンバーには無条件でアクセスできるのですわ

\footnotesize \textcolor{lime}{ずんだもん:} アクセス制限がprivate:となっていてもか?

\footnotesize \textcolor{pink}{四国めたん:} はい、問題なくアクセス可能ですわ

\footnotesize \textcolor{lime}{ずんだもん:} だからAreaメソッドの引数にCircleクラスを指定しなくてもOKなのか

\footnotesize \textcolor{pink}{四国めたん:} そうですわね

\footnotesize \textcolor{pink}{四国めたん:} 次にCircleクラスのインスタンス"c"からAreaメソッドを使う場合には、メンバ変数にアクセスする場合と同様に、ドット演算子"."を使いますわ

\footnotesize \textcolor{lime}{ずんだもん:} プログラム例ではdouble area = c.Area();のようにして使っているのだ

ゲッター(getter)とセッター(setter)

\footnotesize \textcolor{pink}{四国めたん:} ところで、クラスのメンバーへのアクセス制限は、デフォルトではprivate:に設定されていますわ

\footnotesize \textcolor{lime}{ずんだもん:} そうだったのだ

\footnotesize \textcolor{pink}{四国めたん:} いろいろと理由はあると思いますが、「クラス内のデータには外部から直接アクセスさせない」と云う強いメッセージですわ

\footnotesize \textcolor{lime}{ずんだもん:} たしかプログラム例ではAreaメソッドからメンバ変数"diameter_"に直接アクセスしていたのだ

\footnotesize \textcolor{lime}{ずんだもん:} でも外部からはどのようにしてクラス内のデータにアクセスするかがわからないのだ

\footnotesize \textcolor{pink}{四国めたん:} はい、private:に設定されたメンバ変数へのアクセスは、public:に設定されたメソッドを使って値の取得と設定を行いますわ

\footnotesize \textcolor{lime}{ずんだもん:} 結構、簡単な解決方法なのだ

\footnotesize \textcolor{pink}{四国めたん:} これらのメソッドを一般的に ゲッター (getter)や セッター (setter)と呼びますわ

\footnotesize \textcolor{lime}{ずんだもん:} なるほどなのだ

\footnotesize \textcolor{pink}{四国めたん:} では ゲッターセッター を使ってプログラム例を書き換えてみましょう

#include <iostream>

#define PI (3.14159265358979323846)

/// @brief 円
class Circle {
  double diameter_;  // 直径

 public:
  /// @brief diameter_のゲッター
  /// @return 直径
  double Diameter() { return diameter_; }

  /// @brief diameter_のセッター
  /// @param diameter 直径
  void Diameter(double diameter) { diameter_ = diameter; }

  double Area() {
    double radius = Diameter() / 2.0;
    double a = radius * radius * PI;
    return a;
  }
};

int main(int argc, char* argv[]) {
  Circle c;
  c.Diameter(10.0);
  double area = c.Area();
  std::cout << "円の直径は" << c.Diameter() << "cmです。" << std::endl;
  std::cout << "円の面積は" << area << "です。" << std::endl;
  return 0;
}

ゲッターとセッターを使った結果

\footnotesize \textcolor{lime}{ずんだもん:} 結果は同じなのだ

\footnotesize \textcolor{pink}{四国めたん:} Circleクラスのメンバ変数のアクセス制限についてはデフォルトのprivate:となりますわ

\footnotesize \textcolor{pink}{四国めたん:} そして ゲッターセッター を含むメソッドのアクセス制限はpublic:指定していますわ

\footnotesize \textcolor{lime}{ずんだもん:} そうなのだ

\footnotesize \textcolor{pink}{四国めたん:} 一般的に ゲッターセッター については、取り扱うメンバ変数名をパスカルケースにした名前を使うと良いでしょう

\footnotesize \textcolor{lime}{ずんだもん:} プログラム例ではメンバ変数"diameter_"に対しては"Diameter"を使っているのだ

\footnotesize \textcolor{pink}{四国めたん:} なお、 ゲッターセッター の違いを示すために、先頭に"Get"や"Set"を付加しても良いかもしれませんわね

\footnotesize \textcolor{lime}{ずんだもん:} なるほどなのだ

\footnotesize \textcolor{pink}{四国めたん:} ちなみに、プログラム例での ゲッターセッター は、同じメソッド名を使用していますわ

\footnotesize \textcolor{pink}{四国めたん:} C言語とは異なり、C++言語では条件が揃えば同じ関数名を使うことができますわ

\footnotesize \textcolor{lime}{ずんだもん:} 詳しく教えて欲しいのだ

\footnotesize \textcolor{pink}{四国めたん:} 詳細については後の回でお話ししますわ

\footnotesize \textcolor{lime}{ずんだもん:} 待っているのだ

クラスの初期化

ゲッターとセッターの動作

\footnotesize \textcolor{lime}{ずんだもん:} ところで ゲッターセッター の処理はどんなふうにすればいいのだ?

\footnotesize \textcolor{pink}{四国めたん:} まず ゲッター は基本的にはメンバ変数の値をそのまま返しますわ

\footnotesize \textcolor{lime}{ずんだもん:} なるほどなのだ

\footnotesize \textcolor{pink}{四国めたん:} セッター は基本的に引数として渡された値をそのままメンバ変数に代入しますわ

\footnotesize \textcolor{lime}{ずんだもん:} りょうかいなのだ

\footnotesize \textcolor{lime}{ずんだもん:} ところで 基本的には と敢えて言っているのはどうしてなのだ?

\footnotesize \textcolor{pink}{四国めたん:} それは場合によっては処理内容が変わる場合もあるからですわ

\footnotesize \textcolor{lime}{ずんだもん:} どんなふうに?

\footnotesize \textcolor{pink}{四国めたん:} 例えば何かの理由でメンバ変数"diameter_"を半径を示す"radius_"に変える必要が出たとしますわ

\footnotesize \textcolor{lime}{ずんだもん:} ふむふむ

\footnotesize \textcolor{pink}{四国めたん:} "radius_"に対する ゲッターセッター は別途作成するとして、"diameter_"の ゲッターセッター はどうするのでしょうか?

\footnotesize \textcolor{lime}{ずんだもん:} 削除するとか?

\footnotesize \textcolor{pink}{四国めたん:} もし、他の誰かがCircleクラスを使ってプログラムを組んでいた場合、"diameter_"の ゲッターセッター を無くすのは難しいと思いませんか?

\footnotesize \textcolor{lime}{ずんだもん:} たしかに...

\footnotesize \textcolor{pink}{四国めたん:} でも、"diameter_"の ゲッターセッター の処理を変えるだけで問題は解決しますわ

\footnotesize \textcolor{lime}{ずんだもん:} そうなのか?

\footnotesize \textcolor{pink}{四国めたん:} とりあえずプログラム例を書き換えてみますわ

#include <iostream>

#define PI (3.14159265358979323846)

/// @brief 円
class Circle {
  double radius_;  // 半径

 public:
  /// @brief diameter_のゲッター
  /// @return 直径
  double Diameter() { return radius_ * 2.0; }

  /// @brief diameter_のセッター
  /// @param diameter 直径
  void Diameter(double diameter) { radius_ = diameter / 2.0; }

  double Area() {
    double radius = Diameter() / 2.0;
    double a = radius * radius * PI;
    return a;
  }
};

int main(int argc, char* argv[]) {
  Circle c;
  c.Diameter(10);
  double area = c.Area();
  std::cout << "円の直径は" << c.Diameter() << "cmです。" << std::endl;
  std::cout << "円の面積は" << area << "です。" << std::endl;
  return 0;
}

半径を使った結果

\footnotesize \textcolor{lime}{ずんだもん:} メンバ変数を"radius_"に変えても同じように動作したのだ

\footnotesize \textcolor{pink}{四国めたん:} はい、メンバ変数"diameter_"を"radius_"に変えた場合でも、"diameter_"の ゲッターセッター の処理を変えただけですみましたわ

\footnotesize \textcolor{lime}{ずんだもん:} たしかにAreaメソッド内の処理やメイン関数内の処理を変更せずに済んでいるのだ

\footnotesize \textcolor{pink}{四国めたん:} もしAreaメソッド内やメイン関数内で、直接"diameter_"にアクセスしていた場合には、どちらも全ての処理を変更する必要がありましたわ

\footnotesize \textcolor{lime}{ずんだもん:} すごく大変になっていたのだ

\footnotesize \textcolor{pink}{四国めたん:} ですのでメンバ変数へのアクセスは、できるだけ ゲッターセッター を使うようにしてくださいね

\footnotesize \textcolor{lime}{ずんだもん:} わかったのだ

ゲッターやセッターは非効率?

ゲッターセッター は基本的に1行の簡単なプログラムです

そんな、たった1行のプログラムのために関数呼び出しを行うのは速度的に 非効率 のような気がします

ただ ゲッターセッター の効率は、あまり気にする必要はありません

後の回で説明しますが、クラス定義内で記述したメソッドの定義は、通常は関数呼び出しにはなりません

C言語における#defineマクロと同様に、メソッドを呼び出す部分が処理内容によって置き換えられます

ゲッターセッター の場合には、メンバ変数やメンバ変数への代入に置き換わりますので、オーバーヘッドは殆ど発生しません

また、最近はコンピューターの性能が上がり、かつコンパイラの性能も上がってるので、 ゲッターセッター を使うことによる速度への影響は考える必要もないでしょう

まとめ

\footnotesize \textcolor{pink}{四国めたん:} お疲れさまでした

\footnotesize \textcolor{lime}{ずんだもん:} おつかれさまなのだ

\footnotesize \textcolor{pink}{四国めたん:} メソッド については以上ですわ

Discussion