風の迷路

移転先:http://fermiumbay13.hatenablog.com/ ここはヤフーブログ終了に伴い12/15閉鎖します

雑録

[ リスト | 詳細 ]

ブログらしいこと一切やってないので、
こっちである程度マトモなことやろうと思います。
(いや、こっちも正直マトモじゃないかも)
初めの記事
記事検索
検索

全54ページ

[1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11]

[ 次のページ ]

転載先URLへの更新 ご協力をお願いします

本ブログ(風の迷路)は、12月15日に閉鎖されます。
いくつかの記事は新ブログ(RPGツクールと数学のブログ)に転載しておりますので、
お手数ですが、本ブログの記事を引用している場合は、転載先のURLに変更頂きますよう、お願いいたします。

転載対象は、基本的に以下の選定基準で決めています:
・新ブログの方針(RPGツクール、数学の話題の提供)に関係するもの。
・本ブログで概ね固有の内容で、何かに活用できそうなもの。

本ブログの記事一覧(転載先URL表)は以下に掲載しております:
https://docs.google.com/spreadsheets/d/1UQx7mLM8YkUQ4Ojm3-0eyYmBVRvbley8We-NYiPTiFU/edit?usp=sharing

上記表にて、D列「URL」が本ブログにおける記事のURLです。
K列「転載要否」が○の記事が、新ブログへの転載対象であり、L列「転載先」のURLに転載されています。
転載要否が○の記事については、D列のURLを、L列のURLに変更いただきたく、お願いします。
転載要否が×の記事については転載しない記事ですので、12月15日以降は閲覧できなくなります。
×の記事のうち、転載すべき記事がございましたら、コメントにてご相談頂けますと幸いです。

また限定記事が○の記事は、一般公開していない記事ですので、
ご相談頂きましても、基本的には転載はしない方針とさせて頂きたく思います。

イメージ 1

!あけましておめでとうございます!

このブログは既に放置され、新しいブログに移転して活動しています。

RPGツクールと数学のブログ
http://fermiumbay13.hatenablog.com/

今後は新ブログに記事が投稿され、このブログには投稿されなくなると思います;_;
でも、このブログを消すことはまずありません。
姑息にもこのブログにある記事を新ブログに転載している現状です。

じゃあ、なんで移転するのだというと
別にヤフーさん批判というわけではないですが、リニューアルされてしまうじゃないですか。
ヤフーブログが一新し、過去に書いた記事もレイアウトなど含めて大きく変わってしまうようで、
今までの投稿がどんなことになるのかちょっと怖くなったので、
逃げという意味ではてなブログに移転することにした、というだけのことです。
新ブログでも投稿のスタイルは全く変わりません。
むしろ数式などを新ブログの方が書きやすいので、今までより見やすい投稿が
出来るんじゃないかと思っています。

ということで、今まで読んで頂いた多くの皆さま本当ありがとうございました。
新ブログのほうもごひいきにです。
何度も書きますが、本ブログを閉鎖とか削除はしませんよ コメントいつでもウェルカムです!

あとがき(切実な会)

イメージ 1

ヤフーブログで10年続けてきました。(放置の時期は多かったわけですが)
言葉の通り10年一昔に感じるものがありまして、10年前はまさかここまで
ブログを書こうという気もなく、そしてこんなに著者自身が
ブログに翻弄されることになるとも思っていなかったことでしょう。

各年の活動の様子を表にまとめましたよ:

濃密度合い(適当)主な内容
2007★★☆☆☆ネタ目的で開設 ゲーム投稿を始め、活発に
2008★★☆☆☆気分で投稿
2009★☆☆☆☆消沈気味 このまま凍結が予想された
2010★★★★☆趣味仲間の出現により一気に加速 第一次活動期と称する(?)
2011★★★☆☆勢いは弱まるが、平常運転
2012★★★★★一番ブログしてた時期 第二次活動期と称する(?)
2013★★☆☆☆友人関係を拗らせて低迷
2014★★☆☆☆低頻度ではあるが気分で投稿を続ける
2015★★☆☆☆さらに低迷 しかし後半に謎のやる気を得て一人で活発化
2016★☆☆☆☆活発化虚しく低迷は続く
2017★☆☆☆☆落ち切った 収束 もうだめだ;w;

開設からちょうど5年目あたり(2012年あたり)が投稿のピークでして
ブログ友達さんと好き勝手に自分のことやお友達さんのことを書き合う文化が根付き、
高頻度で楽しく記事を投稿させてもらっておりました。
ブログを書き合うようなモチベーションでもないと続かないというのは、
上の表からも明らかですね;w; 自分自身が一番びっくりしてますよ。

あんまり悲観的なことばっかり書いていてもしょうがないので
振り返りはこのあたりにしておき、今年もがんばっていきましょう。
幸いにも前年2017年は、ブログの投稿が少なかっただけであって
活動はしており、水面下で色々と充実しておりました。
新ブログで是非とも第三次活動期を起こしていきたいですね。

新ブログもよろしくね ぜひ来てね! 待ってるよ><
ほらもう一回リンク貼るから:
http://fermiumbay13.hatenablog.com/

またこんど!!
(2019/8/2追記)
本記事の内容は新ブログに転載済みです:
https://fermiumbay13.hatenablog.com/entry/2019/04/14/180618

論理回路のシミュレーションをプログラミングで行うための支援ライブラリを作りました。
https://github.com/fermiumbay/LogicSimulator

論理計算を行うだけであれば、単に計算式を記述して答えを出させれば済みますが、
このライブラリは素子を登録してそれぞれ接続し、回路を作ることが出来るのが特徴です。
計算式に基づいて出力値が出るのではなく、出力値を得たい素子の値からたどって計算を行います。
素子を接続して実装することから、順序回路のような循環している回路も作成できます。

本ライブラリはC++用です。(C++11以降)

導入

上のリンクからLogicParts.hとLogicParts.cppをダウンロードし、
cppファイルをコンパイルするソースに入れてください。
使用するときはLogicParts.hをインクルードします。

イメージ 1

例えば上のような回路を実装する場合は次のように記述します。

#include <iostream>
using namespace std;

#include "LogicParts.h"
using namespace LogicParts;

int main() {
    auto A = Terminal();    // 端子Aの定義
    auto B = Terminal();    // 端子Bの定義
    auto C = And(Or(A, B), Nand(A, B));    // 端子Cに論理回路を接続

    A->set(0);    // 端子Aに入力値0をセット
    B->set(1);    // 端子Bに入力値1をセット

    cout << C->get() << endl;    // 端子Cの値を出力
}

最初に各端子を定義して論理回路を作ります。
変数の型は面倒なのでautoで宣言していますが、BaseParts*でもOKです。
後は端子にset関数で入力値をセットして、出力値を得たい端子のget関数を呼べばOKです。
上の例はXor回路なので、A=Bのとき0、A≠Bのとき1が返ります。

半加算器

イメージ 2

上図は半加算器HAの例です。出力が2つありますが、同様にして回路を作れます。

#include <iostream>
using namespace std;

#include "LogicParts.h"
using namespace LogicParts;

int main() {
    auto A = Terminal();    // 端子Aの定義
    auto B = Terminal();    // 端子Bの定義
    auto C = And(A, B);    // 端子CにAnd素子を接続
    auto S = Xor(A, B);    // 端子SにXor素子を接続

    A->set(0);    // 端子Aに入力値0をセット
    B->set(1);    // 端子Bに入力値1をセット

    cout << C->get() << endl;    // 端子Cの値を出力
    cout << S->get() << endl;    // 端子Sの値を出力
}

このHAの部分だけを一つの部品として、
次のように関数を作っておくと便利です。

#include <iostream>
using namespace std;

#include "LogicParts.h"
using namespace LogicParts;

// 半加算器(A, B) = (C, S)
vector<BaseParts*> HA(BaseParts* A, BaseParts* B) {
    auto C = And(A, B);    // 端子CにAnd素子を接続
    auto S = Xor(A, B);    // 端子SにXor素子を接続
    return { C, S };	// 出力端子の配列を返す
}

int main() {
    auto A = Terminal();    // 端子Aの定義
    auto B = Terminal();    // 端子Bの定義
    auto D = HA(A, B);    // 端子DにA, Bを接続したHAを接続
    auto C = D[0];    // A, Bを接続したHAの出力C
    auto S = D[1];    // A, Bを接続したHAの出力S

    A->set(0);    // 端子Aに入力値0をセット
    B->set(1);    // 端子Bに入力値1をセット

    cout << C->get() << endl;    // 端子Cの値を出力
    cout << S->get() << endl;    // 端子Sの値を出力
}

全加算器

イメージ 3

HAを接続して全加算器FAを作ります。

#include <iostream>
using namespace std;

#include "LogicParts.h"
using namespace LogicParts;

// 半加算器(A, B) = (C, S)
vector<BaseParts*> HA(BaseParts* A, BaseParts* B) {
    auto C = And(A, B);    // 端子CにAnd素子を接続
    auto S = Xor(A, B);    // 端子SにXor素子を接続
    return{ C, S };	// 出力端子の配列を返す
}

