オブジェクト指向プログラミング(OOP)のお勉強
C++をベースの考えてみる
c++のコンパイルの仕方
参考
構造化言語では解決できない2つの問題
- グローバル変数
- 再利用性
参考:いまさら聞けない構造化手法とオブジェクト指向の違い(めっちゃわかりやすい) http://www.ogis-ri.co.jp/casestudy/docs/CaseStudy_ESEC2010_StructOO.pdf
OOPの優れた3つの仕組み
- クラス
- ポリモーフィズム
- 継承
これらは構造化言語の課題を解決することができる。重複した無駄なロジックを排除し、必要な機能を整理整頓する仕組みを提供する。
参考:
新人プログラマに知っておいてもらいたい人類がオブジェクト指向を手に入れるまでの軌跡 - Qiita
クラスとは
関連性の強いサブルーチンとグローバル変数を1つにまとめて粒度の大きいソフトウェアを作る仕組み
- サブルーチンと変数をまとめる
- クラスの内部だけで使うサブルーチンを隠す
- 一つのクラスからインスタンスをたくさん作る
クラスの効能1:まとめる
結びつきの強いサブルーチンとグローバル変数を一つのクラスにまとめることができ、以下のメリットが得られる。
- 部品数が減る
- メソッドの名前付けが楽になる
- メソッドが探しやすくなる
まとめ前
- 準備:test.txtにatomと記入
#include <iostream> #include <fstream> using namespace std; fstream file; int fileNO; void openFile(const char* fileName){ file.open(fileName, ios::in); } void closeFile(){ file.close(); } char readFile(){ char str; file.get(str); return str; } int main(){ char result; const char* filename = "test.txt"; openFile(filename); result = readFile(); closeFile(); cout << result << endl; }
結果:
a
まとめ後
#include <iostream> #include <fstream> using namespace std; class TextFileReader{ //クラスという単位で見ると一つに!また、大きなまとまりになるため探しやすい!! private: fstream file; int fileNO; public: void open(const char* fileName){ //名前がシンプルに!!! file.open(fileName, ios::in); } void close(){ file.close(); } char read(){ char str; file.get(str); return str; } }; int main(){ char ch1; char ch2; const char* filename = "test.txt"; TextFileReader reader1; reader1.open(filename); ch1 = reader1.read(); reader1.close(); cout << ch1 << endl; TextFileReader* reader2 = new TextFileReader; reader2->open(filename); ch2 = reader2->read(); reader2->close(); delete reader2; cout << ch2 << endl; }
結果:
a
a
クラスの効能2:隠す
クラスに定義した変数やメソッドを他のクラスから隠すことができる
これにより保守性悪化の原因となるグローバル変数を使わずにプログラムを書くことができる。
上記コードの抜粋↓
class TextFileReader{ private: //privateとするとクラス内部にあるメソッドだけがアクセスできる!変更されたないし、勝手にいじらせへんよ〜 fstream file; int fileNO; public: //アプリケーションのどこからでもアクセスできる!いつでも呼び出してや〜 void open(const char* fileName){ file.open(fileName, ios::in); } void close(){ file.close(); } char read(){ char str; file.get(str); return str; }
ちなみになにもしてしないと勝手にprivateになるよ〜
クラスの効能3:たくさん作る
インスタンスはクラスを定義しておけばそこから実行時にいくつでも作る(メモリ領域を確保する)ことができる
そのインスタンスのメソッドの呼び方↓
インスタンスを格納する変数. メソッド名(引数)
インスタンスを格納する変数ポインタ->メソッド名(引数)
- 準備:robot1.txtにatomと記入 . robot2.txtにbig hero 6 と記入.
#include <iostream> #include <fstream> using namespace std; class TextFileReader{ private: fstream file; int fileNO; public: void open(const char* fileName){ file.open(fileName, ios::in); } void close(){ file.close(); } char read(){ char str; file.get(str); return str; } }; int main(){ char ch1; char ch2; char ch3; const char* filename1 = "robot1.txt"; const char* filename2 = "robot2.txt"; TextFileReader* reader1 = new TextFileReader; //インスタンス一つ目製造 TextFileReader* reader2 = new TextFileReader; //インスタンス二つ目製造 reader1->open(filename1); ch1 = reader1->read(); reader1->close(); reader2->open(filename2); ch2 = reader2->read(); reader2->close(); delete reader1; delete reader2; // open(filename); // result = read(); // close(); cout << ch1 << endl; cout << ch2 << endl; TextFileReader reader3; //インスタンス三つ目製造 reader3.open(filename1); ch3 = reader3.read(); reader3.close(); cout << ch3 << endl; }
結果:
a
b
a
インスタンス変数、グローバル変数、ローカル変数
インスタンス変数
- 別クラスからアクセスできないよう隠すことができる
- 一度インスタンス化されば破棄されるまでメモリ上に残る
ポリモーフィズム(多様性)とは(インターフェースの継承)
共通メインルーチンを作るための仕組み、つまり、呼び出す側のロジックを一本化する
ポリモーフィズムはサブルーチンを呼び出す側のロジックを一本化する仕組み、共通サブルーチンを作る仕組み
継承(実装の継承)
クラス定義の共通部分を別クラスにまとめて、コードの重複を排除する仕組み
型にはまろう
型を指定することのメリットは 1. コンパイラにメモリ領域を教える 1. プログラムのエラーを防止する
型にクラスを指定できる
型チェックには強い型付けと弱い型付けの2種類の方式がある
python:弱い型付け(実行時点でエラーを検出)
ポリモ・継承・型はめのサンプル
基底クラス | 派生クラス | mainクラス |
---|---|---|
Stream | <- InputStream | Polymorphism |
<- ArrayStream |
Stream.h
#ifndef STREAM_H_ #define STREAM_H_ class Stream { public : double Get() const; virtual bool Set() = 0; //純粋仮想関数->このクラス(抽象クラス)は実体をつくることができない。 //virtual bool Set(); //仮想関数 private : double m_n; }; #endif
Stream.cpp
#include "Stream.h" #include <iostream> using namespace std; double Stream::Get() const { return m_n; } // bool Stream::Set() { // cout << "Stream::Set" << endl; // m_n = -1; // return false; // }
InputStream.h
#ifndef INPUTSTREAM_H_ #define INPUTSTREAM_H_ #include "Stream.h" class InputStream : public Stream{ //継承することでGetを定義する必要がなくなる。(コードの重複を排除) public: bool Set(); }; #endif
InputStream.cpp
#include "InputStream.h" #include <iostream> using namespace std; bool InputStream::Set(){ //Setをオーバーライド cin >> m_n; return m_n >= 0; }
ArrayStream.h
#ifndef ARRAYSTREAM_H_ #define ARRAYSTREAM_H_ #include "Stream.h" class ArrayStream : public Stream //継承することでGetを定義する必要がなくなる。(コードの重複を排除) { public : ArrayStream(const double* array); bool Set(); private: const double* m_array; int m_i; }; #endif
ArrayStream.cpp
#include "ArrayStream.h" ArrayStream::ArrayStream(const double* array){ m_array = array; m_i = 0; } bool ArrayStream::Set(){ //Setをオーバーライド m_n = m_array[m_i]; if ( m_n >= 0){ ++m_i; return true; }else{ return false; } }
Polymorphism.cpp
#include "InputStream.h" #include "ArrayStream.h" #include <iostream> using namespace std; bool Average(Stream& stream){ //こうすることで、InputStreamとArrayStreamr両方のAverage関数を作る必要がなくなる。(呼び出す側のロジックの一本化) int count; double avr = 0; for( count = 0 ; stream.Set(); ++count){ //virtual bool Set() = 0;サブクラスのSetが呼ばれる。virtualがないとスーパークラスのSetが呼ばれる。 avr += stream.Get(); } if (count == 0){ return false; } avr /= count; cout << "平均値は" << avr << "です。" << endl; return true; } int main () { InputStream istream; //型の**クラス**指定 Average(istream); //アップキャスト static const double ARRAY[] = {0.5, 1.5, -1}; ArrayStream astream(ARRAY); //型の**クラス**指定 Average(astream); //アップキャスト }
$ g++ Stream.cpp InputStream.cpp ArrayStream.cpp Polymorphism.cpp
結果 :
1
2
3
-1
平均値は2です。
平均値は1です。
パッケージ
クラスをさらにまとめる仕組み
#include <iostream> using namespace std; namespace { //無名名前空間 void PowerOn(){ cout << "Robot Start" << endl; } } namespace Robot1 { void Func(){ cout << "Robot1::Func" << endl; } void Mode(){ cout << "Robot1::Mode" << endl; } } namespace Robot2 { void Func(){ cout << "Robot2::Func" << endl; } } void Func(){ cout << "::Func" << endl; } namespace Robot3{ namespace Robot4{ namespace Robot5{ void Attack(){ cout << "Robot3~5::Attack" <<endl; } } } } namespace R345 = Robot3::Robot4::Robot5; int main() { PowerOn(); Robot1::Func(); Robot1::Mode(); Robot2::Func(); ::Func(); R345::Attack(); return 0; }
例外
戻り値とは違う形でメソッドから特別なエラーを返す仕組み
- 例外を宣言しているメソッドを呼び出す側では例外を処理するロジックを正しく書いていないとプログラムはエラーになる
- そのまま上位にエラーを伝えるときはメソッドで例外を宣言するだけでよい
Exception.cpp
#include <iostream> #include <fstream> #include <cstdlib> using namespace std; void Open(ifstream& file, const char* filename){ file.open(filename); if(! file.is_open()){ throw "ファイルを開けませんでした!"; //メソッドで例外を宣言すると関数外、上位へエラーが伝わる } } void GetLine(ifstream& file, string& line){ getline(file, line); if (file.fail()){ throw "ファイルから読み込めませんでした!"; } } int main(){ try { //OpenやGetLineで投げられたエラーがここで処理される ifstream file; Open(file, "test.txt"); string line; GetLine(file, line); cout << line << endl; }catch(const char* error){ //char -> intにするとエラーになる(terminate called after throwing an instance of 'char const*') cerr << error << endl; return EXIT_FAILURE; } }
test.txtがない場合の結果:
ファイルを開けませんでした!
test.txtが空の場合の結果:
ファイルから読み込めませんでした!
ガベージコレクション
インスタンスを削除する処理をシステムが自動的に実行する仕組み
参考:めっちゃわかりやすい
5分で分かるガベージコレクションの仕組み | geechs magazine
所感
整理整頓の仕組みがたくさんあるな〜。整理整頓上手になりたい。
参考: