コードギャラリー

編集

このコードギャラリーは、さまざまなC++の機能やパターン、ベストプラクティスを示すためのサンプルコード集です。

行列型

編集

C++はユーザーが定義したクラスのメンバー演算子を定義することができます。

#include <iostream>
#include <sstream>
#include <stdexcept>
#include <vector>

template <size_t Rows, size_t Cols, typename T> 
class Matrix {
 private:
  std::vector<T> mat;

 public:
  // コンストラクタ
  Matrix() : mat(Rows * Cols, static_cast<T>(0)) {}

  // ディメンジョンが一致するかチェック
  auto dimensionsMatch(const Matrix &other, const char *operation) const -> bool {
    if (Rows != other.getRows() || Cols != other.getCols()) {
      std::ostringstream errorMessage;
      errorMessage << "Matrix dimensions do not match for " << operation << ": "
                   << "(" << getRows() << "x" << Cols << " and "
                   << other.getRows() << "x" << other.getCols() << ")";
      throw std::invalid_argument(errorMessage.str());
    }
    return true;
  }

  // constexprメソッドで行数と列数を取得
  [[nodiscard]] constexpr auto getRows() const -> size_t { return Rows; }
  [[nodiscard]] constexpr auto getCols() const -> size_t { return Cols; }

  // 加算
  auto operator+(const Matrix &other) const -> Matrix {
    dimensionsMatch(other, __func__);
    Matrix result;
    for (size_t i = 0; i < Rows * Cols; ++i) {
      result.mat[i] = mat[i] + other.mat[i];
    }
    return result;
  }

  // 減算
  auto operator-(const Matrix &other) const -> Matrix {
    dimensionsMatch(other, __func__);
    Matrix result;
    for (size_t i = 0; i < Rows * Cols; ++i) {
      result.mat[i] = mat[i] - other.mat[i];
    }
    return result;
  }

  // 乗算
  auto operator*(const Matrix &other) const -> Matrix {
    if (Cols != other.getRows()) {
      throw std::invalid_argument(
          "Matrix dimensions do not match for multiplication");
    }

    Matrix result;
    for (size_t i = 0; i < Rows; ++i) {
      for (size_t j = 0; j < other.getCols(); ++j) {
        for (size_t k = 0; k < Cols; ++k) {
          result.mat[i * Cols + j] +=
              mat[i * Cols + k] * other.mat[k * other.getCols() + j];
        }
      }
    }
    return result;
  }

  // 除算
  auto operator/(const T &scalar) const -> Matrix {
    if (scalar == 0) {
      throw std::invalid_argument("Division by zero");
    }

    Matrix result;
    for (size_t i = 0; i < Rows * Cols; ++i) {
      result.mat[i] = mat[i] / scalar;
    }
    return result;
  }

  // 要素へのアクセス
  auto operator()(size_t i, size_t j) -> T & {
    if (i >= Rows || j >= Cols) {
      throw std::out_of_range("Matrix indices out of range");
    }
    return mat[i * Cols + j];
  }

  auto operator()(size_t i, size_t j) const -> const T & {
    if (i >= Rows || j >= Cols) {
      throw std::out_of_range("Matrix indices out of range");
    }
    return mat[i * Cols + j];
  }

  // 文字列表現
  [[nodiscard]] auto toString() const -> std::string {
    std::ostringstream oss;
    for (size_t i = 0; i < Rows; ++i) {
      for (size_t j = 0; j < Cols; ++j) {
        oss << mat[i * Cols + j] << " ";
      }
      oss << std::endl;
    }
    return oss.str();
  }

  // ストリーム出力
  friend auto operator<<(std::ostream &os, const Matrix &matrix) -> std::ostream & {
    for (size_t i = 0; i < Rows; ++i) {
      for (size_t j = 0; j < Cols; ++j) {
        os << matrix.mat[i * Cols + j] << " ";
      }
      if (i != Rows - 1) {
        os << std::endl;
      }
    }
    return os;
  }
};

auto main() -> int {
  auto mat1 = Matrix<2, 2, int>();
  auto mat2 = Matrix<2, 2, int>();
  auto mat3 = Matrix<2, 3, int>();

  // auto mat = mat2 + mat3; // この行のコメントをはずすと
  // auto mat = mat2 + mat3;
  //            ~~~~ ^ ~~~~
  // mat.cpp:31:10: note: candidate function not viable: no known conversion from 'Matrix<[...], 3, [...]>' to 'const Matrix<[...], 2, [...]>' for 1st argument
  // Matrix operator+(const Matrix &other) const {
  //        ^
  // となります

  // 要素の設定
  mat1(0, 0) = 1;
  mat1(0, 1) = 2;
  mat1(1, 0) = 3;
  mat1(1, 1) = 4;

  mat2(0, 0) = 5;
  mat2(0, 1) = 6;
  mat2(1, 0) = 7;
  mat2(1, 1) = 8;

  // 加算
  std::cout << mat1 << std::endl
            << " +" << std::endl
            << mat2 << std::endl
            << " =" << std::endl
            << mat1 + mat2 << std::endl
            << std::endl;

  // 減算
  std::cout << mat1 << std::endl
            << " -" << std::endl
            << mat2 << std::endl
            << " =" << std::endl
            << mat1 - mat2 << std::endl
            << std::endl;

  // 乗算
  std::cout << mat1 << std::endl
            << " *" << std::endl
            << mat2 << std::endl
            << " =" << std::endl
            << mat1 * mat2 << std::endl
            << std::endl;

  // 除算
  std::cout << mat1 << std::endl
            << " / 2 =" << std::endl
            << mat1 / 2 << std::endl
            << std::endl;

  return 0;
}

このC++プログラムは、行列演算を行うためのテンプレートクラス Matrix を定義しています。以下に、プログラムの主な要素と機能を解説します。

  1. Matrix クラス:
    • Matrix クラスは、行列を表現するためのテンプレートクラスです。Rows および Cols は行列の行数と列数を示します。T は行列の要素の型を表します。
  2. コンストラクタ:
    • 行列を初期化するデフォルトコンストラクタが定義されています。全ての要素は初期値として 0 が設定されます。
  3. dimensionsMatch メソッド:
    • 行列の次元が一致するかどうかを確認するためのメソッドです。演算が実行される前に、行列の次元が一致していることを確認します。
  4. 四則演算のオーバーロード:
    • +, -, *, / 演算子がオーバーロードされています。これらの演算は、次元の一致を確認した上で、対応する要素ごとの操作を行います。
  5. operator() メソッド:
    • 行列の要素にアクセスするためのメソッドです。範囲外のインデックスへのアクセスをチェックし、要素への読み書きができます。
  6. toString メソッド:
    • 行列を文字列として表現するためのメソッドです。行ごとに要素をスペース区切りで表示し、改行で行を区切ります。
  7. operator<< 演算子のオーバーロード:
    • ストリーム出力演算子 << がオーバーロードされ、行列を標準出力に表示するためのメソッドです。
  8. main 関数:
    • main 関数では、Matrix クラスを使用して行列の加算、減算、乗算、除算を行います。また、要素へのアクセスや表示などが行われています。
  9. 注意事項:
    • Matrix クラスの演算は、行列の次元が一致していることを前提としています。行列の次元が異なる場合、例外がスローされます。
  10. コメント:
    • コメントによれば、auto mat = mat2 + mat3; の行をコメントアウトすると、コンパイルエラーが発生すると説明されています。これは、行列の次元が一致しないため、加算ができないことを示しています。

エラトステネスの篩

編集

このC++のコードは、エラトステネスの篩を使用して指定された数以下の素数を見つけるプログラムです。以下にコードの各部分の解説を示します。

##include <iostream>
#include <array>

auto main() -> int {
    constexpr int n = 100;
    std::array<bool, n + 1> sieve;
    sieve.fill(true);

    for (int i = 2; i <= n; i++) {
        if (sieve[i]) {
            std::cout << i << " ";

            for (int j = 2 * i; j <= n; j += i) {
                sieve[j] = false;
            }
        }
    }

    return 0;
}

最大公約数と最小公倍数

編集
#include <iostream>

// GCDの計算
auto gcd2(int m, int n) -> int {
    if (n == 0) {
        return m;
    }
    return gcd2(n, m % n);
}

// 1つの引数の場合の基本ケース
template <typename T>
auto gcd(T a) -> T {
    return a;
}

// 可変引数の場合の再帰的なGCD
template <typename T, typename... Args>
auto gcd(T first, Args... args) -> T {
    return gcd2(first, gcd(args...));
}

// LCMの計算
auto lcm2(int m, int n) -> int { return m * n / gcd2(m, n); }