// 全加算器(A, B, prevC) = (C, S)
vector<BaseParts*> FA(BaseParts* A, BaseParts* B, BaseParts* prevC) {
    auto D = HA(A, B);    // HAの出力を端子群Dとして定義
    auto E = HA(D[1], prevC);    // HAの出力を端子群Eとして定義
    auto C = Or(D[0], E[0]);    // HAの出力とOr素子を接続して端子Cに接続
    auto S = E[1];    // HAの出力を端子Sに接続
    return{ C, S };	// 出力端子の配列を返す
}

int main() {
    auto A = Terminal();    // 端子Aの定義
    auto B = Terminal();    // 端子Bの定義
    auto prevC = Terminal();    // 端子prevCの定義
    auto C = FA(A, B, prevC)[0];    // A, Bを接続したFAの出力C
    auto S = FA(A, B, prevC)[1];    // A, Bを接続したFAの出力S

    A->set(1);    // 端子Aに入力値1をセット
    B->set(1);    // 端子Bに入力値1をセット
    prevC->set(1);    // 端子prevCに入力値1をセット

    cout << C->get() << endl;    // 端子Cの値を出力
    cout << S->get() << endl;    // 端子Sの値を出力
}

一旦部品を作ってしまえば取り扱いが楽になります。

RSフリップフロップ

イメージ 4

入力端子を循環させれば順序回路も作れます。

#include <iostream>
using namespace std;

#include "LogicParts.h"
using namespace LogicParts;

// RSフリップフロップ
vector<BaseParts*> RS_FF(BaseParts* S, BaseParts* R) {
	auto Q = Nand(Not(S), 0);    // 端子Q
	auto notQ = Nand(Not(R), Q);    // 端子notQ
	Q->input[1] = notQ;    // 端子Qの入力端子の一つにnotQをセット

	return{ Q, notQ };	// 出力端子の配列を返す
}

int main() {
	auto S = Terminal();    // 端子Sの定義
	auto R = Terminal();    // 端子Rの定義
	auto Q = RS_FF(S, R)[0];    // 出力QをA, Bを接続したHAの出力C

	S->set(0); R->set(0); cout << Q->get() << endl;    // 保持
	S->set(1); R->set(0); cout << Q->get() << endl;    // Q=1にセット
	S->set(0); R->set(0); cout << Q->get() << endl;    // 保持
	S->set(0); R->set(1); cout << Q->get() << endl;    // Q=0にセット
	S->set(0); R->set(0); cout << Q->get() << endl;    // 保持
}

notQが未定義の状態でQ = Nand(Not(S), notQ)とは出来ませんから、
上記のように、一旦notQの部分は0(未接続)としておいて、
notQを作ってからQの入力端子にnotQをセットする、という方法で接続しています。


うまいように設計してやれば、大規模な論理回路も作れそうですね!
(2019/8/2追記)
本記事の内容は新ブログに転載済みです:
https://fermiumbay13.hatenablog.com/entry/2019/04/14/191910


イメージ 1

第2回で提示したレストランのやりとりをオブジェクト指向として実装した例です。
登場人物は、お客さん(Customer)、接客(Counter)、料理人(Cook)の3人で、
それぞれBaseCharaを継承して作られます。
料理の情報はFoodInfoクラスにまとめられています。
実際たかがこれだけの処理にこんなに書く必要ないと思いますけど、
例ということでご了承ください^^;
また、Mainクラスで定義されているshowCharaInfoメソッドをてきとうな所に挿入してもらえば
その段階での各キャラの情報(所持金、持っている食べ物)を表示できます。
(Main以外のクラスだと、Main.showCharaInfo();で呼び出せます)
処理の流れを見るのにぜひ活用してください。

// 【Main.java】

// メインクラス
class Main{
    
    // 情報取得用のキャラクターリスト
    public static BaseChara[] charaList = new BaseChara[3];
    
    // キャラクターリストに載っているすべてのキャラクターの情報を表示
    public static void showCharaInfo(){
        for(int i = 0; i < charaList.length; i++){
            charaList[i].showInfo();
        }
    }
    
