C++には名前空間(namespace)というものがあります.
C言語では同じ名前の変数や関数,クラスを作ることができませんでしたが,大規模なプロジェクトになってくるとどうしても重複してしまうことがあります.
そのため,変な接頭語や接尾語を関数につける場合が多くありました.
一方,C++ではライブラリごとやプロジェクトごとに名前空間を指定することで,同じ名前でも所属している名前空間が異なれば別物として扱うことができます.
標準ライブラリはstdという名前空間にあります.
下記ではcoutにstdという名前空間を指定しています.
標準ライブラリのクラスや関数を使う時は,std::をつける
なお,書籍等では
これは名前空間stdの中の物を全部使う(つまりstd::を省略してもよい)という意味になります.
折角,名前が衝突しないように名前空間をつけたのにそれを解除することになるのでやめましょう.
C言語では同じ名前の変数や関数,クラスを作ることができませんでしたが,大規模なプロジェクトになってくるとどうしても重複してしまうことがあります.
そのため,変な接頭語や接尾語を関数につける場合が多くありました.
一方,C++ではライブラリごとやプロジェクトごとに名前空間を指定することで,同じ名前でも所属している名前空間が異なれば別物として扱うことができます.
標準ライブラリはstdという名前空間にあります.
下記ではcoutにstdという名前空間を指定しています.
#include <iostream> int main() { std::cout << "Hello World" << std::endl; return 0; }
標準ライブラリのクラスや関数を使う時は,std::をつける
なお,書籍等では
using namespace std;というおまじないを書きますと解説しているものがありますが,
これは名前空間stdの中の物を全部使う(つまりstd::を省略してもよい)という意味になります.
折角,名前が衝突しないように名前空間をつけたのにそれを解除することになるのでやめましょう.
では画面に出力するiostreamだけを使って非オブジェクト指向のサンプルコードを書いてみます.
下記は2x2行列を逆行列,画面出力,積を計算します.
PrintMatrixやInverseなどの引数がポインタになっています.
ポインタと配列の関係については様々な解説があるので調べてみてください.
無事に最後単位行列が表示されたら成功です.
下記は2x2行列を逆行列,画面出力,積を計算します.
#include <iostream> void PrintMatrix(double* mat) { std::cout << mat[0] << '\t' << mat[1] << std::endl; std::cout << mat[2] << '\t' << mat[3] << std::endl; std::cout << std::endl; } void Inverse(double* mat, double* inv) { double det = mat[0]*mat[3]-mat[1]*mat[2]; if( det != 0 ) { inv[0] = mat[3] / det; inv[1] = -mat[1]/det; inv[2] = -mat[2] / det; inv[3] = mat[0]/det; } } void Multiply(double* mat1, double* mat2, double* mat3) { mat3[0] = mat1[0]*mat2[0]+mat1[1]*mat2[2]; mat3[1] = mat1[0]*mat2[1]+mat1[1]*mat2[3]; mat3[2] = mat1[2]*mat2[0]+mat1[3]*mat2[2]; mat3[3] = mat1[2]*mat2[1]+mat1[3]*mat2[3]; } int main() { double mat[4]; // 2 by 2 matrix mat[0] = 1; mat[1] = 2; mat[2] = 3; mat[3] = 4; double inv_mat[4]; Inverse(mat, inv_mat); std::cout << "mat = " << std::endl; PrintMatrix(mat); std::cout << "inv_mat = " << std::endl; PrintMatrix(inv_mat); double mul_mat[4]; Multiply(mat, inv_mat, mul_mat); std::cout << "mat * inv_mat = " << std::endl; PrintMatrix(mul_mat); ::system("PAUSE"); return 0; }
PrintMatrixやInverseなどの引数がポインタになっています.
ポインタと配列の関係については様々な解説があるので調べてみてください.
無事に最後単位行列が表示されたら成功です.
上記において,
このデータとデータ操作は切っても切れない関係,つまりこのデータのためにだけ存在する関数です.
しかしコード自体にはそのような意味は含まれていません.それを言語仕様でわかるようにしたのがクラスです.
では少しだけクラス化したコードです.
- double mat[4]: データ
- PrintMatrix, Inverse, Multiply: データの操作
このデータとデータ操作は切っても切れない関係,つまりこのデータのためにだけ存在する関数です.
しかしコード自体にはそのような意味は含まれていません.それを言語仕様でわかるようにしたのがクラスです.
では少しだけクラス化したコードです.
#include <iostream> // class Matrix2x2 class Matrix2x2 { public: // Constructor -- initialize of the class Matrix2x2(); void Set(int i, int j, double value); double Get(int i, int j) const; void PrintMatrix() const; Matrix2x2 Inverse() const; Matrix2x2 Multiple(Matrix2x2 const& other) const; private: double mat[4]; }; Matrix2x2::Matrix2x2() { mat[0] = 0.; mat[1] = 0.; mat[2] = 0.; mat[3] = 0.; } void Matrix2x2::Set(int i, int j, double value) { int idx = 2*i+j; if( idx < 0 || idx > 3 ) { std::cout << "Error: Out of range" << std::endl; } mat[idx] = value; } double Matrix2x2::Get(int i, int j) const { int idx = 2*i+j; if( idx < 0 || idx > 3 ) { std::cout << "Error: Out of range" << std::endl; } return mat[idx]; } void Matrix2x2::PrintMatrix() const { std::cout << mat[0] << '\t' << mat[1] << std::endl; std::cout << mat[2] << '\t' << mat[3] << std::endl; std::cout << std::endl; } Matrix2x2 Matrix2x2::Inverse() const { Matrix2x2 inv; double det = Get(0,0)*Get(1,1)-Get(1,0)*Get(0,1); if( det != 0 ) { inv.Set(0,0, this->Get(1,1)/det); inv.Set(0,1,-Get(0,1)/det); inv.Set(1,0,-this->Get(1,0)/det); inv.Set(1,1, Get(0,0)/det); } return inv; } Matrix2x2 Matrix2x2::Multiple(Matrix2x2 const& other) const { Matrix2x2 result; result.Set(0,0, this->Get(0,0)*other.Get(0,0)+this->Get(0,1)*other.Get(1,0)); result.Set(0,1, this->Get(0,0)*other.Get(0,1)+this->Get(0,1)*other.Get(1,1)); result.Set(1,0, this->Get(1,0)*other.Get(0,0)+this->Get(1,1)*other.Get(1,0)); result.Set(1,1, this->Get(1,0)*other.Get(0,1)+this->Get(1,1)*other.Get(1,1)); return result; } // End of Matrix2x2 int main() { Matrix2x2 mat; mat.Set(0, 0, 1); mat.Set(0, 1, 2); mat.Set(1, 0, 3); mat.Set(1, 1, 4); Matrix2x2 inv_mat = mat.Inverse(); std::cout << "mat = " << std::endl; mat.PrintMatrix(); std::cout << "inv_mat = " << std::endl; inv_mat.PrintMatrix(); Matrix2x2 mul_mat = mat.Multiple(inv_mat); std::cout << "mul_mat = " << std::endl; mul_mat.PrintMatrix(); ::system("PAUSE"); return 0; }
クラスは設計図であり,Matrix2x2 matと宣言することで実体を作ります.
この実体のことをインスタンスやオブジェクトと言います.
この場合,m1とm2は同じMatrix2x2ですが,異なるオブジェクトです.
同じ設計図から2つ部品を作ったと思ってください.実体としては異なります.
m1のメンバ関数Inverseを呼んだ時は,当然m1の逆行列を計算します.
外部からだとm1.Inverse()やm2.Printと呼べばいいですが,メンバ関数の中で自分自身を呼ぶにはどうするかというとthisを使います.
わかりやすくするためであって,殆どの場合で省略可能です.
m1.Multiply(m2)においては,'this->Get'で手に入るものがm1(左), other.Getで手に入るものがm2(右)になります.
この実体のことをインスタンスやオブジェクトと言います.
Matrix2x2 m1; Matrix2x2 m2;
この場合,m1とm2は同じMatrix2x2ですが,異なるオブジェクトです.
同じ設計図から2つ部品を作ったと思ってください.実体としては異なります.
m1のメンバ関数Inverseを呼んだ時は,当然m1の逆行列を計算します.
外部からだとm1.Inverse()やm2.Printと呼べばいいですが,メンバ関数の中で自分自身を呼ぶにはどうするかというとthisを使います.
わかりやすくするためであって,殆どの場合で省略可能です.
m1.Multiply(m2)においては,'this->Get'で手に入るものがm1(左), other.Getで手に入るものがm2(右)になります.
使う側の立場で考えてみましょう.
非オブジェクト指向の場合は,
PrintMatrix(double*);
なので,doubleがいくつの配列を想定しているかわかりません.
double a[2];
PrintMatrix(a);
でもコンパイルエラーを生じないのです.
実行時にエラーがおきて落ちます.
オブジェクト指向の場合は
PrintMatrixはそのような間違いをする可能性はなく,クラスMatrix2x2の中に定義したdouble mat[4]が自動的に出力されます.
積でも,右辺としてMatrix2x2を引数とするので,必ず Matrix2x2 = Matrix2x2 * Matrix2x2となります.
読みやすさはまだよくないですが確実に安全性が高まっています.
非オブジェクト指向の場合は,
PrintMatrix(double*);
なので,doubleがいくつの配列を想定しているかわかりません.
double a[2];
PrintMatrix(a);
でもコンパイルエラーを生じないのです.
実行時にエラーがおきて落ちます.
オブジェクト指向の場合は
PrintMatrixはそのような間違いをする可能性はなく,クラスMatrix2x2の中に定義したdouble mat[4]が自動的に出力されます.
積でも,右辺としてMatrix2x2を引数とするので,必ず Matrix2x2 = Matrix2x2 * Matrix2x2となります.
読みやすさはまだよくないですが確実に安全性が高まっています.
コメントをかく