// 1つの引数の場合の基本ケース
template <typename T>
auto lcm(T a) -> T {
    return a;
}

// 可変引数の場合の再帰的なLCM
template <typename T, typename... Args>
auto lcm(T first, Args... args) -> T {
    return lcm2(first, lcm(args...));
}

auto main() -> int {
    // GCDとLCMの計算
    std::cout << "gcd2(30, 45) => " << gcd2(30, 45) << std::endl;
    std::cout << "gcd(30, 72, 12) => " << gcd(30, 72, 12) << std::endl;
    std::cout << "gcd(32, 72, 12, 18) => " << gcd(32, 72, 12, 18) << std::endl;
    std::cout << "lcm2(30, 72) => " << lcm2(30, 72) << std::endl;
    std::cout << "lcm2(30, 42, 72) => " << lcm(30, 42, 72) << std::endl;
    std::cout << "lcm2(32, 42, 72, 18) => " << lcm(32, 42, 72, 18) << std::endl;

    return 0;
}

このプログラムは、C++を使って最大公約数(GCD)と最小公倍数(LCM)を計算する関数を実装しています。具体的には、可変引数テンプレートを使って任意の数の引数に対してこれらの計算を行います。プログラムの各部分について順に解説します。

gcd2関数
2つの整数の最大公約数をユークリッドの互除法を用いて計算します。
nが0の場合、mを返します(基本ケース)。
そうでない場合、gcd2(n, m % n)を再帰的に呼び出します。
gcd関数(基本ケース)
1つの引数の場合、その引数をそのまま返します。
gcd関数(可変引数の場合)
可変引数を再帰的に処理し、GCDを計算します。
gcd(args...)を呼び出して残りの引数のGCDを計算し、それをfirstと共にgcd2で処理します。
lcm2関数
2つの整数の最小公倍数を計算します。
最小公倍数は、m * nをそれらの最大公約数で割ることで求められます。
lcm関数(基本ケース)
1つの引数の場合、その引数をそのまま返します。
lcm関数(可変引数の場合)
可変引数を再帰的に処理し、LCMを計算します。
lcm(args...)を呼び出して残りの引数のLCMを計算し、それをfirstと共にlcm2で処理します。
main関数
さまざまな引数に対してGCDとLCMの計算を行い、その結果を出力します。


まとめ

このプログラムは、再帰テンプレート関数を用いて、可変数の引数に対して最大公約数(GCD)と最小公倍数(LCM)を計算します。基本ケースと再帰ケースを適切に定義することで、任意の数の引数に対しても正確に計算できます。

二分法

編集

このC++のコードは、二分法(Bisection Method)を使用して関数の根を見つける方法を示しています。以下にコードの各部分の説明を示します。

#include <cmath>
#include <iomanip>
#include <iostream>

/**
 * 2分法による方程式の数値解を求める関数
 * @param low 下限値
 * @param high 上限値
 * @param f 数値解を求める対象となる関数
 * @return 方程式の数値解
 */
auto bisection(double low, double high, double (*f)(double)) -> double {
    // 2分法による数値解の計算
    double const x = (low + high) / 2; // 中点を計算
    double const fx = f(x);            // 中点における関数の値

    // 数値解の精度が十分に高い場合、現在の中点を解として返す
    if (std::abs(fx) < 1.0e-10) {
        return x;
    }

    // 中点の関数の値が0より小さい場合、上限を中点に更新
    // そうでなければ、下限を中点に更新
    if (fx < 0.0) {
        low = x;
    } else {
        high = x;
    }

    // 更新された範囲で再帰的に2分法を適用
    return bisection(low, high, f);
}

/**
 * テスト用の関数
 */
auto main() -> int {
    // x - 1 の場合のテスト
    std::cout << std::setprecision(17)
              << bisection(0, 3, [](double x) { return x - 1; }) << std::endl;

    // x^2 - 1 の場合のテスト
    std::cout << std::setprecision(17)
              << bisection(0, 3, [](double x) { return x * x - 1; })
              << std::endl;

    return 0;
}
旧課程(-2012年度)高等学校数学B/数値計算とコンピューター#2分法の例を C++ に移植しました。

このC++コードでは、関数ポインタを使用して関数を引数として渡す方法と、lambda式を使用して匿名関数を渡す方法の2つの方法を示しています。

脚註

編集