    // mainメソッド
    public static void main(String args[]){

        // (食べ物リスト)
        // 0番: オムライス(税込972円)
        // 1番: とんかつ(税込1296円)
        // 2番: たこ焼き(税込648円)
        
        // お客さんの名前はベイ助、所持金1000円、食べたいものは0番オムライス
        Customer customer = new Customer("ベイ助", 1000, 0);

        // 接客の名前はレーラ、所持金2万円
        Counter counter = new Counter("レーラ", 20000);

        // 料理人の名前はポン太、所持金なし
        Cook cook = new Cook("ポン太", 0);

        // showCharaInfoメソッドのためにそれぞれのキャラを代入しておく        
        charaList[0] = customer;
        charaList[1] = counter;
        charaList[2] = cook;
        
        System.out.println(customer.getName() + "は、レストランへ行きました。");

        // お客さんが接客に注文 成功したら後の手続きを行う        
        if(customer.order(counter)){
            
            // 接客が料理人に料理を作るよう依頼し、もらう
            counter.askToCook(cook);
            
            // 接客がお客さんに料理を提供
            counter.provideDish(customer);
            
            // お客さんがそれを食べる
            customer.eat();
        }
        
        System.out.println(customer.getName() + "はレストランを後にしました。おわり");
        
    }
}
// 【BaseChara.java】

// 各キャラクターのスーパークラス
abstract class BaseChara{
    
    // 名前
    private String name;
    
    // 所持金
    private int money;
    
    // 持っている食べ物ID(-1で何も持っていない)
    protected int foodId;
    
    public BaseChara(String name, int money){
        this.name = name;
        this.money = money;
        this.foodId = -1;
        this.sayHello();
    }
    public String getName(){
        return this.name;
    }
    public int getMoney(){
        return this.money;
    }
    public int getFoodId(){
        return this.foodId;
    }
    
    // あいさつ
    abstract public void sayHello();
    
    // 情報を表示
    public void showInfo(){
        System.out.println("【" + this.getName() + "の情報】");
        System.out.println("所持金:" + this.getMoney());
        if(this.getFoodId() != -1){
            System.out.println("持っている食べ物:" + FoodInfo.getFoodName(this.getFoodId()));
        }else{
            System.out.println("食べ物は何も持っていない……");
        }
    }
    
    // しゃべらせる
    protected void speak(String comment){
        System.out.println(this.getName() + "「" + comment + "」");
    }
    
    // お金をcharaに払う
    public void payMoney(int money, BaseChara chara){
        chara.receiveMoney(money);
        this.money -= money;
    }
    
    // お金をもらう
    private void receiveMoney(int money){
        this.money += money;
    }
    
    // 持っている食べ物をcharaに渡す
    public void giveFood(BaseChara chara){
        chara.receiveFood(this.foodId);
        this.foodId = -1;
    }
    
    // 食べ物をもらう
    private void receiveFood(int foodId){
        this.foodId = foodId;
    }
    
}
// 【Customer.java】

// お客さん
class Customer extends BaseChara{
    
    // 欲しい食べ物
    private int hopeFoodId;
    
    public Customer(String name, int money, int hopeFoodId){
        super(name, money);
        this.hopeFoodId = hopeFoodId;
    }
    public void sayHello(){
        this.speak("お客さんですっ!");
    }
    
    // 欲しい食べ物を注文する
    public Boolean order(Counter counter){
        Boolean orderSucceeded = false;
        this.speak(FoodInfo.getFoodName(this.hopeFoodId) + "食べようかな。お願いします!");
        if(this.getMoney() >= FoodInfo.getFoodPrice(this.hopeFoodId)){
            this.payMoney(FoodInfo.getFoodPrice(this.hopeFoodId), counter);
            counter.ordered(this.hopeFoodId);
            orderSucceeded = true;
        }else{
            this.speak("ああ、お金が足りなくて注文できないや……");
        }
        return orderSucceeded;
    }
    
    // 持っている食べ物を食べる
    public void eat(){
        this.speak("いただきまーす!");
        this.speak("やっぱおいしいねぇ、ごちそうさま!");
        this.foodId = -1;
    }
    
}
// 【Counter.java】

// 接客
class Counter extends BaseChara{
    
    // 注文もらった食べ物のID
    private int orderedFoodId;
    
    public Counter(String name, int money){
        super(name, money);
    }
    public void sayHello(){
        this.speak("接客しまーす!");
    }
    
    // 注文を受ける
    public void ordered(int orderedFoodId){
        this.speak("かしこまりました! 少々お待ちください!");
        this.orderedFoodId = orderedFoodId;
    }
    
    // 注文された料理を料理人に作ってもらう
    public void askToCook(Cook cook){
        this.speak(FoodInfo.getFoodName(orderedFoodId) + "の注文入りましたよ!");
        cook.cook(this, orderedFoodId);
    }
    
    // 料理を提供する
    public void provideDish(Customer customer){
        this.speak("おまちどおさまでした! " + FoodInfo.getFoodName(this.getFoodId()) + "です!");
        this.giveFood(customer);
    }
    
}
// 【Cook.java】

// 料理人
class Cook extends BaseChara{
    
    public Cook(String name, int money){
        super(name, money);
    }
    public void sayHello(){
        this.speak("料理作ってやるよ!");
    }
    
    // 料理する
    public void cook(Counter counter, int cookFoodId){
        this.speak("よーし、いま作るから待ってな");
        System.out.println("5分後...");
        this.foodId = cookFoodId;
        this.speak("できた! それじゃ、持ってってくれよ");
        this.giveFood(counter);
    }
    
}
// 【FoodInfo.java】

// 食べ物の情報を持ったクラス
class FoodInfo{
    
    // 消費税率
    private static final double taxRate = 0.08;
    
    // 食べ物の名前一覧
    private static String[] foodName = {"オムライス", "とんかつ", "たこ焼き"};
    
    // 食べ物の税抜き価格一覧
    private static int[] foodPrice = {900, 1200, 600};
    
    public static String getFoodName(int foodId){
        String foodName = "";
        if(foodId >= 0 && foodId < FoodInfo.foodName.length){
            foodName = FoodInfo.foodName[foodId];
        }
        else{
            foodName = "(未定義)";
        }
        return foodName;
    }
    
    public static int getFoodPrice(int foodId){
        int foodPrice = 0;
        if(foodId >= 0 && foodId < FoodInfo.foodPrice.length){
            foodPrice = addTax(FoodInfo.foodPrice[foodId]);
        }
        return foodPrice;
    }
 
    // 税込み価格にする   
    public static int addTax(int price){
        return (int)((1.0 + taxRate) * price);
    }
    
}
(2019/8/2追記)
本記事の内容は新ブログに転載済みです:
https://fermiumbay13.hatenablog.com/entry/2019/04/14/190802


オブジェクト指向講座の最後です。今回は継承とポリモーフィズムを解説します。

第3回 継承

クラスの継承

イメージ 1

本クラスがすでにあったとして、さらに日記クラスを作ろうと考えます。
本クラスは各ページに書いてある文字列があるだけですが、
日記クラスではさらに各ページごとの日付が記録できるようになっています。
日記クラスは本クラスにあるすべての機能を持っているのですが、
本クラスの中身をそのままコピペして作るのはタイヘンですよね。
そこで使えるのが継承です。
本クラスのすべての機能を引き継いで、新たな日記クラスを作ることができます。

まずは本クラスだけ作っておきましょう。
下ではpublicとprivateの他にprotectedというのが出てますが、後述するので
とりあえず一旦はprivateみたいなものとして読んでください。

// 【Book.java】

class Book{
    
    // ページ数
    protected int pageNum;
    
    // 各ページのテキスト内容
    protected String[] text;
    
    public Book(int pageNum){
        this.pageNum = pageNum;
        this.text = new String[pageNum];
        for(int i = 0; i < pageNum; i++){
            this.text[i] = "";
        }
    }
    
    // 本を読む
    public void read(int pageId){
        System.out.println("【" + pageId + "】ページ:" + this.text[pageId]);
    }
    
    // 本に書き込む
    public void write(int pageId, String text){
        this.text[pageId] = text;
    }
    
    // 本のページ数を取得
    public int getPageNum(){
        return this.pageNum;
    }
    
}
// 【Main.java】

class Main{
    public static void main(String args[]){
        Book book = new Book(10);
        book.write(0, "この本は0ページから始まるのだ");
        book.write(1, "これが1ページで");
        book.write(2, "これが2ページである");
        book.write(3, "続きはまだ書いていない");
        book.write(5, "あっ飛ばして書いてしまったわ");
        for(int i = 0; i < book.getPageNum(); i++){
            book.read(i);
        }
    }
}

10ページの本を作り、各ページに文字列を記載しています。
配列が0番から始まるので、ここでは簡単化するため0ページから始まる本としましょう^^;

それでは、この本クラスを継承して、各ページの日付を記録できるようにした日記クラスを作ります。

// 【Diary.java】

class Diary extends Book{
    
    // 作成年月日
    protected int[] year;
    protected int[] month;
    protected int[] day;
    
    public Diary(int pageNum){
        super(pageNum);
        year = new int[pageNum];
        month = new int[pageNum];
        day = new int[pageNum];
    }
    
}

クラスの定義を行うときに「extends Book」とすれば、Bookクラスを継承するという意味になります。
継承すると、すべての変数・メソッドを引き継いで使えるようになります。クラスを拡張できるわけですね。
継承される元のBookクラスみたいなもののことをスーパークラス(親クラス)といい、
継承した先のDiaryクラスみたいなもののことをサブクラス(子クラス)といいます。

ただし、スーパークラスでprivate宣言されているものはサブクラスも使うことができません。
アクセス権限を与えるアクセス修飾子は3種類あります。

public:どこからでもアクセス可能
protected:自分のクラスとそのサブクラスだけアクセス可能
private:自分のクラスだけアクセス可能

本当はもう一つありますけど、ここでは割愛します。
Bookの変数をprotectedとしていたのは、サブクラスであるDiaryは普通に使えますが、
外部のクラスであるMainは使うことができない、という意味で使っていたのでした。

もう一つ、Diaryのコンストラクタに次のような命令があります。
super(pageNum);
これは、スーパークラスのコンストラクタを引数pageNumとして実行するというものです。
Bookクラスのコンストラクタでページ数のセットや各ページの文字列の初期化をしていましたが、
それをDiaryクラスのコンストラクタにコピペするのはめんどくさいので、そのように書きます。

これでDiaryクラスの基本は作れましたので、
実際に各ページに年月日を書き込めるようにしましょう。
年月日の変数それぞれの読み書きメソッド(アクセッサ)を作れば実現できますが、
なんだかめんどくさいですよね。
writeYear, writeMonth, writeDay, readYear, readMonth, readDayと6種類もあります。
Mainの方も1ページ書くごとに書き込みメソッドを全部呼ぶ必要があるので面倒です。

そこで、Bookクラスにもともとあるwriteとreadを上書きして、年月日を書き込むことを考えます。
Bookクラスをいじるのではなく、Diaryクラスに追記する形で上書きをします。

// 【Diary.java】

class Diary extends Book{
    
    // 作成年月日
    protected int[] year;
    protected int[] month;
    protected int[] day;
    
    public Diary(int pageNum){
        super(pageNum);
        year = new int[pageNum];
        month = new int[pageNum];
        day = new int[pageNum];
    }
    
    // 日記を読む
    public void read(int pageId){
        super.read(pageId);
        System.out.println("作成年月日:" + year[pageId] + "年" + month[pageId] + "月" + day[pageId] + "日");
    }
    
    // 日記に書き込む
    public void write(int pageId, String text, int year, int month, int day){
        super.write(pageId, text);
        this.year[pageId] = year;
        this.month[pageId] = month;
        this.day[pageId] = day;
    }
}

Diaryクラスにもreadとwriteのメソッドを用意しました。
superはそれぞれスーパークラスを意味しています。
このように、スーパークラスにあるメソッドをサブクラスで再定義すると、
サブクラスのオブジェクトからは再定義後のメソッドが優先して呼ばれます。
readメソッドのようにメソッドを完全に再定義してしまうことをオーバーライドといいます。
writeメソッドの方は引数がBookクラスのものと違うので、同じ名前でも違うメソッドとして扱われます。
そのため、実はwriteメソッドはBookクラスのものが残っており、2つのメソッドがあります。
このように、同じ名前でも引数が違えば別のメソッドとして扱えて、
そのように新しいメソッドを定義することをオーバーロードといいます。
名前が似ていますが、割と別物です。注意してください。

// 【Main.java】

class Main{
    public static void main(String args[]){
        Diary diary = new Diary(10);
        diary.write(0, "この本は0ページから始まるのだ", 2017, 8, 10);
        diary.write(1, "これが1ページで", 2017, 8, 11);
        diary.write(2, "これが2ページである", 2017, 8, 12);
        diary.write(3, "続きはまだ書いていない", 2017, 8, 13);
        diary.write(5, "あっ飛ばして書いてしまったわ", 2017, 8, 15);
        for(int i = 0; i < diary.getPageNum(); i++){
            diary.read(i);
        }
    }
}

Mainで日記を書けるように書き換えました。
Bookクラスをいじることなく、Bookの機能を引き継いだDiaryが作れましたね。

static

今までクラスはオブジェクトの設計図と紹介しており、
クラスで宣言されている変数やメソッドはオブジェクト化して初めて生まれるものと紹介しました。
次のように時計クラスを作って時刻を表す変数timeを持たせておくと、
時計オブジェクトそれぞれが時刻を持つようになりますね。

// 【Watch.java】

class Watch{
    private String time;
    public Watch(String time){
        this.time = time;
    }
    public void printTime(){
        System.out.println("時計は" + this.time + "を指している");
    }
}
// 【Main.java】

class Main{
    public static void main(String args[]){
        Watch w1 = new Watch("11:15");
        Watch w2 = new Watch("12:20");
        Watch w3 = new Watch("13:45");
        w1.printTime();
        w2.printTime();
        w3.printTime();
    }
}

それぞれの時計が別々の時刻を表しています。

しかし、時計なので何個時計があろうと時刻は同じになって欲しいという気持ちもあります。
時刻はオブジェクト固有ではなくクラス固有のものと考えて、
クラス固有の変数として時刻を決めることにします。
次のように書き換えます。

// 【Watch.java】

class Watch{
    private static String time;
    public static void setTime(String time){
        Watch.time = time;
    }
    public void printTime(){
        System.out.println("時計は" + Watch.time + "を指している");
    }
}
// 【Main.java】

class Main{
    public static void main(String args[]){
        Watch.setTime("11:15");
        Watch w1 = new Watch();
        Watch w2 = new Watch();
        Watch w3 = new Watch();
        w1.printTime();
        w2.printTime();
        w3.printTime();
    }
}

変数timeの頭にstaticを付けました。
これにより変数timeはWatchオブジェクトそれぞれが持つ変数なのではなく、
Watchオブジェクトなら共通で利用可能なクラス固有の単一の変数になります。
このようなクラス固有の変数をクラス変数といい、メソッドの場合はクラスメソッドといいます。
ここではsetTimeメソッドをクラスメソッドにしていますね。
クラス変数をいじるときはthisではなくクラス名を使います。this.timeではなく、Watch.timeです。
thisは自分自身のオブジェクトのことで、クラス変数はオブジェクトではないためです。
こうすれば、すべての時計で同じ時刻を表すようになります。

クラス固有なので、継承してもオーバーライドされないのですね。
以下ではWatchを継承したAppleWatchを作ってそっちでも変数timeを宣言していますが、
どちらもstaticなので、それぞれのクラスが指す時刻は異なります。

// 【Watch.java】

class Watch{
    private static String time;
    public static void setTime(String time){
        Watch.time = time;
    }
    public void printTime(){
        System.out.println("時計は" + Watch.time + "を指している");
    }
}
// 【AppleWatch.java】

class AppleWatch extends Watch{
    private static String time;
    public static void setTime(String time){
        AppleWatch.time = time;
    }
    public void printTime(){
        System.out.println("りんご時計は" + AppleWatch.time + "を指している");
    }
}
// 【Main.java】

class Main{
    public static void main(String args[]){
        Watch.setTime("11:15");
        AppleWatch.setTime("12:30");
        Watch w1 = new Watch();
        Watch w2 = new AppleWatch();
        w1.printTime();
        w2.printTime();
    }
}

抽象クラス

継承は終わったので、ここからはポリモーフィズムの話に入ります。
ポリモーフィズムまで理解すれば、オブジェクト指向はもう掌の中にあります。

食べ物クラスを継承して、うどんクラスとオムライスクラスを作りましょう。
食べ物なので食べられるわけですが、その食べ方がうどんとオムライスで異なります。
すなわち、食べ物クラスの段階では「食べる」という行為が未定義なわけです。

イメージ 2

食べ物が食べれるのは確実なのですが、どうやって食べればいいのか分かりません。
継承したうどんとかオムライスの方で定義してやることになります。
このとき、食べ物クラスの食べるメソッドは抽象メソッドとして定義します。

// 【Food.java】

abstract class Food{
    abstract public void eat();
}
// 【Udon.java】

class Udon extends Food{
    public void eat(){
        System.out.println("箸でうどんを食べた");
    }
}
// 【Omurice.java】

class Omurice extends Food{
    public void eat(){
        System.out.println("スプーンでオムライスを食べた");
    }
}
// 【Main.java】

class Main{
    public static void main(String args[]){
        Udon udon = new Udon();
        Omurice omurice = new Omurice();
        udon.eat();
        omurice.eat();
    }
}

Foodクラスを継承して、UdonクラスとOmuriceクラスを作りました。
Foodのeatメソッドは未定義のままにしておき、継承先で改めて定義して欲しいので、
eatメソッドの先頭にabstractというキーワードを付けています。
これは抽象メソッドにするという意味で、Foodはeatというメソッドは持っているものの
内容は未定義という意味になります。
一つでも抽象メソッドを含んでいるクラスは抽象クラスと呼ばれ、
classの先頭にもabstractキーワードが付くことになります。

抽象クラスには「抽象的な概念」という意味があり、このままでは実体化できないクラスとなります。
ですから、Mainの方で「Food food = new Food();」などと書くとエラーになります。
抽象クラスはオブジェクト化することが出来ません。
これを継承し、抽象メソッドをオーバーライドしたクラスでないとオブジェクトにならないのです。

このままだと食べ物の概念に基づいてクラスを作っただけであり、
こんなのいつ使うのと思われるでしょう。
それは次に説明するポリモーフィズムを活用するために使います。

ポリモーフィズム

イメージ 3

食べ物を食べようとしている人は、単に「食べろ」と言われれば
食べ物によって無意識に食べ方を変えることができます。
このように、実体が何なのかによって処理を自動で切り替える仕組みのことを
ポリモーフィズム(多態性)といいます。

さっきのFood、Udon、Omuriceクラスをそのまま利用して、Mainを次のように書き換えます。

// 【Main.java】

class Main{
    public static void main(String args[]){
        Food udon = new Udon();
        Food omurice = new Omurice();
        udon.eat();
        omurice.eat();
    }
}

変わったのは、udonとomuriceの型だけです。
Food型の変数にUdonとOmuriceのオブジェクトを代入しました。
そうすると、udonとomuriceは中身はそれぞれ異なるけれど、
さながらFoodクラスのオブジェクトかのように扱うことが可能になります。
そこでeatメソッドを呼び出してみると、中身はそれぞれのクラスでオーバーライドしてますから
それぞれのオブジェクトに合った食べ方で食べることになります。これがポリモーフィズムです。

ポリモーフィズムを利用すると、配列を使ってこんなようなこともできます。

// 【Main.java】

class Main{
    public static void main(String args[]){
        Food food[] = {new Udon(), new Omurice(), new Udon(), new Udon(), new Omurice()};
        for(int i = 0; i < food.length; i++){
            food[i].eat();
        }
    }
}

food.lengthとは、配列foodの要素数を表します。ここでは5ですね。
単に食べ物のeatメソッドを呼んでいるだけなのに、
食べ物によって食べ方を変えて実行されることになります。
こうすれば、Mainの方で条件分岐しなくて良くなり、何より直観的に扱えるようになるので便利です。
コードもすっきり整理されますし、オブジェクト主体でやり取りしている感が増します。

なかなかどういう場面でポリモーフィズムを活用しようか
慣れないうちは難しいと思いますが、強力な仕組みですのでぜひぜひ活用していきましょう。

その他

Javaには他にも色々な機能があります。
例えばJavaでは二つ以上のクラスを継承することが禁止されています。(C++は出来ます)
スーパークラスが2つもあるとそれぞれどう扱えばいいのか複雑になるためです。
しかし完全に禁止されてはかえって不便なこともあり、
代わりにインターフェースという機能制限されたクラスが用意されています。
これは、メソッドの中身が全部空の状態で定義されるクラスであり、
継承(インターフェースの場合は実装といいます)してメソッドを再定義して作る必要があるものです。
なんでそんなものを用意するんだという話になりますが、詳細は割愛します。
インターフェースなら何個でも実装して良くなり、Javaではよく活用されています。

他にも、ここで宣言したものはもう書き換えないよという宣言を行うキーワードにfinalがあります。
例えば、public final int a = 5;と変数を宣言すると、a=5と宣言されたのを最後に書き換え不可能になります。
この後にa = 3;とかやるとエラーになります。変数にfinalを付ければ定数と見なせるようになるわけです。
finalはメソッドにも付けることができ、そうするとそのクラスを継承した先でオーバーライドできなくなります。
これ以上メソッドの内容を書き換えることはできない、これで定義は完了だよ、という意味です。
一種のアクセス制限ですが、わざと制限を掛けていくことによって簡単化し、バグりづらくさせるのですね。


以上でオブジェクト指向講座はおわりです。
割といろいろ省略していきましたが、とりあえず最低限これらを知っていれば
オブジェクト指向プログラミングは十分できると思います。

全54ページ

[1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11]

[ 次のページ ]


.
検索 検索
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
フェルミウム湾
フェルミウム湾
男性 / O型
人気度
Yahoo!ブログヘルプ - ブログ人気度について

過去の記事一覧

よしもとブログランキング

もっと見る

[PR]お得情報

お肉、魚介、お米、おせちまで
おすすめ特産品がランキングで選べる
ふるさと納税サイト『さとふる』
ふるさと納税サイト『さとふる』
11/30まで5周年記念キャンペーン中!
Amazonギフト券1000円分当たる!
数量限定!イオンおまとめ企画
「無料お試しクーポン」か
「値引きクーポン」が必ず当たる!

その他のキャンペーン


プライバシー -  利用規約 -  メディアステートメント -  ガイドライン -  順守事項 -  ご意見・ご要望 -  ヘルプ・お問い合わせ

Copyright (C) 2019 Yahoo Japan Corporation. All Rights Reserved.

みんなの更新記事